# Subtype language reference (spec subtype-spec/1, compiler 0.0.1)

Subtype scripts declare what they may touch in a `needs [ ... ]` header and can do nothing else. Files end in `.sub`; `sub run`, `sub check`, `sub explain`, `sub fmt`, `sub test`.

## Permissions (`needs`)

- `read`: read files and list folders. Items: paths: ./dir (everything below), ./dir/*.ts (files directly in it). Example: `needs [ read [./src] ]`
- `write`: create and change files. Items: paths, as for read; write does not imply read. Example: `needs [ write [./out] ]`
- `run`: run programs; there is no shell, so | ; & > < and backticks are errors. Items: quoted commands: "git status" (and any arguments), "git commit -m *" (exactly these arguments, * is one word). Example: `needs [ run ["git status"] ]`
- `net`: reach hosts over http and https. Items: quoted hosts: "api.github.com:443", "*.example.org", "*". Example: `needs [ net ["api.github.com:443"] ]`
- `env`: read environment variables (the values are secrets). Items: quoted names: "HOME", "AWS_*". Example: `needs [ env ["HOME"] ]`
- `clock`: read the time and sleep. Items: bare word (no list). Example: `needs [ clock ]`
- `random`: random numbers. Items: bare word (no list). Example: `needs [ random ]`

Rules:

- A script can do only what its `needs` allows; anything else is an error when it is checked, and again when it runs.
- A smaller permission is a subtype of a bigger one: `read [./src/a]` fits inside `read [./src]`, `run ["git status"]` inside `run ["git"]`, one host inside `*.example.org`.
- A function with its own `needs` runs inside the smaller set, and may not ask for more than the script has.
- A capability value is never bigger than `needs`; `demote(a, b)` only hands over less.
- A library (`use`) has no `needs` of its own: its functions say what they need, and the script that calls them must allow it.
- A `Secret` cannot reach text, `print`, `log`, or a function that does not take a `Secret`.
- A path is compared after symbolic links are resolved, so a link cannot lead out of a permitted folder.

## Keywords

- `needs`: the permission header: everything the script may touch, first thing in the file
- `let`: names a value (`let mut` makes the name changeable); also starts a pattern in `if (let ...)`
- `mut`: a name that can be assigned again
- `fn`: a function (a declaration at the top level, or a value inside an expression)
- `if`: a condition in parentheses; also a guard after a `match` pattern
- `else`: the other branch of an `if`
- `for`: goes through a list, map, set or text; the item may be a pattern
- `in`: between the item and the collection of a `for`
- `while`: repeats while a condition holds
- `break`: leaves the innermost loop
- `continue`: goes on with the next round of the innermost loop
- `match`: takes a value apart with patterns; the cases must cover every value
- `or`: what to do when the left side failed: a value or a block (`error` is the failure inside)
- `return`: leaves the function (or the script, with an exit code) with a value
- `true`: the yes value
- `false`: the no value
- `type` (contextual): a new name for a type, optionally refined
- `enum` (contextual): a type with a fixed set of variants
- `error` (contextual): a kind of failure, optionally inside a family; also the failure inside an `or { }` block
- `use` (contextual): brings in another file of declarations
- `defer` (contextual): runs an expression when the block ends, however it ends
- `test` (contextual): a test block, run by `sub test`
- `where` (contextual): the condition of a refined type
- `matching` (contextual): a refined text type that must match a pattern

## Operators (lowest first)

- `or` (handle a failure): the left side, or the right side (a value or a block) when the left failed
- `??` (coalesce): the left side when it is there, else the right (for `Option` and failures)
- `||` (or): yes when either side is yes (the right is not evaluated if the left is)
- `&&` (and): yes when both sides are yes (the right is not evaluated if the left is no)
- `== !=` (equal, not equal): structural equality (records, lists, tuples, sets; 1 equals 1.0); a secret equals nothing
- `< > <= >=` (compare): orders numbers, and text by characters
- `.. ..=` (range): the list of whole numbers from a up to (or through) b
- `+ -` (add, subtract): arithmetic; `+` also joins text and lists
- `* / %` (multiply, divide, remainder): arithmetic; whole numbers overflow into an error, `/` by zero is an error
- `! -` (not, negate): turns yes into no; flips the sign of a number
- `? . ?. [] ()` (postfix): `?` passes a failure up, `.f` a field, `?.f` an optional field, `[i]` an item, `(a)` a call; no space before `[` or `(`
- `= += -= *= /= %=` (assign): changes a `mut` name, a field (`r.n = 1`) or an item (`xs[0] = 1`)

## Literals

- Int: `123  1_000  0xFF  0b1010  0o17`. a 64-bit whole number; a result that does not fit is an error, never a wrap-around
- Float: `1.5  2e9  1.5E-3`. a 64-bit fraction; a whole number is accepted where a Float is wanted
- Bool: `true  false`. yes or no
- String: `"text {expr} \n"`. text with escapes (\n \t \r \0 \" \\ \{ \} \xNN \u{N}) and `{expression}` parts; `{x:>8}` formats
- String (raw): `r"no \escapes {here}"  r#"quotes " ok"#`. text exactly as written
- String (triple): `"""several lines"""`. text that spans lines; a line break right after the opening quotes is dropped
- Path: `./src/main.ts  ../up  ~/notes  /etc/hosts  ./out/{name}.txt`. a file path written without quotes; `{expr}` parts allowed
- List: `[1, 2, 3]`. an ordered collection of one kind of value
- Record: `{ name: "ann", age: 3 }  { ..r, age: 4 }`. named fields of different types; `{ ..r, f: v }` is a copy with changes
- Tuple: `(1, "a")`. a fixed number of values of different types; `t.0` is the first
- Map: `#{ "a": 1, "b": 2 }`. text keys to one kind of value; `m["a"]` looks one up
- Set: `#[1, 2, 3]`. members once each, in the order added
- Function: `fn(a, b) { a + b }`. a function value that captures the names it uses
- Capability: `read[./src]  run["git status"]`. a permission as a value, never bigger than `needs`; `demote` makes one smaller

## Types

- `Int`: whole number (64-bit)
- `Float`: fraction; an Int is accepted where a Float is wanted
- `Bool`: true or false
- `String`: text; a Path is accepted where a String is wanted
- `Path`: a file path
- `Unit`: no value
- `Any`: any value (the checker does not look inside)
- `Never`: a function that never returns
- `List<T>`: list of T
- `Map<T>`: text keys to T
- `Set<T>`: set of T
- `Option<T>`: Some(x) or None
- `Result<T, E>`: a T, or a failure of family E (`Result<T>` means any failure)
- `Secret<T>`: a T that cannot be printed, put in text or compared with ==; not a subtype of T nor T of it
- `Task<T>`: work started with `spawn`
- `(A, B)`: tuple
- `Fn<A, B>`: a function from A to B (the last type is the result)

Failure families (a failure kind fits where its family is wanted): Failure, IoError <: Failure, NotFound <: IoError, OsDenied <: IoError, Io <: IoError, ParseError <: Failure, Parse <: ParseError, OutOfRange <: Failure, NetworkError <: Failure, NetUnavailable <: NetworkError, TaskFailed <: Failure, Cancelled <: TaskFailed, Refinement <: Failure, Assertion <: Failure.

## Built-in functions

- `print(Any...) -> Unit`: writes the values, separated by spaces, and a new line, to standard output
- `log(Any...) -> Unit`: the same, to standard error
- `read(Str) -> Result<Str, IoError>` [needs read]: the text of a file
- `exists(Str) -> Bool` [needs read]: whether a file or folder exists
- `list_dir(Str) -> Result<List<Str>, IoError>` [needs read]: the names in a folder, sorted
- `write(Str, Str) -> Result<Unit, IoError>` [needs write]: writes text to a file
- `run(Any) -> RunResult` [needs run]: runs a program (a command string or a list of words, no shell); a program that cannot start is a result with `failed` set and code 127
- `fetch(Str) -> Result<Str, NetworkError>` [needs net]: an HTTP GET; the body as text
- `env(Str) -> Result<Secret<Str>, NotFound>` [needs env]: an environment variable, as a secret
- `now() -> Int` [needs clock]: milliseconds since 1970
- `sleep(Int) -> Unit` [needs clock]: waits this many milliseconds
- `random() -> Int` [needs random]: a random non-negative integer
- `read_in(CapRead, Str) -> Result<Str, IoError>` [needs read]: reads a file inside a read capability
- `write_in(CapWrite, Str, Str) -> Result<Unit, IoError>` [needs write]: writes a file inside a write capability
- `demote(T, T) -> T`: hands over a smaller capability
- `args() -> List<Str>`: the arguments the script was run with
- `len(Any) -> Int`: the length of a string, list, map or record
- `lines(Str) -> List<Str>`: splits text into lines
- `split(Str, Str) -> List<Str>`: splits text at a separator
- `trim(Str) -> Str`: removes spaces at both ends
- `join(List<Any>, Str) -> Str`: joins items with a separator
- `contains(Any, Any) -> Bool`: whether a string contains text, or a list contains an item
- `starts_with(Str, Str) -> Bool`: whether text begins with the given text
- `ends_with(Str, Str) -> Bool`: whether text ends with the given text
- `replace(Str, Str, Str) -> Str`: replaces every occurrence
- `upper(Str) -> Str`: the text in capital letters
- `lower(Str) -> Str`: the text in small letters
- `slice(T, Int, Int) -> T`: the characters of text, or the items of a list, from the first position up to, not including, the second (negative positions count from the end); `xs[1..3]` is the same
- `slice_from(T, Int) -> T`: everything from a position on; `xs[2..]` is the same
- `slice_to(T, Int) -> T`: everything before a position; `xs[..3]` is the same
- `index_of(Str, Str) -> Option<Int>`: where text first occurs (in characters)
- `repeat(Str, Int) -> Str`: the text repeated n times
- `chars(Str) -> List<Str>`: the characters, one string each
- `str(Any) -> Str`: any value as text (a secret is refused)
- `secret_eq(Any, Any) -> Bool`: compares a secret with a secret or text
- `join_path(Str, Str) -> Path`: appends a part to a path
- `path_parent(Str) -> Str`: the folder a path is in
- `path_name(Str) -> Str`: the last part of a path
- `path_stem(Str) -> Str`: the last part without its extension
- `path_ext(Str) -> Str`: the extension without the dot
- `int(Any) -> Result<Int, ParseError>`: a number from text, or a float cut to a whole number
- `float(Any) -> Result<Float, ParseError>`: a number from text or an integer
- `abs(T) -> T`: the size of a number without its sign (an error for the smallest whole number)
- `min(T, T) -> T`: the smaller of two numbers (or two texts)
- `max(T, T) -> T`: the larger of two numbers (or two texts)
- `floor(Float) -> Int`: the whole number at or below a fraction
- `ceil(Float) -> Int`: the whole number at or above a fraction
- `round(Float) -> Int`: the nearest whole number (halves go away from zero)
- `sqrt(Float) -> Float`: the square root
- `pow(Float, Float) -> Float`: a number raised to a power
- `push(List<T>, T) -> List<T>`: a new list with the item added at the end
- `first(List<T>) -> Option<T>`: the first item, if the list has one
- `last(List<T>) -> Option<T>`: the last item, if the list has one
- `get(List<T>, Int) -> Option<T>`: the item at a position, if there is one
- `unwrap_or(Option<T>, T) -> T`: the value inside, or the fallback when there is none
- `is_some(Option<T>) -> Bool`: whether an Option holds a value
- `is_none(Option<T>) -> Bool`: whether an Option is empty
- `take(List<T>, Int) -> List<T>`: the first n items
- `drop(List<T>, Int) -> List<T>`: all but the first n items
- `reverse(List<T>) -> List<T>`: the list backwards
- `sort(List<T>) -> List<T>`: numbers or text, smallest first
- `sort_by(List<T>, Fn(T) -> Any) -> List<T>`: sorted by a key
- `range(Int, Int) -> List<Int>`: the numbers from the first up to, not including, the second
- `map(List<T>, Fn(T) -> U) -> List<U>`: a list of what a function gives for each item
- `filter(List<T>, Fn(T) -> Bool) -> List<T>`: the items a function says yes to
- `fold(List<T>, U, Fn(U, T) -> U) -> U`: combines the items one by one, starting from a value
- `any(List<T>, Fn(T) -> Bool) -> Bool`: whether a function says yes to at least one item
- `all(List<T>, Fn(T) -> Bool) -> Bool`: whether a function says yes to every item
- `find(List<T>, Fn(T) -> Bool) -> Option<T>`: the first item a function says yes to, if there is one
- `map_new() -> Map<T>`: an empty map
- `map_set(Map<T>, Str, T) -> Map<T>`: a new map with the key set
- `map_get(Map<T>, Str) -> Option<T>`: the value for a key, if there is one
- `map_has(Map<T>, Str) -> Bool`: whether a map has a key
- `map_remove(Map<T>, Str) -> Map<T>`: a map without a key
- `map_keys(Map<T>) -> List<Str>`: the keys, sorted
- `map_values(Map<T>) -> List<T>`: the values, in key order
- `json_parse(Str) -> Result<Any, ParseError>`: text to lists, maps, numbers, text, yes/no values
- `json_stringify(Any) -> Str`: a value as JSON text
- `matches(Str, Str) -> Bool`: whether text matches a pattern (letters, . * + ? [] [^] ^ $ | () {n,m} \d \w \s)
- `regex_find(Str, Str) -> Option<Str>`: the first piece of text that matches a pattern
- `regex_replace(Str, Str, Str) -> Str`: replaces every match of a pattern
- `format_time(Int) -> Str`: milliseconds since 1970 as 2026-10-12T09:30:00Z
- `assert(Bool) -> Unit`: stops with a failure when the value is false
- `assert_eq(T, T) -> Unit`: stops with a failure when the values differ
- `tuple(Any...) -> Any`: a tuple of the values: `(1, "a")` is `tuple(1, "a")`; `t.0` is its first part
- `map_from(Any...) -> Map<Any>`: a map from alternating keys and values: `#{ "a": 1 }` is `map_from("a", 1)`
- `map_entries(Map<T>) -> List<Tuple<Str, T>>`: the (key, value) pairs of a map
- `set_of(T...) -> Set<T>`: a set of the values, each once: `#[1, 2, 3]` is `set_of(1, 2, 3)`
- `set_from_list(List<T>) -> Set<T>`: the members of a list, each once
- `set_add(Set<T>, T) -> Set<T>`: a set with one more member
- `set_remove(Set<T>, T) -> Set<T>`: a set without a member
- `set_has(Set<T>, T) -> Bool`: whether a set has a member
- `set_union(Set<T>, Set<T>) -> Set<T>`: the members of either set
- `set_intersect(Set<T>, Set<T>) -> Set<T>`: the members of both sets
- `set_diff(Set<T>, Set<T>) -> Set<T>`: the members of the first set that the second does not have
- `set_to_list(Set<T>) -> List<T>`: the members as a list, in the order they were added
- `set_at(T, Any, Any) -> T`: a copy of a list, map, record or tuple with one item replaced: `xs[i] = v` is `xs = set_at(xs, i, v)`
- `enumerate(List<T>) -> List<Tuple<Int, T>>`: the items with their positions: `for (i, x in enumerate(xs))`
- `zip(List<T>, List<U>) -> List<Tuple<T, U>>`: pairs of items from two lists, as many as the shorter list has
- `insert(List<T>, Int, T) -> List<T>`: a list with an item put in at a position
- `remove_at(List<T>, Int) -> List<T>`: a list without the item at a position
- `pop_last(List<T>) -> List<T>`: a list without its last item
- `clear(T) -> T`: an empty list, map, set or text
- `extend(T, T) -> T`: two lists joined, or two maps merged (the second wins), or two sets united
- `truncate(List<T>, Int) -> List<T>`: the first n items
- `fmt_spec(Any, Str) -> Str`: a value formatted with a spec such as `>8` or `.2f`: `"{x:.2f}"` is `fmt_spec(x, ".2f")`
- `panic(Str) -> Never`: stops the script with a message
- `spawn(Fn() -> T) -> Task<T>`: starts a task; it is cancelled when the block that started it ends, unless awaited
- `await(Task<T>) -> Result<T, TaskFailed>`: waits for a task
- `cancel(Task<T>) -> Unit`: asks a task to stop

## Commands

- `sub check <file.sub> [--strict]`: find problems; --strict also fails on warnings
- `sub explain <file.sub|file.wasm> [--json]`: what the script can do, in plain words (or as data)
- `sub manifest <file.sub|file.wasm> [--centcom]`: the permission set and calls, as JSON (--centcom: an approval card and agent allow rules for Centcom)
- `sub build <file.sub> [-o out.wasm] [--wasi]`: compile to a WebAssembly module (--wasi: a WASI command module)
- `sub run <file.sub|file.wasm> [--yes] [--root DIR] [--timeout SECS] [--max-output BYTES] [--fetch-max BYTES] [--interp] [-- args...]`: compile (if needed), ask to approve the permissions, run
- `sub test <file.sub> [--yes] [--root DIR]`: run the test blocks of a script
- `sub fmt <file.sub>... [--check] [--stdout]`: one layout for every script (idempotent)
- `sub tokens <file.sub>`: the tokens, one per line (the lexer in Rust; examples/selfhost has the one in Subtype)
- `sub init [dir]`: start a project: main.sub, a library and a test
- `sub spec [--json|--markdown|--grammar]`: the language specification from the compiler's own tables

Exit codes: 0 done; 1 failed, or did not check; 2 usage error; 3 a permission was denied while running; 4 the permissions were not approved.

## Diagnostics

- E0101 a string is never closed: A `"` starts a string and needs another `"` to end it (a raw string ends with its own quote and hashes).
- E0102 a character that is not part of the language: Single `&` and `|` do not exist (use `&&` and `||`); there are no pipes, run each program with `run`. `|` only separates alternatives in a pattern.
- E0103 a number cannot be read: Whole numbers go up to 9223372036854775807; a hex, binary or octal number needs digits; a float must be finite.
- E0104 an escape in a string is not valid: Escapes are \n \t \r \0 \" \\ \{ \} \xNN (up to 7F) and \u{N} (1 to 6 hex digits).
- E0105 a `{` in a string is never closed, or is empty: `{expression}` puts a value in a string; write `\{` for a literal brace.
- E0106 strings inside `{...}` are nested too deeply: The limit is 16 levels; compute the inner value first with `let`.
- E0110 the parser expected something else: The message says what was expected and what was found.
- E0111 a bracket is never closed: Close the `(`, `[` or `{` opened earlier.
- E0112 a statement does not end where it should: Put each statement on its own line.
- E0113 semicolons do not exist: A line break ends a statement.
- E0114 `needs` must be the first thing in the file: Move the `needs [ ... ]` block above all other code.
- E0115 more than one expression inside `{...}` of a string: Only one name or expression fits in `{...}`.
- E0116 a space between a function name and `(`: Write the call with the bracket right after the name.
- E0117 a field name cannot contain `{...}`: Field names are fixed text.
- E0118 a pattern, file path or test name cannot contain `{...}`: These are fixed text.
- E0121 a permission must be written out in full: Permissions are fixed text, so a reader can approve them without running the script.
- E0122 a name where a permission item is needed: Names are only allowed in a function's own `needs`, for its parameters.
- E0123 not a permission: The permissions are read, write, run, net, env, clock and random.
- E0124 a permission that takes no list: `clock` and `random` are bare words.
- E0130 a declaration in a place where it is not allowed: Functions, types, enums, error kinds, `use` and tests go at the top level.
- E0131 the condition of `if` or `while` is not in parentheses: Write `if (condition) { ... }`.
- E0140 a range with no ends: Write at least one end: `1..`, `..9`, `1..=9`.
- E0141 a range that is empty: The first end is after the second.
- E0142 a variant name must start with a capital letter: Capital letters tell variants from names.
- E0150 `break` or `continue` outside a loop: They only make sense inside `for` or `while`; use `return` to leave a function.
- E0151 this cannot be assigned to: Assign to a name, a field (`r.n = 1`) or an item (`xs[0] = 1`).
- E0152 `..` must be last in a list pattern: Write `[first, second, ..rest]`.
- E0170 not a library path: A path in `use` starts with ./ or ../ and ends in .sub.
- E0171 a library cannot be read: Check the path, relative to the file with the `use`.
- E0172 too many files: At most 64 files are used by one script.
- E0173 a library has a `needs` block: Put `needs` on the functions that need something; the script that calls them must allow it.
- E0174 a library holds a statement that runs: A library holds declarations only.
- E0199 nested too deeply: The source nests deeper than the compiler reads.
- E0201 a call is not allowed by `needs`: Add the permission to `needs`, or remove the call.
- E0202 a shell operator in a command: `run` has no shell: `|`, `;`, `&`, `>`, `<`, backticks and `$(` have no meaning. Give the program and its arguments, or run each program on its own.
- E0203 a command that is empty or cannot be read: A command is a program and its arguments.
- E0204 a URL that cannot be used: Only http and https URLs with a host are fetched.
- E0210 a permission item that is not valid: The message says what is wrong with the path, command or host.
- E0211 the wrong kind of item for a permission: `run`, `net` and `env` take quoted text; `read` and `write` take paths.
- E0220 a capability value is bigger than `needs` allows: A capability can only be made from what the script was given.
- E0221 a call needs more than is allowed here: The function called asks for a permission that the caller's set does not include.
- E0222 `demote` must hand over less: The second capability must be inside the first.
- E0223 a function asks for more than the script has: A function's `needs` must fit inside the script's.
- E0224 a capability value holds exactly one item: Make one capability per item.
- E0225 a name in `needs` that is not a parameter: The names in a function's `needs` must be its parameters.
- E0301 a secret in a string: Secrets cannot be put into text.
- E0302 a secret passed to a function that prints: `print`, `log` and the like never take a secret.
- E0303 a secret passed to a parameter that is not a Secret: Declare the parameter `Secret<String>`.
- E0310 a name is not defined: Declare it with `let`; the message suggests close names.
- E0311 there is no such function: The message suggests close names.
- E0312 the wrong number of arguments: The message says how many are wanted.
- E0313 a built-in cannot be redefined, or a function is defined twice: Choose another name.
- E0314 a name declared with `let` cannot be changed: Declare it with `let mut`.
- E0501 not supported by the WebAssembly backend: The interpreter accepts it; the compiled module does not yet.
- E0601 not a type: The message suggests close names.
- E0602 a type name is used twice: Choose another name.
- E0603 not a failure family: A failure kind can be inside a family declared with `error`.
- E0604 a variant name is used twice: Variant names are unique in a program.
- E0610 a value of the wrong type: The message says what was expected and what was found, and how to convert.
- E0611 two branches give different types: Make `if`, `match`, `or` and `??` give one kind of value.
- E0612 a function returns on some paths only: Return a value on every path, or on none.
- E0613 no such field: The message lists the fields.
- E0620 `?` on a value that cannot fail: Remove the `?`.
- E0621 `?` in a function declared not to fail: Declare the failure in the return type, or handle it with `or`.
- E0630 an operator on values it does not work with: The message says what the operator needs.
- E0640 a pattern that does not fit the value: A case must be about the kind of value being matched.
- E0641 a variant pattern with the wrong number of parts: Name every value of the variant, or use `_`.
- E0642 a `match` that does not cover every value: The message names a missing case. Add it, or end with `_ => ...`. A case with an `if` condition does not count.
- E0643 alternatives of a pattern bind different names: Bind the same names in each `|` alternative.
- E0644 a `let` pattern that may not fit: Use `if (let ...)` or `match` for a pattern that may not fit.
- E0645 a format that is not valid: Write `{value:[[fill]align][sign][#][0][width][,][.precision][type]}`.
- E0650 a literal that does not fit a refined type: The type's condition is in the message; convert a computed value with `Port(x)`.
- E0651 a refinement pattern that is not valid: The pattern after `matching` must be a valid regular expression.
- W0203 a permission listed twice: Remove the repeat.
- W0204 an item listed twice: Remove the repeat.
- W0205 an item already covered by a wider one: Remove the narrower item.
- W0401 a permission the script never uses: Ask for less.
- W0402 an item of a permission that is never used: Remove it.
- W0403 a permission that is broader than it looks: `run ["sh"]`, `net ["*"]` and paths outside the project allow far more than they say.
- W0601 a failure used as a value without being handled: It stops the script when it happens. Add `?` or `or`.
- W0602 `or` or `??` on a value that cannot fail or be empty: Remove the `or` or `??` part.
- W0603 the failure of a call is ignored: Add `?`, or `or { ... }` to say what to do.
- W0642 a case that can never be reached: An earlier case already matches everything.
- R0100 the script stopped while running: A step failed and nobody handled it, a pattern did not fit, a number was too large, or a limit was reached.
- R0101 a permission was denied while running: The script tried something its `needs` does not allow; exit code 3.

## Grammar

```ebnf
program   = [ needs NL ] { toplevel NL } ;
needs     = "needs" "[" [ entry { ( "," | NL ) entry } [ "," ] ] "]" ;
entry     = ( "read" | "write" | "run" | "net" | "env" ) list
          | ( "clock" | "random" ) [ list ] ;                 (* bare = granted; [] = none *)
list      = "[" [ item { "," item } [ "," ] ] "]" ;
item      = PATH | STRING | IDENT ;                             (* IDENT only in a function's needs *)
toplevel  = stmt | decl | "use" STRING | "test" STRING block ;   (* `use`, decl and `test` at the top only *)
decl      = "type" IDENT "=" type [ "where" range | "where" "len" range | "matching" STRING ]
          | "enum" IDENT "{" variant { "," variant } "}"
          | "error" IDENT [ "(" param { "," param } ")" ] [ "<:" IDENT ] ;
variant   = UPPER-IDENT [ "(" type { "," type } ")" ] ;
range     = INT ".." [ "=" ] INT | INT ".." | ".." [ "=" ] INT ;
stmt      = ( "let" [ "mut" ] | "mut" ) ( IDENT [ ":" type ] | pattern [ ":" type ] ) "=" expr
          | target ( "=" | "+=" | "-=" | "*=" | "/=" | "%=" ) expr   (* target = IDENT { "." IDENT | "[" expr "]" } *)
          | "for" "(" ( IDENT | pattern { "," pattern } ) "in" expr ")" block
          | "while" "(" ( expr | "let" pattern "=" expr ) ")" block
          | "break" | "continue"
          | "return" [ expr ]
          | "defer" expr
          | "fn" IDENT "(" [ param { "," param } [ "," ] ] ")" { "needs" needs-list | "->" type } block
          | expr ;
param     = IDENT [ ":" type ] ;
type      = IDENT [ ( "<" | "[" ) type { "," type } ( ">" | "]" ) ] | "(" type { "," type } ")" ;
block     = "{" { stmt NL } "}" ;
expr      = or-else ;
or-else   = coalesce { "or" ( block | coalesce ) } ;            (* `error` is the failure inside a block:
                                                                  error.kind, error.message *)
coalesce  = logic-or [ "??" coalesce ] ;
logic-or  = logic-and { "||" logic-and } ;
logic-and = equality { "&&" equality } ;
equality  = compare { ( "==" | "!=" ) compare } ;
compare   = range-ex { ( "<" | ">" | "<=" | ">=" ) range-ex } ;
range-ex  = additive [ ( ".." | "..=" ) additive ] ;            (* a..b is range(a, b) *)
additive  = term { ( "+" | "-" ) term } ;
term      = unary { ( "*" | "/" | "%" ) unary } ;
unary     = ( "!" | "-" ) unary | postfix ;
postfix   = primary { "?" | "." ( IDENT | INT ) [ "(" args ")" ] | "?." IDENT | "[" index "]" | "(" args ")" } ;
                                    (* "[" and "(" with no space before; `x.f(a)` is `f(x, a)` *)
index     = expr | [ additive ] ( ".." | "..=" ) [ additive ] ;  (* a slice *)
primary   = INT | FLOAT | "true" | "false" | STRING | PATH | IDENT [ "(" args ")" ]
          | ( "read" | "write" | "run" | "net" | "env" ) list   (* a capability value, "[" glued *)
          | "(" expr ")" | "(" expr "," { expr "," } ")"          (* a tuple *)
          | "[" [ args ] "]" | record
          | "#" "{" [ expr ":" expr { "," expr ":" expr } ] "}"   (* a map *)
          | "#" "[" [ args ] "]"                                  (* a set *)
          | "fn" "(" [ param { "," param } ] ")" [ "->" type ] block      (* a closure *)
          | "if" "(" ( expr | "let" pattern "=" expr ) ")" block [ "else" ( if | block ) ]
          | "match" "(" expr ")" "{" { pattern [ "if" expr ] "=>" ( block | expr ) ( "," | NL ) } "}" ;
record    = "{" [ IDENT ":" expr { "," IDENT ":" expr } ] "}" | "{" ".." expr { "," IDENT ":" expr } "}" ;
pattern   = alt { "|" alt } ;
alt       = "_" | IDENT | INT | "-" INT | INT ( ".." | "..=" ) INT | STRING | "true" | "false"
          | UPPER-IDENT [ "(" pattern { "," pattern } ")" ]
          | "(" pattern { "," pattern } ")"                       (* a tuple *)
          | "[" [ pattern { "," pattern } ] [ ".." [ IDENT ] ] "]"  (* a list, with a rest *)
          | "{" IDENT [ ":" pattern ] { "," IDENT [ ":" pattern ] } [ ".." ] "}" ;
STRING    = '"' { char | "\n" | "\t" | "\"" | "\\" | "\{" | "\}" | "\xNN" | "\u{N}"
                | "{" expr [ ":" spec ] "}" } '"'
          | 'r' { '#' } '"' { any } '"' { '#' } | '"""' { any } '"""' ;
PATH      = ( "./" | "../" | "~/" | "/" ) { path-char | "{" expr "}" } | "." | ".." ;
INT       = digits | "0x" hex | "0b" bin | "0o" oct ;  (* with _ between digits *)
FLOAT     = digits "." digits [ exp ] | digits exp ;
```
