|
| |
The Simple SIMION Language (SL): Language Reference(c) 2003-2004 Scientific Instrument Services, Inc. Created November 2003 by David Manura. Updated: $Date: 2004/07/16 19:42:02 $.
|
|
This document describes the SL user programming language provided by the SIMION SL Toolkit for use in the SIMION 3D Ion and Electron Optics simulation program (http://www.simion.com). SL is an alternative, higher-level language than the standard user programming language built into SIMION (referred to here as ``PRG'' code due to the file extension given to the program files).
First, why would you need a programming language (such as PRG or SL) when simulating ion optics? Well, to customize the calculation, dynamically change or oscillate voltages on potential arrays, respond to events, perform intermediate calculations, or perturb ion paths in unique ways. In fact, user programs are used in a number of the examples that come with SIMION, such as the Buncher lens and the quadrupole, which change/oscillate potential array voltages and record events via simple SIMION PRG programs (these are the BUNCHER.PRG, QUAD.PRG, QUADIN.PRG, and QUADOUT.PRG files).
The problem though, which the SL Compiler solves, is that the PRG language built into SIMION is limited, albeit functional. For those familiar, using PRG code can be likened to programming in the Forth language, using a Hewlett-Packard Reverse-Polish Notation (RPN) calculator, or writing assembly on a stack-based virtual CPU. In other words, it could be simpler. The SL compiler is intended to make writing user programs in SIMION much simpler by allowing you to use high-level programming language constructs as you'd expect in any power scientific application such as Excel or Matlab. In fact, SL even allows you to call custom remotely running C++ code during the simulation through the SL Remote interface (other languages may be supportable as well upon request). So, you can even write parts of your custom code in a general purpose programming language (but with some performance penalty compared to SL or PRG due to the interprocess communication overhead).
For SIMION to understand your SL code, you need the SL Compiler (http://www.simion.com/sl), which is a tool that translates SL code such as
if x[n+1] + 2 > velocity * exp(t) print("Operating limits have been exceeded! velocity=#", velocity) else print("Everything ok") endif
into equivalent PRG code such as
RCL n 1 + ARCL x 2 + RCL velocity RCL t E^X * X>Y GTO __label1 MESS ;# Everything ok GTO __label2 RCL velocity MESS ;# Operating limits have been exceeded! velocity=# LBL __label1 LBL __label2
The latter can be understood and executed directly by SIMION. As programs get large, it is easily seen how the former notation is much easier to understand and maintain. This document does not describe the SL compiler tool (see the Tutorial--compiler_tut.html--for compiler usage) but instead describes only the SL language.
Compatibility note: SL is compatible with SIMION 7.0. It has not been tested with SIMION 6.0, although 6.0 should work as well with few, if any, changes.
Just to give a taste of SL, below is an real-world example program written in the SL language. It can be used as an exact replacement for the BUNCHER.PRG program in the Buncher lens example (http://www.simion.com/examples/buncher/) that comes with SIMION but of course is much easier to read. In summary, the Buncher lens is used to focus dispersed ions approaching it by cutting off the electric field as the ions get close to the lens. The effect is that ions arriving in the lens first are decelerated more than ions arriving later so that all ions subsequently converge at a certain point. The below program achieves this as follows. At the start of simulation, the program updates the potential energy (PE) surface, it sits and does nothing until time switch_time, and then it then turns off the lens voltage (adj_elect01), changes the ion color (ion_color) to blue (color 3), marks the ion position on the display, and causes the PE surface display to be again updated.
# main buncher control program adjustable switch_time = 1.7 # switch time in microseconds adjustable buncher_voltage = 900 # buncher deceleration voltage static update_flag = 1 # pe surface update flag # adjust time step sub tstep_adjust # Let's make sure a time step ends right on the switch time. if ion_time_of_flight < switch_time ion_time_step = min(ion_time_step, switch_time-ion_time_of_flight) endif endsub # buncher voltage control sub fast_adjust adj_elect01 = if(ion_time_of_flight < switch_time, buncher_voltage, 0) endsub # display update sub other_actions if switch_time == ion_time_of_flight ion_color = 3 # switch ion color to blue mark() # mark transition location of ion update_flag = 1 # set pe surface update on next time step elseif update_flag == 1 update_flag = 0 update_pe_surface = 1 endif endsub
Listing #1.
Details of the individual statements above will be described throughout this document.
The are many ways in which the SL language could have been designed differently. The following following major design criteria have been used in SL.
You should first read some of the introductory section in the SIMION 7.0 manual on user programming (pages I-1 to I-3, I-6, and possibly also I-17 to I-34) before proceeding with the section. Many of the concepts described there also apply to SL (e.g. the various segments and reserved variables defined). A summary of some of these concepts will be provided below as well.
SIMION allows the user to associate a unique SIMION program (*.PRG file) with each potential array. The PRG program responds to events and dynamically adjusts the flight and environment of ions traversing though that potential array as time progresses. For example, you might use a PRG file to cause the voltage potential of an electrode to oscillate according to a sinusoidal waveform or print out some calculation results at the end of ion flight.
When writing a custom program in SL rather than in PRG, you instead use the SL compiler to automatically generate the corresponding PRG file from your SL file. This generated PRG file can then be used and executed by SIMION as usual.
Subroutines (a.k.a. segments)
As shown in Listing #1 above, an SL program, like a PRG program, consists of one or more subroutines (referred to as a segment in PRG code), possibly proceeded by variable declarations and optionally interspersed with comments. A subroutine in SL is in many ways similar to a function or subroutine in other languages. It is a block of custom code with an identifying name.
SIMION gives special meaning to the below subroutine names. If you define a subroutine having one of these names, the SIMION calculation engine will execute that subroutine at various points during the calculation (and hence you are able to customize the calculation). Refer to the User Programming section of the SIMION manual for details on these subroutines (a.k.a. segments).
You may also define subroutines with other names and call them from the standard subroutines.
Variables
As usual, variables are used in your program to store temporary data and maintain state. In SIMION, declared variables can be of either the adjustable or static variety. This concept of adjustable v.s. static is specific to SIMION and is not seen in general purpose programming languages (think of it as different types of global scoping), so special attention must be given to it. Adjustable and static variables differ in their lifetime and in how they are presented to the users running the program.
Adjustable and static variables must be defined as the beginning of your program (as seen with the ``adjustable'' and ``static'' statements in Listing #1 above). Adjustable and static variables are similar in that they both are globally visible to all concurrently running user programs. That is, one program can read and modify the adjustable and static variables of another running program.
In addition to adjustable and static variables, SIMION supports reserved and temporary variables.
sub fast_adjust factor = cos(ion_time_of_flight + 0.5*3.14)*10 + 1 adj_elect01 = adj_elect01 * factor adj_elect02 = adj_elect02 * factor endsub
The previous section describes what a program is. The next question is, How do I write one? As in any language, a number of grammatical rules must be obeyed to keep the reader (i.e. the computer) happy.
x = cos(5); y = x*x; if(y > 10) y = 10;
Not so with SL! To keep the SL syntax simple, you must instead write the above code in this format:
x = cos(5) y = x*x if y > 10 y = 10 endif
In particular, each statement must exist on one and only one line. You may not, for example, rewrite a long line
x = cos(theta * factor + offset) * magnitude + dc_offset
as two separate lines
x = cos # invalid syntax (theta * factor + offset) * magnitude + dc_offset
unless you explicitly use an explicit line continuation operator (``...'') to extend the current line:
x = cos ... (theta * factor + offset) * magnitude + dc_offset
However, in many obvious cases, such as where a parenthesis or bracket is not yet closed, or between certain binary operators, you may omit the ``...'' for brevity:
x = cos(theta * factor + offset) + v[2 + 3] + 100 if x == 1 + 5 and y == 2 print("ok") endif
This is called ``implicit'' continuation and is inspired by the Python programming language (http://www.python.org).
x = cos(5) # do some trig y = x # square it * x if y > 10 # limit y y = 10 endif
(Note: PRG code instead uses ';' as the comment character. Use of # is more conformant with other languages.)
x = cos(x) + x*y + 2/(3 + 4)
which is equivalent to
x = (cos(x) + (x*y)) + (2/(3 + 4))
rather than the ``prefix'' notation stack operations in PRG code:
x COS x y * + 2 3 4 + / +
We'll get to the other rules throughout this document, but this should be a start.
Static and adjustable variables must be declared outside of any subroutine and should be specified near the top of your program:
adjustable switch_time = 5 static test = 2.2E-5
Can can assign an intial value to these variables as shown above. If the assignment value is ommited, it defaults to zero:
adjustable color # same as adjustable color = 0
Assignment statements assign the value of the right-hand expression to the variable named on the left-hand side.
x = 5 y = x # x now is 5 x = 3 + 2*(8/4 + 5*(2-3)) # x now is -3 x = sin(abs(x) + y) # x now is sin(8), or ~0.9894 x = min(9,max(4,5)) # x now is 5 (radius, azimuth, elevation) = rect3d_to_polar3d(x, y, z) # function returning multiple values (x, y, z) = (1, 2, 3+4) # x=1, y=2, z=7
A special case is shown in the rect3d_to_polar3d example. This function takes three arguments as input (x, y, and z) and returns three values (a ``tuple'') as output (radius, azimuth, and elevation). So, it is possible to assign the result of a function to a tuple if the function returns a tuple.
Arrays store a collection of numbers that are referenced by an index. Arrays are declared at the top of your program as such:
# declare an adjustable array "stuff" of size 10 adjustable[10] stuff # declare a static array "more_stuff" of size 10 static[10] more_stuff
(PRG note: This is equivalent to the ADEFA and ADEFS commands in PRG code.) You must specify an array bound.
Despite the desires of programming purists, arrays are 1-indexed as in SIMION (i.e. the first element is at location 1 not location 0). Array elements are referenced as such:
stuff[5] = 2 # set the fifth element of stuff to 5 x = stuff[5+y] + stuff[1] # set x to the sum of the (5+y)-th element # and first elements of stuff. stuff[x+y*2] = stuff[x+2] # a more complex example
Side note: in PRG code, the above would be accomplished as such:
2 5 ASTO stuff 5 y + ARCL stuff 1 ARCL stuff STO x x 2 + ARCL stuff x y 2 * + ASTO stuff
You can also save and load an array to/from an file:
array_load(stuff, "myfile.txt") array_save(stuff, "myfile.txt")
Refer to the SIMION manual on ALOAD and ASAVE opcodes for file format information (p I-11).
Note: SL, like PRG code, provides no intrinsic support for multidimensional arrays. A multidimensional arrays can be simulated with a single dimensional array:
three[(z * ysize + y) * xsize + x + 1] = 5
which is analogous to setting element (x,y,z) to 5, assuming three is a three-dimensional array of dimensions xsize, ysize, zsize that is also zero-indexed.
The following binary operators are supported: +, -, *, /, and % with their usual semantics. The '%' operator is for modular arithmetic. ``a % b'' is the remainder of a divided by b. Note: the result is considered undefined if b is not positive or either operand is non-integer. Note the behavior if the divisor is negative: ``-7 % 4'' gives 1 since -7 + 2*4 = 1.
A few commands require strings, which are delimited by double quote (``) characters.
print("Hello World!") array_load(stuff, "myfile.txt") array_load(stuff, "myfile.txt")
A few special cases are worth noting.
print("Test,#", 1) # a test
As expected, the pound (#) inside the string is considered part of the string, not as the beginning of a comment.
SIMION recognizes a few escape characters. For example in,
print("123\n234\t345")
the \n is interpeted as ``new-line'' and \t is interpreted as ``tab''.
Note: \\ and \`` displays as empty strings (SIMION restriction).
SL supports the standard conditional statements, as well as Boolean logic.
if x == 3 print("ok") endif if x < 3 print("ok") else print("test") endif if x < 3 print("ok") elseif x < 2 print("123") else print("test") endif
Conditionals can also contain compound Boolean logic:
if x < 3 or not y < 2 and z > 3 print("test") endif
Note that, as usual, 'not' has higher precedence than 'and', which in turn has higher precedence than 'or', so the above is equivalent to
if x < 3 or ((not y < 2) and z > 3) print("test") endif
Note: The endif is always required, so this is INCORRECT:
if x < 3 # invalid syntax: missing endif print("ok")
An alternate syntax for conditionals is to use the functional if:
x = if(y>0,log(y),0)
This is equivalent to
if y > 0 x = log(y) else y = 0 endif
The conciseness of the former is sometimes convenient. Cascading functional if's are also supported:
x=if(x==1,11,if(x==2,12,13))
Advanced note: ``short-circuiting'' is supported in that the following
sub initialize x = if(1==0, f(1), f(2)) endsub sub f(n) returns(m) print("#", n) m = 0 endsub
outputs simply ``2\n'' not ``1\n2\n''.
C++ Note: the SL functional-if is similar to the ``condition ? y : z'' construct in the C++ language.
SL also has standard support for loops:
# prints integers 1 to 10 inclusive for x = 1 to 10 print("x=#", x) endfor # prints integers 1 to 10 inclusive in reverse for x = 10 to 1 step -1 print("x=#", x) endfor # prints numbers 10, 9.5, 8, 8.5, ... 1 for x = 10 to 1 step -0.5 print("x=#", x) endfor # a nested for with a limit being an expression for x = 1 to 5 for y = 1 to x+1 print("x=#,y=#", x, y) endfor endfor
The limits and step are evaluated once before the loop starts, so modifications to these values inside the loop have no effect on the loop itself. For example,
y = 5 n = 1 for x = 1 to y step n print("#", x) y=1 n=2 endfor
still outputs ``1\n\2\n3\n4\n5\n''.
Here's some more examples:
# another way to print integers 1 to 10 inclusive. # This also sums the numbers. x=1 while x <= 10 print("x=#", x) x = x + 1 endwhile print("sum=#", x) # a loop using a compound conditional x=1 y=1 while x*x <= 25 and y < 5 print("x=#", x) x = x + 1 y = x - 1 endwhile
To print text to the screen (or to a file if output is being saved to a file), use the ``print'' command.
print("Hello World!")
The print command can also print numbers and variables interpolated into the string:
print("Velocity=# and mass=#", velocity, mass)
So, if velocity contained 100 and mass contained 2, the above would print
Velocity=100 and mass=2
C/C++ note: This somewhat resembles the printf function in the C language. It is based on the MESS command in PRG code. In SL, ``mess'' is actually synonymous with ``print'', but use of the latter is preferred.
SIMION comes with various built-in subroutines for performing mathematical computations, coordinate transformations, and data manipulation.
u = exp(sqrt(2)) + sin(3)
Some of these function input and output multiple values:
# convert 3D rectangular coordinates to 3D polar coordinates. (radius, azimuth, elevation) = rect3d_to_polar3d(x, y, z)
A full list of built-in subroutines is given in the Built-in and Utility Subroutines section below.
SL allows you to define your own custom subroutines in addition to the built-in ones.
sub terminate x = 4 y = x*x alert(x,y) endsub sub alert(a,b) print("Here are the results:") print("a=#, b=#, sub=#", a, b, a+b) endsub
Subroutines can also return values:
sub terminate x = (-b + descriminant(a,b,c)) / (2*a) endsub sub descriminant(a,b,c) returns(d) d = sqrt(b*b - 4*a*c) end
Subroutines can return multiple values (like the built-in rect3d_to_polar3d function):
sub terminate (x, y, z) = f(1, 2, 3) print("#,#,#", x, y, z) endsub sub f(x,y,z) returns(x2,y2,z2) (x2, y2, z2) = (cos(x), cos(y), cos(z)) endsub
Subroutines can even by recursive, as shown in the illustrative example below. (Warning: these function are most efficiently coded using iterative algorithms, not recursive ones--this is only for demonstration.)
sub terminate print("5!=#", factorial(5)) for n=0 to 7 print("fibonacci(#)=#", n, fibonacci(n)) endfor endsub sub factorial(n) returns(n) if n > 1 n = n * factorial(n-1) else n = 1 endif endsub sub fibonacci(n) returns(n) if n >= 2 n = fibonacci(n-1) + fibonacci(n-2) endif endsub
When executed, the above outputs
5!=120 fibonacci(0) = 0 fibonacci(1) = 1 fibonacci(2) = 1 fibonacci(3) = 2 fibonacci(4) = 3 fibonacci(5) = 5 fibonacci(6) = 8 fibonacci(7) = 13
Note: In PRG code, subroutines and labels are local to a segment, so you cannot call a subroutine defined in one segment from another segment. In SL, this restriction is removed--a subroutine is defined by itself and may be called by any other subroutine.
adjustable x = 0 adjustable y = 3 sub initialize x = cube(y) endsub sub terminate x = cube(y) endsub sub cube(n) returns(a) a = n*n*n endsub
Although SL fades the boundary between SIMION segments and subroutines, a few technical differences remain. Mainly, a segment or subroutine cannot call another segment.
sub initialize # a standard "segment" subroutine print("init") endsub sub terminate # a standard "segment" subroutine test() # ok initialize() # invalid! not allowed endsub sub test # a custom subroutine print("test") initialize() # invalid! not allowed endsub
Limitation: custom subroutines can take and return no more than eight parameters at a time (a largely SIMION-imposed limitation).
The following subroutines are built into SIMION. Their behavior in SL is very similar to their behavior in PRG code except that they are accessed using the simplified SL notation. For details on these subroutines, refer to the SIMION manual (p. I-7).
pow10(a) returns(b)pa_coords_to_array_coords(x,y,z) returns(x,y,z)azimuth_rotate(angle,x,y,z) returns(x,y,z)degrees(angle) returns(angle)elevation_rotate(angle,x,y,z) returns(x,y,z)returns(ke)rect_to_polar(x,y) returns(radius,theta)rect3d_to_polar3d(x,y,z) returns(r,azimuth,elevation)wb_coords_to_pa_coords(x,y,z) returns(x,y,z)wb_orient_to_pa_orient(x,y,z) returns(x,y,z)polar_to_rect(radius,theta) returns(x,y)polar3d_to_rect3d(radius,azimuth,elevation) returns(x,y,z)radians(angle) returns(angle)ke_to_speed(ke,mass) returns(speed)pa_coords_to_wb_coords(x,y,z) returns(x,y,z)pa_orient_to_wb_orient(x,y,z) returns(x,y,z)abs(a) returns(b)acos(a) returns(b)array_load(v, "myarray.txt")
array_save(v, "myarray.txt")
asin(a) returns(b)atan(a) returns(b)beep()click()cos(a) returns(b)exp(a) returns(b)frac(a) returns(b)int(a) returns(b)key() returns(b)ln(a) returns(b)log(a) returns(b)mark()nint(a) returns(b)nop()run_stop()rand()redraw_screen()seed(a)sin(a) returns(b)sqrt(a) returns(b)tan(a) returns(b)
SL supports a number of built-in utility functions in addition to the functions supported by the PRG language.
min(a,b) returns(c) - return the minimum of a and b.x = min(3,5) # x set to 3
max(a,b) returns(c) - return the maximum of a and bx = max(3,5) # x set to 5
floor(a) returns(b) - return the floor of a (i.e. smallest integer no greater than a)print("#,#,#,#,#,#,#", floor(2,1.9,1.1,0,-0.1,-0.9,-1))
outputs ``2,1,1,0,-1,-1,-1''. Compare this to int(x), which would output ``2,1,1,0,0,0,-1''.
ceil(x) - return the ceiling of x (i.e. smallest integer no
less than x)print("#,#,#,#,#,#,#", ceil(2,1.9,1.1,0,-0.1,-0.9,-1))
outputs ``2,2,2,0,0,0,-1''.
You can import symbols from one SL file into another SL file by using an ``import'' statement:
# first.sl import "second.sl" sub initialize x = 5 test() endsub
where the file ``second.sl'' might contain
# second.sl adjustable x sub test print("#", x) endsub
Importing can be useful when files become large or when multiple files need to share the same code.
The path specified in the import statement is a relative path with respect to the file containing the import statement. So, if second.sl was actually in the parent directory of first.sl, you would instead do
import "../second.sl"
Languages comparison note: The import mechanism is similar in purpose to the import statement in other programming languages (e.g. Java/Python). It is also similar to a lesser degree to the ``include'' statement in C/C++ but is more intelligent. ``include'' statements are typically implemented by a preprocessor that is run before compilation, while ``import'' statements are typically built into the language itself.
During the simulation, SL code can call a subroutine defined in a remotely running C++ program (other languages may soon be supported as well) provide you declare the subroutine using the ``declaresub'' statement:
declaresub change_velocity(ionnum, tof, vx, vy) ... returns(vx, vy) remote(1) declaresub get_color(x, y, z) ... returns(color) remote(2) sub other_actions (ion_vx_mm, ion_vy_mm) = change_velocity(ion_number, ion_time_of_flight, ion_vx_mm, ion_vy_mm) color = get_color(ion_px_gu, ion_py_gu, ion_pz_gu) endsub
In the above, the subroutines ``change_velocity'' and ``get_color'' are implemented as C++ code in a separate program created by you. The declaresub statements identify the number of input parameters, number of return values, and something called a ``remote id'' to be explained here. Since your custom program in C++ might define multiple functions, and SL does not know the names of your C++ function, there must be some way for SL to distinguish between these C++ functions. This is what the remote ID does. In your C++ program, you assign a remote ID (an integer) to each remotely callable C++ function. Then you specify those remote IDs in the corresponding ``declaresub'' statements in your SL program.
There may be a variable number of input parameters and return values, just as in non-remote subroutines defined with the ``sub'' statement. Input parameters are sent to the remote program. Return values are filled with data received from the remote program.
Warning: the remote call adds interprocess communication overhead and will slow your simulation down. Therefore, it is preferred that you use remote calls sparingly and instead write most your code in SL. Calling remote code only a few times during the simulation (e.g. at the ``terminate'' segment``) will have little or no noticable effect on performance. However, calling remote code at each time step (or multiple times per timestep, such as in the ''fast_adjust`` segment) will have a moderate effect (10-100x slowdown).
Writing C++ code that can be called by SIMION is explained in a separate article in the SL Remote documentation, but basically the C++ would look like this:
#include <simion/remote.h>
// uncomment below line or link in SL remoting implementation
//#include <simion/remote.cpp>
#include <iostream>
using namespace std;
using namespace simion;
void change_velocity(
double ion_num, double tof, double vx, double vy,
double& vx_out, double vy_out&)
{
vx_out = vx * ion_num + tof;
vy_out = vy * ion_num + tof;
cout << "velocity changed" << endl;
}
void get_color(
double x, double y, double z, double& color)
{
color = (x + y + z) % 16;
}
int main()
{
// create remote object
SLRemote remote;
// register remotely callable C++ functions
remote.handler(1, slremote_handler(change_velocity));
remote.handler(2, slremote_handler(get_color));
// wait and handle remote calls
remote.run();
remote.wait_for_run_complete();
return 0;
}
This section is for more advanced users familiar with the Perl programming language and is not needed to use SL.
You can embed Perl code into an SL program by proceeding such code with the % character as in this example:
% sub if_test
% {
% my($id, $test) = @_;
% my $code = <<TEXT;
%if $test
% num_good = num_good + 1
%else
% print("[$id] fail")
%endif
%num_tests = num_tests + 1
%TEXT
% return $code;
% }
%
sub terminate
num_good = 0
num_tests = 0
% &if_test('t1', '1+3 == 2+2')
endsub
The embedded Perl code is executed at compile time (not at runtime) and the results of each embedded code snipped is inserted as a string into the SL program. Therefore, the above code is transformed into the following before compilation into SIMION opcodes:
sub terminate num_good = 0 num_tests = 0 if 1+3 == 2+2 num_good = num_good + 1 else print("[t1] fail") endif num_tests = num_tests + 1 endsub
One way we have used this, as hinted above, is to automate the creation of some of the regression tests on the compiler. You may find other uses of it as well (e.g. use it as a macro language).
Since the embedded notation (%) can be ugly, you may prefer importing most of your Perl code from a separate file as such:
% require 'tests.pl'; undef
The special ``undef'' is required since SL by default inserts the result of each embedded code snipped as a string into your SL program. Since this is not desired in this case, we append an undef to make the snippet evaluate to nothing.
Beware that (%) lines next to each other merge and are evaluated together. The following are not the same:
% 1; % 2;
v.s.
% 1;
% 2;
The first case is one snippet of code that evaluates to 2. The second case is two separate pieces of code that evaluate to 1 and 2 respectively.
Many of the built-in functions and statements in PRG code and SL are analogous. A few differences are mentioned below. Although PRG typically recognizes two names (a short form and a long form) for each function, SL typically only allows one cannonical name as shown in the list of Built-in Subroutines above. Names are kept alphanumeric and as short as is sufficiently unambiguous and readable.
Some PRG opcodes are no longer relevant and have no exact replacement function in SL since SL syntax provides a much cleaner mechanism for achieving the same functionality.
if x==y print("they are equal") endif
As in many programming languages, this distinguishes the equality operator from the assignment statement (which is a very different thing:
x = y
For additional information on the SL language, refer to the SL example programs.