Difference between revisions of "User:Toy Wylie/Phoenix/Preprocessor"
(Fixing the rest of the broken <lsl> tags) |
m (Changing code indents to actual <source> tags) |
||
Line 25: | Line 25: | ||
Create a file in your include folder with the name <tt>debug.lsl</tt>. Copy the following snippet into this file: | Create a file in your include folder with the name <tt>debug.lsl</tt>. Copy the following snippet into this file: | ||
<source lang="lsl2"> | |||
#ifdef DEBUG | |||
debug(string text) | |||
{ | |||
llOwnerSay(text); | |||
} | |||
#else | |||
#define debug(dummy) | |||
#endif | |||
</source> | |||
Now create a new LSL script and copy the following code into the script editor: | Now create a new LSL script and copy the following code into the script editor: | ||
<source lang="lsl2"> | |||
#define DEBUG | |||
#include "debug.lsl" | |||
default | |||
{ | |||
state_entry() | |||
{ | |||
debug("Debugging with the Phoenix LSL Preprocessor."); | |||
} | |||
} | |||
</source> | |||
Save the script. You will see the "Debugging with the Phoenix LSL Preprocessor" message in your chat console. Now change the first line of the LSL script to: | Save the script. You will see the "Debugging with the Phoenix LSL Preprocessor" message in your chat console. Now change the first line of the LSL script to: | ||
<source lang="lsl2"> | |||
#undef DEBUG | |||
</source> | |||
Save the script again. You will see that the debugging message is gone. This is a very handy way to enable and disable all debugging code in one single line. To understand what really happens, have a look at the "Postprocessed" tab. You can clearly see how the debug line will simply disappear, a lonely ";" the only trace of it having been there. | Save the script again. You will see that the debugging message is gone. This is a very handy way to enable and disable all debugging code in one single line. To understand what really happens, have a look at the "Postprocessed" tab. You can clearly see how the debug line will simply disappear, a lonely ";" the only trace of it having been there. | ||
Line 63: | Line 69: | ||
Example: | Example: | ||
<source lang="lsl2"> | |||
default | |||
{ | |||
state_entry() | |||
{ | |||
integer i; | |||
switch(i) | |||
{ | |||
case 1: | |||
{ | |||
llOwnerSay("1"); | |||
// fallthrough to case 2 | |||
} | |||
case 2: | |||
{ | |||
llOwnerSay("1 or 2"); | |||
// no fallthrough | |||
break; | |||
} | |||
case 3: | |||
{ | |||
llOwnerSay("3"); | |||
// fallthrough to default | |||
} | |||
default: | |||
{ | |||
llOwnerSay("3 or default"); | |||
} | |||
} | |||
} | |||
} | |||
</source> | |||
===7. Lazy Lists addition=== | ===7. Lazy Lists addition=== | ||
Line 98: | Line 106: | ||
Assigning values to list indexes is always a cumbersome thing to do in LSL (<tt>llListReplaceList()</tt> needed). Lazy Lists can help you a little by providing a way to just say: | Assigning values to list indexes is always a cumbersome thing to do in LSL (<tt>llListReplaceList()</tt> needed). Lazy Lists can help you a little by providing a way to just say: | ||
<source lang="lsl2"> | |||
myList[index] = value; | |||
</source> | |||
Sadly it is not really possible to get list elements the same way because the return data can be of different types. | Sadly it is not really possible to get list elements the same way because the return data can be of different types. | ||
Line 141: | Line 151: | ||
Example 1: | Example 1: | ||
<source lang="lsl2"> | |||
#define CHANNEL 12345 | |||
llOwnerSay((string) CHANNEL); // CHANNEL will be replaced with the literal 12345 stated in the #define above | |||
</source> | |||
<tt>#define</tt> can also take parameters to apply to the replacement code: | <tt>#define</tt> can also take parameters to apply to the replacement code: | ||
Line 148: | Line 160: | ||
Example 2: | Example 2: | ||
<source lang="lsl2"> | |||
#define OS(b,c) llOwnerSay(b+c) | |||
OS("Test","123"); // will expand to: llOwnerSay("Test"+"123") | |||
</source> | |||
Example 3: Making strings out of parameters | Example 3: Making strings out of parameters | ||
<source lang="lsl2"> | |||
#define OS(a) llOwnerSay(#a) | |||
OS(1234); // will expand to: llOwnerSay("1234"); | |||
</source> | |||
Example 4: Using <tt>""##""</tt> to concatenate parameters | Example 4: Using <tt>""##""</tt> to concatenate parameters | ||
<source lang="lsl2"> | |||
#define OS(a,b) llOwnerSay((string) a##b) | |||
OS(1234,5678); // will expand to: llOwnerSay((string) 12345678); | |||
</source> | |||
===8.2 <tt>#undef</tt>=== | ===8.2 <tt>#undef</tt>=== | ||
Line 171: | Line 189: | ||
Example: | Example: | ||
<source lang="lsl2"> | |||
#define OWNER_ONLY | |||
... | |||
#ifdef OWNER_ONLY | |||
key var=llGetOwner(); | |||
#else | |||
key var=llDetectedKey(0); | |||
#endif | |||
</source> | |||
===8.4 <tt>#if</tt> and <tt>#elif</tt>=== | ===8.4 <tt>#if</tt> and <tt>#elif</tt>=== | ||
Line 185: | Line 205: | ||
Example: | Example: | ||
#define DEBUGLEVEL 2 | |||
<source lang="lsl2"> | |||
#if DEBUGLEVEL==1 | |||
llOwnerSay("Point reached"); | |||
#elif DEBUGLEVEL==2 | |||
llOwnerSay("Lots of more data here"); | |||
#else | |||
llOwnerSay("Unknown debug level: "+(string) DEBUGLEVEL); | |||
#endif | |||
</source> | |||
===8.5 <tt>#warning</tt> and <tt>#error</tt>=== | ===8.5 <tt>#warning</tt> and <tt>#error</tt>=== | ||
Line 203: | Line 225: | ||
Example 1: | Example 1: | ||
<source lang="lsl2"> | |||
#warning This include file is obsolete! | |||
</source> | |||
Example 2: | Example 2: | ||
<source lang="lsl2"> | |||
#error This include file does not work anymore. Please update. | |||
</source> | |||
===8.6 <tt>#include</tt>=== | ===8.6 <tt>#include</tt>=== | ||
Line 215: | Line 241: | ||
Examples: | Examples: | ||
<source lang="lsl2"> | |||
#include "command_ids.lsl" | |||
#include "general_functions.lsl" | |||
#include "hud/layout.lsl" | |||
</source> | |||
'''NOTE:''' Be careful about including files from within <tt>#include</tt>s. You might <tt>#include</tt> a file twice, if you are not really keeping track, and this will lead to problems. This issue is usually addressed by using so-called "Include guards". You basically have a conditional compile that sets a <tt>#define</tt> macro and checks if it's already there. If it's not, <tt>#include</tt> the contents of the file. If it was set, ignore the contents. | '''NOTE:''' Be careful about including files from within <tt>#include</tt>s. You might <tt>#include</tt> a file twice, if you are not really keeping track, and this will lead to problems. This issue is usually addressed by using so-called "Include guards". You basically have a conditional compile that sets a <tt>#define</tt> macro and checks if it's already there. If it's not, <tt>#include</tt> the contents of the file. If it was set, ignore the contents. | ||
Line 223: | Line 251: | ||
Example: | Example: | ||
<source lang="lsl2"> | |||
#ifndef SCRIPT_NAME_LSL | |||
#define SCRIPT_NAME_LSL | |||
your_script_starts_here() | |||
{ | |||
} | |||
#endif //SCRIPT_NAME_LSL | |||
</source> | |||
These <tt>#ifndef</tt>, <tt>#define</tt> and <tt>#endif</tt> commands make sure that the <tt>#include</tt> happens only once regardless of how often the file is actually referenced with <tt>#include</tt>. | These <tt>#ifndef</tt>, <tt>#define</tt> and <tt>#endif</tt> commands make sure that the <tt>#include</tt> happens only once regardless of how often the file is actually referenced with <tt>#include</tt>. |
Latest revision as of 08:40, 29 August 2015
Phoenix LSL Preprocessor
an overview by Toy Wylie, rev. 0.8
1. Overview
Developing scripts in LSL can be tedious, especially if you are creating scripts that use the same things over and over again, and you keep copying pieces of your older scripts to new creations. And even within one project, you often have to copy parts of your scripts into sub-scripts (example: link message numbers as identifiers for commands). Changing any of these numbers or updating and fixing bugs in older variants of the code might result in different versions of the same functions being used or in bugs found in older versions not being fixed in newer creations. The Phoenix LSL Preprocessor is a tool to help you circumvent a lot of these problems.
Adding and removing debugging statements is another thing with which the Preprocessor can be helpful. Usually you have debugging functions in the script to see if all is working fine, and you take them out before release. But this in itself creates new places for mistakes as well as the opportunity for some debugging output to remain in the script on release day because it was overlooked during final screening. Using the Phoenix LSL Preprocessor gives you a very simple way of making sure that no debug output is left in your final release.
2. Setup
To enable the Phoenix LSL Preprocessor, open your preferences panel (CTRL-P), click on "Phoenix", "Page 2", and then on "Inventory". Mark the checkbox "Enable LSL Preprocessor Filesystem Includes" and click on "Set". This will open a file requester, prompting you to point at a directory on your local computer. This is the place where you store all your LSL include files. Next, open any LSL script, click on "Advanced", and select "Enable Preprocessor". Then close the script.
3. How it Works
After setting up, you will see two tabs in your LSL editor: "Script" and "Preprocessed" ("Postprocessed" in older versions). The first is your active scripting window; the second shows the output of the Preprocessor, essentially the content that gets stored in the script. The postprocessed script contains the entire source code of what you have written in a comment block at the beginning, and the postprocessed output following this comment block. This ensures backwards compatibility with older versions of Phoenix and regular viewers that do not support preprocessing at all.
NOTE: Since the LSL/Mono compiler gets the script after preprocessing, the line numbers in error messages refer to the postprocessed script. So if you are getting compiler errors, be sure to look for the error in the "Postprocessed" tab rather than in your original script.
4. A Short Example
Create a file in your include folder with the name debug.lsl. Copy the following snippet into this file:
#ifdef DEBUG
debug(string text)
{
llOwnerSay(text);
}
#else
#define debug(dummy)
#endif
Now create a new LSL script and copy the following code into the script editor:
#define DEBUG
#include "debug.lsl"
default
{
state_entry()
{
debug("Debugging with the Phoenix LSL Preprocessor.");
}
}
Save the script. You will see the "Debugging with the Phoenix LSL Preprocessor" message in your chat console. Now change the first line of the LSL script to:
#undef DEBUG
Save the script again. You will see that the debugging message is gone. This is a very handy way to enable and disable all debugging code in one single line. To understand what really happens, have a look at the "Postprocessed" tab. You can clearly see how the debug line will simply disappear, a lonely ";" the only trace of it having been there.
5. The Optimizer
Including files has one disadvantage though. You will get the complete contents of the file, if you need it or not. But the Phoenix LSL Preprocessor uses an optimizing technique, which only keeps the things you really used in your code and removes all global functions and variables you didn't reference in your script. This makes sure that your scripts don't get burdened with a lot of unused code. You can enable or disable this functionality in the script editor's "Advanced" menu.
6. switch/case addition
The Preprocessor also adds a set of new commands to the LSL editor which has been sorely missing until now: the switch/case construct known from many other languages. You can enable support for this construct in the script editor's "Advanced" menu. switch/case is a handy replacement for if(...) else if() chains. Additionally, switch/case supports "fallthrough" from one case to another, so you can chain up several cases with different conditions. A break statement is used to prevent fallthrough. The default case is used if none of the cases match the switch() condition.
Example:
default
{
state_entry()
{
integer i;
switch(i)
{
case 1:
{
llOwnerSay("1");
// fallthrough to case 2
}
case 2:
{
llOwnerSay("1 or 2");
// no fallthrough
break;
}
case 3:
{
llOwnerSay("3");
// fallthrough to default
}
default:
{
llOwnerSay("3 or default");
}
}
}
}
7. Lazy Lists addition
Assigning values to list indexes is always a cumbersome thing to do in LSL (llListReplaceList() needed). Lazy Lists can help you a little by providing a way to just say:
myList[index] = value;
Sadly it is not really possible to get list elements the same way because the return data can be of different types.
8. Preprocessor Commands and Macros
The Preprocessor understands the following commands:
#define #undef #ifdef #ifndef #if #elif #else #endif #warning #error #include
There are a few more, but these are not really useful within LSL.
Additionally, you can use the following macros in your scripts to help with debugging and giving you other useful information:
__FILE__ - the full path to the script as it would appear in the include cache. The top script only uses its name.
__LINE__ - the line of the current script where it is expanded; this starts at line 0
__SHORTFILE__ - the name of the current script without full file path
__AGENTID__ - a string-encapsulated version of the agent's key who compiles the script
__AGENTKEY__ - same as above, legacy version
__AGENTIDRAW__ - a nonstring-encapsulated version of the agent's key who compiles the script
__AGENTNAME__ - a string-encapsulated version of the agent's full name who compiles the script
__ASSETID__ - a string-encapsulated version of the assetid of the current script; may return "NOT IN WORLD" or a nonstring-encapsulated null key in rare circumstances
Thanks to Zwagoth Klaar for this list.
8.1 #define
#define creates a case-sensitive macro which will be replaced while saving and compiling a script. This can be applied as simple constant numbers, strings, and even functions. What happens is a literal text replacement in the source code. Because of this, be careful with ";" inside your macros. These usually don't cause any harm; but within a one-line conditional, things can break in unexpected ways if it creates a ";;" in the end of a line, for example.
The following examples will give you an overview of what #define does. Have a look at the "Postprocessed" tab to see what the Preprocessor creates from the source.
Example 1:
#define CHANNEL 12345
llOwnerSay((string) CHANNEL); // CHANNEL will be replaced with the literal 12345 stated in the #define above
#define can also take parameters to apply to the replacement code:
Example 2:
#define OS(b,c) llOwnerSay(b+c)
OS("Test","123"); // will expand to: llOwnerSay("Test"+"123")
Example 3: Making strings out of parameters
#define OS(a) llOwnerSay(#a)
OS(1234); // will expand to: llOwnerSay("1234");
Example 4: Using ""##"" to concatenate parameters
#define OS(a,b) llOwnerSay((string) a##b)
OS(1234,5678); // will expand to: llOwnerSay((string) 12345678);
8.2 #undef
Removes a macro previously set up with #define. If the macro was not made in the first place, nothing happens. This is a useful way to enable or disable parts of the source code for debugging. See Section 4 above for an example on how to use it.
8.3 #ifdef and #ifndef, #else and #endif
This command is a part of conditional preprocessing in association with #else and #endif. #ifdef checks if a macro has been previously #defined. It doesn't matter if the macro has actually a value assigned to it. It just needs to be #defined. If it has, all of the code after #ifdef up to #endif or #else gets replaced into the postprocessed code. #ifndef does the exact opposite. If a macro does not exist, the code goes into the Preprocessor.
Example:
#define OWNER_ONLY
...
#ifdef OWNER_ONLY
key var=llGetOwner();
#else
key var=llDetectedKey(0);
#endif
8.4 #if and #elif
These are also conditional preprocessing commands. They take a general condition and pass on the code to the Preprocessor if the condition evaluates to TRUE. They can also be used in conjunction with #else. #elif is the equivalent to else if.
Example:
- define DEBUGLEVEL 2
#if DEBUGLEVEL==1
llOwnerSay("Point reached");
#elif DEBUGLEVEL==2
llOwnerSay("Lots of more data here");
#else
llOwnerSay("Unknown debug level: "+(string) DEBUGLEVEL);
#endif
8.5 #warning and #error
These two commands show a string in the compiler window to warn you about certain problems or to halt compilation immediately due to a fatal error. Right now, both #warning and #error cause the compiler to stop. It is unclear yet if #warning will allow the compiler to continue to completion in the future.
NOTE: If one of these commands is hit by the Preprocessor, the script is NOT saved!
Example 1:
#warning This include file is obsolete!
Example 2:
#error This include file does not work anymore. Please update.
8.6 #include
This is probably the most powerful feature of the Phoenix LSL Preprocessor. It includes whole source code files from your harddisk or from the same folder tree in your inventory into the script you are working on. A small example of this feature can be seen above in Section 4. #include takes a file name relative to the include path set up in your Preferences. You can also include files inside of subfolders. If you are compiling your script from your inventory, the Preprocessor will search the inventory path you are working in, descending into any subfolders, to find the referenced include file. Unused functions and global variable declarations are removed by the Optimizer.
Examples:
#include "command_ids.lsl"
#include "general_functions.lsl"
#include "hud/layout.lsl"
NOTE: Be careful about including files from within #includes. You might #include a file twice, if you are not really keeping track, and this will lead to problems. This issue is usually addressed by using so-called "Include guards". You basically have a conditional compile that sets a #define macro and checks if it's already there. If it's not, #include the contents of the file. If it was set, ignore the contents.
Example:
#ifndef SCRIPT_NAME_LSL
#define SCRIPT_NAME_LSL
your_script_starts_here()
{
}
#endif //SCRIPT_NAME_LSL
These #ifndef, #define and #endif commands make sure that the #include happens only once regardless of how often the file is actually referenced with #include.
9. Known Issues
- Including a file from your hard drive containing a #endif as last line (without linebreak) will not produce a warning about not having a line break but rather a statement error
- This can be fixed by adding a line break to the end of the file
- "Enable Text Compress" will break your script! (At least in its current form. It may be fixed, or removed, in the future.)