machdocs
Home Ecosystem Changelog

Statements

Statements are the runtime steps that fill function bodies and blocks. Every statement ends with ;, except the ones that end with a block {...}.

Conditionals: if / or

An if head opens a branch chain. Each or (cond) { ... } adds another conditional branch, and a trailing or { ... } with no condition is the catch-all. Every body is a block; there is no brace-less single-statement form.

if (cond) {
    ...
}
or (cond) {
    ...
}
or {
    ...                     # final else (no condition)
}

Lexical payload guards

An if (sel p.c) condition opens a compile-time payload guard for the case p.c inside its block. Reading p.c is only permitted within such a dominating guard.

Guards also propagate across logical &&: the right side of sel p.c && expr is guarded by the check on the left. In addition, when an if branch exits the enclosing block using ret, brk, or cnt, the compiler knows the exited case cannot reach subsequent statements.

Guard limits

An or branch is not guarded because the tested condition failed. Logical || and ! do not establish guards. Keep tests to one condition per check when unwrapping payloads.

Loops: for

mach has a single condition-driven loop. The body repeats while the condition holds. There is no for-each form.

var i: i64 = 0;
for (i < 10) {
    i = i + 1;
}

Returning: ret

ret expr; returns a value; bare ret; returns from a void function.

ret expr;                   # return a value
ret;                        # return from a void function

Loop control: brk / cnt

brk exits the enclosing for; cnt skips to the next iteration.

for (i < 10) {
    if (i == 3) { cnt; }
    if (i == 8) { brk; }
    i = i + 1;
}
Note

Both are operand-less, so they are keywords only in their bare brk; / cnt; form. The same word followed by anything else is an ordinary identifier: a variable named cnt reads and assigns normally (cnt = x;), even inside a loop that also uses bare cnt; for control flow.

Deferred cleanup: fin

fin schedules a statement or block to run when the enclosing scope exits, in reverse order of declaration. It is for cleanup that should happen regardless of how the scope is left.

{
    fin counter = counter - 1;
    fin { counter = counter * 2; }

    # ... code ...
}
# at scope exit: fin block runs first, then fin counter = counter - 1

fin takes either a single statement (fin stmt;) or a block (fin { ... }). There is no bare-expression form.

Blocks

{ ... } introduces a new lexical scope. Statements inside run in order, and a block can stand alone to bound the lifetime of locals.

{
    val tmp: i64 = compute();
    use_tmp(tmp);
}

See also