Statements

Grammar§

Statement ::= DeclarationStatement | AssertStatement | BlockStatement | BreakOnStatement | BreakStatement | ContinueOnStatement | ContinueStatement | DeferStatement | ExpressionStatement | ForeachStatement | GotoStatement | IfElseStatement | ReturnStatement | StaticAssertDeclaration | SwitchStatement | VersionBlockStatement | WhileStatement | WithStatement ;

AssertStatement§

Grammar§

StaticAssertDeclaration ::= "static" AssertStatement AssertStatement ::= "assert" "(" Expression ( "," Expression)? ")" ";"

Semantics§

The first expression must be evaluable as a condition. The second expression, when specified, must give a null terminated UTF8 string. When not specified it is automatically set to a StringExpression the represents the source code of the first expression.

When the condition is not verified a message is displayed then either the compilations stops if the assertion is statically evaluated and otherwise the program crash.

assert(echo(is, s8, s32)); // error: assert failure ...

Assertions are enabled with the compile switch --check=asserts

static limitation§

static evaluation of assert is limited to

Unreachable§

assert(0) and assert(false) are used to mark the current block as unreachable. Unless specified, assertions that denote unreachability dont output a message.

AtLabel§

Grammar§

AtLabel ::= "@" Identifier

Semantics§

Specify the GotoStatement target or the optional BreakStatement and ContinueStatement target scope.

BlockStatement§

Grammar§

BlockStatement ::= CurlyBlockStatement | Statement ; CurlyBlockStatement ::= "{" Statement* "}" SingleStatement ::= Statement

Semantics§

When not used for a VersionBlockStatement, a block introduces a scope.

var u64 a;
{
    var u32 a;
    a = 42;     // operate on the u32 one
}
assert(a == 0); // operate on the u64 one

In this scope forward use of declarations is not supported.

Managed locals that are directly declared in the block are freed. After that, it is UB to reference those locals.

var char* text;
{
    var char[+] temp = get();
    text = temp.ptr;
}
// it is UB to use `text` because `temp` is out of scope.

BreakOnStatement§

Grammar§

BreakOnStatement ::= "break" "on" ";"

Semantics§

Explicitly exit the current SwitchStatement.

Managed locals declared from the parent SwitchStatement to the break on are freed.

BreakStatement§

Grammar§

BreakStatement ::= "break" ((AtLabel ("," Expression)?) | Expression)? ";"

Semantics§

If specified, evaluate the expression. If the label is not specified then break the current loop. If the label is specified then break the loop in which the label is declared.

struct F
{
    var s32 m;
}

var F*[][] items;

foreach (var F[] row) in items do
{
    label A;
    foreach (var F f) in row do
    {
        if f.m == 8 do
            break;      // continue to next row
        if f.m == 12 do
            break @A;    // break the foreach that declares row
    }
}

Managed locals declared from the parent ForeachStatement or WhileStatement to the semicolon following the break are freed.

ContinueOnStatement§

Grammar§

ContinueOnStatement ::= "continue" "on" (Expression | "else)"? ";"

Semantics§

The ContinueOnStatement must be located in the body of a OnMatchStatement.

When neither the optional expression nor else is specified then the statement has for effect to jump in the body of the next, lexicographical, OnMatchStatement.

var s32 a;
switch a do
{
    on 0 do { a+=32; continue on;   } // set a and go to next match
    on 1 do {/*a is either 1 or 32*/}
    else do {                       }
}

If the expression is not specified then it is an error to position the statement in the last match body.

var s32 a;
switch a do
{
    on 0 do { a+=32; continue on;   } // set a and go to next match
    on 1 do { continue on;          } // error, cannot find target...
    else do {                       }
}

It is an error not to position the statement at the end of the body

var s32 a;
switch a do
{
    on 0 do { continue on; a+=32;   } // error, stmt not reachable...
    on 1 do {                       }
    else do {                       }
}

If the expression is specified then it must match to an existing match and the statement has for effect to jump in the body of this match.

var s32 a;
switch a do
{
    on 0 do { a+=32; continue on 1;    } // set a and go to match for 1
    on 1 do { /*a is either 1 or 32 */ }
    else do {                          }
}

If the expression is not specified but else is present then the switch must contain the explicit else block which then becomes the continue on target.

var s32 a;
var s32 b;
switch a do
{
    on 0 do { b=1; continue on else; }
    on 1 do { b=2; continue on else; }
    else do {
                // else reached from case 0 and 1
                if b == 1 ||b == 2 do assert(a == 0 || a == 1);
                // else block reached because no match
                else do assert(a != 0 && a != 1);
            }
}

Managed locals declared from the parent SwitchStatement to the continue on are freed.

ContinueStatement§

Grammar§

ContinueStatement ::= "continue" ((AtLabel ("," Expression)?) | Expression)? ";"

Semantics§

If specified, evaluate the expression. If no label is not specified then continue the current loop. If the label is specified then continue the loop in which the label is declared.

struct F
{
    var s32 m;
}

function log(char* text);

var F*[][] items;

foreach (var F[] row) in items do
{
    label A;
    foreach (var F* f) in row do
    {
        if f.m == 12 do
            continue @A, log("encountered 12..."); // continue to next row
    }
}

Managed locals declared from the parent ForeachStatement or WhileStatement to the semicolon following the continue are freed.

DeclarationStatement§

Grammar§

DeclarationStatement ::= Declaration

Semantics§

A simple shell allowing to declare in function bodies.

DeferStatement§

Grammar§

DeferStatement ::= "defer" BlockStatement

Semantics§

Defers the execution of a BlockStatement to the end of the current scope.

The statement cannot be nested.

The statement is not allowed in the ForeachStatement, SwitchStatement, and WhileStatement.

The statement is not allowed in the main body of a function attributed @noreturn.

GotoStatement are not allowed if defers exist in the scope.

ReturnStatement are not allowed inside the statement.

If the scope ends with a non-void ReturnStatement then the defers are executed before the returned expression.

function main(): s32
{
    var s32 a = 0;
    defer
    {
        a = 0;
    }
    a = 1;
    return a;  // returns 0
}

ExpressionStatement§

Grammar§

ExpressionStatement ::= Expression ";"

Semantics§

ForeachStatement§

Grammar§

ForeachStatement ::= "foreach" ForEachVariables "in" Expression "do" BlockStatement ForEachVariables ::= ForeachVariableList | "(" ForeachVariableList ")" ; ForeachVariableList ::= VariableDeclaration ( ";" VariableDeclaration )*

Semantics§

The statement does not support modification of aggregate being iterated.

foreach over numeric ranges§

If the expression is a RangeExpression then the two expressions that give the bounds must have an integer Type. Their value gives the count of iteration, starting from left to right (exclusive).

A single variable is allowed and it must be implictly convertible to the type of the integer range.

foreach const s8 i in 0 .. 8 do
    printf("%d ", i);               // 0 1 2 3 4 5 6 7

foreach over enums and enumsets§

In both case a single variable is allowed.

If the expression gives a [TypeEnum] then the variable consecutively represents each member value.

enum E {e0, e1, e2}
foreach const E e in E do
    printf("%d ", i);   // 0 1 2

If the expression gives a TypeEnumSet value then the variable consecutively represents each member being included in the set.

enum E {e0, e1, e2, e3}
var bool[E] es = [e1,e3];
foreach const E e in es do
    printf("%d ", i);   // 1 3

foreach over arrays§

If the expression gives a TypeStaticArray, TypeSlice, or a TypeRcArray then two variables are allowed.

The first optional variable, the “counter”, represents the current iteration number.

The counter type must be implicitly convertible to the type of the array length.

For TypeSlices and TypeRcArrays this is always usize or ssize.

For TypeStaticArrays this depends on its .length property.

The last variable represents an array element.

The variable type must exactly match to the array element type.

// over an array literal (like a static array)
foreach const char* s in ["0", "1", "2" ] do
    printf("%s", s);    // 0 1 2

// also count
foreach (const s8 counter; const char* s) in ["0", "1", "2" ] do
    printf("%d-%s ", counter + 10, s);  // 10-0 11-1 12-2

foreach over aggregates (random access style)§

A custom type can be directly used in foreach if it implements

struct Array
{
    var s32[+] array;
    function length(): auto                             { return array.length; }
    @operator(a[b]) function getElement(usize index): auto    { return array[index]; }
}

If the foreach expression gives an aggregate or an aggregate pointer then

so that

var Array array;
var u64 sum;
foreach var auto e in array do
{
    sum += e;
}

becomes

var Array array;
foreach const auto __counter in 0 .. array.length() do
{
    var auto e = array.getElement(__counter);
    sum       += e;
}

Two variables are supported. If the foreach counter is explicitly declared then it is reused in the VariableDeclaration inserted in the loop body, so that

var Array array;
var u64 sum;
foreach (const usize c; var auto e) in array do
{
    sum += e;
}

becomes

var Array array;
foreach const usize c in 0 .. array.length() do
{
    var auto e = array.getElement(c);
    sum       += e;
}

foreach over tuples§

If the expression gives a tuple then the loop body is considered as a generic.

The body is applied for each element of the tuple.

This is called tuple unrolling.

If two variables are specified then the first variable is transformed in an ExpressionAlias that gives the element index as an IntegerExpression.

That variable has to be const and can be either of type TypeAuto or of an integral type with a bitwidth equal or higher than 32.

The second or the single variable is transformed in an ExpressionAlias that reads the nth element of the tuple.

That variable has to be const and must be of type TypeAuto.

var t = (0,0.1);
foreach (const i; const v) in t do
{
}

becomes

{
    {
        alias i => 0;
        alias v => t[0];
    }
    {
        alias i => 1;
        alias v => t[1];
    }
}

As the blocks of an IfElseStatement that are statically determined not to be reachable are allowed to contain semantically incorrect code, it is possible to process each element individually.

Such code, which at first glance looks semantically incorrect, works because

var (u32, f64) t = (0,0.1);
foreach (const i; const v) in t do
{
    if i == 0 do v = 1;
    if i == 1 do v = 0.5; // what when v is u32 ?
}

becomes in a first time

{
    {
        alias i => 0;
        alias v => t[0];
        if i == 0 do v = 1;
        if i == 1 do v = 0.5;
    }
    {
        alias i => 1;
        alias v => t[1];
        if i == 0 do v = 1;
        if i == 1 do v = 0.5;
    }
}

then finally

{
    {
        alias i => 0;
        alias v => t[0];
        v = 1;
    }
    {
        alias i => 1;
        alias v => t[1];
        v = 0.5;
    }
}

If unrolling does not occur inside a regular loop then BreakStatements and the ContinueStatements are transformed in GotoStatement.

A BreakStatement has for effect to jump passed the unrolling.

A ContinueStatement has for effect to jump before the next iteration.

Reversed foreach§

The DotExpression property .reverse has for effect to iterate from the last element to the first.

@foreign function printf(char* specifier;... ): s32;

foreach const s8* s in (0 .. 3).reverse do
    printf("%d", s);                            // 2 1 0

foreach (const s8 counter; const char* s) in ["00", "11", "22" ].reverse do
    printf("%d-%s ", counter, s);               // 2-22 1-11 0-00

Tuple unrolling does not support this property.

GotoStatement§

Grammar§

GotoStatement ::= "goto" AtLabel ( "," Expression)? ";"

Semantics§

If specified, evaluate the expression then jump to the position following the [LabelDeclaration] given by the AtLabel identifier.

function func(var s32 max)
{
    var s32 i;
    label nxt;
    i += 1;
    if i < max do
        goto @nxt;
}

IfElseStatement§

Grammar§

IfElseStatement ::= "if" IfCondition "do" BlockStatement ("else" "do"? BlockStatement)? IfCondition ::= Expression

Semantics§

The condition type must be convertible to [TypeBool].

The first block, the “thenBlock” is executed if the condition is verified, otherwise, if the second block, the “elseBlock”, is specified then it is executed.

The statement introduces a scope that is only valid in the “thenBlock”.

The condition is tried at compile time. If the evaluation is possible and if it evaluates to false then the “thenBlock” semantics are not checked. If it evaluates to true then the “elseBlock” semantics are not checked.

ReturnStatement§

Grammar§

ReturnStatement ::= "return" Expression? ";"

Semantics§

Leave the current function. The optional expression gives the return value. It must be implictly convertible to the function return type.

If the current function has no return type then return can be omitted, in which case it is implicitly added as last statement of the body.

Managed locals declared from in the BlockStatement that constitutes the function body are freed.

SwitchStatement§

Grammar§

SwitchStatement ::= "switch" Expression "do" "{" OnMatchStatement+ ("else" "do" BlockStatement)? "}" OnMatchStatement ::= "on" OnMatchExpressions "do" BlockStatement OnMatchExpressions ::= Expression ("," Expression)*

Semantics§

The switch statement is comparable to a suite of tests on a constant condition but with restrictions on what can be tested so that the compiler can always generate fast jump tables.

The restriction are the following:

There are two ways to express a match to the condition. They can be mixed but duplicate cases are not allowed.

Individual matches§

When a list of individual values is specified, the matching block is executed if the condition matches exactly to one value of the list.

var s32 a;
switch a do
{
    on 0,1,2 do {}
    on 3,4,5 do {}
}

Matches over ranges§

When a list of range is specified, the matching block is executed if the condition value is within (right value is inclusive) one of the range of the list.

var s32 a;
switch a do
{
    on 0 .. 2 do {}
    on 3 .. 5 do {}
}

Switch over strings cannot match ranges:

var char[] text;
switch a do
{
    on "aaa" .. "zzz" do {} // error
}

Switch over enum sets§

Individual matches or values covered by a range dont represent the elements of a set.

Set literals are represented using ArrayExpressions

enum E {e0, e1, e2, e3}
var bool[E] es;

switch es do
{
    on e0, e1       do {} // same as `on [e0], [e1]`
    on [e0, e1]     do {} // to match a set made of both
    on e0 .. e2     do {} // same as `on [e0], [e1], [e2]`, and error, duplicated matches
    on [e0 .. e2]   do {} // to match a set made of the three members
}

Implicit break§

After a match, and if the block for the match does not breaks, continues, neither continues on another case nor returns then the program execution continues passed the switch.

var s32 a = 0;
var s32 b;
switch a do
{
    on 0 .. 2 do b = 0; // implicitly go to L0
    on 3 .. 5 do b = 3; // implicitly go to L0
    else      do b = 6; // implicitly go to L0
}
label L0;
assert(b == 0);

Explicit break§

Although breaks are usuall implicit, early breaks can be made using the BreakOnStatement.

Fallthrough§

Fallthroughs are explicitly performed using ContinueOnStatements.

Else block§

If specified the else block is exectued when none of the OnMatchStatements matches.

Missing and checked else block§

When the else block is not specified and if an uncovered match is reached and if the compiler was passed the --check=switches argument then the program displays a diagnostic before crashing.

For example

var s32 a;
switch a do
{
    on 0 .. 2 do {}
    on 3 .. 5 do {}
}

Is turned, under X86, into

var s32 a;
switch a do
{
    on 0 .. 2 do {}
    on 3 .. 5 do {}
    else      do
    {
        printf("<source loc info>: error, switch match `%llu` is not covered", a);
        fflush(0);
        asm("ud2");
    }
}

VersionBlockStatement§

Grammar§

VersionBlockStatement ::= VersionExpression BlockStatement ("else" "do"? BlockStatement)?

Semantics§

Same as VersionBlockDeclaration excepted that statements are allowed.

WhileStatement§

Grammar§

WhileStatement ::= "while" Expression "do" BlockStatement

Semantics§

The statement introduces a scope.

Evaluate the declarations and statements while the condition given by the expression is true.

WithStatement§

Grammar§

WithStatement ::= "with" WithExpressions "do" BlockStatement WithExpressions ::= Expression ( "," Expression)*

Semantics§

The statement introduces a scope.

The with statement is used to introduce one or several resolution symbols. Each expression must either give a class, a struct, an union or a unit.

The expressions are used to resolve IdentExpressions and TypeIdentifiers located in the nested block. Expressions are tried from left to right to perform a “strict” search.

static struct F
{
    var s32 member;
    static struct G
    {
        static var s32 member;
    }
}

var F f;
var s32 member; // never assigned
with f.G, f do
    member = 0; // f.G.member is assigned
with f, f.G do
    member = 0; // f.member is assigned

The first expression that allows to solve an unknown IdentExpression is always selected, even if not semantically correct.

struct S1 { static var s8 a; }
struct S2 { static var s32 a;}

function main()
{
    with S1, S2 do
        a = :u16.max + 1;   // `S1` contains `a` so select it as scope, then...
                            // error, cannot implicitly convert `65535 + 1:u16` of type `u16` to type `s8`
}

If none of the expressions allows to solve an identifier then resolution continues as usual.