Statements
Every statement form and its rules. Statements appear inside function, tree,
method and branch { } bodies.
object Enemy { float x; float vx; int health; }
fn spawn_wave(array<Enemy> enemies) {
repeat (3) { enemies.push(Enemy { x: 0.0, vx: 40.0, health: 10 }); }
}
fn update_enemies(array<Enemy> enemies, float dt) {
int alive = 0;
for (Enemy e in enemies) {
if (e.health <= 0) { continue; }
e.x = e.x + e.vx * dt;
alive++;
}
if (alive == 0) {
spawn_wave(enemies)?;
}
}
Every statement ends with ; or is a { } block. A lone ; (empty
statement) is an error; write { }.
Blocks
{ ... } groups statements and starts a new scope. At the start of a
statement, { always begins a block, never a map literal.
Variable declarations
int count = 0;
Enemy? target;
A statement that starts with a type and a name declares a local; see Local variables.
Expression statements
Any expression followed by ; is a statement, and its value is thrown away.
A call that can fail still needs handling: file_remove(path)?; or
file_remove(path) !! { ... };.
Compound assignment
score += 10;
health -= damage;
speed *= 0.5;
names[i] += " the Brave";
hits[slot] += 1;
x op= vstoresx op vinx, for+=,-=,*=,/=and%=. The operator works as it does on its own:int32wraps,float32stays single precision, andstring += stringorstring += charappends text.- The result must fit the target without a conversion, as in a plain
assignment.
byte b; b += 1;is an error becauseb + 1is anint; writeb = byte(b + 1);. The same holds forchar. - The target can be a variable, a writable field, or an element of an array,
slice or map. A range such as
a[0:2]is not a target. - The target is evaluated once. In
boxes[next()].count += 1,next()runs once, and that one box is both read and written. The target's parts run before the value on the right. - Reading a map element can fail when the key is missing, so
m[k] += 1must be handled, as in(m[k] += 1)?;, unless it is insideif (m ?? k). - Like
=, it is an expression whose value is the stored value, and it groups right to left:a = b += 1adds tob, then assignsbtoa.
Increment and decrement
i++;
grid[x][y]--;
x++meansx += 1;x--meansx -= 1. The target can be a variable, field or element, and is evaluated once:a[f()]++callsfonce.- They are statements only.
int j = i++;,f(i++)and prefix++iare syntax errors. They are also allowed in aforheader.
Copy statement
source => destination; copies elements from one array, slice or buffer into
another.
source => destination;
packet[0:8] => header;
input => output[4:];
dst[0:4] = src[4:8]; // same copy, destination first
- Both sides are an
array<T>,slice<T>orbufferwith the same element type (a buffer holds bytes). - The lengths must match, or the task traps.
- Overlapping source and destination copy correctly.
if
if (hp <= 0) { println("defeated"); } else if (hp < 20) { println("low health"); } else { println("ok"); }
- The condition must be
bool. Numbers and references are not true or false on their own: writecount != 0,p != null. elsebelongs to the nearestifwithout one.- Map check: inside
if (m ?? k) { ... }(orif (m ?? k && ...)), readingm[k]cannot fail, so it needs no?. This works whenmis a local or parameter andkis a local, parameter or literal, until either is reassigned inside the block. Theelsebranch doesn't get this.
switch
switch (key) {
'w', 'k' => { move(0, -1)?; }
's', 'j' => { move(0, 1)?; }
'q' => return;
_ => { beep(); }
}
switch (shape) {
Circle c => { draw_circle(c.r)?; }
Square s => { draw_square(s.side)?; }
_ => { }
}
switch compares one value against each arm's patterns in order and runs
the first arm that matches. The value is worked out once. At most one arm
runs; there is no fallthrough and no break out of a switch.
- Value switch. The value is an
int,int32,byte,char,bool,stringor enum. Each pattern is a constant: a number, char, string orboolliteral, an enum member (Direction.North), or aconst. An arm may list several patterns, separated by commas. A pattern's type must convert to the value's type without an explicit conversion, so an enum value only matches its own members and anintnever matches an enum member (switch pattern does not match the switch value type 'E'). Other expressions are rejected (a switch pattern must be a literal, an enum member or a constant). - Type switch. The value is an object, interface or engine object
(nullable or not). Each pattern is
Type name: the arm runs when the value's actual type isType, withnameholding it as aTypeinside the arm. The rules ofisapply, so aTypethe value can never be is an error. WriteType _to test without a name. A type pattern is its arm's only pattern. A null value matches no type pattern. _matches anything. It is the arm's only pattern and must be the last arm ('_' must be the last switch arm).- A pattern may appear only once (
duplicate switch pattern). - A switch over an enum without
_must list every member; the error names the missing ones (switch over 'E' does not cover Q, R; add them or a '_' arm). Other switches without_do nothing when no arm matches. - An arm's body is a block or a single statement. Variables declared in an arm, including a type pattern's name, exist only in that arm.
breakandcontinuein an arm apply to the enclosing loop.returnandthrowleave the function as usual.- A value that can fail must be handled with
?or!!.
For the expression form, see Expressions.
while
while (fuel > 0.0) { fuel = fuel - burn_rate; }
The condition must be bool and is checked before each pass.
repeat
repeat (3) { println("fire!"); }
Runs the body a fixed number of times. The count is an int, worked out once
before the first pass; zero or negative runs it zero times.
Counted for
for (int i = 0; i < n; i++) { ... }
for (i = 0; i < n; i++) { ... }
for (; jobs.length() > 0; ) { jobs.pop()?; }
for (;;) { ... }
for { ... } // loops forever
- Every part is optional, and so are the parentheses.
- A variable declared in the header exists only inside the loop.
- A missing condition means "forever". A condition must be
bool. continuestill runs the step (i++).
for ... in
for (int score in scores) { total = total + score; }
for (int i, int score in scores) { ranked[i] = score; }
for (view<Motion, Integrate> entity in rows) { ... }
for (string name in ammo) { ... }
for (string name, int count in ammo) { ... }
for (char c in title) { ... }
- Loops over an
array<T>,slice<T>orrows<Q, A>, in index order, over amap<K, V>, or over astring's characters. Channels and buffers cannot be looped over (for requires an array, slice, map, or string value). - With two variables, a sequence gives the index (an
int) and the element; a map gives the key and the value. With one variable, a map gives its keys. - A string gives each Unicode code point as a
char, and with two variables its index counted in characters. These are the same characters ass.chars(): bytes that are not valid UTF-8 read as U+FFFD, one per byte. - Each variable's type must accept what it receives
(
for (float f in ints)works). - For a sequence, the length is read once before the loop. Elements you push during the loop are not visited; removing elements during the loop can make it index past the end and trap. Assigning the index variable does not change which element comes next.
- A map is visited in an unspecified order. Assigning to a key the loop has
reached is fine, but adding a new key to the map during the loop traps
(trap code
-11). The loop keeps iterating the map it started with even if the body assigns the variable it came from. A null map is visited zero times, like a null array. - For numbers, the loop variable is a copy. For objects, it is the same object as the element, so changing its fields changes the element.
select
select waits for the first of several channels to have a value.
select {
int button = clicks.receive() { println("clicked button " + int_to_string(button)); }
string key = keys.receive() { println("pressed " + key); }
else { println("no input this frame"); }
}
- Each arm is
Type name = channel.receive() { ... }. Anything other than areceive()call isa select arm must receive from a channel. The variable exists only in that arm's block. - At least one arm is required.
else, if present, comes last. - Without
else, the select waits, and it can fail if a channel is closed, so it must be in a function that can fail (or inside a!!guard). Withelse, it never waits or fails and can be used anywhere. select { ... } unless ch;also gives up when the channelchfires first, and then fails with-305. It needs the same failing context.unlessafter anelseblock isa select with an else arm never waits; it cannot take 'unless'.
How arms are chosen: select in Concurrency.
Guard blocks
Guards attach error handlers (!!) and cleanup (&&) to a statement or a
block.
file_remove("cache.bin") !! { println("no cache to remove"); };
{
string text = file_read_text("settings.txt")?;
println("volume: " + int_to_string(int_from_string(text)?));
} !! {
println("load failed: " + err.message);
};
Mutex lock = mutex_create()?;
lock.lock()? && { lock.unlock(); };
| Form | Meaning |
|---|---|
call() !! { ... }; | If the call fails, run the handler; err holds the error. |
{ ... } !! { ... }; | If anything in the block fails, run the handler. |
expr && { ... }; | Run the block when the enclosing block exits. |
{ ... } && { ... }; | Run the second block when the first one exits. |
&&is a guard only when{follows directly; otherwise it is logical AND.- Guards chain left to right, each wrapping everything to its left:
{A} && {D} !! {H};runsDbeforeH;{A} !! {H} && {D};runsHbeforeD. - A guarded statement ends with
;.
Full rules: Errors.
break and continue
- Only inside
while,repeat,forandfor ... in(break is only valid inside a loop). They apply to the innermost loop; there are no labels. Aswitchis not a loop:breakin a switch arm leaves the loop around it. - Pending
&&cleanup blocks of the scopes they leave run first. - They cannot be used inside an
&&cleanup block. - They cannot jump out of a
branch { }block, because the block runs as a separate task (break cannot leave a branch block). Loops inside the block can use them normally.
return
return;
return value;
- The value must match the function's result type;
return;is only forvoidfunctions. - In a
branch { }block,returnfinishes the task. The block's task type comes from itsreturnstatements; a block that returns no value istask<void>. - Pending
&&cleanup runs before returning.returncannot be used inside a cleanup block.
Reaching the end of a function
A function, method or tree with a result, and a branch { } block that
returns a value, must not be able to reach its closing brace. The compiler
checks this with a few simple rules. A statement always leaves when it is:
returnorthrow;- a block containing a statement that always leaves;
- an
ifwith anelsewhere both branches always leave (anifwithoutelsenever does); - a
while (true)or aforwith no condition (or a literaltrueone), as long as nobreakin it targets that loop; - a
switchstatement whose arms cover every value (it has a_arm, or lists every enum member or bothtrueandfalse) and where every arm always leaves; - a
selectwhere every arm and theelseblock always leave. Withoutelse, a select that runs no arm fails, which also leaves; the same holds forselect { ... } unless ch;; - a guarded block
{ ... } && { ... };whose guarded block always leaves, or{ ... } !! { ... };where both the guarded block and the handler always leave.
Nothing else counts: other loops, ?, and conditions that merely happen to be
constant all may complete. If the body of a function with a result can
complete, the check fails:
fn sign(int x) -> int {
if (x < 0) { return -1; }
if (x > 0) { return 1; }
} // function 'sign' can reach its end without returning a value
Add the missing return, or turn the last if into an if/else. A
branch { } block reports branch block can reach its end without returning a value. Code after a statement that always leaves is allowed. void
functions, methods and trees may reach their end.
throw
throw new error(3, "bad count");
throw err; // rethrow inside a handler
The value must be an error. throw is allowed in functions that can fail,
in branch { } blocks, and inside !! guards. See
Errors.
Single-statement bodies
The body of if, else, while, repeat and for, and a switch arm,
may be one statement without braces: if (done) return;.
See also
- Guide: Language basics, Error handling