# The SwiftLua Language Guide

This is the full reference for writing SwiftLua scripts. The language is a **deliberate
subset of Lua with defined behaviour**; it is not "just Lua". Where Lua leaves behaviour
unspecified, SwiftLua pins it down, and where a Lua feature would get in the way of
safety or repeatable results, SwiftLua limits it or leaves it out. If you know Lua, read
[Differences from Lua](#differences-from-lua) first. **New to scripting, or coming from
JavaScript or TypeScript?** Start with [Getting started](#getting-started) and come
back here for the precise rules.

Scripts run inside an app, in a sandbox: a script can use exactly the values, functions
and modules the app provides, and nothing else. Every run has limits (on instructions,
call depth, memory, table sizes and string sizes) and stops at the same point every time
one runs out.

In the examples, `-->` shows what a line produces, and `--> error: …` shows that it
stops with an error saying so.

## Safe navigation (dialect extension)

Use `?.field` or `?[key]` when a receiver may be `nil`:

```lua
local response = {data = {me = {user = {email = "a@b"}}}}
response.data?.me.user?.email  --> "a@b"
local optionalList: any = nil
optionalList?[1]              --> nil
```

Only `nil` short-circuits. The receiver is evaluated once; if it is nil, the
result is nil and the rest of that continuous access chain is skipped, including
computed keys, method lookups, calls and their arguments. For a non-nil receiver,
ordinary indexing and metatable behaviour apply. `false` is not treated as nil.
Errors from an executed lookup or call still propagate.

Each `?` guards its own receiver. With `a?.b.c`, nil `a` skips both accesses,
but a present `a` whose `b` is nil still errors at `.c`. Use `a?.b?.c` if both
may be absent. Parentheses end the chain: `(a?.b).c` performs `.c` even when
`a?.b` returned nil. Binary operators also end the chain; `a?.b + 1` still
attempts the addition, while `a?.b or fallback` uses the usual Lua fallback rule
(which also replaces false).

The checker checks accesses against the non-nil receiver type and includes nil
in the result when the receiver may be absent. Unknown fields and invalid index
types remain errors. Safe chains cannot be assignment targets and always produce
exactly one value, including when they end with a function call. A chain ending
in a call may stand alone as a statement, for example `receiver?.notify()`.

There is no `?()` safe-call operator, `?:` safe-method operator, or `??` operator.
Each continuous chain allows at most 32 safe guards (the CoCoCo app may set a lower
parser nesting limit). This syntax is shared by every engine of this dialect; stock Lua and
Luau do not accept it. Optional type annotations such as `string?` are unchanged.

## Key points

If you take four things from this guide, take these:

1. **Reading an undefined global is an error.** It doesn't give `nil`, so typos fail
   loudly.
2. **There are no iterators, coroutines, redefinable operators, `io` or `os`.** If you
   reach for one, this guide lists the alternative, or the thing genuinely can't be done.
3. **Multiple returns follow Lua 5.1.** A call in the last position of a list expands to
   all its values; write `(f())` to keep just one (§4).
4. **Only what §8 lists exists.** The standard library is a closed set: `next`,
   `string.gmatch` and `math.random`, among others, aren't there. Don't assume a name is
   present because it's standard elsewhere.

## 1. Values and types

`nil`, booleans, integers (64-bit, checked), numbers (64-bit floating point), strings
(immutable Unicode text), tables and functions. There is no userdata type and there are
no coroutines; metatables are limited (§5).

- **Integers and numbers.** Integer literals stay integers, and arithmetic between
  integers stays whole except for `/` and `^`, which always give a number. Integer
  overflow **is an error**; it never wraps round. `//` rounds down (towards negative
  infinity), `%` takes the sign of the divisor, and whole-number `//` or `%` by zero is
  an error. Floating-point division by zero gives infinity (`1/0 == math.huge`).
- **Strings** are Unicode text. `#s`, `string.len`, `string.sub` and `string.byte` count
  UTF-8 **bytes**, and an operation that would split a character in the middle is an
  error rather than producing broken text. `..` joins strings and turns integers and
  numbers into text (`"n=" .. 42` gives `"n=42"`); any other type is an error. Escapes
  `\128` to `\255` aren't allowed in string literals, because strings are always valid
  text.
- **Truthiness**: `nil` and `false` are false; everything else, including `0` and `""`,
  is true. `and` and `or` return one of their operands and stop as soon as the answer is
  known.
- **Equality** works across the two numeric forms (`1 == 1.0`), and values of different
  types are simply unequal, never an error. Ordering (`<`, `<=`) needs two numbers or two
  strings (compared byte by byte); anything else is an error, even `nil <= nil`.
- **No automatic conversion in arithmetic**: `"42" + 0` is an error. Convert with
  `tonumber` (§8).

```lua
7 // 2                    --> 3
-7 // 2                   --> -4
-7 % 3                    --> 2
7 / 2                     --> 3.5
1 == 1.0                  --> true
1 == "1"                  --> false
"n=" .. 42                --> "n=42"
0 and "yes" or "no"       --> "yes"
math.maxinteger + 1       --> error: overflow
(tonumber("42") or 0) + 8  --> 50
```

## 2. Variables and scope

```lua
local x = 1                  -- a local, visible to the end of its block
local a, b = 1, 2            -- several at once
x = 5                        -- assignment
x  --> 5
```

Scope follows position, exactly as in Lua: in `local a = a + 1` the right-hand side sees
the *outer* `a`. Reading a global that doesn't exist **is an error** (`unknownGlobal`);
Lua would quietly give `nil`, but SwiftLua treats it as the bug it usually is. In the
default strict mode, *creating* a global is reported before the script runs, so declare
locals instead:

<!-- example {"check": "assignment creates global 'g'"} -->
```lua
g = 10                       -- the checker reports this: declare it with local
```

## 3. Statements

| Form | Notes |
|---|---|
| `if … then … elseif … else … end` | as in Lua |
| `while cond do … end` | as in Lua |
| `repeat … until cond` | the body runs at least once; **the condition can see the body's locals** |
| `for i = a, b [, step] do … end` | counting loop; both bounds included. A step of 0 is an error and the bounds must be numbers. If any of start, limit or step is a floating-point number, the loop variable is one on every pass; it is an integer only when all three are integers |
| `for k, v in ipairs(t) do … end` | the array part, in order (see §5) |
| `for k, v in pairs(t) do … end` | the whole table, in a **defined order** (see §5) |
| `break` | leaves the innermost loop; outside a loop it is an error |
| `do … end` | an explicit block, for scope |
| `return …` | see §4: several values are allowed, up to a limit |
| `local function f(…) … end` | a local function that can call itself |
| `function f(…) … end` | a global function |

There is **no `goto`**, no `continue`, and no compound assignment such as `+=`.

The loop variable of a counting `for` is a fresh copy on each pass, so assigning to it
inside the body doesn't change how many times the loop runs.

`function t.m() end` and `function a.b:m() end` are shorthand for assigning a field; the
`:` form adds a hidden first parameter called `self`. Dotted paths can be as long as you
need. Both are exactly the same as `t.m = function(…) … end`.

```lua
local tries = 0
repeat
  local done = tries >= 2
  tries = tries + 1
until done                   -- the condition sees the body's local
tries  --> 3

local passes = 0
for i = 1, 3 do i = 100; passes = passes + 1 end
passes  --> 3

local out = {}
for i = 1, 3.0 do out[#out + 1] = tostring(i) end   -- a float bound gives float values
string.join(" ", out)  --> "1.0 2.0 3.0"

local counter = { n = 0 }
function counter:bump(by) self.n = self.n + by end
counter:bump(5)
counter.n  --> 5
```

## 4. Functions and multiple returns

Functions are ordinary values, and they capture the variables around them (closures).
Missing arguments arrive as `nil` and extra ones are dropped, as in Lua.

Multiple returns follow **Lua 5.1**: a call, method call or `...` in the **last**
position of a list expands to all of its values; anywhere else it gives just its first
value.

```lua
local function divmod(a, b) return a // b, a % b end

local q, r = divmod(17, 5)      --> q = 3, r = 2
q, r = divmod(9, 2)             --> q = 4, r = 1
local x, y = divmod(9, 2), 7    --> x = 4, y = 7
local a, b, c = divmod(9, 2)    --> a = 4, b = 1, c = nil
```

The rules in full:

1. **Return lists.** The last expression in a `return` passes on every value:
   `return f(args)` and `return 0, f(args)` both pass all of `f`'s results, limited by
   how many the caller takes. `return f(args)` is also a proper **tail call**, so tail
   recursion runs in constant stack depth.
   Parentheses cut the list to one value, as in Lua: `return (f())` returns exactly one.
2. A call in the **last position** of a `local` declaration or assignment fills the
   remaining variables, padding with `nil` and dropping extras.
3. A call in the **last position** of an argument list (including `pcall`'s) or a table
   constructor expands too: `g(f())` passes every value of `f`, and `{f()}` holds them
   all.
4. Each expansion carries at most 250 values (an app can set a different limit), and so
   does a whole argument list that one expands into; more is an error you can catch. So a
   function already holding 250 extra arguments can pass `...` on alone, but not
   `f(x, ...)`.
5. The value your script returns to the app, and the value a module exports (§6), are
   single values: the first, if there are several. A function from the app normally
   returns one value, but some return a result and an error (`local res, err = f(...)`)
   or several values. Functions that wait on the outside world return `(ok, res)`
   instead (§12).

A function from the app that returns a result and an error works like `pcall`, except
the app decides what the error says:

```lua
local function total(text)
  local qty, err = parseQuantity(text)
  if not qty then return "failed: " .. tostring(err) end
  return qty * 5
end

total("4")    --> 20
total("x")    --> "failed: not a quantity"
```

**Variable arguments (`...`).** A function whose parameter list ends in `...` takes any
number of extra arguments. Inside it, `...` is those arguments: `select("#", ...)` counts
them, `select(n, ...)` gives them from position `n`, and `{...}` or `table.unpack` moves
them around. `...` follows the same rules as a call:

```lua
local function log(fmt, ...) return string.format(fmt, ...) end
log("%d/%d", 3, 4)            --> "3/4"

local function count(...) return select("#", ...) end
count("a", nil, "c")          --> 3

local function firstTwo(...)
  local a, b = ...            -- last position: fills two, padded with nil
  return a, b
end
local p, q = firstTwo(1)      --> p = 1, q = nil

local function pack(...) return {...} end
pack(1, 2, 3)                 --> {1, 2, 3}
```

A `...` that isn't last (for example `{..., x}` or `local a, b = ..., 7`) gives one
value, exactly like a call that isn't last. In the main script, `...` holds any
arguments the app passes when it runs the script; most apps use inputs instead, so it is
usually empty.

## 5. Tables

A table has two parts: a dense **array part** (keys `1…n`, with `#t == n`) and a **map
part** for every other key. A floating-point key with a whole value is the same key as
the integer (`t[1.0]` is `t[1]`), and `nil` or NaN keys are errors.

**Holes are defined** (Lua leaves them unspecified): setting the last element to `nil`
removes it; setting one in the middle to `nil` ends the array part there and moves the
rest into the map part; and writing at `#t + 1` appends, pulling in any map entries that
continue the sequence.

```lua
local t = { "a", "b", "c" }
t[3] = nil
#t  --> 2
t[#t + 1] = "z"
t   --> {"a", "b", "z"}
```

**Iteration is defined and repeatable.** A generic `for` accepts exactly two forms,
`ipairs(expr)` and `pairs(expr)`, written directly in the loop. You can't pass iterators
around, and redefining `ipairs` or `pairs` makes the loop an error rather than a
different call.

- `ipairs(t)` walks the array part in index order. **The number of passes is fixed when
  the loop starts** (in standard Lua it is live), so changing the array inside the loop
  doesn't change how many times it runs.
- `pairs(t)` walks the array part in index order and **then the map part in the order
  keys were added**. Lua's order is unspecified; SwiftLua's is the same on every run and
  every platform.
- `pairs` follows the table **as it changes**, by position: adding entries during the
  loop extends it, and removing one shifts the later entries. (Lua forbids adding during
  traversal; SwiftLua defines what happens instead.)

```lua
local keys = ""
for k in pairs({ 10, 20, zebra = 1, apple = 2 }) do
  keys = keys .. tostring(k) .. " "
end
keys  --> "1 2 zebra apple "
```

Table constructors (`{1, 2, x = 3, [k] = v}`) accept positional, named and computed
fields, evaluated left to right.

**Limited metatables.** `setmetatable(t, mt)` and `getmetatable(t)` exist and honour
three fields. **`__index`** (a table, or a function `(t, k)`) answers reads of keys `t`
doesn't have, following a chain of tables; **`__newindex`** (a table, or a function
`(t, k, v)`) receives writes to keys `t` doesn't have; and **`__tostring`** gives the
text for `tostring`, `print` and `inspect`. A chain can be at most 8 tables long: longer,
or circular, is an error. `rawget(t, k)` and `rawset(t, k, v)` bypass both hooks.
`pairs`, `#`, `table.size`, the standard library and the values handed back to the app
all see a table's own entries only. `setmetatable` **refuses** every other field
(`__eq`, `__call`, `__gc`, `__mode`, `__metatable`, `__len`, `__concat` and the operator
fields), so scripts can't redefine operators. Frozen tables can't have a metatable.

Operators *do* work on some types the app provides, such as `Decimal`
(`price * qty + shipping`), but that is a separate mechanism, not a metatable, so writing
an `__add` is refused even then. See "Sealed types" below.

Metatables are exactly enough for the usual class pattern:

```lua
local Account = {}
Account.__index = Account

function Account.new(owner)
  local self = setmetatable({}, Account)
  self.owner = owner
  self.balance = 0
  return self
end

function Account:deposit(amount)
  self.balance = self.balance + amount
  return self.balance
end

local acct = Account.new("Ada")
acct:deposit(50)  --> 50
acct:deposit(25)  --> 75

local Point = {}
Point.__index = Point
Point.__tostring = function(p) return "<Point " .. p.x .. "," .. p.y .. ">" end
tostring(setmetatable({ x = 1, y = 2 }, Point))  --> "<Point 1,2>"
```

### Sealed types (operators)

Some types come from the app, sealed: only the app can create one, and the app decides
which operators it supports. `Decimal`, from the decimal library, is the main example:

```lua
local price, qty, shipping = decimal.new("19.99"), 3, decimal.new("4.95")
local total = price * qty + shipping   -- a Decimal: exact, no floating-point rounding
tostring(total)                        --> "64.92"
total > 0                              --> true
decimal.new("1") == decimal.new("1.000")  --> true
```

- The checker knows the result types: `Decimal + Decimal` is a `Decimal`, and
  `Decimal < Decimal` is a `boolean`, so `local t: Decimal = price * qty` checks.
- An **integer** works alongside a decimal (`price * 3`). A **floating-point number**
  doesn't: `price + 0.1` is an error, because a binary fraction has no exact decimal
  value. That refusal is the point.
- Comparison is by value, as the last line above shows.

Three things this is *not*. It isn't metatables: `setmetatable` refuses an `__add`
whenever it is written. It isn't something a script can declare: only the app can seal a
type. And it can't be forged: a sealed value carries a hidden mark that `pairs`, `rawget`
and returned values never show and no script can set, so `{units = 10, scale = 0}` is not
a decimal, however decimal-like it looks.

## 6. Modules and `require`

Where the app provides modules, `require` loads one while your script runs:

<!-- example {"inputs": {"region": "eu"}, "returns": 120} -->
```lua
local tax = require("tax")                     -- a literal name
local rules = require("rules-" .. region)      -- names can be worked out, too
if 100 >= rules.threshold then
  return tax.apply(100)
end
return 100
```

- Names are looked up **only by the app**. There is no file system, no search path and
  no way to turn text into code. If the app provides no modules, `require` doesn't
  exist.
- A module's top level runs as part of *your* run, within *your* limits. Its one return
  value (usually a table of functions) is what `require` gives you.
- **Each module loads once per run**: requiring the same name again returns the same
  value. Separate runs load separately, so a module's state never carries over between
  runs.
- A module that requires itself, directly or in a circle, is an error (`moduleError`), as
  is a name the app doesn't know (`moduleNotFound`). There is a limit on how many modules
  one run can load.
- Modules share the run's globals, as in Lua, so a module that changes a global changes
  it for the script that required it (and the checker reports creating one, as
  anywhere).
- `require` is always called directly with one argument. Storing it
  (`local r = require`) isn't supported.
- **Loading may take a moment.** Some apps fetch modules from storage or over the
  network. Your `require("x")` looks and behaves the same either way: the run simply
  waits for the module (see §12).
- **Checked across modules.** When a module is named literally (`require("tax")`, not
  `require(name)`), the checker reads the module's own source to learn what it exports.
  So `require("tax").aply` is caught as a typo, and a chain such as
  `require("client").new(key):method()` is checked all the way through. Names that are
  worked out at run time aren't checked.

Some apps also offer a loader under a name of their own, for example to load a saved
script by name. It behaves exactly like `require`, including loading each name once per
run, and returns the module itself, so its functions are callable. A literal name is
checked just like `require("name")`, and a module's exported types (`export type`) can be
named as `mod.Name`.

## 7. Method calls

`receiver:name(args)` works without metatables:

- On a **string**, the method comes from the string library: `s:upper()` is
  `string.upper(s)`, and `("%d!"):format(7)` works.
- On a **table**, it looks up the field and passes the table as the first argument:
  `t:scale(4)` is `t.scale(t, 4)`.
- On anything else it is an error. A missing method is a `notCallable` error.
- The receiver is evaluated exactly once. The argument list is required: `a:b` on its
  own is an error, as in Lua.

```lua
("hello"):upper()          --> "HELLO"
("%d!"):format(7)          --> "7!"
local shape = { size = 3, scale = function(self, by) return self.size * by end }
shape:scale(4)             --> 12
```

## 8. The standard library

Only what is listed here exists, and every library table is frozen (writing to it is an
error). All the functions give the same results everywhere: there are no locale
settings, and no clock or randomness **except** where the app provides them. `time.now()`
exists only when the app provides a clock, and `uuid.v4`/`uuid.v7` only when it provides
a source of randomness (see "time" and "uuid" below). The other time and UUID functions
always give the same result for the same input.

**Basic**: `assert(v [, message])`; `error([value [, level]])` (any value; level 2
blames the caller, 0 adds no position); `type(v)`; `tostring(v)`; `tonumber(v [, base])`
(reads a number from text, or passes a number through, and gives `nil` otherwise; it
never fails, so it works as a check; leading zeros and surrounding spaces are accepted,
`base` can be 2–36, and a `0x` prefix reads hexadecimal; note that arithmetic still does
*not* convert strings); `inspect(v)`; `pcall(f, …)` and `xpcall(f, handler)` (§10);
`select("#" | n, …)`; `unpack(t [, i [, j]])` (also `table.unpack`); `rawequal(a, b)`;
`_VERSION`; and `setmetatable`, `getmetatable`, `rawget`, `rawset` (§5).

```lua
tonumber("42")     --> 42
tonumber(" 7 ")    --> 7
tonumber("ff", 16) --> 255
tonumber("abc")    --> nil
type(tonumber)     --> "function"
```

**`print(…)`** exists only when the app allows it; otherwise the name doesn't exist. It
writes any number of values as `tostring` would, separated by tabs, with no memory
addresses. The app decides where the output goes (a run log, for example), and the
amount of output is limited: going over the limit ends the run (`logLimitExceeded`) and
can't be caught. Trailing `nil` arguments are dropped; `nil`s in the middle print as
`nil`.

**`inspect(v)`** shows a value's structure, for debugging. Tables appear as
`{1, 2, key = value}`: the array part first, then map entries in `pairs` order, with
names written bare and other keys in brackets. Strings are double-quoted with `\n`-style
escapes, nesting stops at depth 4 (`<deep>`) and loops are marked `<cycle>`. The format
is exact and the same on every platform, and never shows addresses or identities.

```lua
inspect({ 1, 2, name = "Ada", ["two words"] = true })  --> '{1, 2, name = "Ada", ["two words"] = true}'
inspect("line\nbreak")                                 --> '"line\\nbreak"'
```

**math**: `floor`, `ceil`, `round` (halves away from zero) and `trunc` (towards zero)
all return integers and are errors on overflow; `round(x, places)` rounds to 0–12
decimal places and returns a number; `abs`, `min` and `max` (any number of values),
`sqrt`; `clamp(x, lo, hi)` (reversed bounds are an error); `sign(x)` (−1, 0 or 1; NaN is
an error); `lerp(a, b, t)`; `approx(a, b, epsilon)` (NaN is never approximately
anything); `isinteger(x)` (true for integers, and for numbers that are exactly whole);
`fmod`, `modf`, `frexp`, `ldexp`, `deg` and `rad`; `exp`, `log(x [, base])`, `log10`,
`pow`, `sin`, `cos`, `tan`, `asin`, `acos`, `atan(y [, x])`, `atan2`, `sinh`, `cosh`
and `tanh`; and the constants `pi`, `huge`, `maxinteger` and `mininteger`.

`fmod` to `rad` give exactly the same result everywhere. `exp`, `log`, `pow` and the
trigonometric and hyperbolic functions can differ in the last digit between platforms,
so compare their results with `math.approx` rather than `==`. **There is no
`math.random`**: scripts give the same result every time they run.

```lua
math.round(2.5)          --> 3
math.round(-2.5)         --> -3
math.round(3.14159, 2)   --> 3.14
math.clamp(15, 0, 10)    --> 10
math.isinteger(4.0)      --> true
math.max(3, 9, 4, 1)     --> 9
math.fmod(7, 3)          --> 1
math.deg(math.pi)        --> 180.0
math.approx(math.sin(math.pi / 2), 1, 1e-12)  --> true
```

**string** (counting bytes; see §1): `len(s)`, `upper(s)`, `lower(s)`,
`sub(s, i [, j])` (Lua's index rules; negative positions count from the end),
`rep(s, n [, sep])`, `find(s, pattern [, init [, plain]])` (patterns: see below),
`byte(s [, i [, j]])`, `char(…)`, `format(fmt, …)`, `split(s, sep)` (the separator is
plain text and can't be empty; empty fields are kept), `join(sep, list)`
(`table.concat` with the separator first; elements must be strings or numbers),
`isempty(s)`, `lines(s)` (splits on `\n` and drops one trailing `\r` per line, so Windows
line endings work; a final newline doesn't add an empty last line, and `""` gives no
lines), `trim`, `ltrim` and `rtrim` (ASCII whitespace), `startswith`, `endswith` and
`contains(s, part)`, `replace(s, old, new [, count])` (plain text, never patterns; an
empty `old` is an error), `padleft` and `padright(s, width [, fill])` (a single fill
character) and `reverse(s)` (reverses by character, the same on every platform).
`upper` and `lower` change the case of all letters, not just ASCII ones.

```lua
string.split("a,,b", ",")          --> {"a", "", "b"}
string.join("-", { 1, 2, 3 })      --> "1-2-3"
string.lines("one\r\ntwo\n")       --> {"one", "two"}
string.padleft("7", 3, "0")        --> "007"
("  hi  "):trim()                  --> "hi"
string.upper("straße é")           --> "STRASSE É"
string.replace("a.b.c", ".", "/")  --> "a/b/c"
```

**String patterns.** SwiftLua supports the **full Lua 5.4 pattern language**: the
classes `%a %d %s %w %l %u %p %c %x` and their uppercase opposites, `.`, sets `[..]` and
`[^..]` with ranges, the repeats `* + - ?`, the anchors `^` and `$`, captures `()`
(including position captures), the back-references `%1`–`%9`, `%b` and `%f`. Matching is
**metered**: a pattern that would take an extreme amount of backtracking ends the run
with `patternComplexityExceeded`, which `pcall` can't catch, while a badly formed
pattern is an ordinary error you can catch. The pattern functions:

- `find(s, pat [, init [, plain]])` gives `(start, end, captures…)` (1-based, both ends
  included) or `nil`. It uses patterns unless `plain` is `true`.
- `match(s, pat [, init])` gives the captures as separate values (or the whole match when
  the pattern has none), or `nil`, as in Lua.
- `captures(s, pat [, init])` gives an array of the captures (position captures as
  integers), or `{wholeMatch}` if the pattern has none, or `nil`.
- `matchall(s, pat)` gives an array of every matching piece of text. It replaces Lua's
  `gmatch`, because a generic `for` accepts only `pairs` and `ipairs` (§5).
- `gsub(s, pat, repl [, n])` gives `(result, count)`. `repl` can be a string (`%0` is the
  whole match, `%1`–`%9` the captures and `%%` a literal `%`), a table looked up by the
  first capture, or a function called for each match (returning the replacement, or
  `nil`/`false` to keep the original). Because `gsub` returns two values, wrap it in
  parentheses, `return (string.gsub(s, " ", "-"))`, when you want only the text.

```lua
string.find("order-1042", "%d+")          --> 7
string.match("order-1042", "(%a+)-(%d+)") --> "order"
string.matchall("a1 b22 c333", "%d+")     --> {"1", "22", "333"}
local out, n = string.gsub("a-b-c", "-", "+")  --> out = "a+b+c", n = 2
```

Limits worth knowing:

- `string.format` supports `%d %i %u %c %o %x %X %e %E %f %g %G %q %s %%` with the
  `- + space # 0` flags; width and precision are at most 99. `%s` writes the value as
  `tostring` would. Anything else is an error.

```lua
string.format("%05.1f|%-4s|%x", 3.14159, "ab", 255)  --> "003.1|ab  |ff"
string.format("%d-%d-%d-%d-%d", 1, 2, 3, 4, 5)       --> "1-2-3-4-5"
```

**table: queries and slices**: `insert(t [, pos], v)` (appends, or inserts at `pos` and
moves later elements up); `remove(t [, pos])` (removes the last element, or the one at
`pos`, moving the rest down, and returns it; a `pos` out of range is an
`invalidArgument` error); `concat(t [, sep])`; `keys(t)`, `values(t)` and `items(t)`
(arrays in `pairs` order; `items` gives `{key = k, value = v}` records, and `values`
holds the same tables rather than copies); `indexof(t, v)` (the position in the array
part, or `nil`); `contains(t, v)` (anywhere in the table); `has(t, k)` (the key has a
non-`nil` value; `false` counts as present); `size(t)` (all entries, where `#t` counts
only the array part); `isempty(t)`; `slice(t, i [, j])` (Lua's index rules);
`reverse(t)` (a new, reversed array); `take(t, n)` and `drop(t, n)` (split the array
part by count; counts below zero count as zero); `append(t, v1 [, …v4])` (adds to the
array part; `nil` arguments are skipped); `clear(t)` (removes every entry in place); and
`flatten(t)` (one level; every element must be a table).

```lua
local list = { "a", "b", "c", "d" }
table.slice(list, 2, 3)     --> {"b", "c"}
table.indexof(list, "c")    --> 3
table.take(list, 2)         --> {"a", "b"}
table.size({ 1, 2, x = 3 }) --> 3
table.flatten({ { 1, 2 }, { 3 } })  --> {1, 2, 3}
```

**table: copying and merging**: `clone(t)` (a shallow copy of the table's own entries;
metatables aren't copied); `deepcopy(t)` (copies all the way down; shared tables and
loops are kept, metatables aren't copied); `merge(a, b)` (a new table, where `b`'s
entries win); `extend(dst, src)` (copies `src` into `dst` and returns `dst`); and
`defaults(t, d)` (fills in only the keys that are `nil` in `t`; an existing `false`
stays).

```lua
table.merge({ a = 1, b = 2 }, { b = 3 })          --> {a = 1, b = 3}
table.defaults({ a = false }, { a = 1, b = 2 })   --> {a = false, b = 2}
```

**table: functions that take a callback** work on the array part, in order. Your
callback runs as ordinary script code, with the usual limits and error rules, and
receives `(value, index)`; you can leave out parameters you don't need, so
`function(v)` works everywhere, and so does passing a built-in such as `tostring`
directly. `map(t, f)` and `collect(t, f)` (two names, one
operation); `filter(t, f)`; `find(t, test)` and `findindex(t, test)` (a function is
used as a test, and anything else is compared for equality; `nil` when absent);
`reduce(t, f, initial)` (combines left to right; `f` receives `(acc, value, index)`);
`each(t, f)`; `any(t, test)` and `all(t, test)`; `count(t [, test])` (the length, or the
number that pass); `partition(t, test)` (returns **one** table, with the passing
elements at `[1]` and the rest at `[2]`); `group_by(t, keyfn)` (a map of arrays: keys in
the order first seen, each group in input order); `tomap(t, keyfn, valuefn)` (later
elements win a key clash); `unique(t)` and `unique_by(t, keyfn)` (the first occurrence
wins, order kept); and `sort(t [, less])`, which sorts `t` **in place** and returns
nothing, as Lua's does,
while `sorted(t [, less])` and `sort_by(t, keyfn [, less])` return a **new** sorted
array. All three are **stable**: equal elements keep their order. Without a comparison
function, the elements must be all numbers or all strings (a mix is an error); a
comparison function receives `(a, b)` (the derived keys, for `sort_by`) and returns true
when `a` comes first. A callback that itself calls one of these functions can nest only
so deep: very deep nesting is a `callDepthExceeded` error.

```lua
local nums = { 5, 3, 8, 1 }
table.map(nums, function(v) return v * 2 end)          --> {10, 6, 16, 2}
table.filter(nums, function(v) return v > 2 end)       --> {5, 3, 8}
table.reduce(nums, function(acc, v) return acc + v end, 0)  --> 17
table.sorted(nums)                                     --> {1, 3, 5, 8}
nums                                                   --> {5, 3, 8, 1}
table.partition(nums, function(v) return v % 2 == 1 end)   --> {{5, 3, 1}, {8}}
table.map({ 1, 2 }, tostring)                          --> {"1", "2"}
table.sort(nums)
nums                                                   --> {1, 3, 5, 8}

local people = { { name = "Ada", team = "x" }, { name = "Bo", team = "y" }, { name = "Cy", team = "x" } }
local byTeam = table.group_by(people, function(p) return p.team end)
#byTeam.x                                              --> 2
table.sort_by(people, function(p) return p.name end)[1].name  --> "Ada"
```

**set**: `new(list)` (duplicates collapse), `has(s, v)`, `add(s, v)` and `remove(s, v)`
(change the set and return it), `union(a, b)`, `intersection(a, b)` and
`difference(a, b)` (new sets), and `tolist(s)` (members in the order first added). A set
is a plain table mapping each member to `true`, and members can be any valid table key.

```lua
local seen = set.new({ "a", "b", "a" })
set.has(seen, "a")                                   --> true
set.tolist(set.union(seen, set.new({ "c" })))        --> {"a", "b", "c"}
```

**Handy globals**: `default(v, fallback)` (the fallback only when `v` is `nil`; `false`
passes through); `coalesce(a [, …d])` (the first non-`nil` of up to 4); `pipe(x, f1
[, …f4])` (applies the functions left to right; with none it returns `x`);
`assertf(cond, fmt [, …3 values])` (`assert` with a `string.format` message, built only
when it fails); and `typecheck(v, expected [, name])` (an error you can catch, naming the
problem, or returns `v`).

```lua
default(nil, 5)                           --> 5
default(false, 5)                         --> false
coalesce(nil, nil, "third")               --> "third"
pipe(3, function(x) return x + 1 end, function(x) return x * 10 end)  --> 40
```

**time** gives instants and durations as values that can't be changed: each method
returns a *new* value, so calls chain. Only fixed offsets from UTC are supported (no
named time zones or daylight saving). Create an instant with
`time.new({year=, month=, day=, hour=, minute=, second=, millis=, offset=})` (the
parentheses are required; missing parts take defaults and an impossible date is an
error), `time.fromepoch(ms [, offsetMinutes])` or `time.parse(isoString)` (ISO 8601).
Durations come from `time.period({days=, hours=, minutes=, seconds=, millis=})` or
`time.millis`, `seconds`, `minutes`, `hours` and `days(n)`.
Instant methods: `:epoch_millis()`, `:offset()`, `:year()`, `:month()`, `:day()`,
`:hour()`, `:minute()`, `:second()`, `:add(p)` and `:subtract(p)` (giving an instant),
`:diff(other)` (giving a duration), `:before`, `:after` and `:equals(other)`,
`:start_of_minute`, `_hour`, `_day`, `_month` and `_year()`, `:to_offset(minutes)`,
`:with({…})` and `:format([fmt])` (the `%Y %m %d %H %M %S %z %%` codes; with no
format, ISO 8601). Duration methods: `:millis()`, `:add(p)` and `:negate()`.
`time.now()` gives the time of the run, but **only when the app provides a clock**;
otherwise it doesn't exist.

```lua
local start = time.parse("2026-03-30T09:15:00Z")
start:add(time.days(3)):format("%Y-%m-%d")              --> "2026-04-02"
start:start_of_month():format()                         --> "2026-03-01T00:00:00Z"
time.parse("2026-03-31T00:00:00Z"):diff(start):millis() --> 53100000
start:to_offset(60):hour()                              --> 10
```

**uuid**: `uuid.parse(s)` (the canonical lowercase form, or `nil`), `uuid.is_valid(s)`,
`uuid.version(s)` and `uuid.nil_uuid()` always exist. Creating new ones depends on the
app: `uuid.v4()` needs a source of randomness, and `uuid.v7()` needs randomness **and** a
clock, because it includes the time. Without them, the name doesn't exist.

```lua
uuid.parse("0190A5C8-8C3E-7B4A-9F21-3D6C2A1B0E9F")   --> "0190a5c8-8c3e-7b4a-9f21-3d6c2a1b0e9f"
uuid.version("0190a5c8-8c3e-7b4a-9f21-3d6c2a1b0e9f") --> 7
uuid.is_valid("not-a-uuid")                          --> false
```

**The extra libraries** (`json`, `base64`, `hex`, `url`, `csv`, `decimal`, `stats`,
`template`, `bit32`, `hash`, `tpath`, `validate` and `text`) exist only where the app
offers them; see [Extra libraries](#libraries). Don't assume they're there.

**Not present**: `io`, `os`, `debug`, `package` and `coroutine`; `load`, `loadstring` and
`dofile`; `next`, `collectgarbage`, `_G`, `getfenv` and `setfenv`; `math.random` and
`string.gmatch`; `goto`; and first-class iterators of any kind. `require` and `print`
exist only where the app provides them. **The bitwise operators `& | ~ << >>` don't
exist**: they are syntax errors, found before the script runs. Use the `bit32` extra
library instead.

Some things are deliberately missing:

| Item | Why |
|---|---|
| `iter.*` (lazy iterators) | `pairs` and `ipairs` are part of the loop syntax (§5), so first-class iterators don't fit. Use the table functions that take a callback. |
| `fun.identity`, `constant`, `partial`, `compose`, `noop` | Each is a one-line Lua function; `pipe` covers composition. |
| `string.escape_pattern` | Not yet provided. |
| `class(base)` | It would need `__call`, which is refused. The class pattern in §5 does the job. |
| `serialize(value)` | Not yet provided: `inspect` is for people to read, not for loading back. |
| `module.new` | A module is a plain table and needs no helper (§6). |
| `require_optional(name)` | Use `pcall(function() return require(name) end)`: a missing module is an error you can catch. |
| `reload(name)` | Modules load once per run by design, and nothing is kept between runs to reload (§6). |
| `path.*` | Not yet provided. |
| A bytes type | Binary data stays with the app, as objects (§11), and the app's functions work on it. Strings are always text. |

## 9. Type checking

Scripts are checked before they run. The checking mode goes on the first line:

- `--!strict` (the default): unknown globals, unknown fields, wrong argument types or
  counts, and creating globals are errors. Local variables take their types from their
  first value, a local function's parameters from how they're used in arithmetic, and
  table literals are recognised as arrays or records.
- `--!nonstrict`: those findings become warnings; outright contradictions (calling a
  number) are still errors. So are an unknown field on a record whose fields are all
  known, and an unknown global that the script never assigns and CoCoCo does not
  provide, because neither can exist at run time.
- `--!nocheck`: no checking.
- `--!strict-nil`: strict, and reading an array or map element gives an *optional*
  type, so check it (`if v then …`) or give a default (`v or fallback`) before using it.
  Declared record fields don't count, because they are a promise.

<!-- example {"check": "got optional<number>"} -->
```lua
--!strict-nil
local prices: {number} = { 10, 20 }
return prices[5] + 1          -- reported: prices[5] may be nil
```

<!-- example {"returns": 1} -->
```lua
--!strict-nil
local prices: {number} = { 10, 20 }
return (prices[5] or 0) + 1   -- fine
```

Under `--!nonstrict`, softer findings (a wrong argument type, a global read before the
script assigns it) are **warnings** rather than errors, which helps when adding types
gradually, while real contradictions stay errors: calling a number, an unknown field on a
known type, or a global the script never assigns and CoCoCo does not provide (such as
`os.time()`), because that read can only fail when it runs.

**`--!doc` comments.** A `--!doc <description>` line describes what code is for, for the
people and tools that read it. In a module, a `--!doc` at the top describes the module,
and one (or several lines of them, which join up) just above an exported function or
field (`function M.fn()` or `M.fn = …`) describes that function or field. It has no
effect when the script runs.

**Type annotations.** Local variables, parameters and return values can be annotated. This
is a SwiftLua addition to Lua's syntax:

```lua
local total: number = 0
local names: {string} = {}
local maybe: integer? = nil
local rates: {[string]: number} = { GBP = 1.25 }
local function apply(amount: number, code: string): number
  return amount * rates[code]
end
apply(8, "GBP")  --> 10
```

The type forms are: a name, `T?` (optional), `{T}` (array), `{[K]: V}` (map), `{}` (the
empty record), `A | B` (either), `(T, …) -> R` (function), `(T)` (grouping) and literal
types (`"active"`, `42`, `true`, `nil`), combined freely. A function may return several
values (`(T) -> (A, B)`, `() -> ()`, `function f(): (A, B)`), checked one by one:

```lua
local id: integer | string = 1
local s: "active" | "archived" = "active"
local lookup: {[string]: number} = {}
local maybe: Status | nil = nil          -- the same as Status?
local double: (number) -> number = function(x) return x * 2 end
local callback: ((number) -> number)? = nil   -- grouping for an optional function
local function minmax(xs: {number}): (number, number)
  return math.min(xs[1], xs[2]), math.max(xs[1], xs[2])
end
double(21)  --> 42
local lo, hi = minmax({ 9, 4 })  --> lo = 4, hi = 9
```

Names other than the built-in ones (like `Status` above) are types the app provides. The
`|` and `->` symbols exist only inside annotations; they aren't operators. `->` binds
loosest: `(number) -> string | nil` is a function returning `string | nil`.

**Naming your own types.** Name a shape with `type`, including records:

```lua
type Line = { sku: string, qty: number }
type Order = { lines: {Line}, status: "open" | "closed" }
type Node = { value: number, next: Node? }     -- a type can refer to itself

local function total(order: Order): number
  local sum = 0
  for _, line in ipairs(order.lines) do sum = sum + line.qty end
  return sum
end

local chain: Node = { value = 1, next = { value = 2 } }   -- optional fields can be left out
total({ lines = { { sku = "A", qty = 4 } }, status = "closed" })  --> 4
```

A record can leave out its optional fields: a missing key is `nil`, and writing `k = nil`
stores nothing, so the two are the same.

A type name applies to the whole file from where it's declared and only affects
checking: the statement does nothing when the script runs. `type` is still the ordinary
function (`type(x)`); only the exact form `type Name = …` declares a type. Declaring a
name twice, or reusing the name of a type the app provides, is an error: the app's names
come first.

**Casts.** `expr :: T` tells the checker a type it can't work out itself:

<!-- example {"inputs": {"payload": "{\"lines\": [], \"status\": \"open\"}"}} -->
```lua
type Order = { lines: {any}, status: "open" | "closed" }
local order = json.decode(payload) :: Order   -- you know the shape; the checker doesn't
order.status  --> "open"
local x: any = 2
1 + x :: number                               --> 3
```

`::` binds tightly: `1 + x :: number` casts `x`, not the sum. A cast between unrelated
types (`5 :: string`) is an error: casts move between related types, they don't jump.
Casts only affect checking. When the script runs, the value is whatever it really is, and
every run-time rule and limit still applies, whatever a cast claims.

A cast is **not** a call: `f() :: T` in the last position gives one value. It does
**not** fill the remaining variables or return values the way a bare `f()` would (see the
rules in §4). An annotation is a promise: it takes priority over what the checker would
work out, values assigned must fit it, annotated parameters type the function body and
every call, and a return annotation applies to every `return`. Annotations only affect
checking.

**The app's names are typed.** Everything the app provides carries its real type into
checking. The app's functions are checked against their declared parameters, including
grouped ones (if the app declares `orders.fetch(id: integer)`, then
`orders.fetch("x")` is reported, however deeply the names are grouped). Data the app
provides is typed by its actual shape (with a `config` value that has a field
`retries`, `config.retires` is reported as an unknown field, including in nested tables
and array elements). Objects, enums and modules from the app bring their types too, as
described in this section and in §6 and §11. An app can still declare a deliberately
looser type for a value whose shape varies from run to run. Some of the app's functions
are **generic**: a function declared as `pick(items: {T}, chooser: (T) -> boolean): T`
gives a result typed by what you passed, and the chooser's parameter takes its type from
the elements, exactly like the built-in `table` functions.

<!-- example {"check": "type.bad-argument"} -->
```lua
local ok, order = orders.fetch("x")   -- reported: id must be an integer
```

**Enums from the app.** When the app provides an enum, your script gets a frozen table
of its values, each named after itself, and the enum's name is a type: any one of its
values.

```lua
local s: Status = Status.active
s  --> "active"
transition(s)  --> "now active"     -- the app's functions can require the type
s = Status.archived                 -- fine
s = "deleted"                       -- fine: the text is one of the values
```

<!-- example {"check": "did you mean 'active'?"} -->
```lua
local s: Status = "activ"           -- reported, listing the allowed values
```

A misspelled value (`Status.actve`) is an error, as is assigning anything that isn't one
of the values, including a plain `string` variable. The **app also checks at run time**,
even under `--!nocheck` (an `invalidArgument` error). Two rules keep enums ordinary:
enum values join, compare and take methods like the strings they are; and a local with
no annotation, set from a value (`local x = Status.active`), is a plain `string`, so
annotate it to keep the enum type.

<!-- example {"error": "expects union<\"active\"|\"archived\"|\"deleted\">, got string"} -->
```lua
--!nocheck
local s = "activ"
transition(s)                       -- stopped when the script runs
```

**Growing a table.** A local that starts as a table literal gains fields as you add them,
so the usual way of building a table is fully typed:

```lua
local cfg = {}            -- an empty record
cfg.retries = 3           -- cfg now has retries: number
cfg.label = "prod"        -- and label: string
cfg.retries  --> 3
```

<!-- example {"check": "type.unknown-field"} -->
```lua
local cfg = {}
cfg.retries = 3
return cfg.retires        -- reported: an unknown field (a typo caught)
```

```lua
local function newJob(name: string)
  local job = { name = name, id = nil }   -- id declared here, for later
  job.dueAt = "tomorrow"                  -- still being built: the field is added
  return job
end
local job = newJob("print")               -- fixed from here on
job.id = "job-1"                          -- fine: id was declared
job.id  --> "job-1"
```

Writing a new field adds it; reading a field that isn't there is an error; and writing
an existing field must fit its type (`m.weight = "heavy"` into a `number` field is an
error). A declared type is enforced strictly; a type the checker worked out allows
widening between numbers, `nil`, and changes of table shape, just as a local can be
reassigned. Everywhere else the shape is fixed: an annotation is a promise
(`local p: Point` doesn't grow), a second name for the table (`local u = t`) sees the
shape at that point, and a function sees the table as it was when it was called, so a
table returned from a function is fixed for the caller. To fill a field in later, include
it when the table is built (`id = nil`, which lets the field hold any type) or annotate it
with a type that has an optional field; the error on the write says so. Array literals stay arrays: a table mixing an
array and fields needs a declared record or map type.

A record or array literal can still be used where a **map** type is expected: a record
`{a = 1, b = 2}` fits `{[string]: number}`, and an array `{1, 2, 3}` fits
`{[integer]: number}` (each field or element must fit the map's value type). So
`table.merge({a = 1}, {b = 2})` and other functions that take maps check with plain
literals.

**Typed standard library.** The table functions that take callbacks are fully typed: the
callback's `(value, index)` parameters take their type from the elements (so
`table.map(flags, function(v) return v + 1 end)` is an error when `flags` is
`{boolean}`), results have real element types (`map` returns an array of what the
callback returns, `filter` and `sort` keep the input's element type, and `find` returns
an *optional* element, so check it), `reduce` carries the accumulator's type from its
starting value, and `group_by` and `tomap` build typed maps. Loop variables take their
type from the elements too: `for _, v in ipairs(xs)` gives `v` the element type of
`xs`. `pcall(f, …)` gives `(boolean, R | string)` where `R` is what `f` returns, so check
with `type(v)` before using it as `R` when `R` could itself be a string.

**Narrowing.** The checker follows your code. A condition refines types in the branches
it guards, and the refinement holds through the shapes Lua code really uses:

```lua
local function describe(value: integer | string): string | integer
  if type(value) == "string" then     -- value is a string here
    return value .. "!"
  else
    return value + 1                  -- and an integer here
  end
end
describe("hi")  --> "hi!"

local function bump(maybe: number?): number
  if maybe == nil then return 0 end   -- an early return
  return maybe + 1                    -- maybe is not nil from here on
end
bump(nil)  --> 0

local function add(first: number?, second: number?): number
  if first ~= nil and second ~= nil then   -- both are narrowed
    return first + second
  end
  return 0
end
add(2, 3)  --> 5

type Message = { kind: "add", amount: number } | { kind: "note", text: string }
local function handle(msg: Message): number | string
  if msg.kind == "add" then           -- testing a shared literal field
    return msg.amount                 -- picks the matching record shape
  else
    return msg.text                   -- and the other branch gets the rest
  end
end
handle({ kind = "add", amount = 4 })  --> 4
```

The forms it recognises: truthiness (`x`, `not x`); comparisons with `nil`;
`type(x) == "kind"` (including `"table"` and `"function"`); equality with a literal or
enum value (an integer literal works against a `number`: `401 == 401.0`); `and` and `or`
(the right-hand side knows what the left-hand side established, so
`if errors and #errors > 0` works); and `if <cond> then return/error end` guards (whose
opposite holds for the rest of the block). Field paths (`a.b.c`) narrow; call results and
`t[k]` don't, so put them in a local first. The right-hand side of `or` knows the
left-hand side was false, so `if res == nil or res.status ~= 401 then` works too. Assigning to a narrowed variable ends the
narrowing: the assignment is checked against the variable's *declared* type, and the
narrowed view follows the new value. A field that exists on only some members of a union
is an error until you narrow, and indexing a value that might not be a table (a `pcall`
result, say) is an error until you check it. That's the checker telling you which check
to write.

**`pcall` pairs.** `local ok, v = pcall(f)` links the two: `if ok then` narrows `v` to
what `f` returns, and the other branch (or an `if not ok then return end` guard) narrows
`v` to the error message string. Assigning to either variable unlinks them.

**Covering every case.** Branches understand which enum values they cover, cumulatively:
each `elseif` and the `else` know which values earlier branches took:

```lua
local function label(s: Status): string
  if s == Status.active then
    return "live"
  elseif s == Status.archived then
    return "kept"
  else
    return "gone: " .. s        -- s can only be "deleted" here, and the checker knows it
  end
end
label(Status.deleted)  --> "gone: deleted"
```

A branch that can never run (a repeated case, an impossible type test) gets a
**warning**, and an `if`/`elseif` chain on an enum with no `else` and missing values gets
a **warning naming the missing values**. Both are only ever warnings: add the branch or
an `else` to clear them. Once every value is covered, a defensive `else` is still allowed
and raises nothing.

The checker helps you get things right early; the rules are enforced when the script runs
regardless.

## 10. Errors and limits

`error("message")` and failed `assert`s raise errors, as does any mistake at run time
(a type mismatch, an overflow, an unknown global and so on). An error that isn't caught
ends the run, and the app receives it with the line it happened on and the calls that
led there.

**`pcall`.** `pcall(f, …)` calls `f` and returns `true` followed by all of `f`'s
results, or `false` and the error: the value passed to `error` (any value), or the
message of a run-time error. `xpcall(f, handler)` passes the error to `handler` first.
Error messages don't start with a file and line: `pcall` gives exactly the text passed to
`error`, and the position goes to the app with the error instead. One rule keeps both
safe:

- **Can be caught**: `error()` and `assert`, and mistakes in values: `typeMismatch`,
  `unknownGlobal`, `notCallable`, `invalidArgument`, `arithmeticOverflow`,
  `moduleNotFound`, `moduleError` and similar.
- **Can't be caught**: running out of a limit, or the app stopping the run. These pass
  straight *through* `pcall`: `instructionLimitExceeded`, `callDepthExceeded`,
  `memoryLimitExceeded`, `tableLimitExceeded`, `stringLimitExceeded`,
  `timeoutExceeded`, `logLimitExceeded`, `hostResourceLimitExceeded` and
  `cancelledByHost`. A script can never outlast its limits by catching them.

```lua
local ok, err = pcall(function() error({ code = 42 }) end)
ok  --> false
type(err) == "table" and err.code  --> 42

local _, plain = pcall(function() error("boom") end)
plain  --> "boom"

local fine, why = xpcall(function() error("boom", 0) end, function(e) return "handled " .. e end)
fine  --> false
why   --> "handled boom"
```

Every run has limits, set by the app: on instructions, call depth, memory, entries per
table, bytes per string, output, modules loaded and objects from the app (§11). Running
out of one ends the run with an error naming it (`instructionLimitExceeded`,
`callDepthExceeded` and so on) at a predictable point: the same script with the same
input stops at the same place every time, on every platform. Write scripts that do a
bounded amount of work: a loop over a large amount of data is stopped, not slowed down.

## 11. Objects from the app

An app can give you **objects**: a function creates one, and you call its methods with
`:`. Behind the object is something the app keeps for you, such as a document being
built or a connection.

```lua
local doc = document.new("Order 1042")   -- the app's function creates the object
doc:line("2 x widget"):line("1 x gizmo")  -- methods can chain, if the app allows
doc:render()  --> "Order 1042\n2 x widget\n1 x gizmo"
```

What an object is, precisely:

- `type(obj) == "table"`. The table is **frozen** (writing to it is an error) and holds
  an entry called `__handle`, a small sequence number, plus any fields the app chooses
  to show; that is all `pairs`, `inspect` and returned values ever see. There is no
  identity or address, ever: the same script produces the same numbers on every run.
- The real object stays **with the app**, and you reach it only through its methods.
  Binary data works the same way: it reaches your script as objects, never as raw bytes.
- **An object lasts for one run.** Objects from an earlier run mean nothing, and the app
  releases them when the run ends, whether it succeeded or failed.
- Methods can take **callbacks**, and a callback can call methods on the same object
  while the outer method is still running. This is allowed, and limited like any nesting
  (the call-depth and other limits in §10 all apply inside).
- **Errors are ordinary.** A made-up or out-of-date object, an object of the wrong kind,
  or a bad argument is an `invalidArgument` error you can catch. Creating too many
  objects runs out of a limit, which can't be caught.
- The checker knows objects' types: each kind of object has a type name (for example
  `local d: Document`), unknown methods and wrong arguments are reported, and a method
  that chains returns the same type again.
- Repeatable results depend on the app here: a method that reads a clock or the network
  gives different results on different runs, as any function from the app could.

<!-- example {"check": "type.unknown-field"} -->
```lua
local doc = document.new("Draft")
doc:lnie("typo")                         -- reported: no such method
```

## 12. Functions that wait on the outside world

Some of the app's functions do real work outside your script: reading a database,
calling a web service. To your script they look like any other call, and each one
returns a pair, **`(ok, res)`**:

```lua
local ok, order = orders.fetch(1042)   -- waits while the app fetches the order
ok  --> true
if ok and type(order) == "table" then
  #order.lines  --> 2
end

local found, err = orders.fetch(7)
found  --> false
err    --> "no such order"
```

While the app works, **your run pauses**, then carries on exactly where it left off.
There is no `await` keyword and no callback. Things to know:

- **Always `(ok, res)`.** On success `ok` is `true` and `res` is the value; on a failure
  you can recover from, `ok` is `false` and `res` is the error. If you take only one
  value (`local ok = orders.fetch(...)`), you get just the `true` or `false`.
- **Each call has its own time limit.** If a call takes too long, it is a
  `hostCallTimeout` error that you **can** catch with `pcall` and recover from. Time
  spent waiting does **not** usually count towards your script's own time limit.
- **Which functions wait is up to the app.** From your side, a function the app provides
  just works when you call it; there's nothing to switch on.

## Differences from Lua

The complete list. Each is deliberate and described above; don't "fix" code towards
standard Lua's behaviour.

**Needed for safety.**

1. **No code from text** (`load` and similar). `require` exists only when the app
   provides modules, and finds only what the app provides; it can only be called, not
   stored or passed around.
2. **No `io`, `os`, `debug` or coroutines**, and no `goto`. `print` exists only where the
   app allows it (§8), and its output is limited. Results are the same on every run unless
   the app provides a clock or randomness.
3. **Metatables honour only `__index`, `__newindex` and `__tostring`** (§5):
   `setmetatable` refuses the rest, so scripts can't redefine operators, and the app never
   runs a script's metatable function when it reads a table.
4. **Library tables are frozen.**
5. **Everything is limited**: at most 250 values per expansion (§4), a single value where a
   script returns to the app or a module exports, at most 8 tables in an
   `__index`/`__newindex` chain, and running out of a limit can't be caught, nor can the
   app stopping the run (§10).
6. **`ipairs` and `pairs` are part of the loop syntax**: a generic `for` accepts nothing
   else, and there are no first-class iterators. That's why there's no `next`; use
   `table.isempty(t)` to test for an empty table. `gmatch` is replaced by `matchall`,
   which returns an array.

**Errors where standard Lua would carry on.**

7. **Reading an undefined global is an error**, rather than giving `nil`.
8. **Integer overflow is an error**, rather than wrapping round.
9. **No automatic string-to-number conversion in arithmetic**: use `tonumber`.
10. **Strings are UTF-8 text.** String functions count bytes but never split a character,
    and `\128`–`\255` escapes aren't allowed.
11. **No bitwise operators** (`& | ~ << >>`): use the `bit32` extra library.
12. **`break` outside a loop is an error**, as are statements after `return` in a block.

**Different, but predictable.**

13. **`ipairs` fixes its number of passes when the loop starts**; `pairs` order is defined
    (the array part, then map entries in the order added) and so is traversal of a table
    that changes (§5).
14. **Table holes are defined** (§5).
15. **Error messages carry no file and line prefix**: `pcall` gives exactly `"boom"`, and
    the position goes to the app separately.
16. **`string.upper` and `string.lower` handle all letters**, not just ASCII.
17. **Some math functions can differ in the last digit between platforms** (`exp`, `log`,
    `pow` and the trigonometric functions; §8).
18. **`tostring` never shows addresses** (tables are `"table"`, functions `"function"`),
    and `inspect` is likewise repeatable and shows no identities.
19. **Method calls work without metatables** (§7): strings find methods in the string
    library, and tables by field lookup.
20. **String patterns** (the full Lua 5.4 language) are metered: a pattern that needs
    extreme backtracking ends the run, and can't be caught.

**Additions.** Type annotations (`local x: number`, `function f(a: integer): string`) are
a SwiftLua addition that plain Lua would reject. The standard library's additions beyond
Lua 5.1 (`inspect`, the table, set, string and math helpers, and the handy globals) add
to Lua rather than change it. Two differ in shape from common Lua habits:
`table.sort_by` returns a new array, and `table.partition` returns one table holding both
halves. (`table.sort` sorts in place, as in Lua.)

## Common mistakes

Each of these fails to check, stops with an error, or quietly does the wrong thing.

- **Testing a global that may not exist.** `if someGlobal then` is an error if
  `someGlobal` was never defined. Use a local with a default.
- **Using `pcall` to survive a limit.** Running out of a limit can't be caught, by
  design, so `pcall` won't rescue a runaway loop.
- **Reaching for `gmatch`, `next` or `math.random`.** None exist; §8 lists the
  alternatives where there is one.
- **Writing `a & b` or `a << n`.** The bitwise operators aren't part of the language; use
  the `bit32` library, where the app offers it.
- **Writing to a library table** (`string.trim = …`). They are frozen, so it's an error.
- **Taking one value from a function that waits** (`local res = orders.fetch(1)`). You
  get the `true` or `false`, not the data (§12).
- **Working round a missing feature.** If something genuinely can't be done here
  (coroutines, files, a library the app doesn't offer), say so rather than writing code
  that won't run.