Language

Control Flow

Conditionals, loops, switch, exceptions, and optional chaining — all with familiar syntax.

if / else

Parentheses around the condition are required. Braces around each branch are required:

let score = 85

if (score >= 90) {
    out "A"
} else if (score >= 75) {
    out "B"
} else if (score >= 60) {
    out "C"
} else {
    out "F"
}

while

let i = 0
while (i < 5) {
    out i
    i++
}
// → 0, 1, 2, 3, 4

for

C-style for loop. The variable is scoped to the loop — it doesn't exist after the closing brace:

for (let i = 0; i < 5; i++) {
    out i
}
// → 0, 1, 2, 3, 4

// Sum of an array by index
let nums = [10, 20, 30, 40, 50]
let sum = 0
for (let i = 0; i < nums.length; i++) {
    sum += nums[i]
}
out sum   // → 150

for-in

Iterate directly over every element in an array, or every character in a string:

let fruits = ["apple", "banana", "cherry"]
for (let fruit in fruits) {
    out fruit
}
// → apple, banana, cherry

// Over a string — visits each character
let result = ""
for (let c in "abc") {
    result = result + c + "-"
}
out result   // → a-b-c-
Note: The loop variable in for-in is a copy. Mutating it doesn't change the original array. Use an index loop if you need to modify elements.

do-while

Runs the body at least once, even if the condition is false from the start:

let i = 0
do {
    out i
    i++
} while (i < 3)
// → 0, 1, 2

// Runs once even when condition is false
let x = 100
do {
    out "ran once"
} while (x < 0)   // → ran once

break and continue

// break — exit the loop early
for (let i = 0; i < 10; i++) {
    if (i == 5) { break }
    out i
}
// → 0, 1, 2, 3, 4

// continue — skip to the next iteration
for (let i = 0; i < 5; i++) {
    if (i == 2) { continue }
    out i
}
// → 0, 1, 3, 4

Labeled loops

Break or continue an outer loop from inside a nested one:

outer: for (let i = 0; i < 3; i++) {
    for (let j = 0; j < 3; j++) {
        if (j == 1) { continue outer }   // skip inner, go to next i
        out "{i},{j}"
    }
}
// → 0,0   1,0   2,0

switch

Match a value against cases. No fall-through — only the matched case runs. One case can match multiple values:

let day = 3

switch (day) {
    case 1: { out "Monday" }
    case 2: { out "Tuesday" }
    case 3: { out "Wednesday" }
    default: { out "Other" }
}
// → Wednesday

// Multiple values per case
switch (day) {
    case 1, 2, 3, 4, 5: { out "Weekday" }
    case 6, 7:           { out "Weekend" }
}

Exceptions

Use throw to raise an error, try/catch to handle it, and finally for cleanup that always runs:

fn int divide(int a, int b) {
    if (b == 0) { throw "Cannot divide by zero" }
    return a / b
}

try {
    let result = divide(10, 0)
    out result
} catch (e) {
    out "Error: {e}"   // → Error: Cannot divide by zero
} finally {
    out "Done."        // always runs
}

You can throw any value — strings, numbers, objects:

throw 404
throw { code: 500, message: "Internal error" }

catch and finally are both optional. Use just finally when you only need cleanup:

try {
    riskyOperation()
} finally {
    cleanup()   // runs even if riskyOperation throws
}

Optional chaining

Use ?. to call a method or access a field only when the value is not null. If it is null, the whole expression returns null instead of crashing:

let s = null
let upper = s?.toUpperCase()   // s is null → upper = null, no error

// Combine with ?? for a safe fallback
let result = s?.toUpperCase() ?? "no value"
out result   // → no value

// Chain multiple ?.
let value = a?.getNext()?.getValue() ?? 0