Control flow

mc's core brings if, loop, break N and continue N; teko adds C#'s while, do ... while, for, foreach, both spellings of switch and the ternary. The core's forms are not replaced — loop { } stays exactly what it is.

Loops #

writtenmeans
loop { }unconditional; leave it with a break
while (c) stmt / do stmt while (c);the body may be a block or a single statement
for (init; cond; step) stmtthe step is i++, i--, i += e, i -= e or i = e
for (;;) stmtruns until a break
foreach (T x in xs) stmta T[], a local fixed array or an inline array field

break N leaves N enclosing loops at once and continue N restarts the N-th one, so no labels are needed; a level beyond the current depth is refused where it is written. A continue inside a do lands on the condition and inside a for it runs the step, so a loop that would otherwise never advance still terminates.

A { } block is a scope: a name declared inside it stops answering at the }, where a local of counted type is released (memory.md).

switch, twice #

The statement reads its subject once and takes constant labels — an integer literal or a const — optionally guarded by when. Labels that share a body are written one after the other; a non-empty body may not fall out, so it ends with break, break N, continue or return. default may stand anywhere and is tried last. Inside a switch, break leaves the switch, break 2 also leaves the loop around it, and continue reaches that loop.

The expression is e switch { K => a, K or L => b, _ when c => d, _ => z }: or joins the labels of one arm, when guards an arm, and a _ arm is required — it is the unconditional base of the chain, so it cannot itself carry a when.

The ternary #

c ? a : b has C#'s precedence and associates to the right, so c1 ? a : c2 ? b : d chains. Only the taken arm is evaluated, the two arms must have the same type, and a ternary of two objects picks between references without allocating. scope { } is a lexical scope of its own, for dependency injection (di.md).

One program #

// expect-exit: 42
#include "rt.tk"

const i64 THREE = 3;

i64 classify(i64 n) {
    i64 r = 0;
    switch (n) {
        case 1:
            r = 10;
            break;
        case 2:
        case THREE:                       // a const label, sharing a body
            r = 20;
            break;
        case 10 when n > 5:               // a guarded label
            r = 30;
            break;
        default:
            r = 99;
            break;
    }
    return r;
}

i64 pick(i64 v) {
    return v switch { 1 => 100, 2 or 3 => 200, _ when v > 100 => 999, _ => 0 };
}

i64 main() {
    i64 sum = 0;

    i64 i = 0;
    loop {                                // the core's own unconditional loop
        i = i + 1;
        if (i == 3) break;
    }
    while (i < 5) i = i + 1;              // a single-statement body

    for (i64 m = 0; m < 5; m++) {
        if (m == 2) continue;             // the step still runs
        sum = sum + 1;
    }

    for (i64 a = 0; a < 3; a++) {
        for (i64 b = 0; b < 3; b++) {
            if (b == 1) break 2;          // leaves both loops at once
            sum = sum + 10;
        }
    }

    i64 xs[3];
    xs[0] = 1;
    xs[1] = 2;
    xs[2] = 3;
    foreach (i64 v in xs) {
        sum = sum + v;
    }

    if (classify(THREE) != 20) return 1;
    if (classify(10) != 30) return 2;
    if (classify(7) != 99) return 3;
    if (pick(2) != 200) return 4;
    if (pick(101) != 999) return 5;

    i64 chosen = sum > 10 ? 2 : 0;        // only the taken arm is evaluated
    return i + sum + chosen + 15;
}

match does not exist (the switch expression covers it) and a bare when statement is not taught. Every form and its limits: control-flow.md.

Edit this page