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 —
a var declared in the initializer stays visible (with its final value)
after the loop ends.
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 arr = ["a", "b", "c"]
for (var i = 0; i < len(arr); i++) {
print(i, "-", arr[i])
}
# 0 - a
# 1 - b
# 2 - c
Foreach: for with colon, and the foreach keyword
Two spellings for the same loop. Use for (name : iterable) when you want
to name the loop variable; use foreach (iterable) as shorthand when the
implicit each variable is enough:
var arr = [10, 20, 30]
for (val : arr) {
print(val) # 10, 20, 30
}
foreach (arr) {
print(each) # same thing: 10, 20, 30
}
What the loop variable holds
A single variable holds the element for arrays, but a
{key, value} pair object for objects:
var obj = {a: 1, b: 2}
for (pair : obj) {
print(pair.key, pair.value) # a 1, then b 2 (any order)
}
To bind key and value separately, destructure with a comma pair —
k, v, exactly two identifiers:
for (k, v : obj) {
print(k, v) # objects: key string, value
}
for (i, v : arr) {
print(i, v) # arrays: 0 10, 1 20, 2 30
}
Only the name and name, name headers are valid — anything else before
the colon is rejected at parse time.
The foreach keyword and each
foreach iterates with the element (or pair) bound to the implicit variable
each:
foreach (arr) {
print(each)
}
foreach (obj) {
print(each.key, each.value)
}
each is reserved — it can be read but never assigned, shadowed, declared,
or used as a parameter name, and for (each : arr) is rejected. The loop
variable(s), named or implicit, exist only inside the loop body.
Iterating anything that is not an array or object is an error
(INVALID_ITERABLE: foreach requires an array or object).
Switch
Scrii has no enumerators, so switch is built to dispatch on plain data
instead: any value — a command string, a key char, a status number, nil —
can be a case. Matching intent reads directly off the values, which keeps
dispatch tables readable without a separate enum declaration to maintain.
Cases fall through C-style: after a matching case runs, execution continues
into the next case body unless the body ends with break — so break is
needed to stop at one case:
var x = 1
switch (x) {
1: print("one")
2: print("two") break
default: print("other") break
}
# prints "one", then "two"
Case values
A case matches when its value equals the switch subject (==). Almost
anything that evaluates to a value can be a case — literals of any type,
variables, and expressions:
var cmd = "quit"
switch (cmd) {
"help": print("commands: help, quit") break
"quit": print("bye") break
default: print("unknown command") break
}
var key = 'q'
switch (key) {
'q': print("quit key") break
default: print("other key") break
}
var ready = true
switch (ready) {
true: print("go") break
false: print("wait") break
}
var missing = nil
switch (missing) {
nil: print("nothing there") break
default: print("something there") break
}
Negated values and parenthesized expressions work too, and are evaluated when
the switch runs:
var v = 5
switch (v) {
-1: print("minus one") break
(2 + 3): print("five") break
default: print("something else") break
}
# prints "five"
var target = 42
switch (target) {
target: print("self match") break
default: print("no match") break
}
# prints "self match"
default catches everything that matched nothing else. It may appear
anywhere in the switch body — first, middle, or last — and still only runs on
no match:
switch (99) {
1: print("one") break
default: print("fallback") break
2: print("two") break
}
# prints "fallback"
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.