Difference between revisions of "How to make writing LSL scripts easier"

From Second Life Wiki
Jump to navigation Jump to search
Line 1: Line 1:


'''This article is currently being worked on.'''
'''This article is currently being worked on.'''




==How to make writing LSL scripts easier==
==How to make creating LSL scripts easier==




Line 12: Line 11:
===About===
===About===


This article is about tools you can use to make creating LSL scripts
This article is about tools you can use to make creating LSL scripts easier.  It tries to explain how to combine and how to use them for this very purpose.
easier.  It tries to explain how to combine and use them for this very
 
purpose.
One of these tools is a preprocessor called cpp.  It comes with [http://gcc.gnu.org/ GCC].  Among the tools mentioned in this article, cpp is the tool which can make the biggest difference to how you write your sources and to how they look.
 
 
The idea of using cpp comes from the [http://opengate.ma8p.com/open9.tar.gz source] of the [http://ma8p.com/~opengate/ Open Stargate Network] stargates.
 
I want to thank the developers of these stargates.  Not only their stargates are awesome.  The idea of using cpp for LSL scripts is also awesome.  It has made it so much easier to create LSL scripts that I have made many I otherwise would never have made.
 
 
This article is accompanied by a [https://github.com/Ratany/lsl-repo git repository]. It is recommended that you pull (download) the repository.  The repository contains some example sources used with this article, a few perl scripts and a Makefile.  They are organised in a directory structure which they are designed to work with.  You will have your own build system for LSL scripts when you have pulled this repository.
 
The repository does not contain basic standard tools like perl, cpp, make or an editor.  This article does not attempt to tell you how to install these.  You know better how to install software on your computer than I do, and how you install software also depends on which operating system you are using.


The idea of using cpp comes from the
I´m using Linux, which is freely available in many different distributions.  I´m using [http://fedoraproject.org/ Fedora], so to install [http://www.gnu.org/software/emacs/ emacs], I run 'yum install emacs'If you don´t use Fedora, telling you this probably doesn´t help you.  Or you switch to Fedora; it works remarkably well.
[http://opengate.ma8p.com/open9.tar.gz source] of the
[http://ma8p.com/~opengate/ Open Stargate Network] stargatesIt´s
not my idea, I´m merely using it.


I want to thank the developers of these stargates.  Not only their
Documentation is plentifulYou may find different software that does the same or similar things and may work better for you or not.
stargates are awesome.  The idea of using cpp for LSL scripts is also
awesomeIt has made it so much easier to create LSL scripts that I
have made many I otherwise would never have made.


===Editing this Article===
===Editing this Article===


This article is written in my favourite editor.  When I update the
This article is written in my favourite editor.  When I update the article on the wiki, I edit the whole article, delete everything and paste the new version in.  That´s simply the easiest and most efficient way for me.
article on the wiki, I edit the whole article, delete everything and
paste the new version in.  That´s simply the easiest and most
efficient way for me.


Unfortunately, this means that your modifications may be lost when I
Unfortunately, this means that your modifications may be lost when I update the article and don´t see them.
update the article and don´t see them.


'''Please use the "discussion" page of this article to post contributions or to suggest changes so they can be added to the article.'''
'''Please use the "discussion" page of this article to post contributions or to suggest changes so they can be added to the article.'''


===Tools===
===Repository===
 
It´s probably more fun to be able to try out things yourself while reading this article.  If you want to do so, pull the [https://github.com/Ratany/lsl-repo repository].  You can run 'git clone https://github.com/Ratany/lsl-repo.git' or [https://github.com/Ratany/lsl-repo/archive/master.zip | download] it as an archive.
 
Once cloned or downloaded and unpacked, you have a directory called "lsl-repo".  Inside this directory, you have another one called "projects".


This section tries to give an overview of some tools you can use.  It
You can put the "projects" directory anywhere you like, and you can give it a different name if you want to.
does not tell you how to install them.


It´s your computer you´re reading this article with, and you know
Inside the "projects" directory, you have more directories like this:
better how to install software on your computer than I do.  How you
install software also depends on what operating system you are using:


I´m using Linux, which is freely available in many different
distributions.  I´m using [http://fedoraproject.org/ Fedora], so to
install [http://www.gnu.org/software/emacs/ Emacs], I run 'yum install
emacs'.  If you don´t use Fedora, telling you this probably doesn´t
help you.  Or you switch to Fedora; it works remarkably well.


====Editor====
.
!-- bin
!-- clean-lsl.sed -> make/clean-lsl.sed
!-- example-1
!-- include
!-- lib
!-- make
!-- memtest
!-- rfedip-reference-implementation
!-- template-directories
`-- your-script


To create scripts, you do need an editor.  All sl clients I´ve seen
have one built in, and it´s the worst editor I´ve ever seen.
Fortunately, you don´t need to use it.


You can use any editor you like.  You probably want an editor that at
When you rename or move "bin", "include", "lib", "make" or "clean-lsl.sed", your build system will not work anymore (unless you edit some files and make adjustments). Don´t rename or move them.
least gets the indentations right and has useful functions like
'indent-region', 'sort-lines', 'goto-matching-fence',
'whitespace-cleanup' etc., lets you define macros and write your own
functions, highlights matching brackets, supports code-folding and
tags, does customizable syntax highlighting, lets you edit several
files at once, restores them when you restart the editor and has
something like 'auto-revert-mode' so you can read the debugging
messages of your scipts which they write into your chat.txt --- plus
lots of other things like regexp search/replace that don´t come to
mind at the moment.


The built-in script editor is useful for replacing the scripts you
'''Do not use names for directories or files that contain special characters or whitespace.''' Special characters ''may'' work, whitespace probably doesn´t.  Maybe it works fine; I haven´t tested.
have been working on in your favourite editor and to look at the error
 
messages that may appear when your script doesn´t compile.  Don´t use
Suppose you want to try out "example-1".  Enter the directory "example-1" and run 'make':
it for anything else.
 
 
<lsl>
[~/src/lsl-repo/projects/example-1] make
generating dependencies: dep/example-1.di
generating dependencies: dep/example-1.d
preprocessing src/example-1.lsl
  39 1202 dbg/example-1.i.clean
[~/src/lsl-repo/projects/example-1]
</lsl>
 
 
You will find the LSL script as "projects/example-1/bin/example-1.o". A debugging version is "projects/example-1/dbg/example-1.i".  When you modify the source ("projects/example-1/src/example-1.lsl") just run make again after saving it, and new versions of the script will be created that replace the old ones.
 
Files are generated in the "dep" directory.  They are needed by the build system to figure out when a script needs to be rebuilt.  You can ignore those.  A file is generated in the "make" directory.  The file is needed to automatically replace scripts with the script editor built into your sl client, which will be described later.
 
Now you can log into sl with your sl client.  Make a box, create a "New Script" in the box and open the "New Script".  It shows up in the built-in editor.  Delete the script in the built-in editor and copy-and-paste the contents of "projects/example-1/bin/example-1.o" into the built-in editor.  Then click on the "Save" button of the built-in editor.  (There is a way to do this kind of script-replacement automatically. It will be explained later.)
 
The "template-directories" is what you copy when you want to write your own source.  Suppose you make a copy of the "template-directories" and name the copy "my-test".  You would then put your own source into "my-test/src", like "my-test/src/my-test.lsl".
 
Of course, you can have several source files in "my-test/src".  For all of them, scripts will be created.  The "my-test" directory is the project directory for your project "my-test".  When you are working on an object that requires several scripts, you´ll probably want to keep them all together in the project directory you made for this object.
 
 
If you get an error message when you run 'make', you are probably missing some of the tools the build system uses.  Install the missing tools and try again.
 
===Tools you need for the build system===
 
Required tools:
 
 
* basename: [http://www.gnu.org/software/coreutils/ coreutils]
 
* cpp: [http://gcc.gnu.org/ GCC]
 
* echo: [http://www.gnu.org/software/coreutils/ coreutils]
 
* editor: your favourite editor, [http://www.gnu.org/software/emacs/ Emacs] is recommended
 
* make: [http://www.gnu.org/software/make/ Make]
 
* perl: [http://www.perl.org/ | Perl]
 
* sed: [http://sed.sourceforge.net/ sed]
 
 
Optional tools:
 
 
* astyle: [http://astyle.sourceforge.net/ astyle]
 
* etags: [http://www.gnu.org/software/emacs/ Emacs]
 
* pdflatex: [http://tug.org/texlive/ TeX Live]
 
* rm: [http://www.gnu.org/software/coreutils/ coreutils]
 
* wc: [http://www.gnu.org/software/coreutils/ coreutils]
 
 
You can replace "make/Makefile" with "make/Makefile-no-optional" and try without the optional tools.  You can make "echo" optional, but automatically replacing scripts won´t work unless you can find a replacement for "@echo "// =`basename $@`" > $@" in the Makefile. Since perl is required, you could replace "echo" (and "basename" and "sed") with perl scripts.  You can probaly make further modifications so you won´t need perl, but what does one without it?
 
===Editor===
 
To create scripts, you do need an editor to write your sources.  All sl clients I´ve seen have one built in, and it´s the worst editor I´ve ever seen.  Fortunately, you don´t need to use it.
 
The built-in script editor is useful for replacing the scripts you have been working on in your favourite editor and to look at the error messages that may appear when your script doesn´t compile.  Don´t use it for anything else.


Not using the buit-in script editor has several advantages like:
Not using the buit-in script editor has several advantages like:
Line 87: Line 144:
* You don´t need to be logged in to work on your sources.
* You don´t need to be logged in to work on your sources.


* You can preprocess and postprocess and do whatever else you like with your sources before uploading a script.
* You can preprocess and postprocess and do whatever else you like with your sources before uploading a script:  You can use a build system.


* You can share your sources and work together with others because you can use tools like [http://git-scm.com/ git] and websites like [https://github.com/ github].  (Even when you don´t share your sources, git is a very useful tool.)
* You can share your sources and work together with others because you can use tools like [http://git-scm.com/ git] and websites like [https://github.com/ github].  Even when you don´t share your sources, git is a very useful tool.


* How fast you can move the cursor in the editor does not depend on the FPS-rate of your sl client.
* Your window manager handles the windows of your editor so that you are not limited to the window of your sl client which doesn´t do a great job when it comes to managing windows.  You are not limited to a single monitor, and how fast you can move the cursor in the editor does not depend on the FPS-rate of your sl client.


* You can use fonts easier to read for you, simply change the background and foreground colours and switch to the buffer in which you read your emails or write a wiki article like this one ...
* You can use fonts easier to read for you, simply change the background and foreground colours and switch to the buffer in which you read your emails or write a wiki article like this one ...
Line 97: Line 154:
* Though it doesn´t crash, a decent editor has an autosave function and allows you to restore files in the unlikely case that this is needed.
* Though it doesn´t crash, a decent editor has an autosave function and allows you to restore files in the unlikely case that this is needed.


* And yes, you can use a decent editor :)
* And yes, you can use a decent editor, even your favourite one :)


=====Emacs=====


[http://www.gnu.org/software/emacs/ Emacs] is my favourite editorIt
You can use any editor you like. An editor is one of your most important toolsUse the best one you can find.
does not only get the indentations right and does all the other things
mentioned above.  It offers much more and has been around since a
while.  If you had learned to use it twenty-five years ago, that would
have saved you the time wasted with other editors which nowadays may
not even exist anymore.


======Syntax Highlighting======
====Emacs====


Emacs comes with syntax highlighting for many programming languages.
[http://www.gnu.org/software/emacs/ Emacs] is my favourite editor.  Give it a try, and don´t expect to learn everything about it within a day.  It´s enough when you can move the cursor around, type something and load and save files.  Over time, you will learn more just by looking up how to do something and trying it and finding ''your'' way of doing what you need.  Lots of documentation is available.
It doesn´t come with syntax highlighting for LSL, but LSL syntax
 
highlighting modes for emacs are available.
By default, the cursor keys move the cursor.  Ctrl+xf loads a file into a new buffer and Ctrl+xs saves it.  Ctrl+x b lets you switch to another buffer.  These are usually written as C-xf, C-xs and C-x-b.
 
Emacs is worthwhile to learn.  It´s the only editor you need to learn about because you don´t need any others.  It has been around since a long time.  If you had learned it twenty five years ago, that would have saved you the time wasted with other editors which nowadays may not even exist anymore.
 
It doesn´t hurt to try it out.
 
=====Syntax Highlighting=====
 
Emacs comes with syntax highlighting for many programming languages. It doesn´t come with syntax highlighting for LSL, but LSL syntax highlighting modes for emacs are available.


You can use [[Emacs_LSL_Mode]].
You can use [[Emacs_LSL_Mode]].


A mofied version of lsl-mode is in the
A mofied version of lsl-mode is in the [https://github.com/Ratany/lsl-repo git repository that accompanies this article] as "emacs/lsl-mode.el".  This version has been modified for use with a file I named "lslstddef.h" ("projects/include/lslstddef.h" in the repository).  Following sections will refer to and use this file.
[https://github.com/Ratany/lsl-repo git repository that accompanies this article]
 
as "emacs/lsl-mode.el".  This version has been modified for use with a
\add ~/.emacs
file I named "lslstddef.h" ("projects/include/lslstddef.h" in the
 
repository).  Following sections will refer to and use this file.
=====Tags=====


======Tags======
Emacs supports so-called tags.  Using tags allows you to find the declaration of functions, defines, macros and variables you use in your sources without searching.  It doesn´t matter in which file they are.


Emacs supports so-called tags.  Using tags allows you to find the
The build system creates a so-called tags-file for youEmacs can read it automatically. Esc+. (M-.) lets you enter a tag you want to go to and takes you there.
declaration of functions, defines, macros and variables you use in
your sources without searching.  It doesn´t matter in which file they
are.  This will be described in following sections.


======Git======
=====Git=====


Emacs can support [http://git-scm.com/ git] (and other CVS systems).
See [http://www.gnu.org/software/emacs/tour/ this page]:  A number of version control systems are supported, and [http://git-scm.com/ git] is one of them. Additional packages are available for this --- I haven´t tried any of them yet.
You may need to install additional packages for it.  I haven´t tried
out any of them yet.


=====Other Editors=====
====Other Editors====


In case you find that you don´t get along with emacs, you may want to
In case you find that you don´t get along with emacs, you may want to check out this [[LSL_Alternate_Editors|page about editors]].  Try [http://en.wikipedia.org/wiki/Vi vi], or vim, instead.
check out this [[LSL_Alternate_Editors|page about editors]].  Try
[http://en.wikipedia.org/wiki/Vi vi], or vim, instead.


You don´t need to try any other editors.
You don´t need to try any other editors. [http://en.wikipedia.org/wiki/User:Dozen/E_(text_editor_family)#EPM_.28OS.2F2.29 EPM] was nice, but it crashed all the time.
[http://en.wikipedia.org/wiki/User:Dozen/E_(text_editor_family)#EPM_.28OS.2F2.29 EPM]
was nice, but it crashed all the time.


In any case, use an editor you are happy with because it does what you
In any case, use an editor you are happy with because it does what you want.  An editor is one of the most essential tools.  You want your tools to work the way you want them to.  You don´t want tools that get into your way.
want.  An editor is one of the most essential tools.  You want your
tools to work the way you want them to.  You don´t want tools that get
into your way.


====Automatically replacing Scripts====
===Automatically replacing Scripts===


The editor built into sl clients comes with buttons like to save and
The editor built into sl clients comes with buttons like to save and to reset the script which is being edited.  There is also a button labled "Edit".  As the label of this button suggests, the button is for editing the script.  The built-in editor is not for editing the script, though it´s not impossible to kinda use it for that.
to reset the script which is being edited.  There is also a button
labled "Edit".  As the label of this button suggests, the button is
for editing the script.  The built-in editor is not for editing the
script, though it´s not impossible to kinda use it for that.


[[Image:Cpp-wiki-builtineditor.jpg|thumb|Built-in Editor with "Edit" button]]
[[Image:Cpp-wiki-builtineditor.jpg|thumb|Built-in Editor with "Edit" button]]


Open a script in your sl client and click on the edit button of the
Open a script in your sl client and click on the edit button of the built-in editor.  You might get an error message telling you that the debug setting "ExternalEditor" is not set.  Don´t panic!  Set this debug setting so that your favourite editor is used, and click on the edit button again.  This is merely to show the concept, and things will get more convenient.
built-in editor.  You might get an error message telling you that the
debug setting "ExternalEditor" is not set.  Don´t panic!  Set this
debug setting so that your favourite editor is used, and click on the
edit button again.  This is merely to show the concept, and things
will get more convenient.


[[Image:Cpp-wiki-externaleditor.jpg|thumb|Debug Setting "ExternalEditor"]]
[[Image:Cpp-wiki-externaleditor.jpg|thumb|Debug Setting "ExternalEditor"]]


Your favourite editor should now start and let you edit the script.
Your favourite editor should now start and let you edit the script. This works because your sl client saves the script which you see in the built-in editor to a file on your storage system.  Then your sl client starts whatever you specified in the debug setting as "external editor".  To tell your favourite editor which file the script has been saved to, the place holder %s is used with the debug setting "ExternalEditor".  The place holder is replaced with the name of the file your sl client has saved.
This works because your sl client saves the script which you see in
the built-in editor to a file on your storage system.  Then your sl
client starts whatever you specified in the debug setting as "external
editor".  To tell your favourite editor which file the script has been
saved to, the place holder %s is used with the debug setting
"ExternalEditor".  The place holder is replaced with the name of the
file your sl client has saved.


Once the script has been saved to a file, your sl client monitors this
Once the script has been saved to a file, your sl client monitors this file for changes.  When the file changes --- which it does when you edit and then save it with your favourite editor --- your sl client replaces the script in the built-in editor with the contents of the file.
file for changes.  When the file changes --- which it does when you
edit and then save it with your favourite editor --- your sl client
replaces the script in the built-in editor with the contents of the
file.


This is nice because you could write a new script while you are
This is nice because you could write a new script while you are offline.  When you are ready to test the script, you could log in with your sl client, build a box, create a new script in the box, open the script, click on the "Edit" button, delete the contents of the new script in your favourite editor, insert the script you have written and save it.
offline.  When you are ready to test the script, you could log in with
your sl client, build a box, create a new script in the box, open the
script, click on the "Edit" button, delete the contents of the new
script in your favourite editor, insert the script you have written
and save it.


Your sl client would notice that the file changed and put it into the
Your sl client would notice that the file changed and put it into the built-in editor.  It would let the sl server compile the script and show you the error messages, if any.  When your script compiles and works right away, you´re done --- if not, you can use the built-in editor to check the error messages and your favourite editor to change the source as needed.  Then you need to replace the script again.
built-in editor.  It would let the sl server compile the script and
show you the error messages, if any.  When your script compiles and
works right away, you´re done --- if not, you can use the built-in
editor to check the error messages and your favourite editor to change
the source as needed.  Then you need to replace the script again.


At this point, things suddenly aren´t nice anymore: Replacing the
At this point, things suddenly aren´t nice anymore: Replacing the script with your favourite editor all the time is a stupid process (unless you program your editor to do it for you).  Even when you have only one script, having to replace the script over and over again while you are adding to it and debugging it sucks.  It sucks even more when your object has 11 scripts working toegether and a little change makes it necessary to replace all of them.
script with your favourite editor all the time is a stupid process
(unless you program your editor to do it for you).  Even when you have
only one script, having to replace the script over and over again
while you are adding to it and debugging it sucks.  It sucks even more
when your object has 11 scripts working toegether and a little change
makes it necessary to replace all of them.


You could mess around with the built-in editor and try to edit your
You could mess around with the built-in editor and try to edit your script further with it, saving the replacing.  That also sucks: The built-in editor is unusable, and you end up with at least two versions of the script.  Since you want the script safely stored on your storage system and not lose your work when your sl client crashes or the like, you end up replacing the script on your storage system with the version you have in the built-in editor every now and then.  When your object has 11 scripts that work together, it eventually becomes a little difficult to keep an overview of what´s going on --- not to mention that you need a huge monitor with a really high resolution to fit 11 built-in editor windows in a usable way on your screen to work on your scripts since there is no C-x b with the built-in editor to switch to a different buffer ...
script further with it, saving the replacing.  That also sucks: The
built-in editor is unusable, and you end up with at least two versions
of the script.  Since you want the script safely stored on your
storage system and not lose your work when your sl client crashes or
the like, you end up replacing the script on your storage system with
the version you have in the built-in editor every now and then.  When
your object has 11 scripts that work together, it eventually becomes a
little difficult to keep an overview of what´s going on --- not to
mention that you need a huge monitor with a really high resolution to
fit 11 built-in editor windows in a usable way on your screen to work
on your scripts since there is no C-x b with the built-in editor to
switch to a different buffer ...


Fortunately, there is a much better and very simple solution: A
Fortunately, there is a much better and very simple solution: A program which is started by your sl client ''instead'' of your favourite editor can replace the script for you.  It´s the same concept from above you have tried out now.  The concept does not require that you must abuse your favourite editor to replace scripts.
program which is started by your sl client ''instead'' of your
favourite editor can replace the script for you.  It´s the same
concept from above you have tried out now.  The concept does not
require that you must abuse your favourite editor to replace scripts.


You can easily write a program that replaces files yourself.  I wrote
You can easily write a program that replaces files yourself.  I wrote one when I got tired of all the replacing.  It´s not really a program, but a [http://www.perl.org/ perl] script.  It´s written in perl because perl is particularly well suited for the purpose.  It looks like this:
one when I got tired of all the replacing.  It´s not really a program,
but a [http://www.perl.org/ perl] script.  It´s written in perl
because perl is particularly well suited for the purpose.  It looks
like this:




Line 377: Line 368:




You´ll find this script in the
You´ll find this script in the [https://github.com/Ratany/lsl-repo git repository that accompanies this article] as "projects/bin/replace.pl".  What it basically does is simple:
[https://github.com/Ratany/lsl-repo git repository that accompanies this article]
as "projects/bin/replace.pl".  What it basically does is simple:


* look at the name of the file which is supplied as a command line argument; when it´s not named like "*.lsl", start your favourite editor to edit the file
* look at the name of the file which is supplied as a command line argument; when it´s not named like "*.lsl", start your favourite editor to edit the file
Line 394: Line 383:




Entries in the table are so-called key-value pairs.  The table looks
Entries in the table are so-called key-value pairs.  The table looks like this:
like this:




Line 409: Line 397:




Note: The space between the colon and the file name is mandatory
Note: The space between the colon and the file name is mandatory (because the perl script is written like that).
(because the perl script is written like that).


This means when you have a script in the built-in script editor like
This means when you have a script in the built-in script editor like
Line 428: Line 415:




and click on the "Edit" button of the built-in script editor, the
and click on the "Edit" button of the built-in script editor, the script will automatically be replaced with the contents of the file "/path/to/example-script.o".
script will automatically be replaced with the contents of the file
"/path/to/example-script.o".


To make this work, you need to:
To make this work, you need to:
Line 450: Line 435:
'''You should first try out the perl script to see if it works.'''
'''You should first try out the perl script to see if it works.'''


When you have your sl client start it, it is difficult or impossible
When you have your sl client start it, it is difficult or impossible to see error messages you may get when running the perl script:
to see error messages you may get when running the perl script:




Line 462: Line 446:




You see what I mean: You forgot to adjust the file name of the look-up
You see what I mean: You forgot to adjust the file name of the look-up table (or forgot to create the table) and get an error message because the file doesn´t exist.  If you had your sl client start the perl script, apparently nothing would happen when you click the "Edit" button.
table (or forgot to create the table) and get an error message because
the file doesn´t exist.  If you had your sl client start the perl
script, apparently nothing would happen when you click the "Edit"
button.


Once you have verified that the perl script works, you´re
Once you have verified that the perl script works, you´re fine. However, when you have a typing error in the lookup-table, the script in the built-in editor might silently not be replaced with the new version.  (You could modify the perl script so that it beeps when it does not replace the script, or experiment with something like 'xterm -hold -e replace.pl "%s"'.  If 'xterm -hold ...' works, you could do something to run 'make' (see below) automatically when you click the "Edit" button.)
fine. However, when you have a typing error in the lookup-table, the
script in the built-in editor might silently not be replaced with the
new version.  (You could modify the perl script so that it beeps when
it does not replace the script, or experiment with something like
'xterm -hold -e replace.pl "%s"'.  If 'xterm -hold ...' works, you
could do something to run 'make' (see below) automatically when you
click the "Edit" button.)


The perl script starts your favourite editor when it doesn´t find the
The perl script starts your favourite editor when it doesn´t find the key (i. e. "example-script.o") in the table.  You can then edit both the script and the table.  This means that you don´t need to fill the table in advance.  You can start with an empty table ('touch /absolute/path/to/replaceassignments.txt') and add entries as you go.
key (i. e. "example-script.o") in the table.  You can then edit both
the script and the table.  This means that you don´t need to fill the
table in advance.  You can start with an empty table ('touch
/absolute/path/to/replaceassignments.txt') and add entries as you go.


The perl script uses the first entry in the table that matches.  If
The perl script uses the first entry in the table that matches.  If you plan to have multiple scripts with the same name, you can make adjustments to the Makefile (see below) and don´t need to modify the perl script.  It´s a good idea to decide about this before filling your table: When you want multiple scripts with the same name some time later, you will have to edit your table.
you plan to have multiple scripts with the same name, you can make
adjustments to the Makefile (see below) and don´t need to modify the
perl script.  It´s a good idea to decide about this before filling
your table: When you want multiple scripts with the same name some
time later, you will have to edit your table.


The build system (see below) creates "compressed" versions of the
The build system (see below) creates "compressed" versions of the scripts and versions that are not compressed.  The "compressed" versions are named *.o and the not-compressed versions are named *.i.
scripts and versions that are not compressed.  The "compressed"
versions are named *.o and the not-compressed versions are named *.i.


The "compressed" versions can be pretty unreadable, which can make
The "compressed" versions can be pretty unreadable, which can make debugging difficult.  The not-compressed version is formattted with astyle (see below) to make it easier to read.  It can be used for debugging.  (The "compressed" version is what you want to put as a script into your object.)
debugging difficult.  The not-compressed version is formattted with
astyle (see below) to make it easier to read.  It can be used for
debugging.  (The "compressed" version is what you want to put as a
script into your object.)


To see the debugging version of the script in the built-in editor,
To see the debugging version of the script in the built-in editor, change the first line of the script in the built-in editor from like "// =example-script.o" to "// =example-script.i" and click on the "Edit" button.  That puts the version of the script which is not compressed into the built-in editor.  Switch the "i" back to an "o", click the "Edit" button and the "compressed" version is put back.
change the first line of the script in the built-in editor from like
"// =example-script.o" to "// =example-script.i" and click on the
"Edit" button.  That puts the version of the script which is not
compressed into the built-in editor.  Switch the "i" back to an "o",
click the "Edit" button and the "compressed" version is put back.


Above perl script does the replacing for you, provided that the
Above perl script does the replacing for you, provided that the debugging version is in the "dbg" directory and the "compressed" version is in the "bin" directory and that both "dbg" and "bin" are in the same directory.
debugging version is in the "dbg" directory and the "compressed"
version is in the "bin" directory and that both "dbg" and "bin" are in
the same directory.


When you use the build system, you do not need to add entries to the
When you use the build system, you do not need to add entries to the lookup-table.  They are automatically added as needed when you run 'make' (see below).
lookup-table.  They are automatically added as needed when you run
'make' (see below).


====A Preprocessor====
====A Preprocessor====


The [http://en.wikipedia.org/wiki/Preprocessor Wikipedia article]
The [http://en.wikipedia.org/wiki/Preprocessor Wikipedia article] about preprocessors gives an interesting explanation of what a preprocessor is and provides an overview.  For the purpose of making it easier to create LSL scripts, a preprocessor as is used with [http://en.wikipedia.org/wiki/C_(programming_language) C] (and C++) compilers is a very useful tool.
about preprocessors gives an interesting explanation of what a
preprocessor is and provides an overview.  For the purpose of making
it easier to create LSL scripts, a preprocessor as is used with
[http://en.wikipedia.org/wiki/C_(programming_language) C] (and C++)
compilers is a very useful tool.


The particular preprocessor I´m using is called "cpp" and comes with
The particular preprocessor I´m using is called "cpp" and comes with
Line 540: Line 483:




That doesn´t sound like much, yet these are rather powerful features.
That doesn´t sound like much, yet these are rather powerful features. Some of the advantages that come with these features are:
Some of the advantages that come with these features are:




Line 569: Line 511:




There are probably lots of advantages that don´t come to mind at the
There are probably lots of advantages that don´t come to mind at the moment.  You can be creative and find uses and advantages I would never think of.
moment.  You can be creative and find uses and advantages I would
never think of.


=====How cpp works=====
=====How cpp works=====


Some extensive documentation about cpp can be found
Some extensive documentation about cpp can be found [http://gcc.gnu.org/onlinedocs/cpp/ here].
[http://gcc.gnu.org/onlinedocs/cpp/ here].


Imagine you want to create a script that figures out which agent on
Imagine you want to create a script that figures out which agent on the same parcel is the furthest away from the object the script is in and moves the object towards this agent when the owner of the object touches it.
the same parcel is the furthest away from the object the script is in
and moves the object towards this agent when the owner of the object
touches it.


In the following, the source for the script will be developed piece by
In the following, the source for the script will be developed piece by piece.  You can find the complete source in the [https://github.com/Ratany/lsl-repo git repository that accompanies this article] in "projects/example-1/src/example-1.lsl".
piece.  You can find the complete source in the
[https://github.com/Ratany/lsl-repo git repository that accompanies this article]
in "projects/example-1/src/example-1.lsl".


Obviously, the script will need a touch event, a way to find out which
Obviously, the script will need a touch event, a way to find out which agent is the furthest away and a a way of moving to the agent.
agent is the furthest away and a a way of moving to the agent.


In this case, [[LlSetKeyframedMotion|keyframed motion]] (KFM) is a
In this case, [[LlSetKeyframedMotion|keyframed motion]] (KFM) is a good way to get the object moving: It doesn´t require the object to be physical and thus lets it move through obstacles like walls.  KFM requires the object to use the so-called "prim equivalency system", which means that it must either be mesh, or the physics type of one of its prims must be either "convex hull" or "none".  This can either be set in the editing window when editing the object, or by the script. The physics type of the root prim cannot be "none", and since it´s hard to say whether "none" or "convex hull" is better for a particular object, the script will simply use "convex hull".  Since the physics type of the object needs to be set only once, the script should use a state_entry event to set it.  It can be set with [[LlSetLinkPrimitiveParamsFast#llSetLinkPrimitiveParamsFast|llSetLinkPrimitiveParamsFast()]].
good way to get the object moving: It doesn´t require the object to be
physical and thus lets it move through obstacles like walls.  KFM
requires the object to use the so-called "prim equivalency system",
which means that it must either be mesh, or the physics type of one of
its prims must be either "convex hull" or "none".  This can either be
set in the editing window when editing the object, or by the script.
The physics type of the root prim cannot be "none", and since it´s
hard to say whether "none" or "convex hull" is better for a particular
object, the script will simply use "convex hull".  Since the physics
type of the object needs to be set only once, the script should use a
state_entry event to set it.  It can be set with
[[LlSetLinkPrimitiveParamsFast#llSetLinkPrimitiveParamsFast|llSetLinkPrimitiveParamsFast()]].


Finding out which agent is the furthest away from the object requires
Finding out which agent is the furthest away from the object requires to find out what agents there are.  A sensor is unsuited because the agent could be out of sensor range, and the sensor would report no more than the closest sixteen agents.  Using [[LlGetAgentList|llGetAgentList()]] works much better for this.  It is also necessary to find out what the distances between the agents and the object are, for which [[LlGetObjectDetails|llGetObjectDetails()]] can be used.
to find out what agents there are.  A sensor is unsuited because the
agent could be out of sensor range, and the sensor would report no
more than the closest sixteen agents.  Using
[[LlGetAgentList|llGetAgentList()]] works much better for this.  It is
also necessary to find out what the distances between the agents and
the object are, for which [[LlGetObjectDetails|llGetObjectDetails()]]
can be used.


The object should move to a position at "some distance" from the
The object should move to a position at "some distance" from the agent.  It seems nicer to use a random distance rather than a fixed one.  This can easily be done by creating a "random vector", i. e. a vector the components of which are randomized.  What [[LlFrand||llFrand()]] returns is not really a random number, but it´s sufficient for this purpose.
agent.  It seems nicer to use a random distance rather than a fixed
one.  This can easily be done by creating a "random vector", i. e. a
vector the components of which are randomized.  What
[[LlFrand||llFrand()]] returns is not really a random number, but it´s
sufficient for this purpose.


As with all sources, I want to use some of the defines that are
As with all sources, I want to use some of the defines that are gathered in my "lslstddef.h".  One of them is the macro "RemotePos()". It is a shortcut for using llGetObjectDetails() to find out the position of and object or prim or agent.  Another one is "SLPPF()", a shortcut for llSetLinkPrimitiveParamsFast() because llSetLinkPrimitiveParamsFast() is awfully long.
gathered in my "lslstddef.h".  One of them is the macro "RemotePos()".
It is a shortcut for using llGetObjectDetails() to find out the
position of and object or prim or agent.  Another one is "SLPPF()", a
shortcut for llSetLinkPrimitiveParamsFast() because
llSetLinkPrimitiveParamsFast() is awfully long.


This makes for a source (example-1_A) that looks like this:
This makes for a source (example-1_A) that looks like this:
Line 650: Line 553:




The '#include' in the first line is a so-called preprocessor
The '#include' in the first line is a so-called preprocessor directive.  It tells the preprocessor to do something.  There are other directives, like "#define", "#undef", "#if", "#endif" and some others.
directive.  It tells the preprocessor to do something.  There are
other directives, like "#define", "#undef", "#if", "#endif" and some
others.


"#include" tells cpp to find a file "lslstddef.h" within some directories it
"#include" tells cpp to find a file "lslstddef.h" within some directories it searches through by default and to make it so as if the contents of this file were part of the source.  Suppose you have the source saved as "example-1.lsl": The preprocessor includes "lslstddef.h" into "example-1.lsl".  It´s output will not contain the first line because the first line is a preprocessor directive.
searches through by default and to make it so as if the contents of
this file were part of the source.  Suppose you have the source saved
as "example-1.lsl": The preprocessor includes "lslstddef.h" into
"example-1.lsl".  It´s output will not contain the first line because
the first line is a preprocessor directive.


The source ("example-1.lsl") is never modified by the preprocessor.
The source ("example-1.lsl") is never modified by the preprocessor. Including "lslstddef.h" makes everything in "lslstddef.h" known to the preprocessor.  The preprocessor produces an output which is a copy of the source with modifications applied to it as the directives, like the ones in "lslstddef.h", demand.
Including "lslstddef.h" makes everything in "lslstddef.h" known to the
preprocessor.  The preprocessor produces an output which is a copy of
the source with modifications applied to it as the directives, like
the ones in "lslstddef.h", demand.


"lslstddef.h" has a lot of directives for the preprocessor.  Most of
"lslstddef.h" has a lot of directives for the preprocessor.  Most of them define something with "#define".  When something is defined with "#define", it is a so-called define.  Macros can also be defined with "#define".  Macros are called macros because they are defines that take parameters.
them define something with "#define".  When something is defined with
"#define", it is a so-called define.  Macros can also be defined with
"#define".  Macros are called macros because they are defines that
take parameters.




You may have noticed that in example-1_A, it doesn´t read
You may have noticed that in example-1_A, it doesn´t read "touch_start()" but "event touch_start()".  That´s because I write my sources like that: Putting "event" before events makes them easier to find and the source looks nicer and is easier to read for me.  In your sources, you can do it the same way or use something else than "event" or just write "touch_start()" instead.  You can see what it looks like on my screen in the example picture --- I currently use green on black in emacs.  It sticks out, and "event" makes a good search string that saves me searching for particular events.
"touch_start()" but "event touch_start()".  That´s because I write my
sources like that: Putting "event" before events makes them easier to
find and the source looks nicer and is easier to read for me.  In your
sources, you can do it the same way or use something else than "event"
or just write "touch_start()" instead.  You can see what it looks like
on my screen in the example picture --- I currently use green on black
in emacs.  It sticks out, and "event" makes a good search string that
saves me searching for particular events.


[[Image:Cpp-wiki-example-1_A.jpg|thumb|Easier to read]]
[[Image:Cpp-wiki-example-1_A.jpg|thumb|Easier to read]]


The LSL compiler wouldn´t compile the source, not only because it
The LSL compiler wouldn´t compile the source, not only because it doesn´t know what to do with "#include" directives, but also because it doesn´t compile statements like "event touch_start()".  Therefore, "lslstddef.h" defines that "event" is nothing:
doesn´t know what to do with "#include" directives, but also because
it doesn´t compile statements like "event touch_start()".  Therefore,
"lslstddef.h" defines that "event" is nothing:




Line 698: Line 574:




By including "lslstddef.h", cpp knows this and replaces all occurances
By including "lslstddef.h", cpp knows this and replaces all occurances of "event" with nothing, making "event" disappear from the result. This doesn´t mean that "event" is not defined.  It´s a define.  You can use the directive "#ifdef" with such defines:
of "event" with nothing, making "event" disappear from the result.
This doesn´t mean that "event" is not defined.  It´s a define.  You
can use the directive "#ifdef" with such defines:




Line 711: Line 584:




This would create a define named "test" only if "event" is defined.
This would create a define named "test" only if "event" is defined. Much like cpp replaces "event" with nothing, it would now replace "test" with "1" because "test" is defined to be "1".
Much like cpp replaces "event" with nothing, it would now replace
"test" with "1" because "test" is defined to be "1".




The next thing the example script needs is to set the physics
The next thing the example script needs is to set the physics type. The define "SLPPF" is a shortcut for "llSetLinkPrimitiveParamsFast", so use that.  The script also needs to check if it was the owner of the object who touched it and do nothing when it was someone else.  It needs to know the position of the object the script is in and what agents are around so it can figure out which agent is the furthest away from the object. Add all this to the source, which is now example-1_B:
type. The define "SLPPF" is a shortcut for
"llSetLinkPrimitiveParamsFast", so use that.  The script also needs to
check if it was the owner of the object who touched it and do nothing
when it was someone else.  It needs to know the position of the object
the script is in and what agents are around so it can figure out which
agent is the furthest away from the object. Add all this to the
source, which is now example-1_B:




Line 760: Line 624:




in "lslstddef.h", the preprocessor replaces "event" with nothing and
in "lslstddef.h", the preprocessor replaces "event" with nothing and "SLPPF" with "llSetLinkPrimitiveParamsFast".
"SLPPF" with "llSetLinkPrimitiveParamsFast".




Lists in LSL can be a pita to work with.  You end up using functions
Lists in LSL can be a pita to work with.  You end up using functions to pick out list elements like [[LlList2Key|llList2Key()]].  They combine with functions that return lists and lead to unwieldy expressions like
to pick out list elements like [[LlList2Key|llList2Key()]].  They
combine with functions that return lists and lead to unwieldy
expressions like




Line 788: Line 648:




Lists are sometimes strided, and then you also need to remember which
Lists are sometimes strided, and then you also need to remember which item of a stride is to be interpreted as what.  Your code becomes hard to write, hard to read and hard to modify.  You can use defines and macros to avoid this.
item of a stride is to be interpreted as what.  Your code becomes hard
to write, hard to read and hard to modify.  You can use defines and
macros to avoid this.


The list used in example-1_B is not strided, simple and a local
The list used in example-1_B is not strided, simple and a local variable.  Using a define and macros to make it useable is overdoing it, but I want to show you how it can be done.
variable.  Using a define and macros to make it useable is overdoing
it, but I want to show you how it can be done.


You can put preprocessor directives anywhere into your source.  They
You can put preprocessor directives anywhere into your source.  They don´t need to be kept in seperate files.  Traditionally, they had to be at the beginning of a line.  This isn´t required anymore --- but do not indent them!  They are not part of your code.  They modify your code.  You could see them as "metacode".
don´t need to be kept in seperate files.  Traditionally, they had to
be at the beginning of a line.  This isn´t required anymore --- but do
not indent them!  They are not part of your code.  They modify your
code.  You could see them as "metacode".


With example-1_B, four things can be defined to make the list of
With example-1_B, four things can be defined to make the list of agents easier to use:
agents easier to use:




Line 816: Line 666:




Those will be used for a loop that iterates over the list.  This leads
Those will be used for a loop that iterates over the list.  This leads to example-1_C:
to example-1_C:




Line 856: Line 705:




Since the list is a local variable, the defines are placed after its
Since the list is a local variable, the defines are placed after its declaration.  You could place them somewhere else, maybe at the top of the source.  Placing them inside the scope of the list makes clear that they are related to this particular list.
declaration.  You could place them somewhere else, maybe at the top of
the source.  Placing them inside the scope of the list makes clear
that they are related to this particular list.


The define "iSTRIDE_agents" is the stride of the list, 1 in this
The define "iSTRIDE_agents" is the stride of the list, 1 in this case. Using "--agent;" in the loop instead of "agent -= iSTRADE_agents;" would be better, but it´s done for the purpose of demonstration.  When you have a strided list, it is a good idea to always use a define for it: It doesn´t hurt anything and makes it easy to modify your source because there is only one place you need to change.  With strided lists, use defines for the indices of stride-items so you don´t need to go through the macro definitions to make sure the indices are ok when you change the composition of the list.  You can look at "projects/rfedip-reference-implementation/src/rfedipdev-enchain.lsl" in the [https://github.com/Ratany/lsl-repo git repository that accompanies this article] for a better example.
case. Using "--agent;" in the loop instead of "agent -=
iSTRADE_agents;" would be better, but it´s done for the purpose of
demonstration.  When you have a strided list, it is a good idea to
always use a define for it: It doesn´t hurt anything and makes it easy
to modify your source because there is only one place you need to
change.  With strided lists, use defines for the indices of
stride-items so you don´t need to go through the macro definitions to
make sure the indices are ok when you change the composition of the
list.  You can look at
"projects/rfedip-reference-implementation/src/rfedipdev-enchain.lsl"
in the [https://github.com/Ratany/lsl-repo git repository that
accompanies this article] for a better example.


"kAgentKey" is a macro.  It´s a define that takes parameters.  You
"kAgentKey" is a macro.  It´s a define that takes parameters.  You might think the parameter is an integer which is an index to the list in which the UUIDs of the agents are stored.  It is not: The parameter is ''used'' an index.  The preprocessor doesn´t know anything about lists or integers.
might think the parameter is an integer which is an index to the list
in which the UUIDs of the agents are stored.  It is not: The
parameter is ''used'' an index.  The preprocessor doesn´t know
anything about lists or integers.


There are no types involved with macro parameters as are with
There are no types involved with macro parameters as are with variables: When you declare a function in LSL that takes parameters, you need to declare what type the parameters are of.  Macro parameters don´t have a type.  The preprocessor replaces stuff literally.  You could use a vector or some random string as a parameter with "kAgentKey".  Cpp would do the replacement just fine.  It replaces text like a search-and-replace operation you perform with your favourite editor does.
variables: When you declare a function in LSL that takes parameters,
you need to declare what type the parameters are of.  Macro parameters
don´t have a type.  The preprocessor replaces stuff literally.  You
could use a vector or some random string as a parameter with
"kAgentKey".  Cpp would do the replacement just fine.  It replaces
text like a search-and-replace operation you perform with your
favourite editor does.


Unfortunately, you cannot define defines or macros with defines:
Unfortunately, you cannot define defines or macros with defines: "#define NEWLIST(name, stride) #define list name; #define iSTRIDE_##name stride" will cause cpp to stop proprocessing and to print an error message.  Maybe I´ll write a perl script to pre-preprocess my sources ... that might be easier than learning [http://www.gnu.org/software/m4/ | m4].
"#define NEWLIST(name, stride) #define list name; #define
iSTRIDE_##name stride" will cause cpp to stop proprocessing and to
print an error message.  Maybe I´ll write a perl script to
pre-preprocess my sources ... that might be easier than learning
[http://www.gnu.org/software/m4/ | m4].


"vAgentPos" and "fAgentDistance" are also macros and work the same way
"vAgentPos" and "fAgentDistance" are also macros and work the same way as "kAgentKey".  The code to figure out which agent is the one furthest away from the object is simple, and I´ll skip over it.  It gets interesting when the movement is implemented, so here is example-1_D, the complete source:
as "kAgentKey".  The code to figure out which agent is the one
furthest away from the object is simple, and I´ll skip over it.  It
gets interesting when the movement is implemented, so here is
example-1_D, the complete source:




Line 979: Line 793:




There isn´t much new to it.  "unless" is defined in "lslstddef.h" and
There isn´t much new to it.  "unless" is defined in "lslstddef.h" and speaks for itself.  "fUNDETERMINED" and "fIsUndetermined" speak for themselves, too: There are no negative distances between positions, and what "llVecDist()" returns is never negative.  There may be no agents on the parcel, in which case the list of agents would be empty.  It´s length would be 0 and the code in the while loop would not be executed.  The variable "distance" would still have the value it was initialized with when it was declared.  What the furthest agent is would be undetermined, and that´s exactly what the code tells you.
speaks for itself.  "fUNDETERMINED" and "fIsUndetermined" speak for
themselves, too: There are no negative distances between positions,
and what "llVecDist()" returns is never negative.  There may be no
agents on the parcel, in which case the list of agents would be
empty.  It´s length would be 0 and the code in the while loop would
not be executed.  The variable "distance" would still have the value
it was initialized with when it was declared.  What the furthest agent
is would be undetermined, and that´s exactly what the code tells you.


The movement of the object should look nice.  It looks nice when the
The movement of the object should look nice.  It looks nice when the object always moves at the same speed.  "llSetKeyframedMotion()" doesn´t use speed. You have to specify the duration of the movement. Since the distance between the object and agents it moves to varies, the duration of the movement is adjusted according to the distance. The minimum duration "llSetKeyframedMotion()" works with is 1/45 second.  "fSecondsForMeters" makes sure that the duration is at least 1/45 second through the "FMax" macro which is defined in "lslstddef.h": "FMax" returns the greater value of the two parameters it is supplied with.
object always moves at the same speed.  "llSetKeyframedMotion()"
doesn´t use speed. You have to specify the duration of the
movement. Since the distance between the object and agents it moves to
varies, the duration of the movement is adjusted according to the
distance. The minimum duration "llSetKeyframedMotion()" works with is
1/45 second.  "fSecondsForMeters" makes sure that the duration is at
least 1/45 second through the "FMax" macro which is defined in
"lslstddef.h": "FMax" returns the greater value of the two parameters
it is supplied with.


"vRandomDistance" is used to help with that the object doesn´t hit the
"vRandomDistance" is used to help with that the object doesn´t hit the agent it moves to and ends up at some random distance from them, with a minimum offset.  "PosOffset" from "lslstddef.h" works with two vectors and gives the offset between them as a vector.  It´s one of my favourite macros because I don´t need to ask myself anymore what "offset" is supposed to mean: "here - there" or "there - here".  The offset is simply "PosOffset(here, there)" when it´s an offset from here to there and "PosOffset(there, here)" when it´s offset from there to here.  "llSetKeyframedMotion()" uses offsets ...
agent it moves to and ends up at some random distance from them, with
a minimum offset.  "PosOffset" from "lslstddef.h" works with two
vectors and gives the offset between them as a vector.  It´s one of my
favourite macros because I don´t need to ask myself anymore what
"offset" is supposed to mean: "here - there" or "there - here".  The
offset is simply "PosOffset(here, there)" when it´s an offset from
here to there and "PosOffset(there, here)" when it´s offset from there
to here.  "llSetKeyframedMotion()" uses offsets ...


The important thing here is this: Why divide the distance and
The important thing here is this: Why divide the distance and introduce another vector for the offset when you could simply do it in one line like this:
introduce another vector for the offset when you could simply do it in
one line like this:




Line 1,022: Line 809:




That´s one thing all these macros are for, aren´t they?  Yes they are
That´s one thing all these macros are for, aren´t they?  Yes they are --- and you need to be aware of what they turn into.  Cpp is designed to preprocess code for a C or C++ compiler, and those are probalby a lot smarter than an LSL compiler.  The C compiler optimizes "1.0/45.0" by replacing it with the result of the division.  C has a ternary operator which makes life a lot easier not only for writing macros like "FMax": "#define MAX(a,b) (((a) > (b)) ? (a) : (b))" works for both integers and floats --- compared to
--- and you need to be aware of what they turn into.  Cpp is designed
to preprocess code for a C or C++ compiler, and those are probalby a
lot smarter than an LSL compiler.  The C compiler optimizes "1.0/45.0"
by replacing it with the result of the division.  C has a ternary
operator which makes life a lot easier not only for writing macros
like "FMax": "#define MAX(a,b) (((a) > (b)) ? (a) : (b))" works for
both integers and floats --- compared to
"#define FMax(x, y) (((llFabs((x) >= (y))) * (x)) + ((llFabs((x) <
"#define FMax(x, y) (((llFabs((x) >= (y))) * (x)) + ((llFabs((x) <
(y))) * (y)))" and another one for integers.
(y))) * (y)))" and another one for integers.
Line 1,045: Line 825:




Letting aside that the code would be totally different when written in
Letting aside that the code would be totally different when written in C, a C compiler would optimze that.  I don´t know what the LSL compiler does.
C, a C compiler would optimze that.  I don´t know what the LSL
compiler does.


'''Always be aware of what your macros turn into.'''  The code from
'''Always be aware of what your macros turn into.'''  The code from example-1_D turns into:
example-1_D turns into:




Line 1,064: Line 841:




It´s still awful to read.  What did you expect?  The source looks nice
It´s still awful to read.  What did you expect?  The source looks nice and clear because macros and defines are used, wich is one of the reasons to use them.  More importantly, this code doesn´t employ numerous obsolete divisions because I checked the outcome and modifed my source.
and clear because macros and defines are used, wich is one of the
reasons to use them.  More importantly, this code doesn´t employ
numerous obsolete divisions because I checked the outcome and modifed
my source.


'''Always check the script for what your macros turn into.'''  It´s
'''Always check the script for what your macros turn into.'''  It´s easy to check, just load the debugging version into your favourite editor.  You can even use the built-in editor: change the "o" for an "i" and click on the "Edit" button as described earlier.
easy to check, just load the debugging version into your favourite
editor.  You can even use the built-in editor: change the "o" for an
"i" and click on the "Edit" button as described earlier.


One thing is new in example-1_D: The defines that were used to make
One thing is new in example-1_D: The defines that were used to make the list with the UUIDs of the agents usable are undefined with "#undef".  It´s not necessary to do this, it´s tidy.  As mentioned before, the list has local scope.  That´s the reason I undefined the defines that relate to it.  It ends their "scope" as well.  You can create new defines with the same names outside this scope if you like. They are undefined and out of the way.  Perhaps at some time, you include something into your source that happens to use defines with the same name.  You would get an error message and have to go back to the perhaps old source and figure out where you can undefine them.  It doesn´t hurt to undefine them right away and saves you time later.
the list with the UUIDs of the agents usable are undefined with
"#undef".  It´s not necessary to do this, it´s tidy.  As mentioned
before, the list has local scope.  That´s the reason I undefined the
defines that relate to it.  It ends their "scope" as well.  You can
create new defines with the same names outside this scope if you
like. They are undefined and out of the way.  Perhaps at some time,
you include something into your source that happens to use defines
with the same name.  You would get an error message and have to go
back to the perhaps old source and figure out where you can undefine
them.  It doesn´t hurt to undefine them right away and saves you time
later.


======Script memory and performance with cpp======
======Script memory and performance with cpp======


You may have seen or written scripts that use variables as constants.
You may have seen or written scripts that use variables as constants. Look at this example:
Look at this example:




Line 1,137: Line 895:




You can find this example in the
You can find this example in the [https://github.com/Ratany/lsl-repo git repository that accompanies this article] as "projects/memtest/src/memtest.lsl".
[https://github.com/Ratany/lsl-repo git repository that accompanies this article]
as "projects/memtest/src/memtest.lsl".


It shows that the script needs 8 bytes more memory when you use either
It shows that the script needs 8 bytes more memory when you use either a float or an integer variable --- or both! --- instead of defines. That means when you declare a variable, declaring another one doesn´t make a difference, provided that the variables are of the types float and integer.
a float or an integer variable --- or both! --- instead of defines.
That means when you declare a variable, declaring another one doesn´t
make a difference, provided that the variables are of the types float
and integer.


I give up artifical testing at this point.  Either the test or the
I give up artifical testing at this point.  Either the test or the results must be flawed.  One thing I wanted to know is whether it makes a difference if you have several lines with a number (like 1 or 1.0) vs. the same several lines with a variable instead of the number. This test doesn´t show that, perhaps due to optimizations the LSL compiler performs.
results must be flawed.  One thing I wanted to know is whether it
makes a difference if you have several lines with a number (like 1 or
1.0) vs. the same several lines with a variable instead of the
number.   This test doesn´t show that, perhaps due to optimizations
the LSL compiler performs.


I use defines for constants --- not because it apparently may save 8
I use defines for constants --- not because it apparently may save 8 bytes, but because it makes sense.  That you can switch between defines and variables for constants makes testing with "real" scripts easy.
bytes, but because it makes sense.  That you can switch between
defines and variables for constants makes testing with "real" scripts
easy.


'''Test with your actual script what difference it makes.'''
'''Test with your actual script what difference it makes.'''


It seems obvious that you would waste memory when you use a macro that
It seems obvious that you would waste memory when you use a macro that the preprocessor turns into a lengthy expression which occurs in the script multiple times.  When it matters, test it; you may be surprised.
the preprocessor turns into a lengthy expression which occurs in the
script multiple times.  When it matters, test it; you may be
surprised.


You can also try to test for performance.  Provided you find a
You can also try to test for performance.  Provided you find a suitable place to set up your test objects and let the tests run for at least a week, you may get valid results.  Such tests could be interesting because you may want to define a function instead of a macro --- or a function that uses a macro --- to save script memory when the macro turns into a lengthy expression.  In theory, the macro would be faster because using a function requires the script interpreter to jump around in the code and to deal with putting parameters on the stack and with removing them while the macro does not.  But in theory, declared variables use script memory.
suitable place to set up your test objects and let the tests run for
at least a week, you may get valid results.  Such tests could be
interesting because you may want to define a function instead of a
macro --- or a function that uses a macro --- to save script memory
when the macro turns into a lengthy expression.  In theory, the macro
would be faster because using a function requires the script
interpreter to jump around in the code and to deal with putting
parameters on the stack and with removing them while the macro does
not.  But in theory, declared variables use script memory.


When programming in C and you have a statement like
When programming in C and you have a statement like
Line 1,197: Line 929:




LSL is stupid in that it will call the function or evaluate the
LSL is stupid in that it will call the function or evaluate the expression the macro turned into even when '(A < 6)' evaluates to false.  It´s impossible for the whole condition to evaluate to true when '(A < 6)' is false, and evaluating a condition should stop once its outcome has been evaluated.  Calling a function takes time, and the function even might be fed with illegal parameters because B could have an illegal value the function cannot handle, depending on what the value of A is:
expression the macro turned into even when '(A < 6)' evaluates to
false.  It´s impossible for the whole condition to evaluate to true
when '(A < 6)' is false, and evaluating a condition should stop once
its outcome has been evaluated.  Calling a function takes time, and
the function even might be fed with illegal parameters because B could
have an illegal value the function cannot handle, depending on what
the value of A is:




Line 1,233: Line 958:




This example will yield a math error because some_function() is called
This example will yield a math error because some_function() is called even when it should not be called.  Such nonsense should be fixed.  Calling the function is an undesireable side effect.  To work around it so that some_function() isn´t called, you need to add another "if" statement, which eats memory.
even when it should not be called.  Such nonsense should be
fixed.  Calling the function is an undesireable side effect.  To work
around it so that some_function() isn´t called, you need to add
another "if" statement, which eats memory.


Depending on what the function or the macro does, your "if" statements
Depending on what the function or the macro does, your "if" statements can affect performance.  This is independent of using a preprocessor, but since you may be tempted to use macros in your sources when you use a preprocessor, you may be tempted to use them in "if" statements and create more undisirable side effects than you otherwise would.
can affect performance.  This is independent of using a preprocessor,
but since you may be tempted to use macros in your sources when you
use a preprocessor, you may be tempted to use them in "if" statements
and create more undisirable side effects than you otherwise would.


'''Avoid "if" statements which evaluate expressions that should not be evaluated.'''
'''Avoid "if" statements which evaluate expressions that should not be evaluated.'''
Line 1,320: Line 1,037:




Imagine you defined 'plus3times3' without any parentheses at all.  You´d
Imagine you defined 'plus3times3' without any parentheses at all.  You´d waste hours trying to figure out why your script yields incredibly weird results.
waste hours trying to figure out why your script yields incredibly
weird results.


To get it right:
To get it right:
Line 1,341: Line 1,056:




There is quite a difference between 63 and 324.  Use parentheses.  I also
There is quite a difference between 63 and 324.  Use parentheses.  I also always use parentheses with conditions because it makes them unambiguous:
always use parentheses with conditions because it makes them unambiguous:




Line 1,360: Line 1,074:




That is not ambiguous.  Since "&&" usually takes precedence over "||",
That is not ambiguous.  Since "&&" usually takes precedence over "||", it´s different from the version without parentheses.  (It probably doesn´t matter in this case, but I´m not even trying to figure it out. It does matter in C because it has an effect on which of the expressions are evaluated and wich are not.  Simply write what you mean, with parentheses, and don´t let LSL get you to develop a bad style.)  When you have the above "if" statements in a macro, use parentheses around "A", "B", "C" and "D", for reasons shown above.
it´s different from the version without parentheses.  (It probably
doesn´t matter in this case, but I´m not even trying to figure it
out. It does matter in C because it has an effect on which of the
expressions are evaluated and wich are not.  Simply write what you
mean, with parentheses, and don´t let LSL get you to develop a bad
style.)  When you have the above "if" statements in a macro, use
parentheses around "A", "B", "C" and "D", for reasons shown above.


Save yourself the hassle.  My LSL scripts tend to have lots of
Save yourself the hassle.  My LSL scripts tend to have lots of parentheses, a lot of them not strictly needed because they come from macro definitions.  They don´t hurt anything.  Not having them can hurt badly.
parentheses, a lot of them not strictly needed because they come from
macro definitions.  They don´t hurt anything.  Not having them can
hurt badly.


=====How to use cpp=====
=====How to use cpp=====


Finally, there is the question of how to use cpp.  Cpp has a lot of
Finally, there is the question of how to use cpp.  Cpp has a lot of command line parameters, and one particularly useful when using cpp for LSL is '-P'.  The manpage of cpp says:
command line parameters, and one particularly useful when using cpp
for LSL is '-P'.  The manpage of cpp says:




Line 1,384: Line 1,086:




The LSL compiler is one of the programs that would be confused by line
The LSL compiler is one of the programs that would be confused by line markers.  You can see what the line markers look like when you preprocess one of your scripts with
markers.  You can see what the line markers look like when you
preprocess one of your scripts with




Line 1,394: Line 1,094:




By default, cpp writes its output to stdout, i. e. it appears on your
By default, cpp writes its output to stdout, i. e. it appears on your screen.  Since it isn´t very useful to have your LSL scripts appear on your screen like that, you may want to redirect the output into a file:
screen.  Since it isn´t very useful to have your LSL scripts appear on
your screen like that, you may want to redirect the output into a
file:




Line 1,405: Line 1,102:




This creates, without the line markers, a file that contains your
This creates, without the line markers, a file that contains your preprocessed source.  This file is named "your_script.i".
preprocessed source.  This file is named "your_script.i".


It´s that simple.
It´s that simple.


You can now write a script and use defines and macros and preprocess
You can now write a script and use defines and macros and preprocess it.  You can log in with your sl client, make a box, create a new script in the box, open the script, click on the "Edit" button and replace the default script that shows up in your favourite editor with the one you wrote and preprocessed.
it.  You can log in with your sl client, make a box, create a new
script in the box, open the script, click on the "Edit" button and
replace the default script that shows up in your favourite editor with
the one you wrote and preprocessed.


It´s not entirely convenient yet because you still need to replace the
It´s not entirely convenient yet because you still need to replace the script manually.  You may also want to include files into your script, like "lslstddef.h".  You don´t want to keep another copy of "lslstddef.h" with all your scripts and include it with '#include "lslstddef.h"'.  You only need one copy of "lslstddef.h", and it´s a good idea to keep it in a directory, perhaps with other headers you use.
script manually.  You may also want to include files into your script,
like "lslstddef.h".  You don´t want to keep another copy of
"lslstddef.h" with all your scripts and include it with '#include
"lslstddef.h"'.  You only need one copy of "lslstddef.h", and it´s a
good idea to keep it in a directory, perhaps with other headers you
use.


Cpp has another parameter for this: '-I'.  '-I' tells cpp where to
Cpp has another parameter for this: '-I'.  '-I' tells cpp where to look for files you include with something like '#include <lslstddef.h>'.
look for files you include with something like '#include <lslstddef.h>'.


If you haven´t pulled (i. e. downloaded) the
If you haven´t pulled (i. e. downloaded) the [https://github.com/Ratany/lsl-repo git repository that accompanies this article], please do so now.
[https://github.com/Ratany/lsl-repo git repository that accompanies this article],
please do so now.


When you look into the "projects" directory from the repository,
When you look into the "projects" directory from the repository, you´ll find a few directories in it:
you´ll find a few directories in it:




Line 1,446: Line 1,128:




The "bin" directory contains tools; "include" is for files like
The "bin" directory contains tools; "include" is for files like "lslstddef.h" which you may want to include into many of your sources. The "lib" directory is much like the "include" directory; the difference is that "include" is for header files which provide defines and macros, while the "lib" directory is for files parts of which will be included into your sources when you set up defines which get these parts included.  Those parts can be functions or defines or macros, and they are put together in files depending on their purpose.
"lslstddef.h" which you may want to include into many of your
sources. The "lib" directory is much like the "include" directory; the
difference is that "include" is for header files which provide defines
and macros, while the "lib" directory is for files parts of which will
be included into your sources when you set up defines which get these
parts included.  Those parts can be functions or defines or macros,
and they are put together in files depending on their purpose.


For example, "lib/getlinknumbers.lsl" provides functions related to
For example, "lib/getlinknumbers.lsl" provides functions related to figuring out the link numbers of prims. "getlinknumbers.lsl" is a library of such functions, macros and defines.  Since it´s a library, it´s in the "lib" directory: "lib" as in "library".
figuring out the link numbers of prims. "getlinknumbers.lsl" is a
library of such functions, macros and defines.  Since it´s a library,
it´s in the "lib" directory: "lib" as in "library".


The "template-directories" directory is a template which you can copy
The "template-directories" directory is a template which you can copy when you start writing a new script.  It contains directories to put particular files, like the "src" directory where you put the source you´re writing.  It also has a "lib" directory which can hold files you want to include into one or several of the sources in the "src" directory, but which are too specific to the particular project that you don´t want to put them into the general "projects/lib" or "projects/include" directory.
when you start writing a new script.  It contains directories to put
particular files, like the "src" directory where you put the source
you´re writing.  It also has a "lib" directory which can hold files
you want to include into one or several of the sources in the "src"
directory, but which are too specific to the particular project that
you don´t want to put them into the general "projects/lib" or
"projects/include" directory.


Now when preprocessing your source with cpp, you may want to include
Now when preprocessing your source with cpp, you may want to include "lslstddef.h".  Since you´re starting a new project, you first copy the template directories:
"lslstddef.h".  Since you´re starting a new project, you first copy
the template directories:




Line 1,489: Line 1,152:




Now you can use your favourite editor, write your script and save it
Now you can use your favourite editor, write your script and save it as "projects/your-script/src/your-script.lsl":
as "projects/your-script/src/your-script.lsl":




Line 1,507: Line 1,169:




As described above, you run 'cpp -P your_script.lsl > your_script.i'.
As described above, you run 'cpp -P your_script.lsl > your_script.i'. What you get looks like this:
What you get looks like this:




Line 1,521: Line 1,182:




This tells you that cpp didn´t find "lslstddef.h".  You need to tell
This tells you that cpp didn´t find "lslstddef.h".  You need to tell cpp where to find it:
cpp where to find it:




Line 1,531: Line 1,191:




Using '-I', you tell cpp to look for files that are to be included in
Using '-I', you tell cpp to look for files that are to be included in the directory '../../include'.  Since you do this from within the directory 'projects/your-script/src', cpp will look into 'projects/include' --- where it can find "lslstddef.h".
the directory '../../include'.  Since you do this from within the
directory 'projects/your-script/src', cpp will look into
'projects/include' --- where it can find "lslstddef.h".


You can now look at the outcome:
You can now look at the outcome:
Line 1,552: Line 1,209:




You may think that this is inconvenient.  You do not want to type
You may think that this is inconvenient.  You do not want to type something like 'cpp -I../../include -P your_script.lsl > your_script.i' for every script.  You don´t want to do this for every script when a file included by the source may have changed.  You don´t want to do this for all the scripts that are part of your project just in case you have modified one of them, and you don´t want to forget to preprocess all the scripts that need to be preprocessed every time you modified something.
something like 'cpp -I../../include -P your_script.lsl >
your_script.i' for every script.  You don´t want to do this for every
script when a file included by the source may have changed.  You don´t
want to do this for all the scripts that are part of your project just
in case you have modified one of them, and you don´t want to forget to
preprocess all the scripts that need to be preprocessed every time you
modified something.


Fortunately, you don´t need to do that.  There is a tool that can do
Fortunately, you don´t need to do that.  There is a tool that can do it for you.  It´s called "make".
it for you.  It´s called "make".


====Make====
====Make====


Very nicely said, [http://www.gnu.org/software/make/ Make] "is a tool
Very nicely said, [http://www.gnu.org/software/make/ Make] "is a tool which controls the generation of executables and other non-source files of a program from the program's source files."
which controls the generation of executables and other non-source
files of a program from the program's source files."




As you may have noticed, in the previous section of this article I
As you may have noticed, in the previous section of this article I have referred to "scripts" (LSL scripts) and "sources", without mentioning what the difference is.  The source is what you write.  It can be an LSL script that can be compiled by an LSL compiler right away.  That´s probably what you have written until you read this article.
have referred to "scripts" (LSL scripts) and "sources", without
mentioning what the difference is.  The source is what you write.  It
can be an LSL script that can be compiled by an LSL compiler right
away.  That´s probably what you have written until you read this
article.


After reading this article, you may not want to write plain LSL
After reading this article, you may not want to write plain LSL scripts anymore.  You may want to use a decent editor, a preprocessor and Make.  What you´ll write is still source, but it may be source which cannot be compiled by an LSL compiler right away because it needs to go through a preprocessor first.  That´s what I was trying to refer to as "source".
scripts anymore.  You may want to use a decent editor, a preprocessor
and Make.  What you´ll write is still source, but it may be source
which cannot be compiled by an LSL compiler right away because it
needs to go through a preprocessor first.  That´s what I was trying to
refer to as "source".


You could say that
You could say that '''An LSL script is the output of a preprocessor that processes a source file.''' I don´t write LSL scripts anymore.  I write source files that can be turned into LSL scripts.  It´s much easier to create LSL scripts that way than it is to write them.
'''An LSL script is the output of a preprocessor that processes a source file.'''  
I don´t write LSL scripts anymore.  I write source files that can be
turned into LSL scripts.  It´s much easier to create LSL scripts that
way than it is to write them.


In the sense of Make, the LSL script falls under "other non-source
In the sense of Make, the LSL script falls under "other non-source files".  Make isn´t used here to generate an [http://en.wikipedia.org/wiki/Executable | executable].  LSL scripts are not exectutable, and apparently the LSL compiler compiles them into [http://en.wikipedia.org/wiki/Bytecode | byte code].
files".  Make isn´t used here to generate an
[http://en.wikipedia.org/wiki/Executable | executable].  LSL scripts
are not exectutable, and apparently the LSL compiler compiles them
into [http://en.wikipedia.org/wiki/Bytecode | byte code].


"Other non-source files" can also mean documentation.  The provided
"Other non-source files" can also mean documentation.  The provided makefile has entries that use pdflatex to create a PDF file from a [http://www.latex-project.org/ LaTeX] source and to copy the PDF into the project directory.  I don´t write PDF files.  I write source files that can be turned into PDF files.  It´s much easier to create PDF files that way than it is to write them :)
makefile has entries that use pdflatex to create a PDF file from a
[http://www.latex-project.org/ LaTeX]
source and to copy the PDF into the project directory.  I don´t
write PDF files.  I write source files that can be turned into PDF
files.  It´s much easier to create PDF files that way than it is to
write them :)




Preprocessing "your-script.lsl" like in the previous section generates
Preprocessing "your-script.lsl" like in the previous section generates a "non-source file" from a source file.  You have controlled the generation of the non-source file manually, which can be inconvenient.
a "non-source file" from a source file.  You have controlled the
generation of the non-source file manually, which can be inconvenient.


More conveniently, Make can do this job for you.  All you need for
More conveniently, Make can do this job for you.  All you need for that is Make, and a so-called Makefile.  It´s called Makefile because it tells Make what it needs to do to generate files from files, and under which conditions which files need to be created.
that is Make, and a so-called Makefile.  It´s called Makefile because
it tells Make what it needs to do to generate files from files, and
under which conditions which files need to be created.


You do not need to understand Make or Makefiles to make creating LSL
You do not need to understand Make or Makefiles to make creating LSL scripts easier.  You find a suitable Makefile in the [https://github.com/Ratany/lsl-repo git repository that accompanies this article]. The Makefile is "projects/make/Makefile".
scripts easier.  You find a suitable Makefile in the
[https://github.com/Ratany/lsl-repo git repository that accompanies this article].
The Makefile is "projects/make/Makefile".


This Makefile is symlinked into the template-directories.  There is
This Makefile is symlinked into the template-directories.  There is only one Makefile for all your projects.  It works for all of them.  This has the advantage --- or disadvantage --- that when you change the Makefile, the changes apply to all your projects.
only one Makefile for all your projects.  It works for all of
them.  This has the advantage --- or disadvantage --- that when you
change the Makefile, the changes apply to all your projects.


Should you need to adjust the Makefile because a particular project
Should you need to adjust the Makefile because a particular project has special requirements, remove the symlink to the Makefile from the projects´ directory.  Copy make/Makefile into the projects´ directory and then modify the copy.
has special requirements, remove the symlink to the Makefile from the
projects´ directory.  Copy make/Makefile into the projects´ directory
and then modify the copy.




=====How to use Make=====
=====How to use Make=====


Enter the directory of the project you are working on and run 'make'.
Enter the directory of the project you are working on and run 'make'. That´s all.
That´s all.


You can run make from within emacs: M-x compile.  Emacs will show you
You can run make from within emacs: M-x compile.  Emacs will show you the output in a buffer.  If there are error messages, you can move the cursor to the error message and press enter and emacs shows you the line in the file that triggered the error message.  To compile again, you can use 'recompile': M-x recompile.  You can make a key-binding for that with something like '(global-set-key [C-f1] 'recompile)' in your ~/.emacs and recompile your source conveniently from within your favourite editor --- which by now is, of course, emacs --- by simply pressing Ctrl+F1.
the output in a buffer.  If there are error messages, you can move the
cursor to the error message and press enter and emacs shows you the
line in the file that triggered the error message.  To compile again,
you can use 'recompile': M-x recompile.  You can make a key-binding
for that with something like '(global-set-key [C-f1] 'recompile)' in
your ~/.emacs and recompile your source conveniently from within your
favourite editor --- which by now is, of course, emacs --- by simply
pressing Ctrl+F1.


=====Make "your-example.lsl"=====
=====Make "your-example.lsl"=====


Assume you want to try out the "your-script.lsl" source from
Assume you want to try out the "your-script.lsl" source from above. You need to make an LSL script which you can put into an object for this.  Suppose you make a box and name it "testbox".
above. You need to make an LSL script which you can put into an object
for this.  Suppose you make a box and name it "testbox".


Enter the directory for the project ("/projects/your-script/") and run
Enter the directory for the project ("/projects/your-script/") and run 'make':
'make':




Line 1,664: Line 1,261:




This has done a few things which it doesn´t tell you about.  It merely
This has done a few things which it doesn´t tell you about.  It merely tells you that it was compling --- you could say preprocessing instead since your script isn´t really compiled --- and that the resulting LSL script has 8 lines and 153 characters.
tells you that it was compling --- you could say preprocessing instead
since your script isn´t really compiled --- and that the resulting LSL
script has 8 lines and 153 characters.


You probably can´t wait to see the results now.  Two non-source files
You probably can´t wait to see the results now.  Two non-source files were generated from the source files:
were generated from the source files:




Line 1,678: Line 1,271:




The first one is the so-called debugging version, the second one is
The first one is the so-called debugging version, the second one is the version you put into the "textbox" as a script.  The second one is "compressed" and not easy to read.  You can put either into the "testbox" as a script.
the version you put into the "textbox" as a script.  The second one is
"compressed" and not easy to read.  You can put either into the
"testbox" as a script.


If you remember the perl script that automatically replaces the
If you remember the perl script that automatically replaces the scripts in the built-in editor
scripts in the built-in editor




With the Makefile from the repository, Make does a bit more than only
With the Makefile from the repository, Make does a bit more than only running cpp.  It will show you what it does when you run 'make -n', and the '-B' option tells Make to do everything it would, much as if you were running it for the first time for your new source:
running cpp.  It will show you what it does when you run 'make -n',
and the '-B' option tells Make to do everything it would, much as if
you were running it for the first time for your new source:




Line 1,708: Line 1,294:




Make runs cpp, [http://www.gnu.org/software/sed/ sed],
Make runs cpp, [http://www.gnu.org/software/sed/ sed], [http://en.wikipedia.org/wiki/Wc_(Unix) wc], [http://astyle.sourceforge.net/ astyle], etags (which comes with emacs), [http://en.wikipedia.org/wiki/Cat_(Unix) cat], [http://www.gnu.org/software/coreutils/ echo] and [http://www.gnu.org/software/coreutils/ md5sum].
[http://en.wikipedia.org/wiki/Wc_(Unix) wc],
[http://astyle.sourceforge.net/ astyle], etags (which comes with
emacs), [http://en.wikipedia.org/wiki/Cat_(Unix) cat],
[http://www.gnu.org/software/coreutils/ echo] and
[http://www.gnu.org/software/coreutils/ md5sum].


It also runs i2o.pl, which is a perl script.  You can find it in the
It also runs i2o.pl, which is a perl script.  You can find it in the [https://github.com/Ratany/lsl-repo git repository that accompanies this article] in the projects/bin directory.
[https://github.com/Ratany/lsl-repo git repository that accompanies this article]
in the projects/bin directory.


i2o.pl comes from the [http://opengate.ma8p.com/open9.tar.gz source]
i2o.pl comes from the [http://opengate.ma8p.com/open9.tar.gz source] of the [http://ma8p.com/~opengate/ Open Stargate Network] stargates.  It´s slighly modified.  The Makefile is from the same source and has also been modified.  The whole idea of preprocessing the source with cpp to generate LSL scripts is from there.  It´s not my idea, I´m merely using it and writing this article about it.
of the [http://ma8p.com/~opengate/ Open Stargate Network]
stargates.  It´s slighly modified.  The Makefile is from the same
source and has also been modified.  The whole idea of preprocessing
the source with cpp to generate LSL scripts is from there.  It´s
not my idea, I´m merely using it and writing this article about it.


When you have successfully run 'make', you will find files in the
When you have successfully run 'make', you will find files in the 'projects/your-script/dbg' and 'projects/your-script/bin' directories. 'dbg' stands for 'debug', 'bin' for 'binary'.  The files in these directories are LSL scripts that have been generated from your source in the 'projects/your-script/src' directory --- in this case, from 'projects/your-script/src/your-script.lsl'.
'projects/your-script/dbg' and 'projects/your-script/bin'
directories. 'dbg' stands for 'debug', 'bin' for 'binary'.  The files
in these directories are LSL scripts that have been generated from
your source in the 'projects/your-script/src' directory --- in this
case, from 'projects/your-script/src/your-script.lsl'.


You will find "your-script.i" and "your-script.i.clean" in the 'dbg'
You will find "your-script.i" and "your-script.i.clean" in the 'dbg' directory.  "your-script.i.clean" is an intermediate file which was created because it is not exactly a good idea to let sed overwrite its input file with its output.  You can delete "your-script.i.clean" if you like, and/or modify the Makefile to delete it for you.
directory.  "your-script.i.clean" is an intermediate file which was
created because it is not exactly a good idea to let sed overwrite its
input file with its output.  You can delete "your-script.i.clean" if
you like, and/or modify the Makefile to delete it for you.


By filtering the output of cpp with sed, a few unwanted things are
By filtering the output of cpp with sed, a few unwanted things are removed from the LSL scripts.  The things removed with sed are introduced through the way I´m using macros which are defined in "lslstddef.h".  Some of those lead to closing-braces followed by a semicolon ("};"), to double-semicolons (";;") or to an otherwise emtpy line with only a semicolon (";").  For example:
removed from the LSL scripts.  The things removed with sed are
introduced through the way I´m using macros which are defined in
"lslstddef.h".  Some of those lead to closing-braces followed by a
semicolon ("};"), to double-semicolons (";;") or to an otherwise emtpy
line with only a semicolon (";").  For example:




Line 1,756: Line 1,316:




This makes debug messages (of debug level 0) disappear when 'DEBUG0'
This makes debug messages (of debug level 0) disappear when 'DEBUG0' does not evaluate to true.  The source may look like this:
does not evaluate to true.  The source may look like this:




Line 1,778: Line 1,337:




The debug message disappears because it´s defined to disappear.  But
The debug message disappears because it´s defined to disappear.  But the semicolon at the end of the line is not part of the macro definition because when a line doesn´t end with a semicolon, emacs indents the following lines in an undesired way: That the semicolon is missing makes emacs figure that the line continues on the next line, which it doesn´t.  If the semicolon was part of the macro definition, I´d still have to put it in to get the indentation as desired.  It only would create a line with two semicolons when the debug message does not disappear.  So when the debug message disappears, the semicolon remains.  Sed removes it by deleting the whole line.
the semicolon at the end of the line is not part of the macro
definition because when a line doesn´t end with a semicolon, emacs
indents the following lines in an undesired way: That the semicolon is
missing makes emacs figure that the line continues on the next line,
which it doesn´t.  If the semicolon was part of the macro definition,
I´d still have to put it in to get the indentation as desired.  It
only would create a line with two semicolons when the debug message
does not disappear.  So when the debug message disappears, the
semicolon remains.  Sed removes it by deleting the whole line.


Sed "knows" what to do because it is instructed by a sed-script.  You
Sed "knows" what to do because it is instructed by a sed-script.  You can find this sed-script as "projects/clean-lsl.sed".
can find this sed-script as "projects/clean-lsl.sed".


Wc is a nice tool to give you statistics about text files.  It can
Wc is a nice tool to give you statistics about text files.  It can count words, bytes, lines etc..  It´s purely informational; I put it into the Makefile because I wanted to see what difference the debug mesages make for the size of the file and number of lines.  You can comment it out in the Makefile, or remove it from the Makefile in case you don´t want it.
count words, bytes, lines etc..  It´s purely informational; I put it
into the Makefile because I wanted to see what difference the debug
mesages make for the size of the file and number of lines.  You can
comment it out in the Makefile, or remove it from the Makefile in case
you don´t want it.


The same goes for astyle, it´s not required.  It reformats the code so
The same goes for astyle, it´s not required.  It reformats the code so that it´s easier to read than the output of cpp.  It´s useful when you want to put the LSL script generated from your source on wiki pages, and for debugging.
that it´s easier to read than the output of cpp.  It´s useful when you
want to put the LSL script generated from your source on wiki pages,
and for debugging.


i2o.pl "compresses" the LSL script.  It removes whitespace and line
i2o.pl "compresses" the LSL script.  It removes whitespace and line end-markers, and it shortens the names of variables.  One advantage is that you may get a script which uncompressed has more than 64kB to work because it fits into "a script": The editor built into sl clients (or, more likely, the server does it) cuts off anything beyond 64kB when you upload something into a notecard or into a script.  Another advantage is that your script may need less memory.  My unverified impression is that "compressed" scripts even run a tiny bit faster, though that seems unlikely since they are compiled to bytecode just like uncompressed ones.  Test it if you like --- since you got less to upload when the script is "compressed", in theory the "compressed" scripts don´t take as long to upload as uncompressed ones and you can test them faster.  The server may even experience not so many difficulties.
end-markers, and it shortens the names of variables.  One advantage is
that you may get a script which uncompressed has more than 64kB to
work because it fits into "a script": The editor built into sl clients
(or, more likely, the server does it) cuts off anything beyond 64kB
when you upload something into a notecard or into a script.  Another
advantage is that your script may need less memory.  My unverified
impression is that "compressed" scripts even run a tiny bit faster,
though that seems unlikely since they are compiled to bytecode just
like uncompressed ones.  Test it if you like --- since you got less to
upload when the script is "compressed", in theory the "compressed"
scripts don´t take as long to upload as uncompressed ones and you can
test them faster.  The server may even experience not so many
difficulties.






[[User:Ratany Resident|Ratany Resident]] 15:12, 9 January 2014 (PST)
[[User:Ratany Resident|Ratany Resident]] 16:06, 10 January 2014 (PST)
{{LSLC|Tutorials}}
{{LSLC|Tutorials}}

Revision as of 17:06, 10 January 2014

This article is currently being worked on.


How to make creating LSL scripts easier

This article is currently being worked on.


About

This article is about tools you can use to make creating LSL scripts easier. It tries to explain how to combine and how to use them for this very purpose.

One of these tools is a preprocessor called cpp. It comes with GCC. Among the tools mentioned in this article, cpp is the tool which can make the biggest difference to how you write your sources and to how they look.


The idea of using cpp comes from the source of the Open Stargate Network stargates.

I want to thank the developers of these stargates. Not only their stargates are awesome. The idea of using cpp for LSL scripts is also awesome. It has made it so much easier to create LSL scripts that I have made many I otherwise would never have made.


This article is accompanied by a git repository. It is recommended that you pull (download) the repository. The repository contains some example sources used with this article, a few perl scripts and a Makefile. They are organised in a directory structure which they are designed to work with. You will have your own build system for LSL scripts when you have pulled this repository.

The repository does not contain basic standard tools like perl, cpp, make or an editor. This article does not attempt to tell you how to install these. You know better how to install software on your computer than I do, and how you install software also depends on which operating system you are using.

I´m using Linux, which is freely available in many different distributions. I´m using Fedora, so to install emacs, I run 'yum install emacs'. If you don´t use Fedora, telling you this probably doesn´t help you. Or you switch to Fedora; it works remarkably well.

Documentation is plentiful. You may find different software that does the same or similar things and may work better for you or not.

Editing this Article

This article is written in my favourite editor. When I update the article on the wiki, I edit the whole article, delete everything and paste the new version in. That´s simply the easiest and most efficient way for me.

Unfortunately, this means that your modifications may be lost when I update the article and don´t see them.

Please use the "discussion" page of this article to post contributions or to suggest changes so they can be added to the article.

Repository

It´s probably more fun to be able to try out things yourself while reading this article. If you want to do so, pull the repository. You can run 'git clone https://github.com/Ratany/lsl-repo.git' or | download it as an archive.

Once cloned or downloaded and unpacked, you have a directory called "lsl-repo". Inside this directory, you have another one called "projects".

You can put the "projects" directory anywhere you like, and you can give it a different name if you want to.

Inside the "projects" directory, you have more directories like this:


. !-- bin !-- clean-lsl.sed -> make/clean-lsl.sed !-- example-1 !-- include !-- lib !-- make !-- memtest !-- rfedip-reference-implementation !-- template-directories `-- your-script


When you rename or move "bin", "include", "lib", "make" or "clean-lsl.sed", your build system will not work anymore (unless you edit some files and make adjustments). Don´t rename or move them.

Do not use names for directories or files that contain special characters or whitespace. Special characters may work, whitespace probably doesn´t. Maybe it works fine; I haven´t tested.

Suppose you want to try out "example-1". Enter the directory "example-1" and run 'make':


<lsl> [~/src/lsl-repo/projects/example-1] make generating dependencies: dep/example-1.di generating dependencies: dep/example-1.d preprocessing src/example-1.lsl

 39 1202 dbg/example-1.i.clean

[~/src/lsl-repo/projects/example-1] </lsl>


You will find the LSL script as "projects/example-1/bin/example-1.o". A debugging version is "projects/example-1/dbg/example-1.i". When you modify the source ("projects/example-1/src/example-1.lsl") just run make again after saving it, and new versions of the script will be created that replace the old ones.

Files are generated in the "dep" directory. They are needed by the build system to figure out when a script needs to be rebuilt. You can ignore those. A file is generated in the "make" directory. The file is needed to automatically replace scripts with the script editor built into your sl client, which will be described later.

Now you can log into sl with your sl client. Make a box, create a "New Script" in the box and open the "New Script". It shows up in the built-in editor. Delete the script in the built-in editor and copy-and-paste the contents of "projects/example-1/bin/example-1.o" into the built-in editor. Then click on the "Save" button of the built-in editor. (There is a way to do this kind of script-replacement automatically. It will be explained later.)

The "template-directories" is what you copy when you want to write your own source. Suppose you make a copy of the "template-directories" and name the copy "my-test". You would then put your own source into "my-test/src", like "my-test/src/my-test.lsl".

Of course, you can have several source files in "my-test/src". For all of them, scripts will be created. The "my-test" directory is the project directory for your project "my-test". When you are working on an object that requires several scripts, you´ll probably want to keep them all together in the project directory you made for this object.


If you get an error message when you run 'make', you are probably missing some of the tools the build system uses. Install the missing tools and try again.

Tools you need for the build system

Required tools:


  • editor: your favourite editor, Emacs is recommended


Optional tools:



You can replace "make/Makefile" with "make/Makefile-no-optional" and try without the optional tools. You can make "echo" optional, but automatically replacing scripts won´t work unless you can find a replacement for "@echo "// =`basename $@`" > $@" in the Makefile. Since perl is required, you could replace "echo" (and "basename" and "sed") with perl scripts. You can probaly make further modifications so you won´t need perl, but what does one without it?

Editor

To create scripts, you do need an editor to write your sources. All sl clients I´ve seen have one built in, and it´s the worst editor I´ve ever seen. Fortunately, you don´t need to use it.

The built-in script editor is useful for replacing the scripts you have been working on in your favourite editor and to look at the error messages that may appear when your script doesn´t compile. Don´t use it for anything else.

Not using the buit-in script editor has several advantages like:


  • You can use a decent editor!
  • You have all your sources safely on your own storage system.
  • Your work does not get lost or becomes troublesome to recover when your sl client crashes, when you suddenly get logged out, when "the server experiences difficulties" again, when the object the script is in is being returned to you or otherwise gets "out of range".
  • You don´t need to be logged in to work on your sources.
  • You can preprocess and postprocess and do whatever else you like with your sources before uploading a script: You can use a build system.
  • You can share your sources and work together with others because you can use tools like git and websites like github. Even when you don´t share your sources, git is a very useful tool.
  • Your window manager handles the windows of your editor so that you are not limited to the window of your sl client which doesn´t do a great job when it comes to managing windows. You are not limited to a single monitor, and how fast you can move the cursor in the editor does not depend on the FPS-rate of your sl client.
  • You can use fonts easier to read for you, simply change the background and foreground colours and switch to the buffer in which you read your emails or write a wiki article like this one ...
  • Though it doesn´t crash, a decent editor has an autosave function and allows you to restore files in the unlikely case that this is needed.
  • And yes, you can use a decent editor, even your favourite one :)


You can use any editor you like. An editor is one of your most important tools. Use the best one you can find.

Emacs

Emacs is my favourite editor. Give it a try, and don´t expect to learn everything about it within a day. It´s enough when you can move the cursor around, type something and load and save files. Over time, you will learn more just by looking up how to do something and trying it and finding your way of doing what you need. Lots of documentation is available.

By default, the cursor keys move the cursor. Ctrl+xf loads a file into a new buffer and Ctrl+xs saves it. Ctrl+x b lets you switch to another buffer. These are usually written as C-xf, C-xs and C-x-b.

Emacs is worthwhile to learn. It´s the only editor you need to learn about because you don´t need any others. It has been around since a long time. If you had learned it twenty five years ago, that would have saved you the time wasted with other editors which nowadays may not even exist anymore.

It doesn´t hurt to try it out.

Syntax Highlighting

Emacs comes with syntax highlighting for many programming languages. It doesn´t come with syntax highlighting for LSL, but LSL syntax highlighting modes for emacs are available.

You can use Emacs_LSL_Mode.

A mofied version of lsl-mode is in the git repository that accompanies this article as "emacs/lsl-mode.el". This version has been modified for use with a file I named "lslstddef.h" ("projects/include/lslstddef.h" in the repository). Following sections will refer to and use this file.

\add ~/.emacs

Tags

Emacs supports so-called tags. Using tags allows you to find the declaration of functions, defines, macros and variables you use in your sources without searching. It doesn´t matter in which file they are.

The build system creates a so-called tags-file for you. Emacs can read it automatically. Esc+. (M-.) lets you enter a tag you want to go to and takes you there.

Git

See this page: A number of version control systems are supported, and git is one of them. Additional packages are available for this --- I haven´t tried any of them yet.

Other Editors

In case you find that you don´t get along with emacs, you may want to check out this page about editors. Try vi, or vim, instead.

You don´t need to try any other editors. EPM was nice, but it crashed all the time.

In any case, use an editor you are happy with because it does what you want. An editor is one of the most essential tools. You want your tools to work the way you want them to. You don´t want tools that get into your way.

Automatically replacing Scripts

The editor built into sl clients comes with buttons like to save and to reset the script which is being edited. There is also a button labled "Edit". As the label of this button suggests, the button is for editing the script. The built-in editor is not for editing the script, though it´s not impossible to kinda use it for that.

Built-in Editor with "Edit" button

Open a script in your sl client and click on the edit button of the built-in editor. You might get an error message telling you that the debug setting "ExternalEditor" is not set. Don´t panic! Set this debug setting so that your favourite editor is used, and click on the edit button again. This is merely to show the concept, and things will get more convenient.

Debug Setting "ExternalEditor"

Your favourite editor should now start and let you edit the script. This works because your sl client saves the script which you see in the built-in editor to a file on your storage system. Then your sl client starts whatever you specified in the debug setting as "external editor". To tell your favourite editor which file the script has been saved to, the place holder %s is used with the debug setting "ExternalEditor". The place holder is replaced with the name of the file your sl client has saved.

Once the script has been saved to a file, your sl client monitors this file for changes. When the file changes --- which it does when you edit and then save it with your favourite editor --- your sl client replaces the script in the built-in editor with the contents of the file.

This is nice because you could write a new script while you are offline. When you are ready to test the script, you could log in with your sl client, build a box, create a new script in the box, open the script, click on the "Edit" button, delete the contents of the new script in your favourite editor, insert the script you have written and save it.

Your sl client would notice that the file changed and put it into the built-in editor. It would let the sl server compile the script and show you the error messages, if any. When your script compiles and works right away, you´re done --- if not, you can use the built-in editor to check the error messages and your favourite editor to change the source as needed. Then you need to replace the script again.

At this point, things suddenly aren´t nice anymore: Replacing the script with your favourite editor all the time is a stupid process (unless you program your editor to do it for you). Even when you have only one script, having to replace the script over and over again while you are adding to it and debugging it sucks. It sucks even more when your object has 11 scripts working toegether and a little change makes it necessary to replace all of them.

You could mess around with the built-in editor and try to edit your script further with it, saving the replacing. That also sucks: The built-in editor is unusable, and you end up with at least two versions of the script. Since you want the script safely stored on your storage system and not lose your work when your sl client crashes or the like, you end up replacing the script on your storage system with the version you have in the built-in editor every now and then. When your object has 11 scripts that work together, it eventually becomes a little difficult to keep an overview of what´s going on --- not to mention that you need a huge monitor with a really high resolution to fit 11 built-in editor windows in a usable way on your screen to work on your scripts since there is no C-x b with the built-in editor to switch to a different buffer ...

Fortunately, there is a much better and very simple solution: A program which is started by your sl client instead of your favourite editor can replace the script for you. It´s the same concept from above you have tried out now. The concept does not require that you must abuse your favourite editor to replace scripts.

You can easily write a program that replaces files yourself. I wrote one when I got tired of all the replacing. It´s not really a program, but a perl script. It´s written in perl because perl is particularly well suited for the purpose. It looks like this:


<perl>

  1. !/bin/perl


  1. This program is free software: you can redistribute it and/or
  2. modify it under the terms of the GNU General Public License as
  3. published by the Free Software Foundation, either version 3 of the
  4. License, or (at your option) any later version.
  5. This program is distributed in the hope that it will be useful, but
  6. WITHOUT ANY WARRANTY; without even the implied warranty of
  7. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  8. General Public License for more details.
  9. You should have received a copy of the GNU General Public License
  10. along with this program. If not, see
  11. <http://www.gnu.org/licenses/>.


  1. usage: replace.pl <filename>
  2. replace file A with file B when the first line of file A is a key
  3. found in a look-up table; file B is specified by the value of the
  4. key
  5. Entries in the look-up table are lines. Each line holds a key-value
  6. pair, seperated by a colon and a space: 'key: value'.


use strict; use warnings; use autodie;

use File::Copy;


  1. the editor to use
  2. Note: Use an editor that waits before it exits until you are
  3. finished editing the file. Your sl client stops monitoring the file
  4. when the editor exits (or forks off into the background) before
  5. you´re done editing, and it may not replace the contents of its
  6. built-in editor with the contents of the file you´re editing.

my $editor = "emacsclient";

  1. "-c" makes emacsclient create a new frame. If you start your
  2. favourite editor without such a parameter, you want to remove
  3. $editorparam here an in the 'start_editor' function.

my $editorparam = "-c";


  1. a wrapper function to start the editor

sub start_editor

 {
   my (@files_to_edit) = @_;
   system($editor, $editorparam, @files_to_edit);
 }


  1. unless the filename given as parameter is *.lsl, edit the file

unless($ARGV[0] =~ m/.*\.lsl/) {

 start_editor($ARGV[0]);
 exit(0);

}


  1. the file name of the lookup table; specify an absolute path here

my $table = "/absolute/path/to/replaceassignments.txt";


  1. read the first line of the file; unless it matches a pattern like
  2. "// =filename.[o|i]", edit the file and the lookup table

open my $script, "<", $ARGV[0]; my $line = <$script>; close $script; chomp $line; $line =~ s!// =!!; unless(($line =~ m/.*\.o/) || ($line =~ m/.*\.i/))

 {
   start_editor($ARGV[0], $table);
   exit(0);
 }


  1. look up the key in the lookup table
  2. the key is looked up for *.o and *.i so that only one entry per
  3. script is needed in the lookup table to cover both *.i and *.o,
  4. provided that the directory structure doesn´t change

my $i_line = $line; $i_line =~ s/\.i$/\.o/; $i_line .= ": "; $line .= ": "; my $replacementfile = undef; open my $assign, "<", $table; while( <$assign> )

 {
   # ignore lines starting with "//" as comments
   #
   unless( m!^//!)
     {

chomp $_; if( m/$line/ || m/$i_line/) { $replacementfile = $'; last; }

     }
 }

close $assign;

if(defined($replacementfile))

 {
   if($line =~ m/.*\.i:/)
     {

$replacementfile =~ s!/bin/!/dbg/!; $replacementfile =~ s/\.o$/\.i/;

     }
   # when the value of the key looks ok, replace the file, otherwise edit
   # the file and the table
   #
   if(($replacementfile =~ m/.*\.o$/) || ($replacementfile =~ m/.*\.i$/))
     {

copy($replacementfile, $ARGV[0]);

     }
 }

else

 {
   start_editor($ARGV[0], $table);
 }

</perl>


You´ll find this script in the git repository that accompanies this article as "projects/bin/replace.pl". What it basically does is simple:

  • look at the name of the file which is supplied as a command line argument; when it´s not named like "*.lsl", start your favourite editor to edit the file
  • otherwise, look at the first line of the file
  • when the line starts with "// =", treat the rest of the line from there as a key; otherwise start your favourite editor to edit the file and another one which is a lookup-table
  • look up the key in a look-up table (a file) that associates the key with a value
  • when the key is found in the table, replace the file saved by the sl client with the file the name of which is specified by the value associated with the key
  • when the key is not found in the table, start your favourite editor to edit the file that was saved by the sl client and to edit the table


Entries in the table are so-called key-value pairs. The table looks like this:


<lsl> example-script.o: /path/to/bin/example-script.o

// // this is a comment //

foobar-script.o: /path/to/bin/foobar-script.o </lsl>


Note: The space between the colon and the file name is mandatory (because the perl script is written like that).

This means when you have a script in the built-in script editor like


<lsl> // =example-script.o

default { state_entry() { Say(0, "test"); } } </lsl>


and click on the "Edit" button of the built-in script editor, the script will automatically be replaced with the contents of the file "/path/to/example-script.o".

To make this work, you need to:


  • modify the perl script to start your favourite editor (in the unlikely case that your favourite editor isn´t emacs)
  • modify the perl script to tell it where to find the table that associates the keys and values ('my $table = "/absolute/path/to/replaceassignments.txt";')
  • change the debug setting "ExternalEditor" so that your sl client starts above perl script instead of your favourite editor
  • create the look-up table (the table can be empty)
  • make sure that your script starts with a line like "// =example-script.o"
  • try out the perl script:


You should first try out the perl script to see if it works.

When you have your sl client start it, it is difficult or impossible to see error messages you may get when running the perl script:


<lsl> [~/tmp] echo "// =example-script.o" > example-script.lsl [~/tmp] ~/src/lsl-repo/projects/bin/replace.pl example-script.lsl Can't open '/absolute/path/to/replaceassignments.txt' for reading: 'No such file or directory' at ~/src/lsl-repo/projects/bin/replace.pl line 97 [~/tmp] </lsl>


You see what I mean: You forgot to adjust the file name of the look-up table (or forgot to create the table) and get an error message because the file doesn´t exist. If you had your sl client start the perl script, apparently nothing would happen when you click the "Edit" button.

Once you have verified that the perl script works, you´re fine. However, when you have a typing error in the lookup-table, the script in the built-in editor might silently not be replaced with the new version. (You could modify the perl script so that it beeps when it does not replace the script, or experiment with something like 'xterm -hold -e replace.pl "%s"'. If 'xterm -hold ...' works, you could do something to run 'make' (see below) automatically when you click the "Edit" button.)

The perl script starts your favourite editor when it doesn´t find the key (i. e. "example-script.o") in the table. You can then edit both the script and the table. This means that you don´t need to fill the table in advance. You can start with an empty table ('touch /absolute/path/to/replaceassignments.txt') and add entries as you go.

The perl script uses the first entry in the table that matches. If you plan to have multiple scripts with the same name, you can make adjustments to the Makefile (see below) and don´t need to modify the perl script. It´s a good idea to decide about this before filling your table: When you want multiple scripts with the same name some time later, you will have to edit your table.

The build system (see below) creates "compressed" versions of the scripts and versions that are not compressed. The "compressed" versions are named *.o and the not-compressed versions are named *.i.

The "compressed" versions can be pretty unreadable, which can make debugging difficult. The not-compressed version is formattted with astyle (see below) to make it easier to read. It can be used for debugging. (The "compressed" version is what you want to put as a script into your object.)

To see the debugging version of the script in the built-in editor, change the first line of the script in the built-in editor from like "// =example-script.o" to "// =example-script.i" and click on the "Edit" button. That puts the version of the script which is not compressed into the built-in editor. Switch the "i" back to an "o", click the "Edit" button and the "compressed" version is put back.

Above perl script does the replacing for you, provided that the debugging version is in the "dbg" directory and the "compressed" version is in the "bin" directory and that both "dbg" and "bin" are in the same directory.

When you use the build system, you do not need to add entries to the lookup-table. They are automatically added as needed when you run 'make' (see below).

A Preprocessor

The Wikipedia article about preprocessors gives an interesting explanation of what a preprocessor is and provides an overview. For the purpose of making it easier to create LSL scripts, a preprocessor as is used with C (and C++) compilers is a very useful tool.

The particular preprocessor I´m using is called "cpp" and comes with GCC.

What a preprocessor does

A preprocessor enables you:


  • to include files into others
  • to define so-called "defines" and so-called "macros"
  • to create different scripts from the same sources through the use of conditionals


That doesn´t sound like much, yet these are rather powerful features. Some of the advantages that come with these features are:


  • You can create libraries and include them into your sources as needed: Your code becomes easily re-usable.
  • Your sources become much easier to modify and to maintain because instead of having to change parts of the code in many places, you can change a definition at one single place --- or code in a library or a header file --- and the change will apply everywhere.
  • Scripts may become less prone to bugs and errors because code is easy to re-use and easier to modify and to maintain. That can save you a lot of time.
  • You may create scripts that use less memory because instead of using variables as constants, you can use so-called defines. This may allow you to do something in one script rather than in two and save you all the overhead and complexity of script-intercommunication.
  • Macros can be used instead of functions, which may save memory and can help to break down otherwise unwieldy logic into small, simple pieces which are automatically put together by the preprocessor.
  • Using macros can make the code much more readable, especially with LSL since LSL is often unwieldy in that it uses lengthy function names (like llSetLinkPrimitiveParamsFast(), ugh ...) and in that the language is very primitive in that it doesn´t even support arrays, let alone user-definable data structures or pointers or references. It is a scripting language and not a programming language.
  • Using macros can make it much easier to create a script because you need to define a macro only once, which means you never again need to figure out how to do what the macro does and just use the macro. The same goes for libraries.
  • Frequently used macros can be gathered in a file like "lslstddef.h" which can be included into all your scripts. When you fix a bug in your "lslstddef.h" (or however you call it), the bug is fixed in all your scripts that include it with no need to look through many scripts to fix the very same bug in each of them at many places in their code. The same goes for bugs fixed in a library.
  • Macros can be used for frequently written code snippets --- like to iterate over the elements of a list --- and generate several lines of code automatically from a single line in your source. That can save you a lot of typing.
  • Conditionals can be used to put code which you have in your source into the resulting LSL script depending on a single setting or on several settings, like debugging messages that can be turned on or off by changing a single digit.
  • Using conditionals makes it simple to create libraries and to use only those functions from libraries which are needed by a particular script.
  • Macros and defines in your sources only make it into a script when they are used.


There are probably lots of advantages that don´t come to mind at the moment. You can be creative and find uses and advantages I would never think of.

How cpp works

Some extensive documentation about cpp can be found here.

Imagine you want to create a script that figures out which agent on the same parcel is the furthest away from the object the script is in and moves the object towards this agent when the owner of the object touches it.

In the following, the source for the script will be developed piece by piece. You can find the complete source in the git repository that accompanies this article in "projects/example-1/src/example-1.lsl".

Obviously, the script will need a touch event, a way to find out which agent is the furthest away and a a way of moving to the agent.

In this case, keyframed motion (KFM) is a good way to get the object moving: It doesn´t require the object to be physical and thus lets it move through obstacles like walls. KFM requires the object to use the so-called "prim equivalency system", which means that it must either be mesh, or the physics type of one of its prims must be either "convex hull" or "none". This can either be set in the editing window when editing the object, or by the script. The physics type of the root prim cannot be "none", and since it´s hard to say whether "none" or "convex hull" is better for a particular object, the script will simply use "convex hull". Since the physics type of the object needs to be set only once, the script should use a state_entry event to set it. It can be set with llSetLinkPrimitiveParamsFast().

Finding out which agent is the furthest away from the object requires to find out what agents there are. A sensor is unsuited because the agent could be out of sensor range, and the sensor would report no more than the closest sixteen agents. Using llGetAgentList() works much better for this. It is also necessary to find out what the distances between the agents and the object are, for which llGetObjectDetails() can be used.

The object should move to a position at "some distance" from the agent. It seems nicer to use a random distance rather than a fixed one. This can easily be done by creating a "random vector", i. e. a vector the components of which are randomized. What |llFrand() returns is not really a random number, but it´s sufficient for this purpose.

As with all sources, I want to use some of the defines that are gathered in my "lslstddef.h". One of them is the macro "RemotePos()". It is a shortcut for using llGetObjectDetails() to find out the position of and object or prim or agent. Another one is "SLPPF()", a shortcut for llSetLinkPrimitiveParamsFast() because llSetLinkPrimitiveParamsFast() is awfully long.

This makes for a source (example-1_A) that looks like this:


<lsl>

  1. include <lslstddef.h>


default { event touch_start(int t) { // find furthest agent and move to them on touch by owner }

event state_entry() { // set the physics type } } </lsl>


The '#include' in the first line is a so-called preprocessor directive. It tells the preprocessor to do something. There are other directives, like "#define", "#undef", "#if", "#endif" and some others.

"#include" tells cpp to find a file "lslstddef.h" within some directories it searches through by default and to make it so as if the contents of this file were part of the source. Suppose you have the source saved as "example-1.lsl": The preprocessor includes "lslstddef.h" into "example-1.lsl". It´s output will not contain the first line because the first line is a preprocessor directive.

The source ("example-1.lsl") is never modified by the preprocessor. Including "lslstddef.h" makes everything in "lslstddef.h" known to the preprocessor. The preprocessor produces an output which is a copy of the source with modifications applied to it as the directives, like the ones in "lslstddef.h", demand.

"lslstddef.h" has a lot of directives for the preprocessor. Most of them define something with "#define". When something is defined with "#define", it is a so-called define. Macros can also be defined with "#define". Macros are called macros because they are defines that take parameters.


You may have noticed that in example-1_A, it doesn´t read "touch_start()" but "event touch_start()". That´s because I write my sources like that: Putting "event" before events makes them easier to find and the source looks nicer and is easier to read for me. In your sources, you can do it the same way or use something else than "event" or just write "touch_start()" instead. You can see what it looks like on my screen in the example picture --- I currently use green on black in emacs. It sticks out, and "event" makes a good search string that saves me searching for particular events.

Easier to read

The LSL compiler wouldn´t compile the source, not only because it doesn´t know what to do with "#include" directives, but also because it doesn´t compile statements like "event touch_start()". Therefore, "lslstddef.h" defines that "event" is nothing:


<cpp>

  1. define event

</cpp>


By including "lslstddef.h", cpp knows this and replaces all occurances of "event" with nothing, making "event" disappear from the result. This doesn´t mean that "event" is not defined. It´s a define. You can use the directive "#ifdef" with such defines:


<cpp>

  1. ifdef event
  2. define test 1
  3. endif

</cpp>


This would create a define named "test" only if "event" is defined. Much like cpp replaces "event" with nothing, it would now replace "test" with "1" because "test" is defined to be "1".


The next thing the example script needs is to set the physics type. The define "SLPPF" is a shortcut for "llSetLinkPrimitiveParamsFast", so use that. The script also needs to check if it was the owner of the object who touched it and do nothing when it was someone else. It needs to know the position of the object the script is in and what agents are around so it can figure out which agent is the furthest away from the object. Add all this to the source, which is now example-1_B:


<lsl>

  1. include <lslstddef.h>


default { event touch_start(int t) { unless(llDetectedKey(0) == llGetOwner()) { return; }

vector here = llGetPos(); list agents = llGetAgentList(AGENT_LIST_PARCEL, []); }

event state_entry() { SLPPF(LINK_THIS, [PRIM_PHYSICS_SHAPE_TYPE, PRIM_PHYSICS_SHAPE_CONVEX]); } } </lsl>


With


<cpp>

  1. define event
  2. define SLPPF llSetLinkPrimitiveParamsFast

</cpp>


in "lslstddef.h", the preprocessor replaces "event" with nothing and "SLPPF" with "llSetLinkPrimitiveParamsFast".


Lists in LSL can be a pita to work with. You end up using functions to pick out list elements like llList2Key(). They combine with functions that return lists and lead to unwieldy expressions like


<lsl> llList2Vector(llGetObjectDetails(llList2Key(agents, 0), [OBJECT_POS]), 0); </lsl>


which combine with other functions like llVecDist() into even longer expressions like


<lsl> llVecDist(llList2Vector(llGetObjectDetails(llList2Key(agents, 0), [OBJECT_POS] ), 0),

         llList2Vector(llGetObjectDetails(llList2Key(agents, 1), [OBJECT_POS] ), 0));

</lsl>


if you were to compute the distance between two agents.


Lists are sometimes strided, and then you also need to remember which item of a stride is to be interpreted as what. Your code becomes hard to write, hard to read and hard to modify. You can use defines and macros to avoid this.

The list used in example-1_B is not strided, simple and a local variable. Using a define and macros to make it useable is overdoing it, but I want to show you how it can be done.

You can put preprocessor directives anywhere into your source. They don´t need to be kept in seperate files. Traditionally, they had to be at the beginning of a line. This isn´t required anymore --- but do not indent them! They are not part of your code. They modify your code. You could see them as "metacode".

With example-1_B, four things can be defined to make the list of agents easier to use:


  1. a define for the stride of the list: "#define iSTRIDE_agents 1"
  1. a macro that gives the UUID of an agent on the list: "#define kAgentKey(x) llList2Key(agents, x)"
  1. a macro that gives the position of an agent on the list: "#define vAgentPos(x) RemotePos(kAgentKey(x))"
  1. a macro that gives the distance of an agent from the object: "#define fAgentDistance(x) llVecDist(here, vAgentPos(x))"


Those will be used for a loop that iterates over the list. This leads to example-1_C:


<lsl>

  1. include <lslstddef.h>


default { event touch_start(int t) { unless(llDetectedKey(0) == llGetOwner()) { return; }

vector here = llGetPos(); list agents = llGetAgentList(AGENT_LIST_PARCEL, []);

  1. define iSTRIDE_agents 1
  2. define kAgentKey(x) llList2Key(agents, x)
  3. define vAgentPos(x) RemotePos(kAgentKey(x))
  4. define fAgentDistance(x) llVecDist(here, vAgentPos(x))

int agent = Len(LIST_agents); while(agent) { agent -= iSTRIDE_agents; } }

event state_entry() { SLPPF(LINK_THIS, [PRIM_PHYSICS_SHAPE_TYPE, PRIM_PHYSICS_SHAPE_CONVEX]); } } </lsl>


Since the list is a local variable, the defines are placed after its declaration. You could place them somewhere else, maybe at the top of the source. Placing them inside the scope of the list makes clear that they are related to this particular list.

The define "iSTRIDE_agents" is the stride of the list, 1 in this case. Using "--agent;" in the loop instead of "agent -= iSTRADE_agents;" would be better, but it´s done for the purpose of demonstration. When you have a strided list, it is a good idea to always use a define for it: It doesn´t hurt anything and makes it easy to modify your source because there is only one place you need to change. With strided lists, use defines for the indices of stride-items so you don´t need to go through the macro definitions to make sure the indices are ok when you change the composition of the list. You can look at "projects/rfedip-reference-implementation/src/rfedipdev-enchain.lsl" in the git repository that accompanies this article for a better example.

"kAgentKey" is a macro. It´s a define that takes parameters. You might think the parameter is an integer which is an index to the list in which the UUIDs of the agents are stored. It is not: The parameter is used an index. The preprocessor doesn´t know anything about lists or integers.

There are no types involved with macro parameters as are with variables: When you declare a function in LSL that takes parameters, you need to declare what type the parameters are of. Macro parameters don´t have a type. The preprocessor replaces stuff literally. You could use a vector or some random string as a parameter with "kAgentKey". Cpp would do the replacement just fine. It replaces text like a search-and-replace operation you perform with your favourite editor does.

Unfortunately, you cannot define defines or macros with defines: "#define NEWLIST(name, stride) #define list name; #define iSTRIDE_##name stride" will cause cpp to stop proprocessing and to print an error message. Maybe I´ll write a perl script to pre-preprocess my sources ... that might be easier than learning | m4.

"vAgentPos" and "fAgentDistance" are also macros and work the same way as "kAgentKey". The code to figure out which agent is the one furthest away from the object is simple, and I´ll skip over it. It gets interesting when the movement is implemented, so here is example-1_D, the complete source:


<lsl>

  1. include <lslstddef.h>


  1. define fUNDETERMINED -1.0
  2. define fIsUndetermined(_f) (fUNDETERMINED == _f)
  1. define fMetersPerSecond 3.0
  2. define fSecondsForMeters(d) FMax((d), 0.03)
  1. define fRANGE(_f) (_f)
  2. define vMINRANGE(_f) (<_f, _f, _f>)
  3. define fRandom(_fr) (llFrand(fRANGE(_fr)) - llFrand(fRANGE(_fr)))
  4. define vRandomDistance(_fr, _fm) (<fRandom(_fr), fRandom(_fr), fRandom(_fr)> + vMINRANGE(_fm))
  1. define fDistRange 3.5
  2. define fMinDist 0.5


default { event touch_start(int t) { unless(llDetectedKey(0) == llGetOwner()) { return; }

vector here = llGetPos(); list agents = llGetAgentList(AGENT_LIST_PARCEL, []);

  1. define iSTRIDE_agents 1
  2. define kAgentKey(x) llList2Key(agents, x)
  3. define vAgentPos(x) RemotePos(kAgentKey(x))
  4. define fAgentDistance(x) llVecDist(here, vAgentPos(x))

float distance = fUNDETERMINED; int goto; int agent = Len(agents); while(agent) { agent -= iSTRIDE_agents;

float this_agent_distance = fAgentDistance(agent); if(distance < this_agent_distance) { distance = this_agent_distance; goto = agent; } }

unless(fIsUndetermined(distance)) { distance /= fMetersPerSecond; vector offset = vRandomDistance(fDistRange, fMinDist); offset += PosOffset(here, vAgentPos(goto)); llSetKeyframedMotion([offset, fSecondsForMeters(distance)], [KFM_DATA, KFM_TRANSLATION]); }

  1. undef AgentKey
  2. undef AgentPos
  3. undef AgentDistance

}

event state_entry() { SLPPF(LINK_THIS, [PRIM_PHYSICS_SHAPE_TYPE, PRIM_PHYSICS_SHAPE_CONVEX]); } } </lsl>


There isn´t much new to it. "unless" is defined in "lslstddef.h" and speaks for itself. "fUNDETERMINED" and "fIsUndetermined" speak for themselves, too: There are no negative distances between positions, and what "llVecDist()" returns is never negative. There may be no agents on the parcel, in which case the list of agents would be empty. It´s length would be 0 and the code in the while loop would not be executed. The variable "distance" would still have the value it was initialized with when it was declared. What the furthest agent is would be undetermined, and that´s exactly what the code tells you.

The movement of the object should look nice. It looks nice when the object always moves at the same speed. "llSetKeyframedMotion()" doesn´t use speed. You have to specify the duration of the movement. Since the distance between the object and agents it moves to varies, the duration of the movement is adjusted according to the distance. The minimum duration "llSetKeyframedMotion()" works with is 1/45 second. "fSecondsForMeters" makes sure that the duration is at least 1/45 second through the "FMax" macro which is defined in "lslstddef.h": "FMax" returns the greater value of the two parameters it is supplied with.

"vRandomDistance" is used to help with that the object doesn´t hit the agent it moves to and ends up at some random distance from them, with a minimum offset. "PosOffset" from "lslstddef.h" works with two vectors and gives the offset between them as a vector. It´s one of my favourite macros because I don´t need to ask myself anymore what "offset" is supposed to mean: "here - there" or "there - here". The offset is simply "PosOffset(here, there)" when it´s an offset from here to there and "PosOffset(there, here)" when it´s offset from there to here. "llSetKeyframedMotion()" uses offsets ...

The important thing here is this: Why divide the distance and introduce another vector for the offset when you could simply do it in one line like this:


<lsl> llSetKeyframedMotion([PosOffset(here, vAgentPos(goto) + vMINRANGE(0.5)), FMax(distance / fMetersPerSecond, 1.0/45.0)], [KFM_DATA, KFM_TRANSLATION]); </lsl>


That´s one thing all these macros are for, aren´t they? Yes they are --- and you need to be aware of what they turn into. Cpp is designed to preprocess code for a C or C++ compiler, and those are probalby a lot smarter than an LSL compiler. The C compiler optimizes "1.0/45.0" by replacing it with the result of the division. C has a ternary operator which makes life a lot easier not only for writing macros like "FMax": "#define MAX(a,b) (((a) > (b)) ? (a) : (b))" works for both integers and floats --- compared to "#define FMax(x, y) (((llFabs((x) >= (y))) * (x)) + ((llFabs((x) < (y))) * (y)))" and another one for integers.

The line from above turns into:


<lsl> llSetKeyframedMotion([( (llList2Vector(llGetObjectDetails(llList2Key(agents, goto), [OBJECT_POS] ),

                                      0) + (<0.5, 0.5, 0.5>)) - (here) ),
                     ( ( (llFabs( (distance / 3.0) >= (1.0 / 45.0) ) ) * (distance / 3.0) ) + ( (llFabs( (
                                 distance / 3.0) < (1.0 / 45.0) ) ) * (1.0 / 45.0) ) )],
                    [KFM_DATA, KFM_TRANSLATION]);

</lsl>


Letting aside that the code would be totally different when written in C, a C compiler would optimze that. I don´t know what the LSL compiler does.

Always be aware of what your macros turn into. The code from example-1_D turns into:


<lsl> distance /= 3.0; vector offset = ( < (llFrand((3.5)) - llFrand((3.5))), (llFrand((3.5)) - llFrand((3.5))),

                 (llFrand((3.5)) - llFrand((3.5))) > + (<0.5, 0.5, 0.5>));

offset += ( (llList2Vector(llGetObjectDetails(llList2Key(agents, goto), [OBJECT_POS] ),

                          0)) - (here) );

llSetKeyframedMotion([offset, ( ( (llFabs( ((distance)) >= (0.03) ) ) * ((distance)) ) + ( (

                                   llFabs( ((distance)) < (0.03) ) ) * (0.03) ) )], [KFM_DATA, KFM_TRANSLATION]);

</lsl>


It´s still awful to read. What did you expect? The source looks nice and clear because macros and defines are used, wich is one of the reasons to use them. More importantly, this code doesn´t employ numerous obsolete divisions because I checked the outcome and modifed my source.

Always check the script for what your macros turn into. It´s easy to check, just load the debugging version into your favourite editor. You can even use the built-in editor: change the "o" for an "i" and click on the "Edit" button as described earlier.

One thing is new in example-1_D: The defines that were used to make the list with the UUIDs of the agents usable are undefined with "#undef". It´s not necessary to do this, it´s tidy. As mentioned before, the list has local scope. That´s the reason I undefined the defines that relate to it. It ends their "scope" as well. You can create new defines with the same names outside this scope if you like. They are undefined and out of the way. Perhaps at some time, you include something into your source that happens to use defines with the same name. You would get an error message and have to go back to the perhaps old source and figure out where you can undefine them. It doesn´t hurt to undefine them right away and saves you time later.

Script memory and performance with cpp

You may have seen or written scripts that use variables as constants. Look at this example:


<lsl>

  1. include <lslstddef.h>


  1. define WITH_DEFINE_FLOAT 0
  2. define WITH_DEFINE_INT 0


  1. if WITH_DEFINE_FLOAT
  2. define ftest 1.0
  3. else

float ftest = 1.0;

  1. endif
  1. if WITH_DEFINE_INT
  2. define itest 1
  3. else

int itest = 1;

  1. endif


default { event touch_start(int t) { apf("---"); apf("the float is", ftest, "and the int is", itest, " with ", llGetFreeMemory(), " bytes free"); apf("the float is", ftest, "and the int is", itest, " with ", llGetFreeMemory(), " bytes free"); apf("the float is", ftest, "and the int is", itest, " with ", llGetFreeMemory(), " bytes free"); apf("the float is", ftest, "and the int is", itest, " with ", llGetFreeMemory(), " bytes free"); apf("the float is", ftest, "and the int is", itest, " with ", llGetFreeMemory(), " bytes free"); apf("the float is", ftest, "and the int is", itest, " with ", llGetFreeMemory(), " bytes free"); apf("the float is", ftest, "and the int is", itest, " with ", llGetFreeMemory(), " bytes free"); apf("the float is", ftest, "and the int is", itest, " with ", llGetFreeMemory(), " bytes free"); apf("the float is", ftest, "and the int is", itest, " with ", llGetFreeMemory(), " bytes free"); apf("the float is", ftest, "and the int is", itest, " with ", llGetFreeMemory(), " bytes free"); apf("---"); } } </lsl>


You can find this example in the git repository that accompanies this article as "projects/memtest/src/memtest.lsl".

It shows that the script needs 8 bytes more memory when you use either a float or an integer variable --- or both! --- instead of defines. That means when you declare a variable, declaring another one doesn´t make a difference, provided that the variables are of the types float and integer.

I give up artifical testing at this point. Either the test or the results must be flawed. One thing I wanted to know is whether it makes a difference if you have several lines with a number (like 1 or 1.0) vs. the same several lines with a variable instead of the number. This test doesn´t show that, perhaps due to optimizations the LSL compiler performs.

I use defines for constants --- not because it apparently may save 8 bytes, but because it makes sense. That you can switch between defines and variables for constants makes testing with "real" scripts easy.

Test with your actual script what difference it makes.

It seems obvious that you would waste memory when you use a macro that the preprocessor turns into a lengthy expression which occurs in the script multiple times. When it matters, test it; you may be surprised.

You can also try to test for performance. Provided you find a suitable place to set up your test objects and let the tests run for at least a week, you may get valid results. Such tests could be interesting because you may want to define a function instead of a macro --- or a function that uses a macro --- to save script memory when the macro turns into a lengthy expression. In theory, the macro would be faster because using a function requires the script interpreter to jump around in the code and to deal with putting parameters on the stack and with removing them while the macro does not. But in theory, declared variables use script memory.

When programming in C and you have a statement like


<lsl> if((A < 6) && (B < 20)) [...] </lsl>


the part '(B < 20)' will not be evaluated when '(A < 6)' evaluates to false. This is particularly relevant when you, for example, call a function or use a macro:


<lsl> if((A < 6) && (some_function(B) < 20)) [...] </lsl>


LSL is stupid in that it will call the function or evaluate the expression the macro turned into even when '(A < 6)' evaluates to false. It´s impossible for the whole condition to evaluate to true when '(A < 6)' is false, and evaluating a condition should stop once its outcome has been evaluated. Calling a function takes time, and the function even might be fed with illegal parameters because B could have an illegal value the function cannot handle, depending on what the value of A is:


<lsl> integer some_function(integer B) {

   llSay(0, "10 diveded by " + (string)B + " is: " + (string)(10 / B));
   return 10 / B;

}


default {

   touch_start(integer total_number)
   {
       integer B = 20;
       integer A = 5;
       if(A < 8) B = 0;
   
       if((A < 6) && (some_function(B) < 20))
       {
           llSay(0, "stupid");
       }
   }

} </lsl>


This example will yield a math error because some_function() is called even when it should not be called. Such nonsense should be fixed. Calling the function is an undesireable side effect. To work around it so that some_function() isn´t called, you need to add another "if" statement, which eats memory.

Depending on what the function or the macro does, your "if" statements can affect performance. This is independent of using a preprocessor, but since you may be tempted to use macros in your sources when you use a preprocessor, you may be tempted to use them in "if" statements and create more undisirable side effects than you otherwise would.

Avoid "if" statements which evaluate expressions that should not be evaluated.

Use Parentheses

When you look at this simple macro


<cpp>

  1. define times3(x) x * 3

</cpp>


it will multiply its parameter by 3. At least that´s what you think. When your source is


<lsl>

  1. define times3(x) x * 3

[...]

int a = 5; int b = times3(a); llSay(0, (string)b); [...] </lsl>


it will print 15. Most of the time, your source will be more like this:


<lsl>

  1. define times3(x) x * 3

[...]

int a = 10; int b = 5; llSay(0, times3(a - b)); [...] </lsl>


You would expect it to print 15, but it will print -5:


10 - 5 * 3 == 10 - 15 == -5


You need to use parentheses:


<cpp>

  1. define times3(x) (x) * 3

</cpp>


(10 - 5) * 3 == 5 * 3 == 15


You need even more parentheses, because:


<lsl>

  1. define plus3times3(x) (x) * 3 + 3

[...] int a = 10; int b = 5; int c = 20; int d = 15; llSay(0, (string)(plus3times3(a - b) * plus3times3(c - d)); </lsl>


(10 - 5) * 3 + 3 * (20 - 15) * 3 + 3 == 15 + 3 * 5 * 3 + 3 == 15 + 45 + 3 == 63


Imagine you defined 'plus3times3' without any parentheses at all. You´d waste hours trying to figure out why your script yields incredibly weird results.

To get it right:


<lsl>

  1. define plus3times3(x) ((x) * 3 + 3)

[...] int a = 10; int b = 5; int c = 20; int d = 15; llSay(0, (string)(times3(a - b) * times3(c - d)); </lsl>


((10 - 5) * 3 + 3) * ((20 - 15) * 3 + 3) == 324


There is quite a difference between 63 and 324. Use parentheses. I also always use parentheses with conditions because it makes them unambiguous:


<lsl> if(A || B && C || D > 500) [...] </lsl>


is ambiguous. What I meant is:


<lsl> if((A || B) && (C || (D > 500))) [...] </lsl>


That is not ambiguous. Since "&&" usually takes precedence over "||", it´s different from the version without parentheses. (It probably doesn´t matter in this case, but I´m not even trying to figure it out. It does matter in C because it has an effect on which of the expressions are evaluated and wich are not. Simply write what you mean, with parentheses, and don´t let LSL get you to develop a bad style.) When you have the above "if" statements in a macro, use parentheses around "A", "B", "C" and "D", for reasons shown above.

Save yourself the hassle. My LSL scripts tend to have lots of parentheses, a lot of them not strictly needed because they come from macro definitions. They don´t hurt anything. Not having them can hurt badly.

How to use cpp

Finally, there is the question of how to use cpp. Cpp has a lot of command line parameters, and one particularly useful when using cpp for LSL is '-P'. The manpage of cpp says:


  • -P Inhibit generation of linemarkers in the output from the preprocessor. This might be useful when running the preprocessor on something that is not C code, and will be sent to a program which might be confused by the linemarkers.


The LSL compiler is one of the programs that would be confused by line markers. You can see what the line markers look like when you preprocess one of your scripts with


<lsl> cpp your_script.lsl </lsl>


By default, cpp writes its output to stdout, i. e. it appears on your screen. Since it isn´t very useful to have your LSL scripts appear on your screen like that, you may want to redirect the output into a file:


<lsl> cpp -P your_script.lsl > your_script.i </lsl>


This creates, without the line markers, a file that contains your preprocessed source. This file is named "your_script.i".

It´s that simple.

You can now write a script and use defines and macros and preprocess it. You can log in with your sl client, make a box, create a new script in the box, open the script, click on the "Edit" button and replace the default script that shows up in your favourite editor with the one you wrote and preprocessed.

It´s not entirely convenient yet because you still need to replace the script manually. You may also want to include files into your script, like "lslstddef.h". You don´t want to keep another copy of "lslstddef.h" with all your scripts and include it with '#include "lslstddef.h"'. You only need one copy of "lslstddef.h", and it´s a good idea to keep it in a directory, perhaps with other headers you use.

Cpp has another parameter for this: '-I'. '-I' tells cpp where to look for files you include with something like '#include <lslstddef.h>'.

If you haven´t pulled (i. e. downloaded) the git repository that accompanies this article, please do so now.

When you look into the "projects" directory from the repository, you´ll find a few directories in it:


<lsl> projects |-- bin |-- example-1 |-- include |-- lib |-- rfedip-reference-implementation `-- template-directories </lsl>


The "bin" directory contains tools; "include" is for files like "lslstddef.h" which you may want to include into many of your sources. The "lib" directory is much like the "include" directory; the difference is that "include" is for header files which provide defines and macros, while the "lib" directory is for files parts of which will be included into your sources when you set up defines which get these parts included. Those parts can be functions or defines or macros, and they are put together in files depending on their purpose.

For example, "lib/getlinknumbers.lsl" provides functions related to figuring out the link numbers of prims. "getlinknumbers.lsl" is a library of such functions, macros and defines. Since it´s a library, it´s in the "lib" directory: "lib" as in "library".

The "template-directories" directory is a template which you can copy when you start writing a new script. It contains directories to put particular files, like the "src" directory where you put the source you´re writing. It also has a "lib" directory which can hold files you want to include into one or several of the sources in the "src" directory, but which are too specific to the particular project that you don´t want to put them into the general "projects/lib" or "projects/include" directory.

Now when preprocessing your source with cpp, you may want to include "lslstddef.h". Since you´re starting a new project, you first copy the template directories:


<lsl> [~/src/lsl-repo/projects] cp -avx template-directories/ your-script ‘template-directories/’ -> ‘your-script’ ‘template-directories/bin’ -> ‘your-script/bin’ ‘template-directories/dbg’ -> ‘your-script/dbg’ ‘template-directories/doc’ -> ‘your-script/doc’ ‘template-directories/lib’ -> ‘your-script/lib’ ‘template-directories/src’ -> ‘your-script/src’ ‘template-directories/tmp’ -> ‘your-script/tmp’ ‘template-directories/bak’ -> ‘your-script/bak’ ‘template-directories/Makefile’ -> ‘your-script/Makefile’ [~/src/lsl-repo/projects] </lsl>


Now you can use your favourite editor, write your script and save it as "projects/your-script/src/your-script.lsl":


<lsl>

  1. include <lslstddef.h>


default { event touch_start(int t) { afootell("hello cpp"); } } </lsl>


As described above, you run 'cpp -P your_script.lsl > your_script.i'. What you get looks like this:


<lsl> [~/src/lsl-repo/projects/your-script/src] cpp -P your_script.lsl > your_script.i your_script.lsl:1:23: fatal error: lslstddef.h: No such file or directory

#include <lslstddef.h>
                      ^

compilation terminated. [~/src/lsl-repo/projects/your-script/src] </lsl>


This tells you that cpp didn´t find "lslstddef.h". You need to tell cpp where to find it:


<lsl> [~/src/lsl-repo/projects/your-script/src] cpp -I../../include -P your_script.lsl > your_script.i [~/src/lsl-repo/projects/your-script/src] </lsl>


Using '-I', you tell cpp to look for files that are to be included in the directory '../../include'. Since you do this from within the directory 'projects/your-script/src', cpp will look into 'projects/include' --- where it can find "lslstddef.h".

You can now look at the outcome:


<lsl> [~/src/lsl-repo/projects/your-script/src] cat your_script.i default {

touch_start(integer t)
{
 llOwnerSay("(" + (string)( (61440 - llGetUsedMemory() ) >> 10) + "kB) ~> " + "hello cpp");
}

} [~/src/lsl-repo/projects/your-script/src] </lsl>


You may think that this is inconvenient. You do not want to type something like 'cpp -I../../include -P your_script.lsl > your_script.i' for every script. You don´t want to do this for every script when a file included by the source may have changed. You don´t want to do this for all the scripts that are part of your project just in case you have modified one of them, and you don´t want to forget to preprocess all the scripts that need to be preprocessed every time you modified something.

Fortunately, you don´t need to do that. There is a tool that can do it for you. It´s called "make".

Make

Very nicely said, Make "is a tool which controls the generation of executables and other non-source files of a program from the program's source files."


As you may have noticed, in the previous section of this article I have referred to "scripts" (LSL scripts) and "sources", without mentioning what the difference is. The source is what you write. It can be an LSL script that can be compiled by an LSL compiler right away. That´s probably what you have written until you read this article.

After reading this article, you may not want to write plain LSL scripts anymore. You may want to use a decent editor, a preprocessor and Make. What you´ll write is still source, but it may be source which cannot be compiled by an LSL compiler right away because it needs to go through a preprocessor first. That´s what I was trying to refer to as "source".

You could say that An LSL script is the output of a preprocessor that processes a source file. I don´t write LSL scripts anymore. I write source files that can be turned into LSL scripts. It´s much easier to create LSL scripts that way than it is to write them.

In the sense of Make, the LSL script falls under "other non-source files". Make isn´t used here to generate an | executable. LSL scripts are not exectutable, and apparently the LSL compiler compiles them into | byte code.

"Other non-source files" can also mean documentation. The provided makefile has entries that use pdflatex to create a PDF file from a LaTeX source and to copy the PDF into the project directory. I don´t write PDF files. I write source files that can be turned into PDF files. It´s much easier to create PDF files that way than it is to write them :)


Preprocessing "your-script.lsl" like in the previous section generates a "non-source file" from a source file. You have controlled the generation of the non-source file manually, which can be inconvenient.

More conveniently, Make can do this job for you. All you need for that is Make, and a so-called Makefile. It´s called Makefile because it tells Make what it needs to do to generate files from files, and under which conditions which files need to be created.

You do not need to understand Make or Makefiles to make creating LSL scripts easier. You find a suitable Makefile in the git repository that accompanies this article. The Makefile is "projects/make/Makefile".

This Makefile is symlinked into the template-directories. There is only one Makefile for all your projects. It works for all of them. This has the advantage --- or disadvantage --- that when you change the Makefile, the changes apply to all your projects.

Should you need to adjust the Makefile because a particular project has special requirements, remove the symlink to the Makefile from the projects´ directory. Copy make/Makefile into the projects´ directory and then modify the copy.


How to use Make

Enter the directory of the project you are working on and run 'make'. That´s all.

You can run make from within emacs: M-x compile. Emacs will show you the output in a buffer. If there are error messages, you can move the cursor to the error message and press enter and emacs shows you the line in the file that triggered the error message. To compile again, you can use 'recompile': M-x recompile. You can make a key-binding for that with something like '(global-set-key [C-f1] 'recompile)' in your ~/.emacs and recompile your source conveniently from within your favourite editor --- which by now is, of course, emacs --- by simply pressing Ctrl+F1.

Make "your-example.lsl"

Assume you want to try out the "your-script.lsl" source from above. You need to make an LSL script which you can put into an object for this. Suppose you make a box and name it "testbox".

Enter the directory for the project ("/projects/your-script/") and run 'make':


<lsl> [~/src/lsl-repo/projects/your-script] make COMPILING src/your-script.lsl

 8 153 dbg/your-script.i.clean

[~/src/lsl-repo/projects/your-script] </lsl>


This has done a few things which it doesn´t tell you about. It merely tells you that it was compling --- you could say preprocessing instead since your script isn´t really compiled --- and that the resulting LSL script has 8 lines and 153 characters.

You probably can´t wait to see the results now. Two non-source files were generated from the source files:


  1. "projects/your-script/dbg/your-script.i" and
  1. "projects/your-script/bin/your-script.o".


The first one is the so-called debugging version, the second one is the version you put into the "textbox" as a script. The second one is "compressed" and not easy to read. You can put either into the "testbox" as a script.

If you remember the perl script that automatically replaces the scripts in the built-in editor


With the Makefile from the repository, Make does a bit more than only running cpp. It will show you what it does when you run 'make -n', and the '-B' option tells Make to do everything it would, much as if you were running it for the first time for your new source:


<lsl> [~/src/lsl-repo/projects/your-script] make -Bn echo "COMPILING src/your_script.lsl" echo -n "//" > dbg/your_script.i cpp -I../lib -I./lib -I../include -DVERSION='version 0.00' -P src/your_script.lsl | md5sum >> dbg/your_script.i cpp -I../lib -I./lib -I../include -DVERSION='version 0.00' -P -DTIME=`date +0x%H%M%S` -DDATE=`date +0x%Y%m%d` src/your_script.lsl >> dbg/your_script.i sed -f ../clean-lsl.sed dbg/your_script.i > dbg/your_script.i.clean wc -cl dbg/your_script.i.clean astyle --style=allman -f < dbg/your_script.i.clean > dbg/your_script.i cat dbg/your_script.i | sed -f ../clean-lsl.sed | ../bin/i2o.pl `basename dbg/your_script.i` > bin/your_script.o etags src/your_script.lsl ../lib/geometry.lsl ../lib/getlinknumbers.lsl [~/src/lsl-repo/projects/your-script] </lsl>


Make runs cpp, sed, wc, astyle, etags (which comes with emacs), cat, echo and md5sum.

It also runs i2o.pl, which is a perl script. You can find it in the git repository that accompanies this article in the projects/bin directory.

i2o.pl comes from the source of the Open Stargate Network stargates. It´s slighly modified. The Makefile is from the same source and has also been modified. The whole idea of preprocessing the source with cpp to generate LSL scripts is from there. It´s not my idea, I´m merely using it and writing this article about it.

When you have successfully run 'make', you will find files in the 'projects/your-script/dbg' and 'projects/your-script/bin' directories. 'dbg' stands for 'debug', 'bin' for 'binary'. The files in these directories are LSL scripts that have been generated from your source in the 'projects/your-script/src' directory --- in this case, from 'projects/your-script/src/your-script.lsl'.

You will find "your-script.i" and "your-script.i.clean" in the 'dbg' directory. "your-script.i.clean" is an intermediate file which was created because it is not exactly a good idea to let sed overwrite its input file with its output. You can delete "your-script.i.clean" if you like, and/or modify the Makefile to delete it for you.

By filtering the output of cpp with sed, a few unwanted things are removed from the LSL scripts. The things removed with sed are introduced through the way I´m using macros which are defined in "lslstddef.h". Some of those lead to closing-braces followed by a semicolon ("};"), to double-semicolons (";;") or to an otherwise emtpy line with only a semicolon (";"). For example:


<cpp>

  1. if DEBUG0
  2. define DEBUGmsg0(...) DEBUGmsg(__VA_ARGS__)
  3. else
  4. define DEBUGmsg0(...)
  5. endif

</cpp>


This makes debug messages (of debug level 0) disappear when 'DEBUG0' does not evaluate to true. The source may look like this:


<lsl>

  1. include <lslstddef.h>

// [...] int a = Len(lChains); DEBUGmsg0("there are", a / iSTRIDE_lChains, "chains"); // [...] </lsl>


The output of cpp, when 'DEBUG0' is not true, looks like this:


<lsl>

integer a = llGetListLength(lChains);
;

</lsl>


The debug message disappears because it´s defined to disappear. But the semicolon at the end of the line is not part of the macro definition because when a line doesn´t end with a semicolon, emacs indents the following lines in an undesired way: That the semicolon is missing makes emacs figure that the line continues on the next line, which it doesn´t. If the semicolon was part of the macro definition, I´d still have to put it in to get the indentation as desired. It only would create a line with two semicolons when the debug message does not disappear. So when the debug message disappears, the semicolon remains. Sed removes it by deleting the whole line.

Sed "knows" what to do because it is instructed by a sed-script. You can find this sed-script as "projects/clean-lsl.sed".

Wc is a nice tool to give you statistics about text files. It can count words, bytes, lines etc.. It´s purely informational; I put it into the Makefile because I wanted to see what difference the debug mesages make for the size of the file and number of lines. You can comment it out in the Makefile, or remove it from the Makefile in case you don´t want it.

The same goes for astyle, it´s not required. It reformats the code so that it´s easier to read than the output of cpp. It´s useful when you want to put the LSL script generated from your source on wiki pages, and for debugging.

i2o.pl "compresses" the LSL script. It removes whitespace and line end-markers, and it shortens the names of variables. One advantage is that you may get a script which uncompressed has more than 64kB to work because it fits into "a script": The editor built into sl clients (or, more likely, the server does it) cuts off anything beyond 64kB when you upload something into a notecard or into a script. Another advantage is that your script may need less memory. My unverified impression is that "compressed" scripts even run a tiny bit faster, though that seems unlikely since they are compiled to bytecode just like uncompressed ones. Test it if you like --- since you got less to upload when the script is "compressed", in theory the "compressed" scripts don´t take as long to upload as uncompressed ones and you can test them faster. The server may even experience not so many difficulties.


Ratany Resident 16:06, 10 January 2014 (PST)