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.

conditionals
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.

basic while
var sum = 0
var i = 0
while (i < 5) {
    sum = sum + i
    i = i + 1
}
// sum is 10 (0+1+2+3+4)
while with function in condition
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.

do-while
var n = 0
do {
    n = n + 1
} while (n < 10)
// n is 10
Key difference: Even when the condition is initially false, the body runs once:
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.

standard for
for (var i = 0; i < 10; i = i + 1) {
    print(i)
}

Variations

for loop 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

basic switch
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.

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.

multiple cases
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.

default at the beginning
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 (==).

expressions in switch
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.

switch in a loop
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)
exiting a loop from switch with goto
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.

case with block scope
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.

fallthrough with block scope
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.

break in nested loops
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)
combining break and continue
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.

mixed nesting
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)
triple nesting
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.

Safe jump: 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.
Scope rule: You can jump forward, backward, and outward (from an inner scope to an enclosing scope). You cannot jump into a deeper scope.

Forward jumps

Skip over code that should not execute.

forward jump
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.

backward jump (manual loop)
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.

goto inside a function
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.

outward jump
{
    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.

goto to exit nested loops
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.

state machine
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.

jump table
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.

cleanup pattern
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)