Control Flow

Luwu's if statements and loops will look familiar if you've written Python, JavaScript or C. A block opens with a keyword like then or do and closes with end instead of braces or indentation.

Variables

local defines a variable that can be reassigned and const defines a binding that cannot be reassigned.

Input
local lives = 3 -- a local can be reassigned
lives -= 1
const name = "Ada" -- a const can't
print(`{name} has {lives} lives left`)
Output
Ada has 2 lives left

A string in backticks is an interpolated string. Each {...} inside it is replaced by the value of the expression between the braces.

if statements

An if statement runs its block when the condition is true. elseif adds more conditions that are checked in order, and else runs when none of them matched.

Input
const score = 72
if score >= 90 then
    print("A")
elseif score >= 70 then
    print("B")
else
    print("Keep practicing")
end
Output
B

Values are compared with ==, <, >, <= and >=. Not equal is written ~= rather than !=. Conditions combine with the keywords and, or and not.

Truthiness

A condition doesn't have to be a boolean. false, nil and none count as false and every other value counts as true. That includes 0 and the empty string "" even though both count as false in Python and JavaScript.

Input
if 0 then
    print("0 counts as true")
end
if "" then
    print("so does an empty string")
end
Output
0 counts as true
so does an empty string

and and or give back one of their operands instead of true or false. a or b is a when a is truthy and b otherwise. That makes or the usual way to fall back to a default when a value is missing:

Input
const nicknames: { [string]: string? } = { ada = "Countess" }
print(nicknames.ada or "stranger")
print(nicknames.grace or "stranger")
Output
Countess
stranger

or return leaves the function when the value is falsy. or break and or continue do the same for loops:

Input
const prices: { [string]: number? } = { apple = 1.25 }

const function price_of(fruit: string): string
    const price = prices[fruit] or return `{fruit} isn't sold here`
    return `{fruit} costs {price}`
end

print(price_of("apple"))
print(price_of("kiwi"))
Output
apple costs 1.25
kiwi isn't sold here

if expressions

An if statement runs statements. When you only need to pick a value, use an if expression. An if expression must always evaluate to a value. That's why it always has an else branch. Unlike an if statement, it doesn't end with end:

Input
const health = 25
const status = if health > 50 then "healthy" elseif health > 0 then "hurt" else "down"
print(status)
Output
hurt

Loops

Counting with for

A numeric for loop counts from a start number to an end number with both ends included. An optional third number is the step, and a negative step counts down.

Input
for i = 1, 3 do
    print(i)
end
for i = 10, 0, -5 do
    print(i)
end
Output
1
2
3
10
5
0

Looping over a table

To visit every item in a table, write the table after in. Each pass gives you a key and its value. In an array the key is the item's index and the first index is 1, not 0:

Input
const fruits = { "apple", "kiwi", "pear" }
for index, fruit in fruits do
    print(`{index}: {fruit}`)
end
Output
1: apple
2: kiwi
3: pear

Name a variable _ when you don't need it, as in for _, fruit in fruits do. You'll often see ipairs(fruits) or pairs(fruits) after in in Lua code. Both are deprecated in Luwu, and Tables explains how they differ from the plain loop.

while and repeat

A while loop checks its condition before each pass and stops once it's false:

Input
local countdown = 3
while countdown > 0 do
    print(countdown)
    countdown -= 1
end
print("liftoff")
Output
3
2
1
liftoff

repeat ... until runs its body first and checks the condition afterwards. The body always runs at least once and the condition can read locals declared in the body:

Input
const rolls = { 2, 5, 6, 3 }
local tries = 0
repeat
    tries += 1
    const roll = rolls[tries]
until roll == 6
print(`rolled a 6 on try {tries}`)
Output
rolled a 6 on try 3

break and continue

break leaves the loop right away. continue skips the rest of this pass and moves on to the next one. Lua doesn't have continue but Luau and Luwu do.

Input
for i = 1, 10 do
    if i % 2 == 0 then
        continue
    end
    if i > 7 then
        break
    end
    print(i)
end
Output
1
3
5
7

Scope

Luwu is lexically scoped. Where a variable can be used depends on where it's written in the source code and not on how the program runs. A local exists from the line that declares it to the end of its block, and the body of an if, a loop or a function is a block. Code inside a block can use the locals of every block around it:

Input
const total = 12
if total > 10 then
    const message = "that's a lot"
    print(message)
end
-- `message` doesn't exist out here
Output
that's a lot

Going further

Expressive Control Flow picks up where this page leaves off with if const, when chains, do expressions and or return. They let you get a value, check it and decide what to do with it in a single expression.