Control Flow
Cloth’s control-flow constructs are conventional. Conditions, loops, switches, and early-exit statements all use C-style syntax with a few additions: range-based iteration through for-in, the guard statement for defensive checks, and the defer statement for scheduled cleanup.
if / else
An if statement evaluates a bool condition and chooses one of two branches:
if (true) {
println("Hello World");
} else {
println("I am Cloth!");
}The condition must be parenthesized. Both branches are blocks delimited by braces. Chained alternatives are written with else if:
if (count == 0) {
...
} else if (count == 1) {
...
} else {
...
}switch
A switch statement chooses among several alternatives based on the value of an expression:
switch (expr) {
case pattern1: ...
case pattern2: ...
default: ...
}Patterns are compared against the discriminant in source order; the first match runs that case’s body and exits the switch. There is no C-style fall-through — a case body does not drop into the next case. Use break only to exit early from inside a nested loop or to leave the switch from inside a deeper block; ordinary completion of a case body already exits the switch.
The default arm runs when no case pattern matches. It may appear anywhere in the case list, though convention is last.
Patterns
A pattern is any expression of the discriminant’s type. The compiler lowers each case to an equality comparison: integer and pointer discriminants use icmp eq, floating-point discriminants use fcmp oeq, and enum-typed discriminants compare singleton pointers (see Enums).
Common discriminant types and their patterns:
// integer
switch (code) {
case 200: ...
case 404: ...
default: ...
}
// enum (variants are first-class patterns)
switch (color) {
case Color.RED: ...
case Color.GREEN: ...
case Color.BLUE: ...
}Exhaustiveness on enums
When the discriminant types as an enum and the switch has no default: arm, every declared case must appear as a pattern. The compiler rejects an incomplete enum switch with S02F NonExhaustiveSwitch, listing the missing case names. Adding any missing case — or a default: branch — clears the error.
Switches on non-enum types (integers, strings, etc.) don’t enforce exhaustiveness; without a matching pattern and no default:, the switch exits silently.
Loops
Cloth has four loop forms — while, do-while, the C-style for, and for-in. Each is introduced by its keyword and runs a single brace block as its body. The control statements break and continue work uniformly across them.
while
A while loop evaluates its condition before each iteration and runs the body while the condition is true:
i32 j = 0;
while (j < 5) {
j = j + 1;
}The condition must be parenthesized and produce a bool. If the condition is false on entry, the body never runs. There is no body-less form — while (cond); is rejected.
do-while
A do-while loop runs its body once before checking the condition. The closing while (cond); is followed by a mandatory semicolon:
i32 k = 0;
do {
k = k + 1;
} while (k < 3);Use do-while when the body must run at least once — typically when the condition depends on state computed inside the body.
for (C-style)
A C-style for declares an initializer, a condition, and an iterator step in one header. All three parts are required:
for (i32 i = 0; i < 3; i++) {
println(i);
}- Initializer is a single statement: a typed declaration (
Type name = expr;), aletbinding (let name = expr;), or any expression statement followed by;. - Condition is any expression producing a
bool. It runs before each iteration; the body runs only when the condition is true. - Iterator is any expression. It runs after each iteration of the body, before the next condition check.
i++,i = i + 1, and method calls are all legal.
There is no abbreviated form — for (;;) is rejected. When you don’t need an explicit init or step, use while (true) and place initialization before the loop and the step at the end of the body.
The loop variable declared in the initializer is scoped to the loop. It cannot be referenced after the closing brace.
for-in
A for-in loop binds each element of an iterable to a typed loop variable:
for (TypeName name in iterable) {
...
}The syntax is reserved and parses today, but runtime support for iteration is not yet wired through codegen. Until then, write a C-style for with explicit indexing.
Pre/post increment and decrement
++ and -- apply to integer lvalues — local variables and fields whose type is one of i8/i16/i32/i64/u8/u16/u32/u64. Both prefix and postfix forms are supported:
i32 i = 0;
i++; // postfix: result is the old value, the variable becomes 1
++i; // prefix: the variable becomes 2, result is the new value
i--; // postfix decrement
--i; // prefix decrementThe operand must be a writable location. Applying ++ or -- to a literal, a constant, or a non-integer is rejected at compile time. Pre- and post-form differ only when the result of the expression is used: post yields the value before the change, pre yields the value after. In a for-loop iterator step both behave the same, since the produced value is discarded.
Loop control
Two statements alter the flow of the surrounding loop:
break;— terminates the innermost enclosing loop and resumes execution at the statement after it.continue;— skips the rest of the current iteration. Inwhileanddo-while, control jumps back to the condition check. In a C-stylefor, control jumps to the iterator step (so the post-increment still runs) and then re-checks the condition.
i32 j = 0;
while (j < 100) {
if (j == 5) {
break;
}
j = j + 1;
}
// j == 5 here.i32 sum = 0;
for (i32 n = 0; n < 5; n++) {
if (n == 1) {
continue;
}
sum = sum + n;
}
// sum == 9 (0 + 2 + 3 + 4).Both statements end with a semicolon and must appear inside a loop. Using break or continue outside any loop is rejected at compile time. Loop labels (e.g. break outer;) are not supported — control statements always target the innermost enclosing loop.
return
A return statement exits the current function. If the function’s return type is void, no value follows the keyword:
return;Otherwise, an expression provides the function’s result:
return v + other.get();The expression must produce a value of the function’s declared return type.
throw
A throw statement raises an error. The thrown value’s type must match one of the types listed in the enclosing function’s maybe clause:
throw new ParseError("unexpected token");throw does not return; control transfers to the nearest matching handler in the call stack. See Memory and Errors for the full rules.
guard
A guard statement evaluates a condition and, if the condition is false, runs an attached block that must terminate the enclosing function (typically with return or throw).
guard (condition) else {
return;
}guard expresses defensive checks — preconditions that must hold for the rest of the function to be valid. Unlike if, the compiler verifies that the failure block actually exits the enclosing function.
defer
A defer statement schedules a block of code to run when the enclosing scope exits, regardless of how it exits:
defer {
cleanup();
}Multiple defer blocks in the same scope run in reverse declaration order — the most recently deferred block runs first. defer is the right tool for releasing resources whose lifetime is tied to a particular scope, since it ensures the cleanup runs along normal exits, early returns, and thrown errors alike.
Expression statements
Any expression followed by a semicolon is a statement. Function calls in particular are usually used this way:
println("Hello World");When an expression’s value is meant to be discarded, the compiler accepts it without complaint. Expression statements are the bridge between Cloth’s expression-oriented mathematical operators and its statement-oriented control flow.