lus

Examples

Short, idiomatic Lus programs. Each one is self-contained; examples marked playground run in your browser, the rest run locally because they need real sockets, files, or threads. unstable examples need the unstable release channel.

Assignment Conditions #

conditions.lus playground

Variables declared in if/while conditions.

-- Assignment Conditions: Variables declared in if/while conditions.

global print

-- Basic if-assignment: variable declared inline
if x = 42 then
    print("x is truthy:", x)
end

-- Falsy values (nil, false) fail the condition
if y = nil then
    print("This won't print")
else
    print("y was nil, so we're in else")
end

-- Multiple variables: ALL must be truthy
if a, b, c = 1, 2, 3 then
    print("All truthy:", a, b, c)
end

if a, b, c = 1, nil, 3 then
    print("This won't print - b is nil")
else
    print("One was falsy!")
end

-- Elseif chains - variables accumulate
if first = nil then
    print("first")
elseif second = nil then
    print("second")  
elseif third = "winner" then
    -- We can see first and second here too!
    print("third:", third, "first:", first, "second:", second)
end

-- Practical example: result checking
local function divide(a, b)
    if b == 0 then return nil end
    return a / b
end

if result = divide(10, 2) then
    print("10 / 2 =", result)
end

if result = divide(10, 0) then
    print("This won't print")
else
    print("Division by zero!")
end

-- While-assignment: loop until falsy
local count = 5
local function countdown()
    if count > 0 then
        count = count - 1
        return count + 1
    end
    return nil
end

print("Countdown:")
while n = countdown() do
    print("  ", n)
end

-- Const attribute in conditions
if pi <const> = 3.14159 then
    print("Pi is", pi)
    -- pi = 0  -- Would error: attempt to assign to const variable
end

Catch Expression #

catch.lus playground

Handle errors without pcall/xpcall boilerplate.

-- Catch Expression: Handle errors without pcall/xpcall boilerplate.

global print, error, tonumber, type, pairs, tostring

-- Basic catch: returns (ok, result) or (nil, error_message)
local ok, value = catch (1 + 2)
print("1 + 2:", ok, value)  --> true    3

-- Catch an error
local ok, err = catch error("Something went wrong!")
print("Error caught:", ok, err)  --> nil    Something went wrong!

-- Practical: safe number parsing
local function parse_number(str)
    local n = tonumber(str)
    if not n then error("Invalid number: " .. str) end
    return n
end

if ok, num = catch parse_number("42") then
    print("Parsed successfully:", num)
end

if ok, num = catch parse_number("hello") then
    print("Parsed:", num)
else
    print("Failed to parse:", num)  -- num contains the error
end

-- Catch in condition: clean error handling
local function risky_divide(a, b)
    if b == 0 then error("Division by zero") end
    return a / b
end

if success, result = catch risky_divide(10, 2) then
    print("10 / 2 =", result)
end

if success, result = catch risky_divide(10, 0) then
    print("Result:", result)
else
    print("Error:", result)
end

-- Multiple operations with catch
local operations = {
    {10, 2},
    {20, 0},
    {15, 3},
    {8, 0},
    {100, 4}
}

print("\nBatch operations:")
for i, op in pairs(operations) do
    if ok, result = catch risky_divide(op[1], op[2]) then
        print("  ", op[1], "/", op[2], "=", result)
    else
        print("  ", op[1], "/", op[2], "-> ERROR:", result)
    end
end

-- Nested catch for complex error handling
local function complex_operation(x)
    if x < 0 then error("Negative input") end
    if x > 100 then error("Input too large") end
    return x * 2
end

local function safe_wrapper(x)
    if ok, result = catch complex_operation(x) then
        return result
    else
        return 0  -- Default on error
    end
end

print("\nSafe wrapper:")
print("  safe_wrapper(10):", safe_wrapper(10))   --> 20
print("  safe_wrapper(-5):", safe_wrapper(-5))   --> 0
print("  safe_wrapper(200):", safe_wrapper(200)) --> 0

-- =============================================================================
-- Catch Handlers: Transform errors inline (Acquis 20)
-- =============================================================================

print("\n--- Catch Handlers ---")

-- Simplify error messages by extracting just the message part
local function simplify(err)
    -- Error format: "file:line: message\nstack traceback:..."
    -- Use gsub to remove everything from newline onwards, then extract message
    local first_line = err:gsub("\n.*", "")
    local msg = first_line:match("^[^:]+:%d+: (.+)") or first_line
    return msg
end

local ok, err = catch[simplify] error("Something went wrong!")
print("Simplified error:", err)  --> Something went wrong!

-- Logging handler: log errors and pass them through
local function logged(err)
    print("[ERROR LOG]", err)
    return err
end

local ok, result = catch[logged] error("Database connection failed")
print("Caught after logging:", ok, result ~= nil)

-- Error normalization: convert all errors to a standard format
local function normalize(err)
    if type(err) == "string" then
        local first = err:gsub("\n.*", "")
        local msg = first:match("^[^:]+:%d+: (.+)") or first
        return { code = "ERR", message = msg }
    elseif type(err) == "table" then
        return err
    else
        return { code = "UNKNOWN", message = tostring(err) }
    end
end

local ok, err = catch[normalize] error("File not found")
if not ok then
    print("Normalized error:", err.code, "-", err.message)
end

-- Handler with success: handler only called on error
local handler_called = false
local function track_handler(err)
    handler_called = true
    return err
end

local ok, val = catch[track_handler] (100 + 200)
print("Success case - handler called?", handler_called)  --> false
print("Success value:", val)  --> 300

-- Inline anonymous handler
local ok, err = catch[function(e) 
    local first = e:gsub("\n.*", "")
    local msg = first:match("^[^:]+:%d+: (.+)") or first
    return msg:upper() 
end] error("lowercase error")
print("Uppercased error:", err)  --> LOWERCASE ERROR

-- Practical: user-friendly error translation
local translations = {
    ["Division by zero"] = "Cannot divide by zero",
    ["Negative input"] = "Please enter a positive number",
}

local function translate(err)
    -- Extract just the message part before looking up
    local first = err:gsub("\n.*", "")
    local msg = first:match("^[^:]+:%d+: (.+)") or first
    for pattern, message in pairs(translations) do
        if msg:find(pattern, 1, true) then  -- plain match
            return message
        end
    end
    return "An unexpected error occurred"
end

local ok, friendly = catch[translate] risky_divide(10, 0)
print("User-friendly message:", friendly)

CSV Statistics #

csv-stats.lus playground unstable

Parse CSV data and crunch it with the table library.

-- CSV Statistics: Parse CSV data and crunch it with the table library.
global fromcsv, table, tonumber, ipairs, pairs, print

local csv = [[city,quarter,revenue
Paris,Q1,1200
Paris,Q2,1450
London,Q1,980
London,Q2,1330
Berlin,Q1,1110
Berlin,Q2,1080]]

-- fromcsv returns rows of fields; row 1 is the header
local rows = fromcsv(csv)
local sales = {}
for i = 2, #rows do
    sales[#sales + 1] = {
        city = rows[i][1],
        revenue = tonumber(rows[i][3]),
    }
end

local by_city = table.groupby(sales, function(s) return s.city end)

for city, entries in pairs(by_city) do
    local revenues = table.map(entries, function(s) return s.revenue end)
    print(`$city: total $(table.sum(revenues)), mean $(table.mean(revenues))`)
end

Do Expressions #

doexpr.lus playground unstable

Inline blocks that evaluate to a value via `provide`.

-- Do Expressions: Inline blocks that evaluate to a value via `provide`.

global print, math, pairs

-- Basic do expression: compute a value with local scope
local radius = 5
local area = do
    local pi = 3.14159
    provide pi * radius * radius
end
print("Circle area:", area)  --> ~78.54

-- Configuration building with intermediate steps
local config = do
    local base_port = 8000
    local env_offset = 80  -- e.g., from environment
    provide {
        port = base_port + env_offset,
        host = "localhost",
        debug = true
    }
end
print("Server config:", config.host .. ":" .. config.port)

-- Conditional initialization with provide
local score = 750
local level = do
    if score >= 1000 then
        provide "expert"
    elseif score >= 500 then
        provide "intermediate"
    elseif score >= 100 then
        provide "beginner"
    else
        provide "novice"
    end
end
print("Level for", score .. ":", level)  --> intermediate

-- Multi-value provide for parallel assignment
local data = {3, 1, 4, 1, 5, 9, 2, 6}
local min_val, max_val = do
    local lo, hi = math.huge, -math.huge
    for _, v in pairs(data) do
        if v < lo then lo = v end
        if v > hi then hi = v end
    end
    provide lo, hi
end
print("Min/Max:", min_val, max_val)  --> 1    9

-- Do expressions in arithmetic
local base = 100
local adjusted = base + do
    local modifier = 0.15
    provide base * modifier
end
print("Adjusted value:", adjusted)  --> 115

-- Nested do expressions
local result = do
    local outer = 10
    local inner = do
        provide outer * 2
    end
    provide inner + 5
end
print("Nested result:", result)  --> 25

-- Do expression as function argument
local function greet(msg)
    print("Greeting:", msg)
end

local hour = 14  -- 2 PM
greet(do
    if hour < 12 then
        provide "Good morning!"
    elseif hour < 18 then
        provide "Good afternoon!"
    else
        provide "Good evening!"
    end
end)

-- Practical: building a formatted message
local user = { name = "Alice", items = 3 }
local message = do
    local prefix = "[INFO]"
    local content = user.name .. " has " .. user.items .. " items"
    provide prefix .. " " .. content
end
print(message)

Fetch and JSON #

fetch-json.lus

Call an HTTP API, parse the response, write a report.

-- Fetch and JSON: Call an HTTP API, parse the response, write a report.
global network, fromjson, tojson, io, pledge, print

-- Declare exactly what this script may touch, then lock it in.
pledge("network:http", "fs:write", "seal")

local status, body = network.fetch("https://api.github.com/repos/lus-lang/lus")
local repo = fromjson(body)

local report = {
    name = repo.full_name,
    stars = repo.stargazers_count,
    issues = repo.open_issues_count,
}

local out = io.open("repo.json", "w")
out:write(tojson(report))
out:close()
print(`saved $(repo.full_name) ($status)`)
runs locally — needs real network, files, or threads

From Destructuring #

from.lus playground

Extract table fields into local variables.

-- From Destructuring: Extract table fields into local variables.

global print

-- Basic destructuring: field names become variable names
local point = {x = 10, y = 20, z = 30}
local x, y, z from point
print("Extracted:", x, y, z)  --> 10    20    30

-- Works with any table
local config = {
    host = "localhost",
    port = 8080,
    debug = true
}
local host, port, debug from config
print("Config:", host, port, debug)

-- Missing fields become nil
local partial = {a = 1, c = 3}
local a, b, c from partial
print("Partial:", a, b, c)  --> 1    nil    3

-- Combine with const for immutable bindings
local settings = {version = "1.0", name = "MyApp"}
local version <const>, name <const> from settings
print("App:", name, "v" .. version)
-- version = "2.0"  -- Would error: attempt to assign to const

-- Nested tables
local user = {
    name = "Alice",
    profile = {
        age = 30,
        city = "NYC"
    }
}
local name, profile from user
local age, city from profile
print("User:", name, age, city)

-- Practical example: function returning structured data
local function get_result()
    return {
        success = true,
        data = "Hello, World!",
        timestamp = 1234567890
    }
end

local success, data, timestamp from get_result()
if success then
    print("Got data:", data, "at", timestamp)
end

-- Combine with if-assignment
local response = {ok = true, body = "Response body"}
if ok, body from response then
    print("Response:", body)
end

Groups #

groups.lus playground

Structured bindings that unpack into individual variables.

-- Groups: Structured bindings that unpack into individual variables.

global print

-- Define a group with named fields
local point <group> = {
    x = 10,
    y = 20,
    z = 30
}

-- Fields are accessed like table members, but they're real locals
print("Point:", point.x, point.y, point.z)

-- Groups can have attributes on their fields
local config <group> = {
    host <const> = "localhost",  -- immutable field
    port = 8080,
    debug = true
}

print("Server:", config.host .. ":" .. config.port)

-- Multiple groups in one declaration
local a <group>, b <group> = {val = 1}, {val = 2}
print("Two groups:", a.val, b.val)

-- Nested groups for hierarchical data
local player <group> = {
    name = "Hero",
    stats <group> = {
        health = 100,
        mana = 50
    },
    position <group> = {
        x = 0,
        y = 0
    }
}

print("Player:", player.name)
print("Stats:", player.stats.health, "HP,", player.stats.mana, "MP")
print("Position:", player.position.x, player.position.y)

-- Groups in conditions
if result <group> = {ok = true, value = 42} then
    print("Result:", result.ok, result.value)
end

-- Deconstruct group fields with 'from'
local name from player
print("Extracted name:", name)

-- 'from' with if-condition
if name from player then
    print("Player name:", name)
end

http #

http.lus
-- Simple HTTP server in Lus.

global network, pledge, print

pledge("network", "seal")

local server = network.tcp.bind("0.0.0.0", 8080)
print("HTTP server listening on http://localhost:8080")

while client <close> = server:accept() do
    local request_line = client:receive("*l")
    if request_line then
        print("Request: " .. request_line)
        repeat
            local header = client:receive("*l")
        until not header or header == ""
        
        local body = "<h1>Hello world from Lus!</h1>"
        local response = "HTTP/1.1 200 OK\r\n"
            .. "Content-Type: text/html\r\n"
            .. "Content-Length: " .. #body .. "\r\n"
            .. "Connection: close\r\n"
            .. "\r\n"
            .. body
        
        client:send(response)
    end
end
runs locally — needs real network, files, or threads

Optional Chaining #

optchain.lus playground

Safe navigation through potentially nil values.

-- Optional Chaining: Safe navigation through potentially nil values.

global print, pairs

-- Deep config access without crashes
local config = {
    database = {
        primary = { host = "db1.local", port = 5432 },
        -- backup is not defined
    },
    cache = {
        enabled = true,
        ttl = 3600
    }
}

print("Primary host:", config?.database?.primary?.host)  --> db1.local
print("Backup host:", config?.database?.backup?.host)    --> nil (no error)
print("Cache TTL:", config?.cache?.ttl)                  --> 3600
print("Missing path:", config?.logging?.level?.name)    --> nil (no error)

-- Defaults with 'or'
local timeout = config?.network?.timeout or 30
local log_level = config?.logging?.level or "info"
print("Timeout:", timeout)     --> 30
print("Log level:", log_level) --> info

-- Safe method calls
local logger = {
    log = function(self, level, msg)
        print("[" .. level .. "]", msg)
    end
}

local _ = logger?:log("INFO", "Application started")  --> [INFO] Application started

local missing_logger = nil
_ = missing_logger?:log("ERROR", "This won't crash")  --> nothing happens
print("After missing logger call")

-- Safe function calls
local handlers = {
    on_success = function() print("Success!") end,
    -- on_error is not defined
}

_ = handlers.on_success?()  --> Success!
_ = handlers.on_error?()    --> nothing happens (nil, no error)
print("Handlers called safely")

-- Array access
local users = {
    { name = "Alice", profile = { email = "alice@example.com" } },
    { name = "Bob" },  -- no profile
    -- third user doesn't exist
}

print("User 1 email:", users?[1]?.profile?.email)  --> alice@example.com
print("User 2 email:", users?[2]?.profile?.email)  --> nil
print("User 3 email:", users?[3]?.profile?.email)  --> nil

-- Practical: API response handling
local function fetch_user(id)
    if id == 1 then
        return {
            data = {
                user = { name = "Alice", settings = { theme = "dark" } }
            }
        }
    end
    return nil  -- user not found
end

local response = fetch_user(1)
local theme = response?.data?.user?.settings?.theme or "light"
print("User 1 theme:", theme)  --> dark

local response2 = fetch_user(999)
local theme2 = response2?.data?.user?.settings?.theme or "light"
print("User 999 theme:", theme2)  --> light (default)

-- Chaining with method calls and indexing
local app = {
    services = {
        auth = {
            get_user = function(self)
                return { id = 1, name = "Admin" }
            end
        }
    }
}

local user_name = app?.services?.auth?:get_user()?.name
print("Auth user:", user_name)  --> Admin

local missing_service = app?.services?.payments?:process()?.result
print("Missing service result:", missing_service)  --> nil

Pledge Sandbox #

pledge-sandbox.lus

Deny-by-default permissions, enforced by the runtime.

-- Pledge Sandbox: Deny-by-default permissions, enforced by the runtime.
global os, string, pledge, print

-- Grant nothing but the permission system itself, permanently deny
-- command execution, and seal so nothing can be granted later.
pledge("~exec")
pledge("seal")

-- Anything the script never pledged is refused at the call site —
-- and the refusal is an ordinary error you can catch.
local ok, err = catch os.execute("rm -rf /")
print(ok and "uh oh" or `blocked: $(string.match(err, "[^\n]+"))`)

local ok2, err2 = catch os.getenv("SECRET_TOKEN")
print(ok2 and "uh oh" or `blocked: $(string.match(err2, "[^\n]+"))`)
runs locally — needs real network, files, or threads

Runtime Attributes #

attrs.lus playground unstable

Hook into variable assignment for validation and observation.

-- Runtime Attributes: Hook into variable assignment for validation and observation.

global print, type, math, error, tostring, pairs

-- Basic attribute: log all assignments
local function traced(name, value)
    print("[TRACE]", name, "=", value)
    return value
end

local counter <traced> = 0
counter = counter + 1
counter = counter + 1
print("Final counter:", counter)
print()

-- Type enforcement: ensure variable is always a number
local function number_only(name, value)
    if type(value) ~= "number" then
        error(name .. " must be a number, got " .. type(value))
    end
    return value
end

local score <number_only> = 100
score = score + 50
print("Score:", score)
print()

-- Range clamping: keep values within bounds
local function clamped(min, max)
    return function(name, value)
        if value < min then
            return min
        end
        if value > max then
            return max
        end
        return value
    end
end

local volume <clamped(0, 100)> = 50
print("Initial volume:", volume)
volume = 150
-- gets clamped to 100
print("After setting to 150:", volume)
volume = -20
-- gets clamped to 0
print("After setting to -20:", volume)
print()

-- Value transformation: round to integer
local function rounded(name, value)
    return math.floor(value + 0.5)
end

local pixels <rounded> = 10.7
print("Rounded pixels:", pixels)
-- > 11
pixels = 5.2
print("Rounded again:", pixels)
-- > 5
print()

-- Multiple attributes: chain transformations
local function positive(name, value)
    if value < 0 then
        return -value
    end
    -- make positive

    return value
end

local distance <positive, rounded> = -15.7
print("Positive and rounded:", distance)
-- > 16
print()

-- Practical: observable state with change notification
local observers = {}

local function observable(name, value)
    if observers[name] then
        for _, callback in pairs(observers[name]) do
            callback(name, value)
        end
    end
    return value
end

local function on_change(var_name, callback)
    observers[var_name] = observers[var_name] or {}
    observers[var_name][#observers[var_name] + 1] = callback
end

-- Set up observer before creating the variable
on_change("health", function(name, val)
    if val <= 20 then
        print("WARNING: " .. name .. " is critically low!")
    end
end)

local health <observable> = 100
health = 50
health = 15
-- triggers warning
print("Health:", health)

Slices #

slices.lus playground

Extract subsequences from tables, strings, and vectors.

-- Slices: Extract subsequences from tables, strings, and vectors.

global print, table, setmetatable, error

-- Basic table slicing: t[start, end]
local numbers = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}

print("Original:", table.concat(numbers, ", "))
print("numbers[2,5]:", table.concat(numbers[2,5], ", "))  --> 2, 3, 4, 5
print("numbers[7,10]:", table.concat(numbers[7,10], ", ")) --> 7, 8, 9, 10

-- Omit start (defaults to 1)
print("numbers[,3]:", table.concat(numbers[,3], ", "))  --> 1, 2, 3

-- Omit end (defaults to length)
print("numbers[8,]:", table.concat(numbers[8,], ", "))  --> 8, 9, 10

-- String slicing works the same way
local str = "Hello, World!"
print("\nString slicing:")
print("Original:", str)
print("str[1,5]:", str[1,5])   --> Hello
print("str[8,12]:", str[8,12]) --> World

-- Practical: chunk processing with a loop
local data = {10, 20, 30, 40, 50, 60, 70, 80, 90, 100}
print("\nProcessing in chunks of 3:")
local chunk_size = 3
for i = 1, #data, chunk_size do
    local chunk_end = i + chunk_size - 1
    if chunk_end > #data then chunk_end = #data end
    print("  Chunk:", table.concat(data[i, chunk_end], ", "))
end

-- Custom __slice metamethod
local mt = {
    __slice = function(self, start, finish)
        start = start or 1
        finish = finish or 10  -- default range
        local result = {}
        for i = start, finish do
            result[#result + 1] = i * i  -- squares
        end
        return result
    end
}

local squares = setmetatable({}, mt)
print("\nCustom __slice (squares):")
local sq1 = squares[1,5]
print("  squares[1,5]:", sq1[1], sq1[2], sq1[3], sq1[4], sq1[5])  --> 1 4 9 16 25
local sq2 = squares[,3]
print("  squares[,3]:", sq2[1], sq2[2], sq2[3])  --> 1 4 9

String Interpolation #

interpolation.lus playground unstable

Build strings with embedded expressions.

-- String Interpolation: Build strings with embedded expressions.

global print, setmetatable, os, math, string, tostring

-- Basic variable interpolation with $name
local name = "Alice"
local age = 30
print(`Hello, $name! You are $age years old.`)

-- Expression interpolation with $(expr)
local items = 5
local price = 19.99
print(`Total: $(string.format("%.2f", items * price)) for $items items`)

-- Conditional expressions
local score = 85
print(`Grade: $(score >= 90 and "A" or score >= 80 and "B" or "C")`)

-- Math expressions
local radius = 5
print(`Circle area: $(math.pi * radius * radius)`)

-- Table field access
local user = { name = "Bob", role = "admin" }
print(`User $(user.name) has role: $(user.role)`)

-- Function calls in interpolation
local function greet(n)
    return "Hello, " .. n
end
print(`Message: $(greet("World"))`)

-- Multi-line templates
local report = `
=============================
  User Report
=============================
Name: $name
Age: $age
Items: $items
Status: $(items > 0 and "Active" or "Inactive")
=============================`
print(report)

-- Escaping: use $$ for literal $
local price_str = `Price: $$$(items * price)`
print(price_str)
-- > Price: $99.95

-- Objects with __tostring
local Point = {}
Point.__index = Point
Point.__tostring = function(self)
    return "(" .. self.x .. ", " .. self.y .. ")"
end

local function new_point(x, y)
    return setmetatable({ x = x, y = y }, Point)
end

local p = new_point(10, 20)
print(`Point location: $p`)
-- > Point location: (10, 20)

-- Building SQL-like queries (example pattern)
local table_name = "users"
local field = "email"
local value = "test@example.com"
local query = `SELECT * FROM $table_name WHERE $field = '$value'`
print(query)

-- Nested interpolation
local first = "John"
local last = "Doe"
local full = `$first $last`
print(`Full name is: $full`)

Workers #

worker-pool.lus

Spawn an interpreter on the thread pool and stream results back.

-- Workers: Spawn an interpreter on the thread pool and stream results back.
global worker, io, fs, pledge, print

pledge("fs", "load", "seal")

-- A worker runs a script file in its own interpreter state. For a
-- self-contained example we write the worker's body out first.
local body = [[
global worker, ipairs
local args = ...
for _, n in ipairs(args.numbers) do
    worker.message(n * n)
end
worker.message("done")
]]
local f = io.open("square-worker.lus", "w")
f:write(body)
f:close()

-- Arguments are deep-copied into the worker's state.
local w = worker.create("square-worker.lus", { numbers = { 1, 2, 3, 4, 5 } })

while msg = worker.receive(w) do
    if msg == "done" then break end
    print(`square: $msg`)
end

fs.remove("square-worker.lus")