Introduction
The syntax is that of LISP. All statements are enclosed within paranthesis, and the name of the function is always the first element after '('; for example:
(doSomething 1 (doThat "hello") 3)
calls the function doSomething
passing the values 1, the return value of the call to function (doThat “hello”)
and 3 as parameters. In the following, the tag <exp>
will be used whenever a statement is expected.
The language uses dynamic typing, with variables taking one of the following types:
- numbers (integer, long, float, double)
- strings
- lists
- maps (dictionaries)
- lambda
- nil (the only value that evaluates to false)
Functions to manipulate these data types are available, as well as arithmetic and logic operators. Comments are written by enclosing them withing '/*' and '*/' or by prepending a semicolon (in this latter case, the comment extends until the end of the current line).
Variable definition
To define a global variable the (define <symbol> <exp>)
statement is used. The statement must be placed outside function definitions. For example:
(define message "Hello world!")
A variable has a name and a value that can be of any of the available data types. A symbol is a sequence of alphanumeric characters that can include others as well: _,$, %, +, -, =, :, <, >, /, #. The language is case-sensitive, meaning that variables 'a' and 'A' are completely different! Furthermore, different types can be associated to a value. Here are some examples:
(define intVar 1) ;; integer value (define longVar 1l) ;; long value (define floatVar 1.0) ;; float value (define stringVar "This is a string variable")
The choice of a numeric type influences the result of a computation. If two numeric values are used, conversion may take place to convert the lower type to an upper one. The conversion order is as follows: integer, long, float, double. The type of a variable can be changed at any time by setting a new value. More complex types such as lists and maps are defined using a special syntax:
(define alist [1, 2, 3, 4]) (define amap { "key1" : 1, "key2" : 2 })
More specifically, lists are defined using square brackets '[' and ']', and each element is separated by a comma ',', whereas maps are defined using curly brackets '{' and '}', and each pair is composed of a key of type string, a colon ':', and a value of any type. Pairs are separated by commas ','.
Local variables can be defined using the var
statement instead of the define
one:
(var mylocalstring "Hello world")
Local variables cease to exist when the end of the current block is reached.
Setting variable's value
To set the value of an already defined variable use the set!
operator:
(set! <symbol> <exp>)
Function definition
Functions can be defined similarly to variables:
(define (<simbol> <symbol>*) <exp>)
The first symbol identifies the name of the function, whereas the remaining (optional) symbols represent a list of parameters. The <exp
statement is the body of the function, and describes what needs to be done when the function is called. To call a function, the syntax (<symbol> <exp>*)
is used: the symbol represents the name of the function to be called, whereas the list of expressions are the actual parameters. When calling the function, the value of the variables passed as parameters will be associated with the names of the arguments listed in the function definition.
The last parameter of a function can be preceded by three dots …
, and transformed into a catch-all argument in variadic functions. For example:
(define (f a b ...c) ())
Can be used with any number of arguments. The first two will be assigned to a
and b
respectively. The rest will be put in a list in variable c
. Functions cannot be passed as arguments to another function: to do this use lambdas.
Control statements
To group several statement into one, the begin
statement is used:
(begin <exp>'*)
The return value of a begin statement is the return value of the last function.
To control flow based on the value of a variable, the if
statement is used:
(if <condition> <exp> else <exp>)
A <condition>
can be any valid expression: its return value is used to determine the control flow.
If the condition returns a value different from nil
the first statement <exp>
is called, otherwise the last <exp>
is called. The else
tag can be omitted, meaning that the previous syntax is equivalent to:
(if <condition> <exp> <exp>)
Moreover, if there is no expression to be evaluated if the condition returns nil, the else
tag as well as the third <exp>
can be omitted. Another construct that can be used to test conditions is cond
:
(cond <case>* else <exp>)
Where <case>
has the form (<exp> <exp>)
, with the first expression being a condition to be evaluated, and the second one the code to be executed if the former returns a value different from nil
. Note that if multiple conditions are satisfied, only the expression associated to the first one will be executed. An optional else
tag, and associated expression can be specified, and applies when no other condition is evaluated true.
Loops
There exist several different loops: while
, for
and foreach
. The while
construct has the following syntax:
(while <condition> <exp>)
The second expression will be evaluated repeatedly until the first expression returns nil
. The for
statement has the syntax:
(for <exp> <condition> <exp> <exp>)
The first expression is used to initialize the value of a variable, for example (i 0)
. The condition is evaluated at the end of each cycle to determine if the loop is to be continued. The second expression is the increment, such as (set! x (+ x 1))
, and is executed after each cycle. Finally the last expression determines the body of the for
loop.
The foreach
constructs can be used to loop over the elements of a list:
(foreach <symbol> in <exp> <exp>)
The symbol identifies the name of the variable that the elements of the list will be associated to. The first <exp> should return a list, while the last expression is the body of the loop.
Let
The let
construct enables the definition of an environment with local scoping:
(let (<definition>*) <exp>)
where a definition has the form:
(<symbol> <exp>)
and is used to define a local variable that will be valid only within <exp>
.
Lambda
It is possible to define unnamed functions using the lambda
construct. Moreover the lambda can be used to capture the value of some local variables thus enabling customization at runtime:
(lambda (<symbol>*) <exp>)
The sequence of symbols represent the parameters of the lambda. The expression is the body. Any variable that is neither global, nor a parameter, will be encapsulated in the lambda. For example:
(define counter (let ((x -1)) (lambda () (begin (set! x (+ x 1)) x))))
defines a variable named counter
that returns an incremented value of 'x' at each call. In contrast to normal functions, lambdas can be passed to functions as normal values.