Statement ::= DeclarationStatement
| AssertStatement
| BlockStatement
| BreakOnStatement
| BreakStatement
| ContinueOnStatement
| ContinueStatement
| DeferStatement
| ExpressionStatement
| ForeachStatement
| GotoStatement
| IfElseStatement
| ReturnStatement
| StaticAssertDeclaration
| SwitchStatement
| VersionBlockStatement
| WhileStatement
| WithStatement
;
StaticAssertDeclaration ::= "static" AssertStatement
AssertStatement ::= "assert" "(" Expression ( "," Expression)? ")" ";"
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.
Assertions are enabled with the compile switch --check=asserts
static
limitation§
static evaluation of assert
is limited to
bool
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 ::= "@" Identifier
Specify the GotoStatement target or the optional BreakStatement and ContinueStatement target scope.
BlockStatement ::= CurlyBlockStatement
| Statement
;
CurlyBlockStatement ::= "{" Statement* "}"
SingleStatement ::= Statement
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 ::= "break" "on" ";"
Explicitly exit the current SwitchStatement.
Managed locals declared from the parent SwitchStatement to the break on
are freed.
BreakStatement ::= "break" ((AtLabel ("," Expression)?) | Expression)? ";"
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 ::= "continue" "on" (Expression | "else)"? ";"
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 ::= "continue" ((AtLabel ("," Expression)?) | Expression)? ";"
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 ::= Declaration
A simple shell allowing to declare in function bodies.
DeferStatement ::= "defer" BlockStatement
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 defer
s exist in the scope.
ReturnStatement are not allowed inside the statement.
If the scope ends with a non-void ReturnStatement then the defer
s are executed before the returned expression.
ExpressionStatement ::= Expression ";"
ForeachStatement ::= "foreach" ForEachVariables "in" Expression "do" BlockStatement
ForEachVariables ::= ForeachVariableList
| "(" ForeachVariableList ")"
;
ForeachVariableList ::= VariableDeclaration ( ";" VariableDeclaration )*
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
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.
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
length()
function that returns an integer value.@operator(a[b])
function that takes an integer index and returns a value with a type that is compatible with the last foreach
variable.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
.length
DotExpression property is added to the new expressionforeach
body first statement becomes a VariableDeclaration with for initializer an IndexExpression on the new expressionso that
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
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.
becomes
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
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.
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 ::= "goto" AtLabel ( "," Expression)? ";"
If specified, evaluate the expression then jump to the position following the [LabelDeclaration] given by the AtLabel identifier.
IfElseStatement ::= "if" IfCondition "do" BlockStatement ("else" "do"? BlockStatement)?
IfCondition ::= Expression
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 ::= "return" Expression? ";"
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 ::= "switch" Expression "do" "{" OnMatchStatement+ ("else" "do" BlockStatement)? "}"
OnMatchStatement ::= "on" OnMatchExpressions "do" BlockStatement
OnMatchExpressions ::= Expression ("," Expression)*
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:
char[]
TypeThere are two ways to express a match to the condition. They can be mixed but duplicate cases are not allowed.
When a list of individual values is specified, the matching block is executed if the condition matches exactly to one value of the list.
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.
Switch over strings cannot match ranges:
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
}
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);
Although breaks are usuall implicit, early breaks can be made using the BreakOnStatement.
Fallthroughs are explicitly performed using ContinueOnStatements.
If specified the else
block is exectued when none of the OnMatchStatements matches.
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
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 ::= VersionExpression BlockStatement ("else" "do"? BlockStatement)?
Same as VersionBlockDeclaration excepted that statements are allowed.
WhileStatement ::= "while" Expression "do" BlockStatement
The statement introduces a scope.
Evaluate the declarations and statements while the condition given by the expression is true.
WithStatement ::= "with" WithExpressions "do" BlockStatement
WithExpressions ::= Expression ( "," Expression)*
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.