Control Flow
Conditionals, loops, break & continue, and goto/labels — everything that controls execution order in Zym.
Conditionals
if / else
The if statement evaluates a condition and executes a block. Optional else if and else branches handle alternatives.
if (score >= 90) { grade = "A" } else if (score >= 80) { grade = "B" } else if (score >= 70) { grade = "C" } else { grade = "F" }
Conditions follow Zym’s truthiness rules: false, null, and 0 are falsy; everything else is truthy.
Nested conditionals
Conditionals can be nested to any depth. Each else binds to the nearest unmatched if.
if (x > 0) { if (x > 100) { print("large positive") } else { print("small positive") } } else { print("non-positive") }
while Loops
A while loop repeats its body as long as the condition is truthy. If the condition is initially false, the body never executes.
var sum = 0 var i = 0 while (i < 5) { sum = sum + i i = i + 1 } // sum is 10 (0+1+2+3+4)
var count = 0 func next() { count = count + 1 return count } var total = 0 while (next() <= 3) { total = total + 1 } // total is 3, count is 4 (condition checked one extra time)
do-while Loops
A do-while loop executes the body at least once, then repeats while the condition is truthy.
var n = 0 do { n = n + 1 } while (n < 10) // n is 10
var ran = false do { ran = true } while (false) // ran is true — body executed once
for Loops
The for loop has three optional clauses: initializer, condition, and increment.
for (var i = 0; i < 10; i = i + 1) { print(i) }
Variations
// Empty initializer (variable declared outside) var v = 0 for (; v < 4; v = v + 1) { // v is accessible after loop } // Countdown for (var i = 5; i > 0; i = i - 1) { print(i) // 5, 4, 3, 2, 1 } // Infinite loop (all clauses empty) for (;;) { // runs forever until break break }
Variable scoping
Variables declared in the for initializer are scoped to the loop. They do not affect outer variables with the same name.
var i = 100 for (var i = 0; i < 3; i = i + 1) { // inner i shadows outer } // i is still 100
Nested loops can reuse the same variable name safely:
var total = 0 for (var i = 0; i < 2; i = i + 1) { for (var i = 0; i < 3; i = i + 1) { total = total + 1 } } // total is 6 (2 × 3)
switch Statements
The switch statement provides multi-way branching based on a value. Cases fall through by default, so you must explicitly use break to exit.
Basic syntax
var x = 2 var result = "" switch (x) { case 1: result = "one" break case 2: result = "two" break case 3: result = "three" break default: result = "other" } // result is "two"
Fallthrough behavior
If you omit break, execution continues into the next case — this is intentional fallthrough.
var x = 2 var count = 0 switch (x) { case 1: count = count + 1 // falls through case 2: count = count + 10 // falls through case 3: count = count + 100 break default: count = 999 } // count is 110 (0 + 10 + 100)
Multiple cases for the same handler
You can stack multiple case labels to handle different values with the same code.
var day = "Monday" var kind = "" switch (day) { case "Monday": case "Tuesday": case "Wednesday": case "Thursday": case "Friday": kind = "weekday" break case "Saturday": case "Sunday": kind = "weekend" break default: kind = "unknown" } // kind is "weekday"
default clause
The default clause is optional and executes when no case matches. It can appear anywhere in the switch body, not just at the end.
var x = 99 var result = 0 switch (x) { default: result = -1 break case 1: result = 100 break case 2: result = 200 break } // result is -1
Switch with expressions
Both the switch expression and case values can be any expression — they are evaluated and compared at runtime using equality (==).
var base = 10 var multiplier = 2 var result = "" switch (base * multiplier) { case base: result = "equal to base" break case base * 2: result = "double" break case base * 3: result = "triple" break default: result = "other" } // result is "double"
switch in loops
Switch statements work inside loops. break inside a case exits the switch, not the loop. To exit the loop from within a switch, use goto.
var sum = 0 for (var i = 0; i < 5; i = i + 1) { switch (i) { case 0: sum = sum + 1 break // exits switch, not loop case 2: sum = sum + 10 break case 4: sum = sum + 100 break default: sum = sum + 0 } } // sum is 111 (1 + 10 + 100)
var found = false var i = 0 while (i < 10) { switch (i) { case 5: found = true goto done // exit loop entirely default: i = i + 1 } } done: // found is true, i is 5
Empty cases
Cases can be empty, falling through immediately to the next case or default.
var x = 1 var result = 0 switch (x) { case 1: // empty case, falls through case 2: result = 42 break default: result = 99 } // result is 42
Block scope in cases
Each case can have its own block scope using braces. This lets you declare variables that are scoped only to that case.
var x = 2 var result = 0 switch (x) { case 1: { var temp = 100 result = temp + 1 break } case 2: { var temp = 200 // separate scope from case 1 result = temp + 2 break } default: { var temp = 300 result = temp } } // result is 202
Without block scopes, variable declarations in different cases would share the same scope and could cause conflicts.
var x = 1 var sum = 0 switch (x) { case 1: { var value = 10 sum = sum + value } // falls through — block closed, value out of scope case 2: { var value = 20 // different variable, new scope sum = sum + value break } } // sum is 30 (10 + 20)
break & continue
break
break exits the innermost enclosing loop immediately.
var sum = 0 for (var i = 0; i < 10; i = i + 1) { if (i >= 3) break sum = sum + i } // sum is 3 (0+1+2)
continue
continue skips the rest of the current iteration and jumps to the next loop cycle.
var sum = 0 for (var i = 0; i < 5; i = i + 1) { if (i == 2 or i == 4) continue sum = sum + i } // sum is 4 (0+1+3) — skipped 2 and 4
In nested loops
break and continue only affect the innermost loop. To exit an outer loop, use goto.
var total = 0 for (var outer = 0; outer < 3; outer = outer + 1) { for (var inner = 0; inner < 5; inner = inner + 1) { total = total + 1 if (inner == 2) break // only breaks inner loop } } // total is 9 (3 × 3 iterations)
var sum = 0 for (var i = 0; i < 10; i = i + 1) { if (i == 2 or i == 4) continue if (i == 7) break sum = sum + i } // sum is 15 (0+1+3+5+6)
Nested Loops
All loop types can be freely mixed and nested.
var count = 0 var outer = 0 while (outer < 2) { for (var inner = 0; inner < 3; inner = inner + 1) { count = count + 1 } outer = outer + 1 } // count is 6 (2 × 3)
var total = 0 for (var a = 0; a < 2; a = a + 1) { for (var b = 0; b < 2; b = b + 1) { for (var c = 0; c < 2; c = c + 1) { total = total + 1 } } } // total is 8 (2 × 2 × 2)
goto & Labels
Zym supports goto for explicit control flow jumps. A label is an identifier followed by a colon. goto transfers execution to the label unconditionally.
goto is a safe jump — the runtime performs proper cleanup before transferring control. When jumping out of scopes, all local variables in exited scopes are cleaned up and any open upvalues are closed, just as if the scopes had ended normally. This means closures, references, and resource tracking remain consistent even when using goto.Forward jumps
Skip over code that should not execute.
var x = 5 goto skip x = 999 // never reached skip: x = x + 1 // x is 6
Backward jumps
Create manual loops by jumping back to a label.
var count = 0 loop_start: count = count + 1 if (count < 5) goto loop_start // count is 5
Same-scope jumps
Goto works within blocks and function bodies.
func compute() { var result = 0 goto add_value result = 100 // skipped add_value: result = result + 50 return result // returns 50 }
Outward scope jumps
You can jump from a nested scope to an enclosing scope. Inner scopes are cleaned up automatically.
{
var outer = 0
{
{
outer = 30
goto escape
outer = 999 // never reached
}
outer = 888 // never reached
}
outer = 777 // never reached
escape:
outer = outer + 1 // outer is 31
}
Breaking out of loops with goto
Since break only exits the innermost loop, goto can break out of multiple nested loops at once.
var sum = 0 var i = 0 while (i < 10) { sum = sum + i i = i + 1 if (i == 4) goto done } sum = 999 // never reached done: // sum is 6 (0+1+2+3)
Skipping iterations with goto
You can use goto inside loop bodies to skip operations, similar to continue but with more control.
var sum = 0 for (var i = 0; i < 5; i = i + 1) { if (i == 2) goto skip_add sum = sum + i skip_add: var dummy = 0 } // sum is 8 (0+1+3+4) — skipped i==2
goto Patterns
State machine
Labels and goto naturally model state machines.
var state = 0 var result = 0 state_idle: result = result + 1 if (state == 0) { state = 1; goto state_running } goto state_done state_running: result = result + 10 if (state == 1) { state = 2; goto state_error } goto state_done state_error: result = result + 100 goto state_done state_done: result = result + 1000 // result is 1111 (1+10+100+1000)
Jump table
Simulate switch/case with a series of conditional gotos.
var choice = 2 var result = 0 if (choice == 1) goto case_one if (choice == 2) goto case_two if (choice == 3) goto case_three goto case_default case_one: result = 100; goto case_end case_two: result = 200; goto case_end case_three: result = 300; goto case_end case_default: result = 999 case_end: // result is 200
Cleanup / error handling
Use a single cleanup label that multiple exit points jump to — a common pattern in systems programming.
func process(flag) { var resource = 100 if (flag == 1) goto cleanup resource = resource + 10 if (flag == 2) goto cleanup resource = resource + 20 cleanup: resource = resource + 1 return resource } process(1) // 101 (100 + 1) process(2) // 111 (100 + 10 + 1) process(3) // 131 (100 + 10 + 20 + 1)
Error-guarded returns
func safeDivide(a, b) { var result = 0 if (b == 0) goto error result = a / b goto success error: return -1 success: return result } safeDivide(10, 2) // 5 safeDivide(10, 0) // -1
Function Hoisting in Loops
Named functions declared inside a loop body are hoisted within that iteration’s scope, so you can call them before their textual definition.
var sum = 0 for (var i = 0; i < 3; i = i + 1) { sum = sum + helper() // called before definition func helper() { return 10 } } // sum is 30 (3 × 10)