These functions are always available, with no import. They cover
printing, conversions, collections, numbers, data tables (frames),
results, and coroutines. Anything more specialized lives in a module you
bring in with import (see Modules at
the end).
A note on truthiness, since several of these depend on it: only
nil and false are falsey. Everything else,
including 0 and the empty string, is truthy.
#Core
print(...values)
Write the values to stdout, separated by spaces, followed by a newline.
print("hp", 30, "/", 40)
$ scua print.scua
hp 30 / 40
#type
type(v) -> string
The runtime type name of a value: "nil",
"bool", "int", "float",
"string", "array", "table",
"fn", "vec3", and so on.
print(type(42), type(3.5), type("x"), type([1]), type({a=1}), type(nil))
$ scua type.scua
int float string array table nil
#tostring
tostring(v) -> string
The value rendered as text, the same way print renders
it.
print(tostring(42), tostring(3.5), tostring(true))
$ scua tostring.scua
42 3.5 true
#tonumber
tonumber(s) -> int | float | nil
Parse a string as a number and return nil if it isn't
one. It is radix-aware: 0x, 0b, and
0o prefixes work. A whole number parses to an int, one with
a decimal point to a float. Passing a number returns it unchanged. (For
an exact decimal instead of a float, use
decimal.parse.)
print(tonumber("0xff"), tonumber("3.14"), tonumber("nope"))
$ scua tonumber.scua
255 3.14 nil
#assert
assert(cond, msg?)
If cond is truthy, return it. Otherwise raise a fault
carrying msg (or "assertion failed" if you give none). The
fault is catchable with try/rescue or
pcall. See Errors and
faults.
try
assert(1 + 1 == 3, "math broke")
rescue e
print(`caught: {e}`)
end
$ scua assert.scua
caught: math broke
#assert_eq / assert_ne / assert_near
assert_eq(got, want)
assert_ne(a, b)
assert_near(got, want, eps)
The comparison assertions, mainly for tests. assert_eq
faults unless got equals want (by value),
assert_ne faults if a equals b,
and assert_near faults unless
|got - want| <= eps (float-tolerant). A failing
comparison reports both values. See Test your code.
assert_eq(1 + 1, 2)
assert_ne(1, 2)
assert_near(0.1 + 0.2, 0.3, 0.001)
print("ok")
$ scua asserts.scua
ok
#error
error(value)
Raise a recoverable fault carrying value. Catch it with
try/rescue or pcall. Use this for
bugs, not for expected failures; for those, return an
Error(...) result instead.
#pcall
pcall(body) -> Result
Call body with no arguments, catching any fault. Returns
Ok(result) on success or Error(e) if
body faulted, instead of letting the fault propagate.
print(pcall(fn() return 42 end))
print(pcall(fn() error("nope") end))
$ scua pcall.scua
Ok(42)
Error(nope)
#as
as(value, T) -> T
A checked downcast. Verify at runtime that value matches
record type T's shape, and type the result as
T so the checker accepts field access on it. Faults if the
shape doesn't match.
record Point { x: int, y: int }
let p = as({ x = 1, y = 2 }, Point)
print(p.x)
$ scua as.scua
1
#project
project(value, T, audience) -> T
Build an audience-safe view of a record. Returns a deep copy of
value (record type T) keeping only the fields
the audience enum value is allowed to see — a field gated
to a viewer the audience isn't in comes back nil. Recurses
through nested records and { Record } collections, so a
gated field buried in a sub-record is stripped too. The
audience must be an enum value. See Audience gates.
enum Audience { Server, Client }
gate Owner { Audience.Server, Audience.Client }
gate Internal { Audience.Server }
record Player { name: string, secret: int gated Internal, gold: int gated Owner }
let p: Player = { name = "Aria", secret = 42, gold = 100 }
let view = project(p, Player, Audience.Client)
print(`{view.name} {view.gold} {view.secret}`)
$ scua project.scua
Aria 100 nil
#to_int
to_int(e) -> int
The integer behind a payload-free enum variant or a flags value (a plain int
passes through), for arithmetic, indexing, or a wire boundary.
Enum.from_int(n) goes the other way. The method form
e.to_int() does the same thing.
enum Suit { Hearts, Spades, Clubs = 10, Diamonds }
print(to_int(Suit.Spades), Suit.Clubs.to_int(), to_int(Suit.Diamonds))
$ scua toint.scua
1 10 11
#big
big(s) -> big
big(n) -> big
Construct a big
— the huge-magnitude number for idle/incremental games, where reach
matters and precision doesn't. A string specifier
(big("1.2e456")) parses digits straight to the value, so
magnitudes that would overflow a float to infinity are
still legal; big(5)/big(3.14) widen an int or
float. There is no numeric suffix — always construct with
big(...). Arithmetic (+ - * / **) and
comparisons are built in; a big won't mix with a
decimal (exact vs inexact is a compile error).
print(big("1.2e30"))
print(big(2) ** 100)
$ scua big.scua
1.2e30
1.26765e30
#Collections
These work on arrays, which are dense and 0-based. For the full story see Collections.
#len
len(collection) -> int
The number of elements in an array, the number of characters in a string, or the number of fields in a table.
#push
push(array, value)
Append value to array in place.
#pop
pop(array) -> any
Remove and return the last element.
#remove
remove(array, index) -> any
Remove and return the element at index (0-based),
shifting the rest down.
#contains
contains(array, value) -> bool
contains(string, sub) -> bool -- also: s.contains(sub)
Whether array holds value, or whether
string contains the substring sub (the dual
used by frame.filter(col.contains("lit")) — see Search a text
column).
#slice
slice(array, start, end) -> array
A new array of the elements in [start, end). The index
syntax array[start:end] does the same thing and clamps
out-of-range bounds.
#range
range(stop) -> array
range(start, stop) -> array
An array of ints. With one argument it counts from 0; with two it
counts [start, stop). For a loop you usually want the
allocation-free range expression
start:stop instead.
let a = [1, 2, 3]
push(a, 4)
print(a) -- [1, 2, 3, 4]
print(pop(a)) -- 4
print(remove(a, 0)) -- 1
print(a) -- [2, 3]
print(contains(a, 3)) -- true
print(len("hello")) -- 5
print(slice([10,20,30,40], 1, 3)) -- [20, 30]
print(range(5)) -- [0, 1, 2, 3, 4]
print(range(2, 6)) -- [2, 3, 4, 5]
$ scua coll.scua
[1, 2, 3, 4]
4
1
[2, 3]
true
5
[20, 30]
[0, 1, 2, 3, 4]
[2, 3, 4, 5]
#Nested data and paths
These reach into nested tables by path. A path is a
/-separated string, or a first-class path"..."
value. See the path operator for
the inline @ form.
#get
get(root, path) -> any
Read the value at path under root. Returns
nil if any segment is missing.
#getStrict
getStrict(root, path) -> any
Like get, but faults instead of returning
nil when a segment is missing.
#set
set(root, path, value)
Write value at path under
root, creating intermediate tables as needed.
let root = { a = { b = 1 } }
print(get(root, "a/b")) -- 1
print(get(root, "a/z")) -- nil
set(root, "a/c", 99)
print(get(root, "a/c")) -- 99
$ scua paths.scua
1
nil
99
#Numbers
abs, min, max,
floor, ceil, round,
clamp, and sort are always available. They
preserve subtype: an int stays an int and a float stays a float, with no
silent demotion. The full set of transcendental and shaping functions
lives in the math module.
#abs
abs(x) -> number
Absolute value.
#min / max
min(...nums) -> number
max(...nums) -> number
The smallest or largest of the arguments. Ints and floats compare by value.
#floor / ceil / round
floor(x) -> int
ceil(x) -> int
round(x) -> int
Round toward negative infinity, toward positive infinity, or to the nearest int (half away from zero). An int passes through unchanged.
#clamp
clamp(x, lo, hi) -> number
x constrained to [lo, hi].
#fill
fill(n, v) -> array
A new array of n copies of v
(n >= 0) — the common [0, 0, …] init
without a loop. Note: the copies share v if it's a
container, so for a 2D grid allocate a fresh inner array per row
(for r in 0:rows do push(grid, fill(cols, 0)) end).
print(fill(4, 0))
$ scua fill.scua
[0, 0, 0, 0]
#sort
sort(array)
Sort an array ascending in place. Numbers sort before strings; it is stable.
#reserve
reserve(array, n: int) -> array
Pre-size an array to hold at least n elements. Its
length doesn't change — this only sets capacity up front so a fill loop
doesn't re-allocate as it grows. For a large element-typed buffer
({ f64 }, { f32 }, …) the storage goes
straight to its final home. Usually called method-style; returns the
array so it chains.
let samples: { f64 } = []
samples.reserve(1000000)
for i in 0:1000000 do samples.push(1.0 * i) end
print(samples.len())
$ scua reserve.scua
1000000
print(abs(-7), min(3, 1, 2), max(1.5, 9, 2))
print(floor(3.7), ceil(3.2), round(2.5), clamp(15, 0, 10))
let scores = [42, 7, 19, 3, 88]
sort(scores)
print(scores)
$ scua numbers.scua
7 1 9
3 4 3 10
[3, 7, 19, 42, 88]
#format
format(fmt, ...args) -> string
Render fmt, filling each {} or
{:spec} hole from the arguments. It uses the same
mini-language as string interpolation. See Strings.
print(format("{} vs {} — {0} wins", "red", "blue"))
print(format("hp {}/{} ({:.0%})", 30, 40, 30.0 / 40.0))
$ scua format.scua
red vs blue — red wins
hp 30/40 (75%)
#Random numbers
The generator is a seeded value, not hidden global state. The same
seed always replays the same stream, which is what lockstep and replay
need. You thread the generator through each call explicitly. Bring it in
with import rand.
| Function | Returns |
|---|---|
rand.seed(n) |
A generator seeded from number n. |
rand.int(g, lo, hi) |
A uniform int in [lo, hi] (both ends included). |
rand.float(g) |
A uniform float in [0, 1). |
rand.range(g, lo, hi) |
A uniform float in [lo, hi). |
rand.bool(g, p?) |
True with probability p (default 0.5). |
rand.normal(g, mean?, std?) |
A Gaussian sample (default mean 0, std 1). |
rand.choice(g, array) |
A uniformly chosen element. |
rand.weighted(g, weights) |
An index chosen proportional to weights (a loot
table). |
rand.shuffle(g, array) |
Shuffle the array in place. |
rand.fork(g) |
An independent child generator, deterministically derived. |
Every drawing function advances g.
import rand
let g = rand.seed(2026)
print(rand.int(g, 1, 20), rand.int(g, 1, 20), rand.int(g, 1, 20))
print(`a float in [0,1): {rand.float(g):.3f}`)
let loot = ["sword", "shield", "potion", "gold"]
print(rand.choice(g, loot))
$ scua rand.scua
2 15 7
a float in [0,1): 0.792
gold
#Frames (data tables)
A frame is a small columnar data table — named columns
you total, sort, group, and reshape, the way you would a spreadsheet or
a database query. Build one with no import and query it with method
chains. The headline is exact money: a decimal column
totals and groups exactly, where a float-first tool
would drift. For the full story see Data tables.
#frame
frame(columns) -> frame
Build a frame from a table of { name = array, … } —
every column the same length. When you write the columns as a literal,
the column names become known at compile time, so a mistyped column
anywhere downstream is a compile error with a "did you mean" suggestion.
Frames whose columns aren't known up front (a frame
argument, or one from
csv/join/pivot/a host) are
checked at runtime instead.
#csv
csv(text) -> frame
Parse CSV text into a frame: the first line names the columns, each
later line is a row. Cells are typed per value — a whole number becomes
an int, a decimal-point number an exact
decimal (money stays exact), anything else a string, and an
empty cell nil. Quoted fields with embedded commas are
handled (a quoted field may not span multiple lines).
#Operations
Each reads on the frame and returns a new frame (or
a value) — nothing is mutated in place, and there's no hidden row index.
Empty cells (nil) are skipped by
total/mean. In filter and
with, column names are written bare (amount,
region); see Filtering
and computed columns.
| Operation | Returns |
|---|---|
f.rows() |
the number of rows (int) |
f.column_names() |
the column names, in order (array of strings) |
f.column(name) |
one column as an array |
f.head(n) |
a new frame with the first n rows |
f.take(indices) |
a new frame with the rows at the given indices (an int array), in that order |
f.pick(names) |
a new frame with only the named columns, in that order |
f.sort_by(name[, descending]) |
rows ordered by a column (ascending, or descending when the second
argument is true) |
f.total(name) |
the sum of a column — exact for a
decimal column; int→int, float→float |
f.mean(name) |
the average of a column (float) |
f.filter(predicate) |
the rows where the predicate holds — column names written bare
(col.contains(lit) / col.matches(re) for
text) |
f.search(needle[, opts]) |
rows whose string cells match needle — fixed-string by
default; { columns, ignore_case, regex } |
f.with(name, expr) |
a new frame with column name computed per row from
expr (bare column names) |
f.with_column(name, values) |
a new frame with column name set to the pre-computed
values array (one entry per row) — the lower-level form of
with, for when the values are computed outside the
frame |
f.group_sum(key, value) |
group by the key column and sum value per
group → a 2-column frame (key, total) |
f.join(other, key[, kind]) |
join two frames on a shared key column —
"inner" (default) or "left" |
f.pivot(index, columns, values) |
a pivot table: a row per index value, a column per
columns value, each cell the sum of
values |
f.unpivot(ids, values[, name_col, value_col]) |
melt: keep the ids columns, stack the
values columns into a long
name/value pair |
f.row(i) |
one row (0-based) as a table keyed by column name |
f.to_table() |
the frame rendered as a DuckDB-style box-drawing table (a string, for printing or copying); numeric columns right-aligned, every row included |
let sales = frame({
region = ["EU", "US", "EU"],
amount = [10.50d, 20.00d, 5.25d],
})
print(sales.total("amount")) -- 35.75 — exact
print(sales.group_sum("region", "amount"))
$ scua frames.scua
35.75
region total
EU 15.75
US 20.00
(2 rows × 2 cols)
#Rendering and localization
Bake a config table into a finished payload — resolving placeholders, localizing strings, and selecting variants — with no import. For the full story see Rendering and localization.
#render
render(source, locale, external) -> table
Bake source for locale, resolving every
@{path} reference against two roots — self/
(the source itself) and external/ (the data you pass: one
table, or a list of tables merged with later entries winning). Returns a
fresh table. A lone "@{path}" yields the referenced value
(a scalar or a whole subtree); an embedded @{path}
interpolates into the surrounding text. Directive blocks
(@ref, @when, @match,
@each, @fmt, @msg,
@strip) handle subtrees, conditionals, variants, iteration,
locale formatting, and pruning. Pure and total, under hard limits.
#render_tracked
render_tracked(source, locale, external) -> { output, reads }
Like render, but also returns the
read-set for precise cache invalidation:
reads is a deduped list of { ref, origin }
records, where ref is a path that was read (e.g.
"external/name") and origin is which input
supplied it — "self", or the index in the
external list.
#localized
localized(map) -> LocalizedString
A localized string: a table of locale tag → string, e.g.
localized({ en_US = "yes", es_ES = "sí" }). At render time
it collapses to the active locale's string (BCP-47 tags normalize, e.g.
en-US, and a missing tag falls back to its primary subtag).
Keys use underscores (en_US).
#Results
Ok and Error build a result value, the way
SCUA represents an expected success or failure. The postfix ? operator
unwraps an Ok or short-circuits on an Error,
and match takes
a result apart. See Errors and
faults.
Ok(value)
Error(value)
print(Ok(5))
print(Error("boom"))
$ scua result.scua
Ok(5)
Error(boom)
#Coroutines
A coroutine is a function you can suspend and resume. Its suspended state is ordinary data, so it persists and migrates with a partition like any value. See Concurrency and time.
#coroutine
coroutine(body) -> coroutine
Create a suspended coroutine from body. Drive it with
resume.
#resume
resume(co, value?) -> any
Resume a suspended coroutine, optionally passing value
(which becomes the result of the yield that suspended it).
Returns the coroutine's next yield, or its final return value when it
finishes.
#yield
yield(value) -> any
Suspend the current coroutine, handing value to whoever
resumed it. It evaluates to the value passed to the next
resume.
#status
status(co) -> string
The coroutine's state: "suspended",
"running", or "done".
let counter = coroutine(fn()
let i = 0
while true do
i = i + 1
yield(i)
end
end)
print(resume(counter)) -- 1
print(resume(counter)) -- 2
print(status(counter)) -- suspended
$ scua coro.scua
1
2
suspended
#Time and tasks
These belong to the cooperative scheduler. spawn and
wait work inside a partition or a script; now
and dt read the turn's clock. See Concurrency and time.
#spawn
spawn(body)
Schedule body to run concurrently. Inside a partition
the block form spawn do … end does the same. See Partitions and the actor
model.
#wait_all
wait_all(fns) -> array
Run a list of zero-argument functions and wait for
all of them, returning their outcomes in
order (results[0] is the first arm's).
All-settled: one arm failing never cancels the others, so there's always
a slot per arm. Each slot holds that arm's value as-is
— a plain value, or the Ok/Error an I/O arm
returned, or, if the arm faulted, an Error
carrying a tagged fault record you can test with e.fault.
There is no outer Ok/Error, so
never put ? on the call. Waits overlap
only under --io=async; the results are identical either
way. See Run many things
at once.
#map_all
map_all(xs, f, opts?) -> array
Map f over xs, running at most
opts.concurrency at a time (default 8), wait for all, and
align the results with the input. Same semantics as
wait_all: ordered, all-settled, each slot the call's value
as-is, and no ? on the whole. The concurrency
cap throttles how many run at once, so you can fan out over a thousand
inputs without opening a thousand sockets.
#wait
wait(duration)
Suspend the current task for a duration, then resume. Durations are
int64 milliseconds, written with unit suffixes like 2s,
200ms, 1h.
#now
now() -> int
The current time in int64 milliseconds. It is the turn's stamped
clock, frozen for the turn, so two now() calls in the same
turn read the same value. Elapsed time comes from the difference between
two now() readings.
#dt
dt() -> int
Milliseconds elapsed since the previous turn.
#Actors and messaging
Partitions talk to each other through messages. These functions back the message-send syntax; you usually write the surface form rather than calling them directly. See Partitions and the actor model.
tell target.Tag(args)sends a fire-and-forget message.ask target.Tag(args)sends a request and waits for the reply.reply(value)answers the in-flightaskfrom inside a handler.
#Math
The vector, quaternion, and matrix constructors (vec3,
and the type-namespaced quat.axis_angle,
mat4.translate, mat4.id, and the rest — the
flat quat_axis_angle/mat4_translate names
still work) are built in. Their operations are called on the value —
v.length(), v.normalize(),
a.dot(b), a.cross(b) — the receiver names the
operation (the older bare forms like length(v) still work).
So is the color type
—
rgb/srgb/hsv/color_hex
constructors, #rrggbb hex literals, the ~148
color_<name>() CSS colors (plus
color_transparent()), and operations
c.lerp(b, t)/c.lighten(amt)/c.darken(amt)/c.to_hex()
— plus the geometry
shapes
rect/aabox/sphere/capsule/ray/plane
with s.volume(), s.contains(p),
intersect(a, b), and friends. They have their own page: Math.
#Modules
Bring these in with import when you need them. Each is a
record of functions accessed as fields, like
str.trim(...).
math— square roots, trig, interpolation, angle helpers, and constants. See Math.rand— the seeded random-number generator described above.str— string operations:split,join,trim,upper,lower,find,replace,pad_start, and more. See Strings.regex— ReDoS-safe regular expressions (RE2/Rust/ripgrep dialect, linear-time):compile(pattern)→Ok(re)/Error, thenis_match(re, s),find(re, s),find_all(re, s),captures(re, s),replace(re, s, repl),source(re). Offsets are 1-based codepoints;\w \s \dare ASCII in v1. Backreferences/lookaround fail loudly at compile. See Match text with regular expressions.list— functional array operations (signatures below).json—encode(value)(optionallyencode(value, { pretty = true })) anddecode(text).decodereturns a Result you match:Ok(value)on success,Error(message)on bad input. Exactdecimalandmoneyvalues encode as lossless strings (never as JSON numbers).durable— a typed, versioned binary save format (preservesint/float/decimal/big/string/bytes/key, unlike JSON):encode(value, schema_version?)→bytes,decode(bytes)→Ok(any)/Error(assign the result to a typed record to migrate + validate it),version(bytes)→Ok(int)/Error(the stamped version),digest(value)→bytes(a stable SHA-256 content id),verify_roundtrip(value)→Ok(nil)/Error(restore-completeness check). Pure (no capability); pair withfsto write to disk. See Evolve your saved data safely.crypto— Ed25519 signatures for tamper-evident saves:keypair(seed: bytes)→{ public: bytes, secret: bytes }(from a 32-byte host-supplied seed),sign(message, secret)→bytes,verify(message, sig, public)→bool. Sign server-side; the secret key must never reach an untrusted client. See Verifying a save.tomlandini— config-format readers.parse(s)returnsOk(table)(a nested table you can path into withcfg @ "a/b") orError(message)on bad input. Pure (no capability). See Read config files.hash—sha256(data)→bytes(a cryptographic digest), pluscrc32(data)→intandfnv1a(data)→int(deterministic, non-cryptographic checksums).base64andhex— binary-in-text encoding:encode(s)→string, anddecode(s)→Ok(string)/Error(message)(match it or use?, since the input may be malformed).bytes— helpers for the binarybytestype: conversions to/from string, hex, and int arrays (from_string/to_string/from_hex/to_hex/from_array/to_array),slice, and fixed-width number reads (u16le/u32be/f64le/…). See the bytes section of Strings.decimal— named operations on the exactdecimaltype (the12.34dliteral and+ - * /are built in):parse(s)→Ok(decimal)/Error,of(int),from_float(f, scale)/to_float(d)(the explicit, lossy conversions),round(d, scale)(banker's requantize),div(a, b, scale)(division at an explicit scale),scale(d).money— exact money over thedecimaltype, plus a currency layer:of(amount, code)→money(an amount tagged with an ISO 4217 currency, which travels with the value),parse(s)→Ok(decimal)/Error,format(amount)orformat(amount, currency)(symbol + thousands grouping + the currency's decimal places),split(total, n)(penny-exact),round(m)(to the currency's places, banker's rounding),convert(m, code, rate)(to another currency at an explicit rate — no ambient FX),scale(amount, num, den)(rate with banker's rounding). See Handle money.sys— pure, no grant.args()→{ string }(user args after the script path);stdin()→string(whole launch-time stdin,""when nothing is piped);args_table()→ named-arg table (stdin JSON + CLI--keyflags);parse_args(RecordType)→ typed record withwherevalidation (usage exit 2 on violation);emit(value)/fail(reason[, code])(one-shot JSON result on stdout / diagnostic on stderr, then exit);exit(code)(clean stop, 0–255);locale()→ BCP-47 tag ("und"when unset);timezone()→ UTC offset in minutes (0when unset). Locale/timezone are neutral by default and set with--locale/--tz. See Read command-line arguments, Write tools for AI agents, and Set the locale and timezone.time— civil dates over the millisecond clock (now()/dt()), pure (no grant). Construct with the module:time.now(),time.at(ms, offset_min?),time.of(year, month, day, hour?, minute?, second?, ms?, offset_min?),time.parse(s, offset_min?)→Ok(datetime)/Error. Read adatetimewith receiver methods:dt.year()/month()/day()/hour()/minute()/second()/millisecond()/weekday()(ISO 1=Mon..7=Sun)/year_day()→int,dt.parts()→ a record of all of those plusoffset_min,dt.to_ms()→int,dt.offset()→int,dt.with_offset(offset_min),dt.format(pattern)→string,dt.iso()→string. Arithmetic & durations (plain ms):time.add(dt, ms),time.diff(later, earlier)→int,time.humanize(ms)→string,time.duration_parts(ms),time.parse_duration(s)→Ok(int)/Error. Fixed-offset Gregorian — no IANA zones/DST. See Dates and times.env— a capability module needing theenvgrant (scua --allow-env[=NAMES]); without it,import envis a compile error.get(name)→string | nil, an environment variable from the launch-time snapshot (or nil if unset or not in the allowlist). See Read environment variables.fs— a capability module, present only when the host grants it (scua --allow-fs[=DIR]); without the grant,import fsis a compile error. Reads/writes within a rooted directory (no escaping; absolute paths and..are rejected):read(path)→Ok(bytes)/Error,read_text(path)→Ok(string)/Error,write(path, content)→Ok(nil)/Error,list(path)→Ok({ string })/Error,exists(path)→bool,stat(path)→Ok({ size, is_dir, is_file, mtime_ms })/Error,find(path, opts?)→Ok({ record })/Error(walk a tree),grep(pattern, path?, opts?)→Ok({ record })/Error(search file contents — token-economy optionsfiles_with_matches,count_only/matches,invert,max_per_filenarrow the output),grep_text(pattern, path?, opts?)→string(the same search rendered as one compactpath:line: textstring). See Read and write files.http— a capability module with two authorities that don't overlap. The client needs thenetgrant (scua --allow-net[=hosts]):get(url)andpost(url, body)→Ok({ status: int, body: string })/Error, restricted to the granted host allowlist. The server needs the separateservegrant (scua --allow-serve[=ADDR:PORT]):serve(handler, { bind })blocks and answers requests, callinghandler— a plainfn(req) -> resp— once per request.reqis{ method, path, headers, body }(header names lowercased,bodyisbytes); the response is{ status = 200, headers = {}, body = b"" }with abytesbody (a string body is a 500). A handler fault or returnedErrorbecomes a 500 (text withheld unless--serve-debug). Without the matching grant, the function isn't there to call (import httpcompile error, or an undefined name). See Fetch data over HTTP and Serve HTTP requests.net— a capability module; raw TCP sockets. Dialing out needs thenetgrant:dial(host, port)→Ok(connection)/Error(an opaque, non-sendable handle), with the host in the allowlist. Listening needs the separateservegrant (notnet):listen(addr, port)→Ok(listener)/Errorandaccept(listener)→Ok(connection)/Error(waits for the next client; parks under--io=async). Read and write either connection withread(conn, max)/write(conn, data);close(x)closes either a connection or a listener. See Raw TCP sockets and Serve HTTP requests.udp— a capability module needing theudpgrant (scua --allow-udp[=PEERS]); datagrams, which are unreliable, unordered, and unencrypted.connect(host, port)→Ok(socket)/Erroropens a socket locked to one peer;bind(addr, port)(also needs--allow-serve) opens one that hears from anyone. Send withsend(sock, data)/send_to(sock, host, port, data), receive withrecv(sock, opts?)→Ok({ peer, data })/Error, answer a peer that reached you withreply(sock, peer, data). In a receive loop userecv_batch(sock, max, opts?)rather thanrecv.peer_addr(peer)→ an address string (for logging, not a send target),local_port(sock)→int,close(sock). Payloads cap at 1200 bytes (raise it with--udp-max-payload), and a peer may be sent at most 3× the bytes it has sent you. See Send datagrams with UDP.cluster— a capability module needing theclustergrant (scua --allow-cluster[=SECRET]); without it,import clusteris a compile error. A small cluster-wide config table that converges last-writer-wins across nodes (etcd/Consul in miniature, for when you don't want to run one).open(name)→shared, thenset(t, key, value),get(t, key)→ value | nil,has(t, key)→ bool,delete(t, key)(writes a tombstone),subtree(t, prefix)→ table (the live keys underprefix/, keyed by their relative path). Keys are path-shaped and prefix-exclusive (a key is a leaf value or a namespace, never both); values are portable and small (64 KiB cap).join(opts)meshes nodes over gossip (young — the single-node surface is the solid part). See Share config across a cluster.
#The list module
import list, then call as fields:
list.map(xs, f). None of these mutate the input; the ones
that produce a sequence return a new array.
list.map(xs, f)— a new array off(x)for each element.list.filter(xs, pred)— a new array of the elements wherepred(x)is true.list.reduce(xs, f, init)— fold left: starts frominitand computesacc = f(acc, x)across the elements. Note the argument order: the function comes before the initial value.list.find(xs, pred)— the first element wherepred(x)is true, ornil.list.any(xs, pred)— true ifpred(x)holds for at least one element.list.all(xs, pred)— true ifpred(x)holds for every element.list.each(xs, f)— callsf(x)for each element and returns nothing (for side effects).list.count(xs, pred)— how many elements satisfypred.list.sort(xs, less)— a new array sorted by aless(a, b)comparator (returns true ifacomes first). Stable; the original is untouched.list.sum(xs)— the sum of the elements.list.reverse(xs)— a new array in reverse order.list.index_of(xs, v)— the index of the first element equal tov, ornil.list.unique(xs)— a new array with duplicates removed, keeping the first occurrence.list.zip(xs, ys)— a new array of{ a = xs[i], b = ys[i] }up to the shorter length.list.flatten(xss)— concatenates an array of arrays into a single array.