Control Flow
If / elif / else
if (x > 0) {
print("positive")
} elif (x < 0) {
print("negative")
} else {
print("zero")
}
While
var i = 0
while (i < 10) {
print(i)
i++
}
For (C-style)
for (var i = 0; i < 5; i++) {
print(i)
}
The initializer, condition, and increment execute in the enclosing scope.
Step through an array by index with len() bound as the stop condition — the
loop condition re-evaluates every iteration, so it tracks the array length
automatically:
var items = ["a", "b", "c"]
for (var i = 0; i < len(items); i++) {
print(i, "-", items[i])
}
// 0 - a
// 1 - b
// 2 - c
Foreach (for with colon and foreach)
Destructured form (colon-only — the comma form is not supported):
var arr = [10, 20, 30]
for (val : arr) {
print(val)
}
var obj = {a: 1, b: 2}
for (pair : obj) {
print(pair.key, pair.value) // single variable: {key, value} pair
}
for ([k, v] : obj) {
print(k, v) // destructured: key string, value
}
for ([i, v] : arr) {
print(i, v) // arrays: index, element
}
The loop variable(s) are available only inside the body. If the header
starts with [, it must be exactly [key, value]; a bracketed header
without a colon reports a clear error (Expected : after [key, value] in foreach).
Foreach keyword
foreach iterates with the implicit element bound to each:
foreach(arr) {
print(each)
}
foreach(obj) {
print(each.key, each.value)
}
each is reserved — it can be read but never assigned, shadowed, or used
as a parameter name. for (each : arr) is rejected.
Switch
There is no implicit fall-through between separate cases — the switch exits
after the matching case's body runs, so break is conventional but optional:
var x = 2
switch (x) {
1: print("one")
2: print("two")
default: print("other")
}
Case values are expressions: numbers, strings, chars, true/false/nil,
negated values, and parenthesized expressions like (a + b). String
literals (' "str" '), chars, and multi-token expressions such as
(a + b): are all valid.
Fall-through cases
A case list separated by colons — 1:2:3: — is a set of fall-through
cases: execution falls through the listed case labels into a single shared
body. It is a shorthand for giving several values the same handling:
var x = 2
switch (x) {
1:2:3: print("low") break
4:5:6: print("high") break
default: print("other") break
}
Break and continue
continue and break jump control within a loop. The difference is how far
the jump is and what it resumes:
continue— leave the current iteration and start the next one. The rest of the loop body is skipped, but the loop itself keeps running.break— leave the whole loop (the enclosing block) entirely and resume execution right after it.
var i = 0
while (true) {
i++
if (i < 5) { continue } // skip the rest of this iteration, loop again
if (i >= 7) { break } // exit the loop completely
print(i)
}
// prints 5, 6 (i==1..4 hit continue first; i==7 breaks before printing)
This works identically in for, foreach, and for ... : loops — they all
behave the same way inside their body. Another way to think about it:
continue skips to the loop's next check, while break steps right out of
the loop block and lands on whatever comes after it.
While semantics and cooperative yielding
The condition is evaluated before each iteration (a while body may run
zero times). Use break to leave early and continue to skip one iteration.
Busy loops don't lock the engine
Scrii execution is cooperative: each fiber voluntarily yields to the
scheduler at regular points — roughly every 64 statements, at
std.async.sleep, and at std.async.await. So even a loop whose body never
calls any async function, like
var n = 0
while (true) { n++ }
does not permanently lock the engine. Every 64 statements it hands
control back to the scheduler, so concurrently spawned tasks (std.async
fibers) and other workers keep making progress. The loop simply runs as soon
as it gets a timeslice again.
There is a cost, though: a CPU-bound while (true) spins at full speed, so
it's almost never the right tool. Prefer breaking out of a counted loop, or
waiting explicitly with std.async.sleep(ms) when you want to pause without
burning the CPU. A bare infinite loop is mainly useful as a background
worker that does a small amount of work per iteration and then yields — for
example a poll loop that sleeps between checks instead of spinning.