Next: , Previous: Cocytus, Up: Cocytus


3.3.1 Language constructs

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:

3.3.1.1 Constructs for local structure
— Language construct: seq (form1..., form_n)

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 * y
     

has for abstract syntax tree:

          (seq (= x 3)
               (= y 4)
               (*#Int x y))
     

that has for result 12.

— Language construct: block (form1..., form_n)

block acts like seq, except that it also begins a new block of code. All subsequent let 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.

— Language construct: let (type_form, variable_name)

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.

3.3.1.2 Constructs for global structure
— Language construct: define (definition_type name rest)

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.

3.3.1.3 Constructs for changing flow of control
— Language construct: goto (label_name) UNIMPLEMENTED

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.

— Language construct: return (value) UNIMPLEMENTED

Aborts the execution of the current function and returns to the caller, with return value VALUE.

3.3.1.4 Constructs for iteration

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.

— Language construct: loop (form)

Repeatly execute forms, until it reaches one of break, continue, or one of the two preceding change of flow of control commands goto (if the label is outside of the loop) or return.

— Language construct: break ()

break exits the current loop; i.e., acts as if the enclosing loop was executed

— Language construct: continue ()

continue continues the execution at the beginning of the loop.

3.3.1.5 Construct for affectation

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.

— Language construct: = (assignee expression)

= affects the value of EXPRESSION to ASSIGNEE, that must be a correct lvalue.

3.3.1.6 Constructs for pointer manipulation

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.

— Language construct: ref
— Language construct: deref

deref can be used as an lvalue

3.3.1.7 Constructs for structure manipulation
3.3.1.8 Construct for aggregating several values together
— Language construct: tuple expression_1 ... expression_n

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 and b. As there is no assumption behind the memory placement of the tuple, potentially optimized instructions can be used here; for instance if a and b where stored in registers, the above example could have used the x86 instruction xchg, 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.

3.3.1.9 Construct for function calling

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);