In L, new language constructs can be defined, and existing language constructs can be redefined. There are many cases where this may be useful:
L defines a few language constructs that, taken alone, make L approximatively as expressive as C. To have full expressiveness, you need macros and expanders, that are defined in the next section Malebolge.
The standard language constructs are the following:
seq
Executes each of its subforms in turn, and returns the result of the last one.seq
must have at least one form.L's standard syntax for this construct is
,
:x = 3, y = 4, x * yhas for abstract syntax tree:
(seq (= x 3) (= y 4) (*#Int x y))that has for result
12
.
block
acts likeseq
, except that it also begins a new block of code. All subsequentlet
definitions have a scope that ends at the end of the current block. Block must have at least one form, like seq.Note that L's blocks can return a value, unlike C ones. The syntax for L blocks thus differs from C's (even GNU C's) one:
let Int a = { let Int sum = 0; for(let Int i = 0; i <= 10; i++) { sum += i; } sum };This code creates two local variables to do a local calculation, before returning a result. This is particularly handy in combination with macros.
let
creates a new local variable that exists from its declaration until the end of the block.A
let
form can be used as a lvalue, like in the construct:let Int i = 3;that has for abstract syntax tree:
(= (let Int i) 3)but it cannot be used as a rvalue.
define
defines NAME as being a DEFINITION_TYPE with value REST. This special form just really calls the definer associated with DEFINITION_TYPE, with parameters DEFINITION_TYPE, NAME, and REST.Exemple of DEFINITION_TYPE are
function
(for defining new functions)type
(for creating new types),expander
,thread-local
(unimplemented),global
for thread-local and global variables.See Definers for the details.
goto
branches to the label LABEL_NAME. For the goto to be valid, the following condition must be met:The label must appear in a scope “accessible” by the goto instruction, i.e. either the current scope or any parent enclosing scope. It is an error to jump to a label to an unreachable scope.
Aborts the execution of the current function and returns to the caller, with return value VALUE.
You will notice that L does not have any of the standard constructs
for iteration built-in, like C for
, while
, or do
... while
. These can be defined by userlibraries; so a standard
library defines them, but they are not part of the language.
Repeatly execute forms, until it reaches one of
break
,continue
, or one of the two preceding change of flow of control commandsgoto
(if the label is outside of the loop) orreturn
.
break
exits the current loop; i.e., acts as if the enclosing loop was executed
L can be close to hardware, and thus is an imperative language, in the
sense that it defines an imperative construct, =
. By
restricting its use, you can obtain a fully functional language if you
prefer; this is discussed in further sections.
=
affects the value of EXPRESSION to ASSIGNEE, that must be a correct lvalue.
L permits the use of pointers; not doing so result in a huge performance penalty as seen in the “modern” languages, and give up all hope to do low level programming. L is so an unsafe language.
However, L make it easy to hide the use of pointers behing safe constructs; if you make the usage of these constructs mandatory (which is not feasible by now), you transform L into a safe, AND efficient, language.
L has a builtin notion of tuple, that is quite different from what is called tuple in many different languages.
L's tuple only purpose is to consider several values at once. In particular, no special assumption is made about the location of the components of the tuple. Unlike the structure, tuple components are not placed contiguously in memory, for instance.
The following code:
(block (let Int a) (let Int b) (tuple a b))does not do anything, for instance.
Tuples are really useful when it comes to simultaneous affectations. For instance:
(a,b) = (b,a);exchange the values in
a
andb
. As there is no assumption behind the memory placement of the tuple, potentially optimized instructions can be used here; for instance ifa
andb
where stored in registers, the above example could have used the x86 instructionxchg
, which is never used by a normal C compiler.L defines the order of evaluation of the expressions in a tuple to be from left to right. Unlike C programs, L programs can rely on this fact.
It is also important to notice that all expressions of the tuple are evaluated before the assignment takes place. This is what makes the above example to work; as opposed to sequential affectation.
funcall: calls a function. Takes a tuple as an argument, returns a tuple: thus we have multiple return values (this alleviates many use of pointers).
UNIMPLEMENTED: partial affectation of a tuple, using _:
(x,_,color) = f(i); f: (Float,optional Float, Color)<-(Int); (x,y,_) = f(i); f: (Float,Float,optional Color)<-(Int); (x,y) = f(i); f: (Float,Float,optional Color)<-(Int); x = f(i); f: (Float,optional Float,optional Color)<-(Int);