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: Variables declared in if/while conditions.global print-- Basic if-assignment: variable declared inlineif x = 42 then print("x is truthy:", x)end-- Falsy values (nil, false) fail the conditionif y = nil then print("This won't print")else print("y was nil, so we're in else")end-- Multiple variables: ALL must be truthyif a, b, c = 1, 2, 3 then print("All truthy:", a, b, c)endif a, b, c = 1, nil, 3 then print("This won't print - b is nil")else print("One was falsy!")end-- Elseif chains - variables accumulateif 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 checkinglocal function divide(a, b) if b == 0 then return nil end return a / bendif result = divide(10, 2) then print("10 / 2 =", result)endif result = divide(10, 0) then print("This won't print")else print("Division by zero!")end-- While-assignment: loop until falsylocal count = 5local function countdown() if count > 0 then count = count - 1 return count + 1 end return nilendprint("Countdown:")while n = countdown() do print(" ", n)end-- Const attribute in conditionsif pi <const> = 3.14159 then print("Pi is", pi) -- pi = 0 -- Would error: attempt to assign to const variableend
-- 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 errorlocal ok, err = catch error("Something went wrong!")print("Error caught:", ok, err) --> nil Something went wrong!-- Practical: safe number parsinglocal function parse_number(str) local n = tonumber(str) if not n then error("Invalid number: " .. str) end return nendif ok, num = catch parse_number("42") then print("Parsed successfully:", num)endif ok, num = catch parse_number("hello") then print("Parsed:", num)else print("Failed to parse:", num) -- num contains the errorend-- Catch in condition: clean error handlinglocal function risky_divide(a, b) if b == 0 then error("Division by zero") end return a / bendif success, result = catch risky_divide(10, 2) then print("10 / 2 =", result)endif success, result = catch risky_divide(10, 0) then print("Result:", result)else print("Error:", result)end-- Multiple operations with catchlocal 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) endend-- Nested catch for complex error handlinglocal function complex_operation(x) if x < 0 then error("Negative input") end if x > 100 then error("Input too large") end return x * 2endlocal function safe_wrapper(x) if ok, result = catch complex_operation(x) then return result else return 0 -- Default on error endendprint("\nSafe wrapper:")print(" safe_wrapper(10):", safe_wrapper(10)) --> 20print(" safe_wrapper(-5):", safe_wrapper(-5)) --> 0print(" 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 partlocal 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 msgendlocal ok, err = catch[simplify] error("Something went wrong!")print("Simplified error:", err) --> Something went wrong!-- Logging handler: log errors and pass them throughlocal function logged(err) print("[ERROR LOG]", err) return errendlocal ok, result = catch[logged] error("Database connection failed")print("Caught after logging:", ok, result ~= nil)-- Error normalization: convert all errors to a standard formatlocal 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) } endendlocal 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 errorlocal handler_called = falselocal function track_handler(err) handler_called = true return errendlocal ok, val = catch[track_handler] (100 + 200)print("Success case - handler called?", handler_called) --> falseprint("Success value:", val) --> 300-- Inline anonymous handlerlocal 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 translationlocal 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"endlocal ok, friendly = catch[translate] risky_divide(10, 0)print("User-friendly message:", friendly)
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, printlocal csv = [[city,quarter,revenueParis,Q1,1200Paris,Q2,1450London,Q1,980London,Q2,1330Berlin,Q1,1110Berlin,Q2,1080]]-- fromcsv returns rows of fields; row 1 is the headerlocal rows = fromcsv(csv)local sales = {}for i = 2, #rows do sales[#sales + 1] = { city = rows[i][1], revenue = tonumber(rows[i][3]), }endlocal 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
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 scopelocal radius = 5local area = do local pi = 3.14159 provide pi * radius * radiusendprint("Circle area:", area) --> ~78.54-- Configuration building with intermediate stepslocal config = do local base_port = 8000 local env_offset = 80 -- e.g., from environment provide { port = base_port + env_offset, host = "localhost", debug = true }endprint("Server config:", config.host .. ":" .. config.port)-- Conditional initialization with providelocal score = 750local level = do if score >= 1000 then provide "expert" elseif score >= 500 then provide "intermediate" elseif score >= 100 then provide "beginner" else provide "novice" endendprint("Level for", score .. ":", level) --> intermediate-- Multi-value provide for parallel assignmentlocal 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, hiendprint("Min/Max:", min_val, max_val) --> 1 9-- Do expressions in arithmeticlocal base = 100local adjusted = base + do local modifier = 0.15 provide base * modifierendprint("Adjusted value:", adjusted) --> 115-- Nested do expressionslocal result = do local outer = 10 local inner = do provide outer * 2 end provide inner + 5endprint("Nested result:", result) --> 25-- Do expression as function argumentlocal function greet(msg) print("Greeting:", msg)endlocal hour = 14 -- 2 PMgreet(do if hour < 12 then provide "Good morning!" elseif hour < 18 then provide "Good afternoon!" else provide "Good evening!" endend)-- Practical: building a formatted messagelocal user = { name = "Alice", items = 3 }local message = do local prefix = "[INFO]" local content = user.name .. " has " .. user.items .. " items" provide prefix .. " " .. contentendprint(message)
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)`)
-- From Destructuring: Extract table fields into local variables.global print-- Basic destructuring: field names become variable nameslocal point = {x = 10, y = 20, z = 30}local x, y, z from pointprint("Extracted:", x, y, z) --> 10 20 30-- Works with any tablelocal config = { host = "localhost", port = 8080, debug = true}local host, port, debug from configprint("Config:", host, port, debug)-- Missing fields become nillocal partial = {a = 1, c = 3}local a, b, c from partialprint("Partial:", a, b, c) --> 1 nil 3-- Combine with const for immutable bindingslocal settings = {version = "1.0", name = "MyApp"}local version <const>, name <const> from settingsprint("App:", name, "v" .. version)-- version = "2.0" -- Would error: attempt to assign to const-- Nested tableslocal user = { name = "Alice", profile = { age = 30, city = "NYC" }}local name, profile from userlocal age, city from profileprint("User:", name, age, city)-- Practical example: function returning structured datalocal function get_result() return { success = true, data = "Hello, World!", timestamp = 1234567890 }endlocal success, data, timestamp from get_result()if success then print("Got data:", data, "at", timestamp)end-- Combine with if-assignmentlocal response = {ok = true, body = "Response body"}if ok, body from response then print("Response:", body)end
Structured bindings that unpack into individual variables.
-- Groups: Structured bindings that unpack into individual variables.global print-- Define a group with named fieldslocal point <group> = { x = 10, y = 20, z = 30}-- Fields are accessed like table members, but they're real localsprint("Point:", point.x, point.y, point.z)-- Groups can have attributes on their fieldslocal config <group> = { host <const> = "localhost", -- immutable field port = 8080, debug = true}print("Server:", config.host .. ":" .. config.port)-- Multiple groups in one declarationlocal a <group>, b <group> = {val = 1}, {val = 2}print("Two groups:", a.val, b.val)-- Nested groups for hierarchical datalocal 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 conditionsif result <group> = {ok = true, value = 42} then print("Result:", result.ok, result.value)end-- Deconstruct group fields with 'from'local name from playerprint("Extracted name:", name)-- 'from' with if-conditionif name from player then print("Player name:", name)end
-- Simple HTTP server in Lus.global network, pledge, printpledge("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) endend
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]+"))`)
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 assignmentslocal function traced(name, value) print("[TRACE]", name, "=", value) return valueendlocal counter <traced> = 0counter = counter + 1counter = counter + 1print("Final counter:", counter)print()-- Type enforcement: ensure variable is always a numberlocal function number_only(name, value) if type(value) ~= "number" then error(name .. " must be a number, got " .. type(value)) end return valueendlocal score <number_only> = 100score = score + 50print("Score:", score)print()-- Range clamping: keep values within boundslocal function clamped(min, max) return function(name, value) if value < min then return min end if value > max then return max end return value endendlocal volume <clamped(0, 100)> = 50print("Initial volume:", volume)volume = 150-- gets clamped to 100print("After setting to 150:", volume)volume = -20-- gets clamped to 0print("After setting to -20:", volume)print()-- Value transformation: round to integerlocal function rounded(name, value) return math.floor(value + 0.5)endlocal pixels <rounded> = 10.7print("Rounded pixels:", pixels)-- > 11pixels = 5.2print("Rounded again:", pixels)-- > 5print()-- Multiple attributes: chain transformationslocal function positive(name, value) if value < 0 then return -value end -- make positive return valueendlocal distance <positive, rounded> = -15.7print("Positive and rounded:", distance)-- > 16print()-- Practical: observable state with change notificationlocal observers = {}local function observable(name, value) if observers[name] then for _, callback in pairs(observers[name]) do callback(name, value) end end return valueendlocal function on_change(var_name, callback) observers[var_name] = observers[var_name] or {} observers[var_name][#observers[var_name] + 1] = callbackend-- Set up observer before creating the variableon_change("health", function(name, val) if val <= 20 then print("WARNING: " .. name .. " is critically low!") endend)local health <observable> = 100health = 50health = 15-- triggers warningprint("Health:", health)
-- String Interpolation: Build strings with embedded expressions.global print, setmetatable, os, math, string, tostring-- Basic variable interpolation with $namelocal name = "Alice"local age = 30print(`Hello, $name! You are $age years old.`)-- Expression interpolation with $(expr)local items = 5local price = 19.99print(`Total: $(string.format("%.2f", items * price)) for $items items`)-- Conditional expressionslocal score = 85print(`Grade: $(score >= 90 and "A" or score >= 80 and "B" or "C")`)-- Math expressionslocal radius = 5print(`Circle area: $(math.pi * radius * radius)`)-- Table field accesslocal user = { name = "Bob", role = "admin" }print(`User $(user.name) has role: $(user.role)`)-- Function calls in interpolationlocal function greet(n) return "Hello, " .. nendprint(`Message: $(greet("World"))`)-- Multi-line templateslocal report = `============================= User Report=============================Name: $nameAge: $ageItems: $itemsStatus: $(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 __tostringlocal Point = {}Point.__index = PointPoint.__tostring = function(self) return "(" .. self.x .. ", " .. self.y .. ")"endlocal function new_point(x, y) return setmetatable({ x = x, y = y }, Point)endlocal 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 interpolationlocal first = "John"local last = "Doe"local full = `$first $last`print(`Full name is: $full`)
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, printpledge("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, ipairslocal args = ...for _, n in ipairs(args.numbers) do worker.message(n * n)endworker.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`)endfs.remove("square-worker.lus")