Xah Lee, 2007-03.
This page documents some of the problems of the Linden Scripting Language (LSL).
The LSL is a language with a syntax similar to that of C or Javascript. The LSL is supposed to be a high-level scripting language (think of Javascript), in that it supports vector and quaternion arithmetics, and is a interactive, event based language that deals with simulations inside a physics engine, to control characters, movements, building things (architectures, structures, terrains, mechanics ...), controlling weather (or the appearance of it)... inside a virtual world computer simulation, and its users are predominantly “game” players who are not professional programers. However, the language actually lacks many of the qualities of high-level languages. Instead, the programer will have to manually deal with data types declaration/conversion, limit in list length, no nesting of lists or any other flexible data types, no library mechanism, no convenient mechanism in parsing strings (e.g. regex), no dynamic evaluation, etc. The following details some of these problems with code examples.
In high-level languages, variables need not to be declared before use. (e.g. LISP, Mathematica, PHP, Python) Or, the variable needs to be declared but not with a type. (e.g. Javascript) In LSL, variables must be declared, and with type. Here's a example:
vector offset = <0,0,5>; float cubeSide = 2.; string objName = "Xah myObj2"; float rr = 1.;
The type casting follows a style similar in other imperative languages, by prefixing a parenthesized type before a value. It can get painful. Here's a example:
if (p==1) {partyOn(bC,eC, (float) fre, (float) maxAge); llOwnerSay("Okie.");} if (cmd1 == "stat") { llOwnerSay("Spawn rate:" + (string) (1/fre) + "per second\n" + "Bubble age:" + (string) maxAge + "\n" + "Total particles rate per second:" + (string) ( 1/fre * maxAge) ); }
Even in those languages commonly referred to as strictly and statically typed language, often the implicit type casting is more robust than LSL. LSL does not even provide automatic type conversion of numbers (integer, float) to string.
Value must be type casted, and the automatic type casting is not smart.
For example, the “integer” and “float” types are automatically casted into each other, so, one would think there's no difference between “1” and “1.0” in the code since the compiler will automatically convert one to the other. This is not so.
In the following particle system example, if the “1.” is replaced by “1”, the program will still compile but silently fail.
default { state_entry() { llParticleSystem([ PSYS_SRC_PATTERN, PSYS_SRC_PATTERN_EXPLODE, PSYS_SRC_MAX_AGE, 0., PSYS_PART_MAX_AGE, 1., PSYS_SRC_BURST_RATE, 1., PSYS_SRC_BURST_PART_COUNT, 2, PSYS_PART_FLAGS , PSYS_PART_FOLLOW_SRC_MASK ]); } }
The technical excuse is that the compiler do not automatically see types inside a list. (but it does, for example, automatically convert float or integer inside vectors)
List and list processing is a hallmark of high-level languages. In such languages (Perl, Python, Javascript, LISP, Mathematica ...), either there's a general list system where the list elements can be lists themselves with no restriction on type or nesting level or number of items, and or, the language provides many other list types such as keyed lists (aka associative list, hash, dictionary, maps), sets, n-tuples (pairs, vectors, arrays), matrixes, ... etc for specialized purposes or algorithmic properties. LSL provides a list type that can have elements of arbitrary type. (aka heterogeneous list) Other than this, LSL's list facilities is extremely limited. Here are some problems:
List elements in LSL cannot be easily extracted. For example, in high-level languages, list element extraction take a form like this “elem3 = myList[3]” or “elem3 = take(myList,3)” for taking the third element from the list. But in LSL, partly due to the strict requirement of type matching, one has to know the type of the element before extraction, and must use the right function for different types. So, in LSL, to take the element of third index of a list, it would be: “elem3 = llList2Float(myList,3)” or “elem3 = llList2Integer(myList,3)” or any one of the following other functions depending on the type of the element to be extracted: llList2Key, llList2String, llList2List, llList2Rot, llList2Vector.
The need to know the type in advance to call the right function to extract a element of a list severely restricts the usefulness of heterogeneous lists. (llGetListEntryType is provided for finding out the type of a element)
LSL list has a limitation in length, in the sense that a programer cannot start with a list with more than 72 items. Here's a quote from the official LSL wiki ↗:
«A list can grow dynamically as large as needed during execution, and is only limited by the amount of memory that script has available. However, there is a 72 element limit to lists defined at compile time.»
LSL does not provide a keyed-list (aka: associative list, Hash, Dictionary, Map), nor any other data structure that is nested list in nature. (e.g. trees, matrices) Some of these limitations can be worked around. For example, a list can contain element of type vector, and vectors's parts can be accessed individually. This provides a form of a rectangular nested list (a matrix), with elements being floats. For a keyed list, one can work around by building a flat list of alternating keys and values. But overall, the lack of flexible list structure is a severe limitation.
In interactive or artificial intelligence applications, such as interactive visualization systems, computer algebra system, interactive human language processing system, knowledge-base system, etc., it is often required that the language can evaluate code in a dynamic way. Namely, that the language should be able to construct a snippet of code on the fly and evaluate that code as part of its computation. (Perl, Python, Javascript, ... have the function “eval()”, and LISP and Mathematica take the step further by dealing with symbols directly.) Second Life's virtual world environment is a highly interactive environment, where objects interact with users inside the virtual world at all times. The interaction may be in the form of touching, collision, proximity, or “spoken” (typed) commands from nearby users. However, LSL does not provide dynamic evaluation ability. This severely limit the power of LSL. Here's a example:
In Second Life, the standard method for a object to react to commands from a user is by listening to what the user types. For example, i may create a object that when a user types “c1”, where c1 is any one of a list of color names such as “pink, orange, brown, purple, gold”, then the object will change its color accordingly. In the source code, i may define these colors:
vector pink= <1,0.07843,0.5765>; vector orange= <1,0.6471,0>; vector brown= <0.5451,0.1373,0.1373>; vector purple= <0.3333,0.102,0.5451>; vector gold= <1,0.8431,0>;
And suppose the user's command string is parsed, so that i have a variable named “cmd” with value of a string “"pink"”. Now, it would be nice, if i can simply evaluate this as a variable, to obtain the actual RGB vector value for color. Typically, something like “myRGB = eval("red")”. But LSL does not provide any dynamic evaluation abilities.
To work around this, a keyed-list for the colors may be used. For example, “colorList=[pink:rgb1,orange:rgb2,...]” and “myRGB=getValFromKey("red",colorList)”. However, LSL does not provide keyed-list neither. To work around that, perhaps a programer can implement a keyed-list by having a list who's elements are lists of 2 elements, but LSL doesn't allow nested list neither. Nor does LSL provides any sort of Object Oriented programing system, so that a color list can be stored as a object with properties, and individual values can be retrieved by accessor methods. All in all, this makes programing in LSL fairly tedious and severely limits what it can do.
The low-level language's concept of bit mask is heavily employed in LSL. For example, it is used for the parameter PSYS_PART_FLAGS in the function llParticleSystem, as well for a Second Life 3D prim's “param”, as used in on_rez, llRezObject, and many related functions dealing with prims.
Often, a function will need to take many True/False parameters. For example, suppose i have a function that can draw a rainbow, and each color of the rainbow can be turned on or off individually. My function specification can be of this form: “rainbow(red, orange, yellow, green, blue, violet, purple)”. Each parameter is a true or false value. So, to draw a rainbow with only red and yellow stripes on, one would code, for example “rainbow(t,f,t,f,f,f,f)”, where “t” stands for true and “f” stands for false. (or, similar values for the true/false of the language's boolean system)
The problem with this simple approach is that when a function has many parameters, “which position means what” becomes difficult to remember and manage. Alternatively, a high-level language may provide a system for named parameters. So, for example, the function may be called like this with 2 arguments “rainbow(red:t, yellow:t)”, meaning, give the true values to the parameters named “red” and “yellow”. Parameters not given may automatically assumed to have false values by default. Similarly, the language can simply have the function call look like this: “rainbow(red, yellow)”, where omitted parameter names simply means false.
LSL deals with this issue by using a concept of bit-mask that came from low-level languages. From the programer's point of view, the way to call this rainbow function would look like this: “rainbow(red|yellow)”. On the surface, it seems just a syntax variation. But actually, the “red” and “yellow” here are global constants of type integer, defined by the language, and the “|” is actually a bit-wise binary operator. To explain this to a educated person (e.g. a mathematician) but who are not a professional programer, it gets a bit complex as one has to drag in binary notation, boolean operation on binary notation realized as a sequence of slots, and the compiler ease in processing numbers as binary digits, and the compiler writer and language designer's laziness in resorting to these instead of a high-level interface of named parameters.
The hack of using the so-called bit-mask as a interface for functions that need named parameters, is similar to languages using “1” and “0” as the true/false symbols in its boolean system, and languages using the “or” operator “||” as a method of nested “if else” program flow constructs. The problem with these hacks, is that they jam logically disparate semantics into the same construct. Their effects is making the language more difficult to learn, source code more difficult to read, and thus increased programer error and maintaince.
In most languages, there is a library system, where the programer can define a set of functions in a file for a particular purpose. This file can be loaded into the main program, as to make the functions available for use in the main program. This mechanism is variously known as a library, module, package. This allows the programer to build code libraries that can be reused or distributed.
In LSL, there is no library system. Every function must be defined inside a script. A script cannot call functions that are inside another file (and there is a limit on a script's file size).
This makes it difficult to build any substantial program in LSL. For example, one might want to write a chess-playing machine in Second Life. Since Second Life is a virtual world, a chess playing machine in Second Life is ideal, since in this virtual world system the programer can actually create pieces that moves by themselves, or animate human players moving chess pieces. However, because the lack of a library system, combined with the primitive nature of LSL, the cumbersomeness to create a substantial algorithm such as chess playing code, makes it practically impossible.
As a virtual world environment, there are huge potential on what computer programs could emulate in this world. For example, in the field of language translation, voice recognition and synthesizing systems, music synthesizers, robotics, vision systems, 3D cellular automatons. A primitive language with no library system makes it practically impossible to create substantial algorithm or programs.
(LSL does provide communication functions via HTTP to outside servers. This is intended so that LSL programs only act as simple programs or interface, while programs requiring intensive computation (such as a chess engine) should be done by servers outside of the sim. However, this is not a excuse for not providing a library system.)
LSL does not allow any computations outside of state blocks. For example, the following code is a syntax error. However, if the “2*PI” is replaced by “PI”, then it will compile.
float uMax = 2*PI; default { state_entry(){} }
LSL does not allow any code inside a state block but outside a event handler block. For example, the following code is a syntax error. However, if the “integer i=0;” line is moved inside state_entry, then it is OK.
default { integer i=0; state_entry(){} }
...
....
It turns out, that LSL is rather very primitive. For example: “integer i = 3 -1;” is a syntax error, but “integer i = 3 - 1;” is OK.
LSL does not have functions like “isFloat()” that determines the type of a variable/value.
LSL has llAbs() for absolute value, but only works for integers. It has llFabs() if the argument is a float. This is incredibily stupid in a high-level language.
See also:
Page created: 2007-03. © 2007 by Xah Lee.