Difference between revisions of "LSL Style Guide"

From Second Life Wiki
Jump to navigation Jump to search
m (Fixing a derp on the internal link to the conditionals category.)
(→‎Script Structure: Variables do not need to come before UDFs; mention event handlers as a mandatory part of states)
 
(10 intermediate revisions by 8 users not shown)
Line 1: Line 1:
==Script Structure==
{{LSL Header|ml=*}}{{RightToc}}
LSL scripts are comprised of expressions, functions, statements, event handlers and states. The LSL compiler mandates a certain structure to scripts:


Every major open-source project has its own style guide: a '''set of conventions''' (sometimes arbitrary) about how to write code for that project. It is much easier to understand a large codebase when all the code in it is in a '''consistent style'''. These guidelines, referred to collectively as a Style Guide, are not as rigid as the rules required by the language compiler but nonetheless are critical to creating maintainable code. The most critical aspect of a style is that you apply it consistently to the code you write.


==== [[LSL_Variables|Global Variables]] ====
Effective programming in [[LSL Portal|LSL]] requires that developers use a disciplined approach towards formatting and other conventions in their scripts.


Global variables define and can contain data which can be written to or read from in multiple events and states within a script.
{{LSL Tip| Applying a style guide to your code does not only help others read your code. '''It helps you as well''', because in future when you return to the code you wrote before, you'll appreciate reading formatted and annotated code.}}
Here's an example of a simple script which uses global variables to store information in a state_entry event and allow it to be used in a second touch event.
<lsl>
key ownerkey; //Here we start by defining a global variable by the name of "ownerkey" and specify that it stores a key (UUID)


string ownername; //Next we define another global variable which stores the username of the owner of the prim containing this script and specify it is storing a string
== Use third party editors: ==


integer reportonchannel = 0; //In this case we are defining a global variable which stores the numerical channel we will be using later, but in this case we give it a value at the start.
There are many [[LSL Alternate Editors|third party editors]] with [[LSL Portal|LSL]] syntax files available. See an example below of what your workflow would look like using an editor that has syntax highlighting and autocomplete and also applies an indent style to your code.
//As with the last 2, this can be written to but will revert to the specified value when the script is reset.


Using [[LSL Alternate Editors|third party editors]] has a few advantages over using the editor in the viewers:
* Autocompletion helps you avoid typos.
* Autocompletion sometimes not only does a linear autocompletion as in autocompleting <code>llS</code> to <code>llSay</code> but sometimes works even with fuzzy search by autocompleting <code>llslppf</code> to <code>llSetLinkPrimitiveParamsFast</code>.
* The syntax highlighting file colors the code exactly like the code you see in the official Linden Lab viewer making you not having to learn or adapt to a different coloring scheme.
* Autocompletion might also preformat code with indent styles when autocompleting states, events, conditional clauses etc.


default
== [http://en.wikipedia.org/wiki/Indent_style Indent styles]: ==
{
state_entry() //This event is triggered when entering the default state (When the script first runs or if moving to the default state from a user state)
{
ownerkey = llGetOwner(); //Stores the key of the owner of the prim containing this script to the "key ownerkey" global variable
ownername = llGetUsername(ownerkey); //Here we use that newly stored key to find the username of the account associated with the key in "key ownerkey"
//Note that since we have already defined ownerkey and ownername as a key and string respectively, we do not need to specify what type of data they store, unlike local variables
}


touch_start(integer num_detected) //This event is triggered when someone click the prim this script contains (If it's on click action is set to touch in the build floater)
Most people, when they start programming on their own, will have programs that are UGLY to look at - to put it nicely, sometimes mistakenly thinking that such scripts occupy less space when compiled. Such scripts typically look like the following:
{
string ownerkeyasastring = (string)ownerkey; //Let's prepare the key for use in a chat message. llSay requires strings but ownerkey is currently a string.
//Note that this is an inefficent method of preparing data stored in a variable to another type of data.
//Typically this would be done with typecasting in the intended function it is to be used in, but for the purpose of keeping this example easy to follow, I'll use this method.
llSay(reportonchannel,"I am owned by "+ownername+" and their UUID is "+ownerkeyasastring);
//When clicked, the prim will speak on the channel specified in "integer reportonchannel" and say the information we found in the previous event.
}
}
</lsl>


==== [[User-defined_functions|User Defined Functions]] ====
{| width="100%" {{Prettytable}}
|- {{Hl2}}
! '''unstyled source code'''
|-
||
<syntaxhighlight lang="lsl2">default {state_entry(){llSay(0,"Hello World.");}}</syntaxhighlight>
|}


[[User-defined_functions|User defined functions]] follow imediatly after global variables in a script and before the default state. [[User-defined_functions|User Defined Functions]] allow for chunks of code that are repeated often throughout a script to be replaced with a one line function that acts in the same way as standard functions (Linden Library Functions).
However, that code is difficult to read (or at least to '''follow''') - even more so when one is writing a couple hundred lines program. There are several [http://en.wikipedia.org/wiki/Indent_style indent styles] with the two most common styles being:
These are useful when you want to keep your script bloat to a minimum by only having to write that chunk a single time and substitute other instances of it with a single easy line throughout the script.
Here's an example of our earlier script, this time making use of a custom function to replace the functions seen in our state_entry event.
<lsl>
key ownerkey;
string ownername;
integer reportonchannel = 0;


RetrieveOwnerData() //Here we create the user-defined function with the name "RetrieveOwnerData".
{| width="100%" {{Prettytable}}
|- {{Hl2}}
! '''[http://en.wikipedia.org/wiki/Indent_style#K.26R_style K&R style] (used in Javascript based languages)'''
! '''[http://en.wikipedia.org/wiki/Indent_style#Allman_style Allman style] (used in C based languages)'''
|-
||<syntaxhighlight lang="lsl2">
default {
    state_entry() {
        llSay(0, "Hello World.");
    }
}
</syntaxhighlight>
||
<syntaxhighlight lang="lsl2">
default
{
{
ownerkey = llGetOwner();
    state_entry()
ownername = llGetUsername(ownerkey);
    {
//Notice how we took the contents of our earlier state_entry() and placed it in a function.
        llSay(0, "Hello World.");
//When the new function is called, this code will be ran.
    }
}
}
</syntaxhighlight>
|}
The '''K&R style''' conserves space - IN THE SOURCE CODE ONLY, however the '''Allman style''' is easier to read and easier to visually bracket-match. Once a scripter is in the practice of using a particular style, reading code in that style will become easier.
Consistent indenting makes reading both styles easier that's why it '''really comes down to whatever works for you'''. In both methods, indenting is the key indicating factor of levels of scope.


== Naming conventions: ==


default
There are many naming conventions in Second Life. Only the most used ones will be listed below.
{
 
state_entry()
 
{
Global Variables (variables used throughout the entire program) should be lowercase. For example:
RetrieveOwnerData(); //Here we call the function just like we would a normal LL function.
 
//Using a user-defined variable only once in a script is inefficient and shouldn't be used in place of just putting the code in normally if not being used multiple times.
<syntaxhighlight lang="lsl2">
//As for the point at which you deem it necessary? That's personal preference, but personally I go with if I am repeating code 3 or more times in a script.
integer index = 0;
}
string  name  = "Please set one";
</syntaxhighlight>
 
Others prefer to distinguish between global and local variables by prefixing variable names with a character, defining globals in the following manner:
 
<syntaxhighlight lang="lsl2">
integer gIndex;
string  gName  = "Please set one";
</syntaxhighlight>
 
Constant variables should be in ALL CAPS following the style guide used by Linden Labs. For example:
 
<syntaxhighlight lang="lsl2">
integer DIALOG_CHANNEL = -517265;
vector  RED            = <1.0, 0.0, 0.0>;
</syntaxhighlight>
 
Arguments used within a [[Category:LSL User-Defined Functions|user defined function]] or one of the standard [[Event|events]] should be named with '''easily readable and meaningful''' names. When using [[Event|events]], please use the standard names as listed here on the official wiki. An [[Event|overview of all events can be found here]]. Below is an example as can be found for the [[Listen|listen event]]:
 
<syntaxhighlight lang="lsl2">
// ...
 
    listen(integer channel, string name, key id, string message)
    {
        key OwnerKey = llGetOwner();
   
        if (channel == 1 && id == OwnerKey)
            llOwnerSay("Hello Avatar");
    }
 
//  ...
</syntaxhighlight>
 
== Separating code: ==
Some people put too many function calls on one line. As shown in this example, too many function calls on a single line makes the code hard to read and almost impossible to debug.
 
<syntaxhighlight lang="lsl2">
list    lst;
integer NUM_DIGITS = 10;


touch_start(integer num_detected)
default {
{
    touch_start(integer n) {
string ownerkeyasastring = (string)ownerkey;
        string name = llToLower(llGetSubString(llList2String(llParseString2List(llDetectedName(0), [" "], []), 0), 0, NUM_DIGITS - 1));
llSay(reportonchannel,"I am owned by "+ownername+" and their UUID is "+ownerkeyasastring);  
        if (!~llListFindList(lst, (list)name))
}
            lst += name;
//      send a message to the owner, only reaches owner if online and within the same sim
        llOwnerSay(llList2CSV(lst));
    }
}
}
</lsl>
</syntaxhighlight>
 
Now here is the code, with the exact same features, in a simpler way. While hardly anyone could tell you what the above code did, almost everyone can figure out what the below code does.


==== [[default|Default State]] ====
<syntaxhighlight lang="lsl2">
Next up we create our first [[state]]. This comes after user-defined functions and global variables.
list    listOfStrings;
In LSL, a state refers to a the container of events that trigger functions in the script. A script when it first starts will always exist in the initial [[default]] state which is a prerequisite for any LSL script. Scripts can contain multiple states, but only one can be active at any one time and only events that are handled in the active state will trigger functions in the script. A state can only contain one of each [[Category:LSL_Events|event handler]] (such as [[touch_start|Touch_start]] or [[state_entry|State_entry]]).
integer NUM_DIGITS = 10;
See below for an example of a simple script which says hello when clicked for an example of the default state:
<lsl>
default //All LSL scripts must contain a state named "default" and this must be the first state defined in your script.
{
touch_start(integer num_detected)
{
llSay(0,"I was clicked! I'm in the default state!");
}
}
</lsl>


==== [[state|User-Defined States]] ====
Lastly, we include any additional states we want in a script. User-Defined states allow for multiple instances of an [[Category:LSL_Events|event handler]] while effectively turning off other undesired events.
It should be noted that in most cases, you would want to use [[Category:LSL_Conditional|Conditionals]] or [[Timer|timers]] in place of a state but the occasional use still does show up time to time.
Below is our previous script used in the default state example with a user-defined state added.
<lsl>
default  
default  
{
{
state_entry() //This event is triggered when we are entering from another state
    touch_start(integer n)  
{
    {
llSay(0,"I have entered the default state!");
        string  name      = llDetectedName(0);
}
        list    nameAsList = llParseString2List(name, [" "], []);
touch_start(integer num_detected)
        string  firstName  = llList2String(nameAsList, 0);
{
        string  startPart  = llToLower(llGetSubString(firstName, 0, NUM_DIGITS - 1));
llSay(0,"I was clicked!");
        integer index      = llListFindList(listOfStrings, [startPart]);
state two; // This instructs the script to switch to "state two" and stop processing events in the default state.
 
}
        if (index == -1)
            listOfStrings += startPart;


state_exit() //This event is triggered when we are changing to another state
//     send a message to the owner, only reaches owner if online and within the same sim
{
        llOwnerSay(llList2CSV(listOfStrings));
llSay(0,"I am leaving the default state!");
    }
}
}
}
</syntaxhighlight>
LSL lacks an optimizing compiler. For this reason, it may be necessary to balance the two styles to get faster, more compact executable code. Line combination optimization should only be done after the code is working & bug free. Improper optimization can lead to wrong results. Always test optimized code thoroughly.
==Script Structure==
LSL scripts are comprised of expressions, functions, statements, event handlers and states. The LSL compiler mandates a certain structure to scripts:


state two
#User Defined Variables and Functions (see [[LSL Variables]] and [[User-defined functions]])
{
#[[default]] State  (see [[State]]) with at least one [[LSL Events|Event Handler]].
state_entry() //This event is triggered when we are entering from another state
#User Defined States, each with at least one Event Handler.
{
llSay(0,"I have entered state two!");
}


touch_start(integer num_detected)
==LSL Portal Style==
{
llSay(0,"I was clicked! I'm in the second state!");
state default; // This instructs the script to switch back to the default state and stop processing events in "state two".
}


state_exit() //This event is triggered when we are changing to another state
Here in the [[LSL Portal]] we expect consistent white-spacing, and it should look similar to either '''K&R''' or '''Allman'''. We do not require strict adherence to either. We do not use tabs (as they take up an exorbitant amount of screen space) but some number of spaces, usually between 2 and 6, 4 being the most popular value and the value you will see almost everywhere. Generally speaking, it's taboo to edit an article just to fix the white-spacing, unless it's inconsistent (the edit should make it consistent, it should not change the style). Generally speaking, it's taboo to edit an article to change the style. Exceptions do exist, if you are making substantial changes to most of the code in the article, and it makes sense to change the style of the rest of the code (such that it all matches), you may do so. The purpose of this restriction is to keep the peace, some people are really quite attached to their style. Considering that style is a personal preference of little consequence, nothing is gained by forcing one style onto the community, especially when they will go ahead and use whatever style they want disregarding what we mandate. When in doubt or conflict please use the discussion page, a majority are monitored and will be responded to.
{
llSay(0,"I am leaving state two!");
}
}
</lsl>


Take great care when changing states in a script! Changing a state during an if or some loops can have adverse effects! Make sure to read up on [[state|states]] for more information.
If you find yourself desiring to make small changes to white-space despite it being frowned upon, we will ask you to first consider adding an example to one of the articles that [[:Category:LSL_Needs_Example|needs an example]] or is missing some [[:Category:LSL FixMe|key content]].

Latest revision as of 04:52, 12 June 2022

Every major open-source project has its own style guide: a set of conventions (sometimes arbitrary) about how to write code for that project. It is much easier to understand a large codebase when all the code in it is in a consistent style. These guidelines, referred to collectively as a Style Guide, are not as rigid as the rules required by the language compiler but nonetheless are critical to creating maintainable code. The most critical aspect of a style is that you apply it consistently to the code you write.

Effective programming in LSL requires that developers use a disciplined approach towards formatting and other conventions in their scripts.

KBcaution.png Important: Applying a style guide to your code does not only help others read your code. It helps you as well, because in future when you return to the code you wrote before, you'll appreciate reading formatted and annotated code.

Use third party editors:

There are many third party editors with LSL syntax files available. See an example below of what your workflow would look like using an editor that has syntax highlighting and autocomplete and also applies an indent style to your code.

Using third party editors has a few advantages over using the editor in the viewers:

  • Autocompletion helps you avoid typos.
  • Autocompletion sometimes not only does a linear autocompletion as in autocompleting llS to llSay but sometimes works even with fuzzy search by autocompleting llslppf to llSetLinkPrimitiveParamsFast.
  • The syntax highlighting file colors the code exactly like the code you see in the official Linden Lab viewer making you not having to learn or adapt to a different coloring scheme.
  • Autocompletion might also preformat code with indent styles when autocompleting states, events, conditional clauses etc.

Indent styles:

Most people, when they start programming on their own, will have programs that are UGLY to look at - to put it nicely, sometimes mistakenly thinking that such scripts occupy less space when compiled. Such scripts typically look like the following:

unstyled source code
default {state_entry(){llSay(0,"Hello World.");}}

However, that code is difficult to read (or at least to follow) - even more so when one is writing a couple hundred lines program. There are several indent styles with the two most common styles being:

K&R style (used in Javascript based languages) Allman style (used in C based languages)
default {
    state_entry() {
        llSay(0, "Hello World.");
    }
}
default
{
    state_entry()
    {
        llSay(0, "Hello World.");
    }
}

The K&R style conserves space - IN THE SOURCE CODE ONLY, however the Allman style is easier to read and easier to visually bracket-match. Once a scripter is in the practice of using a particular style, reading code in that style will become easier.

Consistent indenting makes reading both styles easier that's why it really comes down to whatever works for you. In both methods, indenting is the key indicating factor of levels of scope.

Naming conventions:

There are many naming conventions in Second Life. Only the most used ones will be listed below.


Global Variables (variables used throughout the entire program) should be lowercase. For example:

integer index = 0;
string  name  = "Please set one";

Others prefer to distinguish between global and local variables by prefixing variable names with a character, defining globals in the following manner:

integer gIndex;
string  gName  = "Please set one";

Constant variables should be in ALL CAPS following the style guide used by Linden Labs. For example:

integer DIALOG_CHANNEL = -517265;
vector  RED            = <1.0, 0.0, 0.0>;

Arguments used within a or one of the standard events should be named with easily readable and meaningful names. When using events, please use the standard names as listed here on the official wiki. An overview of all events can be found here. Below is an example as can be found for the listen event:

//  ...

    listen(integer channel, string name, key id, string message)
    {
        key OwnerKey = llGetOwner();
    
        if (channel == 1 && id == OwnerKey)
            llOwnerSay("Hello Avatar");
    }

//  ...

Separating code:

Some people put too many function calls on one line. As shown in this example, too many function calls on a single line makes the code hard to read and almost impossible to debug.

list    lst;
integer NUM_DIGITS = 10;

default {
    touch_start(integer n) {
        string name = llToLower(llGetSubString(llList2String(llParseString2List(llDetectedName(0), [" "], []), 0), 0, NUM_DIGITS - 1));
        if (!~llListFindList(lst, (list)name))
            lst += name;
//      send a message to the owner, only reaches owner if online and within the same sim
        llOwnerSay(llList2CSV(lst));
    }
}

Now here is the code, with the exact same features, in a simpler way. While hardly anyone could tell you what the above code did, almost everyone can figure out what the below code does.

list    listOfStrings;
integer NUM_DIGITS = 10;

default 
{
    touch_start(integer n) 
    {
        string  name       = llDetectedName(0);
        list    nameAsList = llParseString2List(name, [" "], []);
        string  firstName  = llList2String(nameAsList, 0);
        string  startPart  = llToLower(llGetSubString(firstName, 0, NUM_DIGITS - 1));
        integer index      = llListFindList(listOfStrings, [startPart]);

        if (index == -1)
            listOfStrings += startPart;

//      send a message to the owner, only reaches owner if online and within the same sim
        llOwnerSay(llList2CSV(listOfStrings));
    }
}

LSL lacks an optimizing compiler. For this reason, it may be necessary to balance the two styles to get faster, more compact executable code. Line combination optimization should only be done after the code is working & bug free. Improper optimization can lead to wrong results. Always test optimized code thoroughly.

Script Structure

LSL scripts are comprised of expressions, functions, statements, event handlers and states. The LSL compiler mandates a certain structure to scripts:

  1. User Defined Variables and Functions (see LSL Variables and User-defined functions)
  2. default State (see State) with at least one Event Handler.
  3. User Defined States, each with at least one Event Handler.

LSL Portal Style

Here in the LSL Portal we expect consistent white-spacing, and it should look similar to either K&R or Allman. We do not require strict adherence to either. We do not use tabs (as they take up an exorbitant amount of screen space) but some number of spaces, usually between 2 and 6, 4 being the most popular value and the value you will see almost everywhere. Generally speaking, it's taboo to edit an article just to fix the white-spacing, unless it's inconsistent (the edit should make it consistent, it should not change the style). Generally speaking, it's taboo to edit an article to change the style. Exceptions do exist, if you are making substantial changes to most of the code in the article, and it makes sense to change the style of the rest of the code (such that it all matches), you may do so. The purpose of this restriction is to keep the peace, some people are really quite attached to their style. Considering that style is a personal preference of little consequence, nothing is gained by forcing one style onto the community, especially when they will go ahead and use whatever style they want disregarding what we mandate. When in doubt or conflict please use the discussion page, a majority are monitored and will be responded to.

If you find yourself desiring to make small changes to white-space despite it being frowned upon, we will ask you to first consider adding an example to one of the articles that needs an example or is missing some key content.