Advanced Language Features
Like other languages that share C-like syntax, blocks and scope are fundamental to understanding how to write good code in LSL. Generally speaking, in LSL a code block is any code set off by braces within a global function or an event handler, as shown here:
string GlobalString = "Hi!"; // this global variable is // visible anywhere // in the LSL program integer factorial(integer n) // this is a user-defined // function more on them // below { // this brace begins the code // associated with // the function factorial string local = "Bye!"; // a local variable is // visible in the block it is // created in and any new // blocks created within that // block llSay(0, GlobalString); // global variables can be // used anywhere in the LSL // program llSay(0, local); // local is available anywhere // inside the factorial if (n < 3) { // the if statement creates another code block float pi = 3.14; // pi is only available // inside the if block llSay(0, local); // local is available // anywhere inside factorial // including inside new // blocks created within it llSay(0, (string)pi); // this works since we're // still inside the block return n; // the return statement // jumps back to the calling // function or event handler } // end of the if block else { // the else clause creates another block float e = 2.71828; // e is only available inside // the else block llSay(0, (string)pi); // ERROR!! We aren't in the // block that made pi // so this is an error! return n*factorial(n - 1); // this is recursion, // more later } // end of the else block llSay(0, local); // local is available // anywhere inside factorial } // end of the factorial block default { // states create scope in the sense that they // determine which event handlers will // be called state_entry() { // this starts a block in state_entry() integer num = 1; // the variable do_something // is a local if (num) num = 4; // even though there are no braces, // this line is actually a code block llSay(0, GlobalString); // global variables may //be used anywhere in the // LSL program llSay(0, (string)factorial(num)); // user-defined functions may be // called form any event handler. // This will say "24". } // end of the state_entry block } // end of the default state
There is a lot going on in the simple example program. Although it is a rather silly bit of code, it shows where blocks begin and end, and introduces the idea of scope. For variables and functions, scope defines when they may be called or used. Within LSL, there are two levels of scope: global and local.
Global scope applies to user-defined functions and global variables. Both global variables and user-defined functions may be used anywhere in an LSL program; thus they are considered to be "globally accessible" and therefore of global scope. In the preceding example, the function factorial and the string GlobalString are globals.
Local scope applies to variables created within code blocks (there are no local functions in LSL). As the example shows, functions, event handlers, and flow control create blocks, and these blocks may have further blocks nested within them. The scope for a local variable is the code block it was created within, as well as any new blocks created within that block. Consider this example:
{ // a code block integer true = TRUE; list foo = ["I", "am", "a", "list"]; // a local list if (true) { // a new code block foo = []; // we can still see foo, //so assigning an empty // list to it } }
Note that these scope rules can allow some really bad coding habits, like in the following example:
{ float pi = 3.14; if (pi) // if a floating point value // is used in an // expression, it is FALSE // if it is exactly // equal to 0.f, // and TRUE for any other value // This is a bad habit, // since floating point // values are often near to 0.f // but not exactly // equal to 0.f. { // start of if code block integer pi = 3; // this local pi has the same name // as pi from // the earlier scope and // is said to "shadow" // the other variable. llSay(0, (string)pi); // Which pi will be used? } // end of the if block else { // start of the else block string pi = "3.1415"; // Ack! Another pi llSay(0, (string)pi); // Now which pi will be // used? } }
Please do not ever write code like this! Although it compiles and (if you are very fortunate) may even work, it will cause you_or anyone you share your code with -- headaches and confusion.
Now that you understand scope, let us turn to functions. In the last section we introduced global functions, the first of the two types of functions in Second Life. The second type, library functions, will be discussed shortly. User-defined functions allow you to create blocks of code that perform a specific task or calculation. They are placed above the default state, either before or after global variables, and are written as follows:
type function_name(type parameter1, type parameter2, . . .) { // do something in here }
The type is the function's return type, which means that if you want the function to return a value after it is called, you need to specify the type. For example, if you wanted to take the absolute value of a float, you could write the following function:
float fabs(float num) { if (num > 0.f) // already a positive number, just return it return num; // the return command returns the value of // the expression that follows it else return -num; // the negation operator returns the -1*num }
This would be used as follows:
float negone = -1.f; float posone = fabs(negone); // passes -1.f to fabs, returns 1.f llSay(0, (string)posone); // will say "1.f"
Notice that fabs takes one parameter of type float. Functions can have any number of parameters (including zero). So, the following are all legal formats for naming a function:
do_something() string do_something_else() vector do_something_too(string target) list do_something_now(integer number, rotation rot1, rotation rot2)
Two important features of LSL functions are that their parameters are passed by value and they support recursion. To understand the concept of pass by value, look at the following function:
integer add_one(integer n) { n = n + 1; return n; } integer number = 10; number = add_one(number);
So, what is the value in number? As you would hope, the value is 11. This is because when number is passed into add_one, it is passed in as its value, 10. Thus, any operations on n within the function are acting on a local copy, not on the variable number. Generally, this is what you want to have happen, but it means that in order for your function to return a value, you have to use the return command. If you had written add_one as follows, the number would still be 10:
add_one(integer n) { n = n + 1; } integer number = 10; add_one(number);
User-defined functions are covered in considerable detail at http://lslwiki.com/lslwiki/wakka.php?wakka=UserDefinedFunction.
The second type of function in LSL is library functions (Figure 8.4). They are built-in functions that are there to perform common tasks or to provide functionality that would be difficult to write in LSL directly. More than 300 functions are built into LSL, and more are being added regularly.) For a comprehensive description of all of the functions, check out http://lslwiki.com/lslwiki/wakka.php?wakka=functions.) It is important to recognize that LSL functions operate just like the user-defined functions. They are available within any event handler or user-generated function, their arguments are passed by value, and they may or may not return a value. One additional aspect is that some of them have a delay value associated with them. This delay exists to protect Second Life from certain types of abuse.
Figure 8.4: Libraryfunction list