SCUA

Manual

Control flow

SCUA branches with if and loops with while and for. Loops walk collections directly, and you count with ranges written a:b. Every block closes with end.

fn describe(n)
  if n < 0 then
    return "negative"
  elseif n == 0 then
    return "zero"
  else
    return "positive"
  end
end
print(describe(-4))
print(describe(0))
print(describe(7))

Run it:

$ scua describe.scua
negative
zero
positive

#if, elseif, else

An if tests a condition after then and runs the branch if the condition is true (see truthiness for what counts as true). You can chain alternatives with elseif and provide a fallback with else. The whole thing closes with end. Both elseif and else are optional.

#Conditional expressions

The same if … then … else … end can also produce a value, so a conditional can go anywhere an expression goes — into a binding, a return, a call argument, a string hole. There's no separate ternary operator to learn; the if you already know is the ternary.

let hp = 0
let status = if hp > 0 then "alive" else "dead" end
print(status)
print(if hp < 0 then -hp else hp end)   -- an inline "abs", no temporary needed
$ scua status.scua
dead
0

When an if is used as a value, the else is required — every path has to produce something, so there's never a silent nil. (As a plain statement, where you're branching for effect rather than for a value, else stays optional.) elseif chains work just as they do in the statement form:

fn classify(n)
  return if n < 0 then "negative"
         elseif n == 0 then "zero"
         else "positive" end
end
print(classify(-4))
print(classify(0))
print(classify(7))
$ scua classify.scua
negative
zero
positive

match is an expression too. Each arm is a single expression, and the match evaluates to the matched arm's value — handy for mapping one value to another:

fn http_label(code)
  return match code
    200 -> "OK"
    404 -> "Not Found"
    _   -> "Other"
  end
end
print(http_label(200))
print(http_label(404))
print(http_label(500))
$ scua http.scua
OK
Not Found
Other

A match expression over a closed enum is still exhaustiveness-checked, so a forgotten case is a compile error rather than a fallthrough. See Pattern matching for the full set of arm shapes — they all work in the value form, including payload binds and when guards.

Note: avoid the old cond and a or b trick for this. It quietly returns the wrong value when a is itself false or nil (both common in game data) — a value-producing if has no such trap.

#while

A while loop runs its body, between do and end, as long as the condition holds:

let n = 1
while n < 100 do
  n = n * 2
end
print(n)

Run it:

$ scua while.scua
128

#for over a collection

for v in collection do ... end walks a collection and binds each element to v in turn. Over an array you get the elements:

let colors = ["red", "green", "blue"]
for c in colors do
  print(c)
end

Run it:

$ scua for-values.scua
red
green
blue

Add a second loop variable to get the index alongside each value. Indices are 0-based:

let colors = ["red", "green", "blue"]
for i, c in colors do
  print(`{i}: {c}`)
end

Run it:

$ scua for-index.scua
0: red
1: green
2: blue

Tables iterate the same way. With one variable you get each value; with two you get the key and the value. See Collections for tables.

#Ranges: a:b

A range a:b counts from a up to but not including b. It's half-open, so 0:n gives exactly n values, 0 through n-1. This pairs cleanly with 0-based indexing.

for i in 0:5 do print(i) end

Run it:

$ scua range.scua
0
1
2
3
4

A range counts as it goes rather than building an array first, so looping over 0:n costs nothing per element beyond the loop itself. (If you do want the numbers as an actual array, the range(a, b) builtin gives you one; see Collections.)

A third component sets the step. 0:10:2 counts up by 2:

for i in 0:10:2 do print(i) end

Run it:

$ scua step.scua
0
2
4
6
8

To count down, call .reversed() on a range. It walks the same half-open set backwards, so the low bound is included and the high bound is not. That avoids the off-by-one you'd get from a bare negative step:

for t in (1:6).reversed() do print(t) end
print("go!")

Run it:

$ scua countdown.scua
5
4
3
2
1
go!

#break and continue

break exits the nearest enclosing loop immediately. Here it stops at the first multiple of 7 at or above 21:

let hit = 0
for k in 21:100 do
  if k % 7 == 0 then
    hit = k
    break
  end
end
print(hit)

Run it:

$ scua break.scua
21

continue skips to the next iteration without running the rest of the body:

for k in 0:10 do
  if k % 2 == 0 then continue end
  print(k)
end

Run it:

$ scua continue.scua
1
3
5
7
9

In nested loops, break and continue apply to the innermost loop only.