Repository navigation
Control Flow
- Blocks
if/elsewhilefor...in- C-Style
forLoops - Range Operator
returnbreak/continueswitchdefermatchExpressiontry/catch/finally- Ternary Expression
- Exception Handling
Blocks are { ... } and appear in:
- function bodies
-
if/else while
A block contains a sequence of statements.
Syntax:
if (cond) {
// then
} else {
// else
}Notes:
- Parentheses are required:
if ( ... ) -
elseblock is optional
Walrus operator pattern:
// Assign and test in one expression
if (result := compute_value()) {
print("Success:", result);
} else {
print("Failed");
}
// Nested with walrus
if (user := find_user(id)) {
if (token := generate_token(user)) {
send_email(user, token);
}
}Syntax:
while (cond) {
// body
}Notes:
- Parentheses are required:
while ( ... ) - Condition is re-evaluated each iteration
Walrus operator pattern:
// Read lines until EOF
while (line := read_line()) {
process(line);
}
// Fetch and process data
while (data := fetch_next()) {
if (is_valid(data)) {
handle(data);
}
}Syntax:
for name in iterable {
// body
}Iterates over:
- Lists: iterates over elements
- Maps: iterates over keys (use
keys(m),values(m), oritems(m))
IMPORTANT: The for k, v in map syntax only works in comprehensions, NOT in regular for loops.
Use items(map) for key-value iteration in regular loops.
Example:
let xs = [10, 20, 30];
for x in xs {
print(x);
}
// Using range() for numeric iteration
for i in range(10) {
print(i); // 0 through 9
}
for i in range(2, 8, 2) {
print(i); // 2, 4, 6
}
// Map iteration: use items() for key-value pairs
let m = {"a": 1, "b": 2};
for pair in items(m) {
print(pair[0], "=", pair[1]);
}
// ❌ WRONG: for k, v in m {...} does NOT work in regular loops
// ✅ RIGHT: Use items() as shown above
// ✅ ALSO WORKS: for k, v in m {...} in comprehensions only\u26a0\ufe0f Understanding Variable Order:
When iterating lists with two variables, the order is (value, index) - opposite to most languages!
let fruits = ["apple", "banana", "cherry"];
// \u26a0\ufe0f Native syntax: (value, index) - COUNTER-INTUITIVE
for fruit, idx in fruits {
print(idx, ":", fruit);
// 0 : apple
// 1 : banana
// 2 : cherry
}RECOMMENDED: Use enumerate() for standard (index, value) order:
// ✅ enumerate() returns [[index, value], ...] pairs
// In regular for loops, access pair elements by index
for pair in enumerate(fruits) {
let idx = pair[0];
let fruit = pair[1];
print(idx, ":", fruit);
// 0 : apple
// 1 : banana
// 2 : cherry
}
// In comprehensions, you can use destructuring syntax
let labeled = [to_str(i) + ": " + val for [i, val] in enumerate([10, 20, 30])];
// ["0: 10", "1: 20", "2: 30"]Note: Destructuring syntax
for [a, b] in ...only works in comprehensions, not in regularforloops.
Best Practice: Always use enumerate() when you need both index and value for clarity and compatibility with other languages.
Syntax:
for (init; condition; increment) {
// body
}All three parts are optional. This enables traditional C-style loops:
// Basic counter loop
for (i = 0; i < 10; i = i + 1) {
print(i);
}
// `let` is allowed in the init clause
for (let i = 0; i < 3; i += 1) {
print(i);
}
// Compound assignments are supported in the header
for (let j = 5; j > 0; j -= 1) {
print(j);
}
// Multiple initializations (using comma isn't supported, use separate statements)
let i = 0;
let j = 10;
for (; i < j; i = i + 1) {
print(i, j);
j = j - 1;
}
// Infinite loop
for (;;) {
if (some_condition()) {
break;
}
}Syntax:
-
start..end- Exclusive range (up to but not including end) -
start..=end- Inclusive range (up to and including end)
Creates a lazy range from start to end:
let nums = 0..5; // range 0..5 (exclusive)
let incl = 0..=5; // range 0..=5 (inclusive)
let down = 10..5; // descending range
let down_incl = 10..=5; // descending inclusive range
// Use with for-in loops
for i in 0..10 {
print(i); // 0 through 9
}
for i in 1..=100 {
print(i); // 1 through 100
}The range operator returns a lazy range object and works in both directions (ascending and descending) automatically.
Syntax:
return;
return expr;Return can appear inside any block; it returns from the current function call.
-
break;exits the innermost loop orswitch -
continue;skips to the next iteration
Valid in while, for...in, and C-style for loops.
Example:
while (true) {
let x = pop(xs);
if (x == nil) { break; }
if (x < 0) { continue; }
print(x);
}Syntax:
switch (expr) {
case 1 { print("one"); }
case 2 { print("two"); }
default { print("other"); }
}Rules:
-
switchevaluatesexpronce, then compares against eachcasevalue in order. - Supported matching is easiest/most common with
int,string,bool, andnil. -
Fallthrough: once a case matches, execution continues into subsequent cases until a
breakis hit or the switch ends. -
break;exits theswitch(without leaving an outer loop). -
default(optional) can appear anywhere. If no case matches, execution starts atdefaultand falls through to the following cases.
Pattern cases (no guards) are supported:
- List destructuring:
case [a, b, ...rest] { ... } - Map destructuring:
case {x, y: yy} { ... } - Type pattern:
case int(x) { ... }
Type patterns match built-in types (nil, bool, int, float, string, list, map, strbuf, range, function, native, promise).
If a class or struct name is in scope, case ClassName(x) or case StructName(x) matches instances and binds x.
Example:
let out = [];
switch (x) {
case 1 { push(out, "one"); }
case 2 { push(out, "two"); break; }
default { push(out, "other"); }
}The defer statement schedules cleanup code to run when the current block exits.
Syntax:
defer expr;
defer { /* block */ }Rules:
- Defers run in last-in, first-out order when leaving the current block.
- Defers run on normal block exit,
return,break,continue,throw, and VM runtime errors. - All registered defers run in reverse order, even when one changes control flow. A return, throw, or runtime error from a defer replaces the earlier result. Cleanup remains subject to host execution limits.
-
deferis usually used for cleanup like closing files or restoring state.
Syntax:
let label = match (x) {
case 1: "one";
case 2: "two";
default: "other";
};Pattern syntax:
let out = match (value) {
case [a, b]: a + b; // list pattern (binds a, b)
case {x, y: z}: x + z; // map pattern (keys x, y; binds y to z)
case int(n): n + 1; // type pattern (binds n)
case _ : "anything"; // wildcard
case n if n > 10: "big"; // guard (optional)
default: "other";
};Rules:
-
matchevaluates the input expression once, then tests eachcasepattern in order. - Patterns can bind identifiers; bindings are visible in the guard and case result.
- Guards (
if expr) are optional and only run if the pattern matches. - The first matching case expression is evaluated and returned.
-
default(optional) is returned if no case matches. - If no case matches and there is no
default, the result isnil.
Syntax:
try {
// work
} catch (e) {
// error handling
} finally {
// cleanup (always runs)
}
// finally can also be a single expression:
try {
work();
} catch (e) {
print(e);
} finally cleanup();Notes:
-
catchis required;finallyis optional. - The
finallyblock runs on normal completion,return,break/continue,throw, and runtime errors. Cleanup remains subject to host execution limits. -
finallycan be a block or a single expression statement. - If
finallyitself returns or throws, it overrides the prior control flow. -
break;inside aswitchexits the switch (not an enclosing loop).
Syntax:
cond ? then_val : else_valEvaluates cond. If truthy, returns then_val, otherwise else_val.
Example:
let max = a > b ? a : b;Syntax:
throw expr;Throws an exception. The value can be any type (commonly a string or map).
Example:
if (x < 0) {
throw "negative value not allowed";
}
throw {"msg": "error", "code": 404};Syntax:
try {
// code that might throw
} catch (e) {
// handle exception
}If code in the try block throws, execution jumps to the catch block with the thrown value in variable e.
Example:
try {
do_risky_operation();
} catch (e) {
print("Error:", e);
}