Skip to main content

blue_lang_runtime/
docs.rs

1//! One line for every name a blue program can call without a `use`.
2//!
3//! The interpreter [`crate::interpreter`] builds binds 228 names a blue
4//! call can spell (`blue_lang_syntax::is_callable_name`, measured 2026-09-29).
5//! The reserved words among its bindings (`if`, `fn`, `case`, …) are described
6//! with the keywords, and the kebab-case rest of tatara-lisp's stdlib is
7//! unreachable, because the lexer reads `-` as an operator. Most are installed by tatara-lisp, which records no description,
8//! and the rest by this crate's own layers. This table is where blue says what
9//! each one does, in blue's calling form: `blue reference` prints it and
10//! `docs/REFERENCE.md` is rendered from it.
11//!
12//! **It cannot drift from the runtime.** `blue-lang-cli/tests/reference.rs`
13//! compares it with a live interpreter in both directions: a name the runtime
14//! binds with no row here, a row naming nothing the runtime binds, and a
15//! signature whose parameter count disagrees with the bound function's arity
16//! are each a red build. A new primitive therefore arrives with its line or
17//! does not arrive.
18//!
19//! Every line was checked against the runtime by running it, not read off a
20//! name. Where a builtin is partial or surprising the line says so, because
21//! that is what an author needs from it.
22//!
23//! # Signatures
24//!
25//! `name(a, b)` takes exactly two; `name(a[, b])` takes one or two;
26//! `name(a, more...)` takes one or more. A name that is not a function (a
27//! special form, a macro, a constant) is written the way a blue program uses
28//! it, and its arity is not checked.
29
30/// What a name is for, so a reference can group it.
31#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
32pub enum Topic {
33    /// Evaluation forms: conditionals, bindings, quoting.
34    Form,
35    /// tatara-lisp forms whose arguments are Lisp shapes; usable from blue
36    /// only where the shape is plain values.
37    Lisp,
38    Number,
39    Text,
40    List,
41    Map,
42    /// Functions that take or return functions.
43    Function,
44    /// Questions about a value's kind or sign.
45    Predicate,
46    Error,
47    Json,
48    Nix,
49    Xml,
50    File,
51    Process,
52    Environment,
53    Clock,
54    /// NATS and HTTP over TCP.
55    Network,
56    Crypto,
57    /// Delayed values.
58    Lazy,
59    /// tatara-lisp's channels and fibers.
60    Concurrency,
61    /// blue's own processes: spawn, mailboxes, links, supervision.
62    Actor,
63    /// Printing to the terminal.
64    Output,
65    /// The master verbs, dispatched on the subject's space.
66    Dispatch,
67}
68
69impl Topic {
70    /// Every topic, in the order a reference presents them.
71    pub const ALL: &'static [Topic] = &[
72        Topic::Form,
73        Topic::Number,
74        Topic::Text,
75        Topic::List,
76        Topic::Map,
77        Topic::Dispatch,
78        Topic::Function,
79        Topic::Predicate,
80        Topic::Error,
81        Topic::Json,
82        Topic::Nix,
83        Topic::Xml,
84        Topic::File,
85        Topic::Process,
86        Topic::Environment,
87        Topic::Clock,
88        Topic::Network,
89        Topic::Crypto,
90        Topic::Output,
91        Topic::Lazy,
92        Topic::Concurrency,
93        Topic::Actor,
94        Topic::Lisp,
95    ];
96
97    /// The heading a reference gives the topic.
98    #[must_use]
99    pub fn title(self) -> &'static str {
100        match self {
101            Topic::Form => "Forms",
102            Topic::Lisp => "tatara-lisp forms",
103            Topic::Number => "Numbers",
104            Topic::Text => "Strings",
105            Topic::List => "Lists",
106            Topic::Map => "Maps",
107            Topic::Function => "Functions",
108            Topic::Predicate => "Predicates",
109            Topic::Error => "Errors",
110            Topic::Json => "JSON",
111            Topic::Nix => "Nix source",
112            Topic::Xml => "XML",
113            Topic::File => "Files and paths",
114            Topic::Process => "Processes",
115            Topic::Environment => "Environment and arguments",
116            Topic::Clock => "Time",
117            Topic::Network => "Network",
118            Topic::Crypto => "Hashing and signatures",
119            Topic::Lazy => "Delayed values",
120            Topic::Concurrency => "Channels and fibers",
121            Topic::Actor => "Blue processes and supervision",
122            Topic::Output => "Terminal output",
123            Topic::Dispatch => "Master verbs",
124        }
125    }
126}
127
128/// One bound name, described.
129#[derive(Clone, Copy, Debug)]
130pub struct NameDoc {
131    pub name: &'static str,
132    pub topic: Topic,
133    /// How a blue program calls it. See the module docs for the grammar.
134    pub signature: &'static str,
135    /// One line: what it returns, and the trap in it when there is one.
136    pub doc: &'static str,
137}
138
139/// `names! { topic: "name" "signature" "doc"; … }` — one row per name, so the
140/// table reads as a table.
141macro_rules! names {
142    ($($topic:ident: $name:literal $sig:literal $doc:literal;)*) => {
143        &[$(NameDoc { name: $name, topic: Topic::$topic, signature: $sig, doc: $doc },)*]
144    };
145}
146
147/// Every name the full interpreter binds that a blue identifier can spell.
148pub const NAMES: &[NameDoc] = names! {
149    // ── forms ─────────────────────────────────────────────────────────────
150    Form: "when" "when(c, body...)" "The body's last value when c is truthy, else nil. An if with no else says the same thing.";
151    Form: "define" "define(name, value)" "Bind a name. Written name = value; the formatter prints that form.";
152    Form: "set!" "set!(name, value)" "Rebind an existing name. A name a forked process inherited is sealed and refuses it.";
153    Form: "lambda" "fn(x) … end" "The function form fn lowers to. Write fn.";
154    Form: "quasiquote" "quote … end" "What quote with unquote lowers to inside defmacro.";
155    Form: "try" "try(expr, catch(e(), handler))" "The Lisp form begin … rescue lowers to. Written `expr rescue fallback` or a begin … rescue e … end block (B0037).";
156    Form: "while" "while(c, body)" "Evaluate body while c is truthy; nil. Needs set! to make progress, so map, filter and reduce are usually the better word.";
157    Form: "comment" "comment(anything...)" "Ignores its arguments and is nil. Use # comments instead.";
158    Form: "eval" "eval(form)" "Evaluate a quoted form.";
159    Form: "macroexpand" "macroexpand(form)" "The expansion of a quoted macro call, for debugging a defmacro.";
160    Form: "delay" "delay(expr)" "A promise that evaluates expr once, on the first force.";
161    // ── tatara-lisp forms ─────────────────────────────────────────────────
162    Lisp: "let" "(let ((x 1)) …)" "Lisp binding form; its binding list has no blue spelling. In blue, write x = 1.";
163    Lisp: "letrec" "(letrec ((f …)) …)" "Lisp recursive binding; its binding list has no blue spelling. In blue, use def.";
164    Lisp: "cond" "(cond (test expr) …)" "Lisp multi-way conditional; its clauses have no blue spelling. In blue, use if … elsif … end.";
165    Lisp: "require" "(require …)" "tatara-lisp's module loader. In blue, use(\"pkg\") imports a bidama.";
166    Lisp: "provide" "(provide …)" "tatara-lisp's module export. A bidama exports every def.";
167    Lisp: "dolist" "(dolist (x xs) …)" "Lisp loop; its binding list has no blue spelling. In blue, map(fn(x) … end, xs).";
168    Lisp: "doseq" "(doseq (x xs) …)" "Same as dolist. In blue, map(fn(x) … end, xs).";
169    Lisp: "dotimes" "(dotimes (i n) …)" "Lisp counted loop. In blue, map(fn(i) … end, range(n)).";
170    Lisp: "match" "(match v (pattern expr) …)" "Lisp pattern matching; its patterns have no blue spelling. blue's case compares values and does not destructure.";
171    Lisp: "defflow" "defflow(name, f, g, more...)" "Define name as a function that pipes its argument through f, then g: defflow(slug, trim, downcase).";
172    Lisp: "defsm" "defsm(name, :initial, s, :transitions, [[from, event, to], …])" "Define a state machine. name(:send, event) moves it and returns the state; name(:current) reads it.";
173    Lisp: "defstrategy" "defstrategy(name, :variant, f, …, :default, g)" "Define name(variant, args...) dispatching to the function under that keyword, or :default.";
174    Lisp: "defvisitor" "defvisitor(name, :tag, f, …, :default, g)" "Define name(tagged) dispatching on the first element of a tagged list.";
175    Lisp: "defactor" "defactor(name, initial, behaviour)" "A single-threaded actor with a mailbox. Its step message is :step!, which blue cannot spell, so from blue it can only be told, never stepped.";
176    Lisp: "defcommand" "defcommand(bus, name, params, body)" "Register a command on a tatara-lisp command bus; params is a Lisp list.";
177    Lisp: "defquery" "defquery(bus, name, params, body)" "Register a query on a tatara-lisp command bus; params is a Lisp list.";
178    // ── numbers ───────────────────────────────────────────────────────────
179    Number: "abs" "abs(x)" "Absolute value, keeping Int or Float.";
180    Number: "min" "min(x, more...)" "The smallest argument. Takes numbers, not a list: apply(min, xs).";
181    Number: "max" "max(x, more...)" "The largest argument. Takes numbers, not a list: apply(max, xs).";
182    Number: "inc" "inc(x)" "x + 1.";
183    Number: "dec" "dec(x)" "x - 1.";
184    Number: "floor" "floor(x)" "The largest Int not above x. floor(a / b) is integer division.";
185    Number: "ceiling" "ceiling(x)" "The smallest Int not below x.";
186    Number: "round" "round(x)" "The nearest Int, halves away from zero: round(2.5) is 3, round(-2.5) is -3.";
187    Number: "truncate" "truncate(x)" "The Int part of x, toward zero: truncate(-2.7) is -2.";
188    Number: "mod" "mod(a, b)" "Euclidean remainder, never negative; what % lowers to.";
189    Number: "modulo" "modulo(a, b)" "The same as mod.";
190    Number: "rem" "rem(a, b)" "The same as mod here: mod(-7, 3), modulo and rem are all 2.";
191    Number: "gcd" "gcd(xs...)" "Greatest common divisor of the arguments; 0 for none.";
192    Number: "lcm" "lcm(xs...)" "Least common multiple of the arguments.";
193    Number: "expt" "expt(base, power)" "base raised to power: expt(2, 10) is 1024.";
194    Number: "sqrt" "sqrt(x)" "Square root, always a Float: sqrt(16) is 4.0, and 4.0 == 4.";
195    Number: "exp" "exp(x)" "e to the x, a Float.";
196    Number: "log" "log(x[, base])" "Natural logarithm, or log to base: log(100, 10) is 2.0.";
197    Number: "sin" "sin(x)" "Sine of x radians.";
198    Number: "cos" "cos(x)" "Cosine of x radians.";
199    Number: "tan" "tan(x)" "Tangent of x radians.";
200    Number: "asin" "asin(x)" "Arcsine; refuses x outside [-1, 1] rather than returning NaN.";
201    Number: "acos" "acos(x)" "Arccosine; refuses x outside [-1, 1] rather than returning NaN.";
202    Number: "atan" "atan(x)" "Arctangent in radians.";
203    Number: "atan2" "atan2(y, x)" "The angle of the point (x, y); y comes first.";
204    Number: "hypot" "hypot(x, y)" "sqrt(x * x + y * y) without the overflow: hypot(3, 4) is 5.0.";
205    Number: "to_int" "to_int(x)" "An Int from a string or a number, truncating a Float; nil for text that is not an integer (\"3.7\" too).";
206    Number: "to_int!" "to_int!(x)" "Like to_int, but raises instead of returning nil.";
207    Number: "to_float" "to_float(x)" "A Float from a number or a numeric string.";
208    Number: "compare" "compare(a, b)" "-1, 0 or 1. Total over two numbers or two strings, which < is not; mixing them raises.";
209    // ── strings ───────────────────────────────────────────────────────────
210    Text: "concat" "concat(x, more...)" "The text of every argument joined, each rendered with to_s. What interpolation lowers to.";
211    Text: "to_s" "to_s(x)" "Text for any value (okite D0005, D0006): nil is \"\", 1.0 is \"1.0\", a list or map its blue literal, a keyword its name.";
212    Text: "string" "string(x)" "tatara-lisp's rendering: :a stays \":a\" and a list prints as (1). Use to_s.";
213    Text: "upcase" "upcase(s)" "The string in upper case. Raises on nil.";
214    Text: "downcase" "downcase(s)" "The string in lower case. Raises on nil.";
215    Text: "trim" "trim(s[, opts])" "The string without leading and trailing whitespace. opts: side: :both (default), :start or :end; chars: the characters to cut instead of whitespace. An unknown option raises :bad_option.";
216    Text: "split" "split(s, sep)" "The fields between separators, empty ones kept (okite D0012): split(\"a,b,\", \",\") is [\"a\", \"b\", \"\"]. An empty sep splits into characters.";
217    Text: "join" "join(xs, sep)" "The elements' text with sep between. The LIST comes first here, and the string first in split.";
218    Text: "join_lines" "join_lines(xs)" "The elements' text, one per line, with no newline after the last: what a string with a \\n escape is written as (B0065).";
219    Text: "unlines" "unlines(xs)" "The elements' text, each followed by a newline: text that ends in a line break (B0065).";
220    Text: "line_break" "line_break" "The one-character line-break string, \"\\n\" (B0065). `newline` prints one.";
221    Text: "replace" "replace(s, from, to)" "Every occurrence of from replaced by to.";
222    Text: "chars" "chars(s)" "The characters as one-character strings.";
223    Text: "starts_with?" "starts_with?(s, prefix)" "Whether s begins with prefix.";
224    Text: "ends_with?" "ends_with?(s, suffix)" "Whether s ends with suffix.";
225    Text: "contains?" "contains?(s, sub)" "Whether the STRING s contains sub. On a list it raises: use member?(x, xs).";
226    Text: "text_index" "text_index(s, sub, from)" "The character index of the first sub in s starting at or after from, or -1. One linear pass; moji's char_index wraps it.";
227    Text: "text_last_index" "text_last_index(s, sub, from)" "The character index of the last sub in s starting at or before from, or -1. One linear pass.";
228    Text: "text_bytes" "text_bytes(s)" "The UTF-8 bytes of a string, as a list of integers 0..255: text_bytes(\"é\") is [195, 169].";
229    Text: "bytes_text" "bytes_text(bytes)" "The string whose UTF-8 bytes these are (a byte list or a string); nil when they are not UTF-8. An element outside 0..255 raises.";
230    // ── lists ─────────────────────────────────────────────────────────────
231    List: "list" "list(xs...)" "A list of the arguments. Written [a, b].";
232    List: "length" "length(seq)" "The length of a list or string. Raises on nil: retsu's size(xs) is total.";
233    List: "count" "count(x)" "The number of elements; 0 for nil.";
234    List: "nth" "nth(i, xs)" "The element at index i, from 0; nil past either end. The INDEX comes first.";
235    List: "first" "first(xs)" "The first element; raises on []. retsu's first answers nil.";
236    List: "second" "second(xs)" "The second element; raises when there is none.";
237    List: "third" "third(xs)" "The third element; raises when there is none.";
238    List: "fourth" "fourth(xs)" "The fourth element; raises when there is none.";
239    List: "last" "last(xs)" "The last element; raises on []. retsu's last answers nil.";
240    List: "rest" "rest(xs)" "All but the first; raises on []. retsu's rest answers [].";
241    List: "next" "next(xs)" "All but the first; raises on [].";
242    List: "butlast" "butlast(xs)" "All but the last; raises on [].";
243    List: "car" "car(xs)" "The first element. Prefer first.";
244    List: "cdr" "cdr(xs)" "All but the first ([] for a one-element list); raises on []. Prefer rest.";
245    List: "cons" "cons(x, tail)" "A new list with x in front. Shares tail rather than copying it, and is in place when tail is held nowhere else.";
246    List: "append" "append(lists...)" "The lists joined, in order; nil counts as []. Persistent: O(log n), sharing structure with the operands, and in place when the first list is held nowhere else (an accumulator in a tail call), so building a list one element at a time is linear.";
247    List: "take" "take(n, xs)" "The first n elements, or all of them. The COUNT comes first.";
248    List: "drop" "drop(n, xs)" "All but the first n. The COUNT comes first.";
249    List: "reverse" "reverse(seq)" "The list or string backwards. Raises on nil.";
250    List: "range" "range(a[, b][, step])" "Ints from a up to but not including b by step; range(n) is 0 to n - 1. Native, so any length is safe.";
251    List: "flatten" "flatten(xs)" "Every nested list spliced in, to any depth.";
252    List: "distinct" "distinct(xs)" "The elements with repeats removed, first occurrence kept, compared with ==.";
253    List: "frequencies" "frequencies(xs)" "A map from each element to how many times it occurs.";
254    List: "zip" "zip(xs, ys)" "[[x1, y1], [x2, y2], …], as long as the shorter list.";
255    List: "interleave" "interleave(xs, ys)" "[x1, y1, x2, y2, …], as long as the shorter list allows.";
256    List: "intersperse" "intersperse(sep, xs)" "xs with sep between each pair. The SEPARATOR comes first.";
257    List: "position" "position(x, xs)" "The index of the first element == x, or -1.";
258    List: "member?" "member?(x, xs)" "Whether some element == x. The ELEMENT comes first.";
259    List: "partition" "partition(f, xs)" "[kept, rejected]: the elements f accepts, then the rest.";
260    List: "sort_keyed" "sort_keyed(key, xs)" "A stable sort by key(x), under compare's rules; native, so safe at any length. Mixed key kinds raise.";
261    List: "iterate" "iterate(f, x, n)" "[x, f(x), f(f(x)), …], n elements.";
262    List: "repeatedly" "repeatedly(f, n)" "A list of n calls to the zero-argument function f. The FUNCTION comes first.";
263    // ── maps ──────────────────────────────────────────────────────────────
264    Map: "get" "get(m, k)" "The value at key k, or nil. Raises on nil and on a list.";
265    Map: "assoc" "assoc(m, k, v)" "A new map with k set to v; m is unchanged. Persistent: O(log n), and in place when m is held nowhere else (an accumulator in a tail call). Raises on nil.";
266    Map: "dissoc" "dissoc(m, k)" "A new map without k; unchanged when k is absent.";
267    Dispatch: "verb_for" "verb_for(space, verb, f)" "Register f as the handler of a master verb for a space. The verb is one of read, need, has?, put, update, delete, create, diff, info, exists?; another name raises :usage. Registering again replaces. Spaces call it at the top of their bidama.";
268    Dispatch: "space_for" "space_for(kind, space)" "Name the space that owns untagged values of a kind: :map, :list, :string, :int, :float, :bool, :keyword or :function. A kind already owned by another space raises :usage.";
269    Dispatch: "in_space" "in_space(space, value)" "The value as a subject of a space: {space: s, of: value}. Master verbs call the space's handler with the value itself. A value already in another space raises :usage.";
270    Dispatch: "space_of" "space_of(value)" "The space a master verb would dispatch the value to (its tag, else the owner of its kind), or nil.";
271    Dispatch: "read" "read(source, more...)" "The whole content of a source, by the source's space: in_space(\"gitto\", repo) reads a file at a ref. Raises :usage when no space owns the subject or the space has no handler.";
272    Dispatch: "need" "need(store, key, more...)" "One entry, raising :not_found when absent. A plain map goes to jisho.";
273    Dispatch: "has?" "has?(store, key, more...)" "Whether the key is present. A plain map goes to jisho.";
274    Dispatch: "put" "put(store, key, value, more...)" "Create or replace an entry; a plain map goes to jisho, which returns a new map.";
275    Dispatch: "update" "update(store, key, f, more...)" "Replace an entry with f of the old value; raises :not_found when absent. A plain map goes to jisho.";
276    Dispatch: "delete" "delete(store, key, more...)" "Make an entry absent; absent counts as done. A plain map goes to jisho; in_space(\"gitto\", repo) deletes a branch or tag.";
277    Dispatch: "create" "create(store, key, value, more...)" "Add a new entry, refusing to overwrite; in_space(\"gitto\", repo) makes a branch or tag.";
278    Dispatch: "diff" "diff(a, b, more...)" "The differences between two values as change records, by the space of a: diff(in_space(\"gitto\", repo), from, to) compares two refs.";
279    Dispatch: "info" "info(subject, more...)" "The current state of the subject as a record: in_space(\"gitto\", repo) is the repository's state.";
280    Dispatch: "exists?" "exists?(subject, more...)" "Whether the subject is there; in_space(\"gitto\", repo) asks about a ref.";
281    Map: "dig" "dig(tree, keys...)" "The value at the path of keys through nested maps and lists, or nil at the first step that is absent or cannot be entered. A list takes an Int index, negative from the end; dig(tree) is tree.";
282    Map: "bury" "bury(tree, keys..., value)" "A new tree with value at the path of keys, every missing level made as a map. Refuses with :bad_path when a value on the way is neither nil, a map nor a list, so data is never replaced silently; a list index may be one past the end; bury(tree, value) is value.";
283    Map: "zipmap" "zipmap(keys, values)" "A map pairing keys with values, as long as the shorter list.";
284    Map: "map_entries" "map_entries(m)" "Every [key, value] pair of a map, ordered by key under compare's rules; [] for nil. Mixed key kinds raise.";
285    // ── functions ─────────────────────────────────────────────────────────
286    Function: "map" "map(f, xs, more...)" "f applied to each element; with two lists, f(x, y) pairwise. The FUNCTION comes first. nil maps to [].";
287    Function: "filter" "filter(f, xs)" "The elements f accepts. The FUNCTION comes first.";
288    Function: "remove" "remove(f, xs)" "The elements f rejects.";
289    Function: "reduce" "reduce(f, [init, ]xs)" "Fold from the left: reduce(fn(acc, x) … end, 0, xs). Without init, the first element is the start.";
290    Function: "foldl" "foldl(f, init, xs)" "reduce with an init: f(acc, x) from the left.";
291    Function: "foldr" "foldr(f, init, xs)" "Fold from the right: f(x, acc).";
292    Function: "find" "find(xs, f)" "The first element f accepts, or nil. The COLLECTION comes first; nil is the empty list. find(f, xs) is still answered, because tatara-lisp's state machines and buses call it so.";
293    Function: "flat_map" "flat_map(xs, f)" "f applied to each element, the lists it returns joined in order (one level); f may return nil for none. The COLLECTION comes first.";
294    Function: "group_by" "group_by(xs, f)" "A map from f(x) to the list of the elements with that key, each list in order. The key must be a scalar. The COLLECTION comes first.";
295    Function: "some" "some(f, xs)" "true when f accepts an element, else nil (not false).";
296    Function: "any?" "any?(f, xs)" "Whether f accepts some element; false for [].";
297    Function: "every?" "every?(f, xs)" "Whether f accepts every element; true for [].";
298    Function: "apply" "apply(f, args..., xs)" "Call f with the elements of the last list as its arguments. With f a name it is written as a splat, max(*xs) (B0042); apply stays for a function computed by an expression.";
299    Function: "identity" "identity(x)" "x.";
300    Function: "const" "const(x)" "A function that ignores its argument and returns x.";
301    Function: "comp" "comp(f, g)" "The function x -> f(g(x)).";
302    Function: "compose" "compose(fs...)" "Right to left: compose(f, g)(x) is f(g(x)).";
303    Function: "pipe" "pipe(fs...)" "Left to right: pipe(f, g)(x) is g(f(x)).";
304    Function: "partial" "partial(f, args...)" "f with its first arguments fixed: partial(f, 1)(2) is f(1, 2).";
305    Function: "flip" "flip(f)" "f with its two arguments swapped.";
306    Function: "juxt" "juxt(fs...)" "The function x -> [f(x), g(x), …].";
307    Function: "tap" "tap(f, x)" "Call f(x) for its effect and return x.";
308    Function: "memoize" "memoize(f)" "f, remembering each argument's answer.";
309    Function: "decorate" "decorate(f, keys-and-values...)" "f with metadata attached; it still calls as f.";
310    Function: "visit" "visit(f, x)" "f(x).";
311    // ── predicates ────────────────────────────────────────────────────────
312    Predicate: "nil?" "nil?(x)" "Whether x is nil (okite D0010). x.nil? says the same and is the spelling blue writes (B0047).";
313    Predicate: "bool?" "bool?(x)" "Whether x is true or false.";
314    Predicate: "boolean?" "boolean?(x)" "The same as bool?.";
315    Predicate: "integer?" "integer?(x)" "Whether x is an Int; integer?(1.0) is false.";
316    Predicate: "float?" "float?(x)" "Whether x is a Float. 6 / 2 is the Int 3, while 7 / 2 is 3.5.";
317    Predicate: "number?" "number?(x)" "Whether x is an Int or a Float.";
318    Predicate: "string?" "string?(x)" "Whether x is a string.";
319    Predicate: "keyword?" "keyword?(x)" "Whether x is a keyword such as :done.";
320    Predicate: "symbol?" "symbol?(x)" "Whether x is a Lisp symbol; false for keywords.";
321    Predicate: "list?" "list?(x)" "Whether x is a list. list?(nil) is true, so test x == nil first.";
322    Predicate: "map?" "map?(x)" "Whether x is a map. A parsed JSON object is not one.";
323    Predicate: "procedure?" "procedure?(x)" "Whether x can be called.";
324    Predicate: "empty?" "empty?(xs)" "Whether xs is nil or has no elements.";
325    Predicate: "null?" "null?(x)" "Whether x is nil or []. The Lisp word; prefer retsu's is_empty.";
326    Predicate: "pair?" "pair?(x)" "Whether x is a non-empty list.";
327    Predicate: "atom?" "atom?(x)" "Whether x is a scalar rather than a list; false for nil.";
328    Predicate: "some?" "some?(x)" "Whether x is not nil.";
329    Predicate: "even?" "even?(n)" "Whether n is even.";
330    Predicate: "odd?" "odd?(n)" "Whether n is odd.";
331    Predicate: "zero?" "zero?(n)" "Whether n is 0.";
332    Predicate: "positive?" "positive?(n)" "Whether n > 0.";
333    Predicate: "negative?" "negative?(n)" "Whether n < 0.";
334    Predicate: "equal?" "equal?(a, b)" "Structural equality; what == lowers to.";
335    Predicate: "eq?" "eq?(a, b)" "Identity: true for the same keyword or string, false for two equal lists. Use ==.";
336    Predicate: "is?" "is?(x, type)" "Whether x has the type named by a keyword: is?(5, :int). Value first.";
337    Predicate: "the" "the(type, x)" "x, after asserting its type: the(:int, 5). Type first.";
338    Predicate: "cast" "cast(type, x)" "x checked against, or narrowed to, the type a keyword names: cast(:float, 5) is 5.0. Type first.";
339    Predicate: "foreign?" "foreign?(x)" "Whether x is an opaque value a host handed in.";
340    Predicate: "promise?" "promise?(x)" "Whether x is a delayed value.";
341    Predicate: "chan?" "chan?(x)" "Whether x is a channel.";
342    Predicate: "go?" "go?(x)" "Whether x is a fiber.";
343    // ── errors ────────────────────────────────────────────────────────────
344    Error: "raise" "raise(kind_or_error[, message[, opts]])" "Raise an error: raise(:not_found, \"no repo #{name}\"); opts is {data: value, cause: e}. raise(e) raises e again unchanged. Uncaught, the run prints kind: message, every cause, the stack, and exits 1. The error's fields are e.kind, e.message, e.data, e.cause and e.trace, the raise site as file:line.";
345    Error: "error" "error(kind, message[, data])" "An error VALUE, raising nothing. throw(error(k, m)) is written raise(k, m) (B0037).";
346    Error: "throw" "throw(err)" "Raise any value. Written raise(err) (B0037).";
347    Error: "error?" "error?(x)" "Whether x is an error value; true inside a catch handler.";
348    Error: "error_message" "error_message(e)" "The message of an error value. Written e.message (B0036).";
349    Error: "error_kind" "error_kind(e)" "The kind of an error value, a keyword. Written e.kind (B0036).";
350    Error: "gensym" "gensym([prefix])" "A fresh unique symbol, for macros that need a private name.";
351    Lisp: "chain_rules" "chain_rules" "Every conversion-chain rewrite the check stage, the formatter and the evaluator share, as [id, slug, outer, soundness, cases] rows; a case is [label, kind or nil, lhs, rhs] with lhs and rhs one-parameter lambda forms for eval. okite's laws evaluate both sides.";
352    Lisp: "builtin_names" "builtin_names" "Every name this reference documents, as strings: what a bare name falls to when no definition or import has it. A catalogue reads it to say which definitions share a builtin's name.";
353    // ── JSON ──────────────────────────────────────────────────────────────
354    Json: "json_parse" "json_parse(text)" "Parse JSON. An object becomes a list of [key, value] pairs, not a map; read it with json_get or deeta. null is nil.";
355    Json: "json_read" "json_read(text)" "Parse JSON with every object a map keyed by its field names, read with get and map_entries; an array is a list, null is nil.";
356    Json: "json_stringify" "json_stringify(v)" "Compact JSON text, a map's keys sorted, a keyword as its name.";
357    Json: "json_get" "json_get(obj, key)" "The field of a parsed JSON object, or nil. Raises when obj is not an object.";
358    Json: "json_get_or" "json_get_or(obj, key, default)" "The field of a parsed JSON object, or default.";
359    Nix: "nix_parse" "nix_parse(source)" "Nix source as rnix's lossless syntax tree: {root, errors}. A node is {kind, start, end, children}, a token {kind, start, end, text}; kinds are rnix's names (NODE_ATTR_SET, TOKEN_IDENT), offsets count characters, and each error is {message, start, end}. The nikkusu bidama queries it.";
360    Xml: "xml_parse" "xml_parse(text)" "Parse an XML document to its root element, a map {name, attributes, children, text}: name keeps its namespace prefix, attributes maps each name to its unescaped value, children lists the child elements, text joins the element's own character data (whitespace between child elements dropped). Comments, processing instructions and the DOCTYPE are dropped. Only the five predefined entities and character references expand: a DTD entity declaration or any other reference raises :xml_refused, so nothing is fetched or multiplied. A document that is not well-formed raises :xml_malformed. The maakuappu bidama queries it.";
361    Xml: "xml_render" "xml_render(element)" "An element map as XML text, escaped so xml_parse reads it back equal: attributes in key order, an element with no text and no children as <name/>. A name that is not an XML name, a character XML 1.0 cannot hold or a value of the wrong kind raises :xml_refused.";
362    // ── files ─────────────────────────────────────────────────────────────
363    File: "read_file" "read_file(path)" "The file's text. Raises when it cannot be read; shisutemu's read_or(path, default) does not.";
364    File: "write_file" "write_file(path, text)" "Replace the file's contents with text; nil.";
365    File: "write_program" "write_program(path, text)" "Write a blue program in its canonical form: text through the formatter, then to the file; raises when text does not parse. Every producer of a .b file writes with it (B0064).";
366    File: "append_file" "append_file(path, text)" "Add text to the end of the file, creating it; nil.";
367    File: "rename_file" "rename_file(from, to)" "Move a file, atomically on one filesystem: write a temporary beside to, then rename.";
368    File: "rm" "rm(path)" "Delete a file; raises when it is absent.";
369    File: "rm_rf" "rm_rf(path)" "Delete a file or a directory tree; nil when there is nothing.";
370    File: "mkdir" "mkdir(path)" "Create one directory; nil if it exists.";
371    File: "mkdir_p" "mkdir_p(path)" "Create a directory and its parents.";
372    File: "path_exists" "path_exists(path)" "Whether anything is at path.";
373    File: "is_file?" "is_file?(path)" "Whether path is a regular file.";
374    File: "is_dir?" "is_dir?(path)" "Whether path is a directory.";
375    File: "file_size" "file_size(path)" "The size in bytes.";
376    File: "file_mtime_ms" "file_mtime_ms(path)" "The last modification time, in Unix milliseconds.";
377    File: "ls" "ls(dir)" "The entries of a directory as full paths, sorted.";
378    File: "walk_dir" "walk_dir(root)" "Every file under root, as paths, in no fixed order: sort before comparing.";
379    File: "glob" "glob(pattern)" "Paths matching * and ** relative to the current directory. An absolute pattern finds nothing; walk_dir and filter instead.";
380    File: "path_join" "path_join(part, more...)" "The parts joined with /.";
381    File: "path_basename" "path_basename(path)" "The last component: \"b.txt\" of \"a/b.txt\".";
382    File: "path_dirname" "path_dirname(path)" "Everything before the last component.";
383    File: "path_extension" "path_extension(path)" "The extension without its dot, or nil.";
384    File: "cwd" "cwd" "The current directory.";
385    // ── processes ─────────────────────────────────────────────────────────
386    Process: "exec_capture" "exec_capture(cmd, args...)" "Run a command, no shell: [[:status, n], [:stdout, s], [:stderr, s]]. Read it with shisutemu's status_of and stdout_of.";
387    Process: "exec_check" "exec_check(cmd, args...)" "Run a command, no shell; its exit status as an Int. Output passes through.";
388    Process: "exec_ok?" "exec_ok?(cmd, args...)" "Whether a command exits 0.";
389    Process: "exec_with_stdin" "exec_with_stdin(input, cmd, args...)" "exec_capture, with input written to the command's stdin. The INPUT comes first.";
390    Process: "exec_exchange" "exec_exchange(bytes, n, ms, cmd, args...)" "One request and reply over a child's stdio, for binary protocols: bytes (Ints 0..=255) are written and stdin stays open until n bytes of stdout arrive, stdout closes or ms pass; stdin then closes, and a child still running at ms is ended. :stdout is the list of bytes read, :status -1 when the child had to be ended.";
391    Process: "exec_with_env" "exec_with_env(env, cmd, args...)" "exec_capture with extra environment variables, given as [[name, value], …].";
392    Process: "exec_into" "exec_into(env, cmd, args...)" "Replace this process with a command run with extra environment variables, given as [[name, value], …]; stdio is inherited. Returns only by raising, when the command cannot start.";
393    Process: "exit_with" "exit_with(code)" "End the program now with this exit status, after flushing stdout and stderr. A typed outcome a caller must branch on (EX_TEMPFAIL 75 for a hold) leaves this way, not as an uncaught error.";
394    Process: "sh_exec" "sh_exec(script)" "Run a script through sh; the same result as exec_capture. Prefer exec_capture, which cannot be injected into.";
395    // ── environment ───────────────────────────────────────────────────────
396    Environment: "getenv" "getenv(name[, default])" "An environment variable, or default (nil when none is given).";
397    Environment: "env_required" "env_required(name)" "An environment variable; raises, naming it, when unset.";
398    Environment: "argv" "argv" "The program's own arguments: what follows the file in blue run file -- a b.";
399    Environment: "argv_get" "argv_get(i[, default])" "One argument by index, or default.";
400    Environment: "self_exe" "self_exe" "The path of the blue running this program, or nil outside the CLI. Spawn it to run blue; a sandbox has no blue on PATH.";
401    // ── time ──────────────────────────────────────────────────────────────
402    Clock: "now" "now" "Unix time in seconds.";
403    Clock: "now_ms" "now_ms" "Unix time in milliseconds.";
404    Clock: "now_ns" "now_ns" "Unix time in nanoseconds.";
405    Clock: "now_rfc3339" "now_rfc3339" "The current UTC time as RFC 3339 text.";
406    Clock: "elapsed_since" "elapsed_since(start_ns)" "Nanoseconds since start_ns, a value of now_ns.";
407    Clock: "sleep" "sleep(seconds)" "Pause for whole seconds.";
408    Clock: "sleep_ms" "sleep_ms(ms)" "Pause for milliseconds.";
409    // ── network ───────────────────────────────────────────────────────────
410    Network: "nats_connect" "nats_connect(url[, timeout_ms])" "A NATS connection to nats://[user:pass@|token@]host[:port] (port 4222), confirmed by a PING before it returns. Plain TCP: a server requiring TLS is refused. Raises when it cannot connect.";
411    Network: "nats_publish" "nats_publish(conn, subject, payload[, reply])" "Publish a string to one subject (no wildcards); nil. With reply, the receiver answers there (a JetStream pull names its inbox this way). Also answers the server's pings, so a publish-only loop stays connected.";
412    Network: "nats_inbox" "nats_inbox(conn)" "A fresh reply subject unique to this connection, for nats_subscribe and the reply of nats_publish.";
413    Network: "nats_request" "nats_request(conn, subject, payload, timeout_ms)" "Publish and wait for one reply; the reply's payload as a string. Raises on timeout and when no one is subscribed (no responders).";
414    Network: "nats_subscribe" "nats_subscribe(conn, subject[, queue])" "A subscription to a subject, wildcards * and > allowed; with a queue group, one member of the group gets each message. Read it with nats_next_message.";
415    Network: "nats_next_message" "nats_next_message(sub, timeout_ms)" "{subject:, payload:, status:, headers:, reply:} for the next message, waiting at most timeout_ms (0 only checks); nil when none came. status is the header block's code (JetStream's 404 and 408) or nil, headers its text or nil. reply is nil unless the sender wants an answer: publish to it.";
416    Network: "nats_unsubscribe" "nats_unsubscribe(sub)" "Stop a subscription; messages already queued for it are dropped. nil.";
417    Network: "nats_close" "nats_close(conn)" "Close the connection; every later call on it raises. nil.";
418    Network: "http_request" "http_request(method, url, headers, body, timeout_ms)" "One HTTP/1.1 request to an http:// url (https is refused): {status:, headers:, body:}, headers as lower-case [name, value] pairs. headers is nil, a map or [[name, value], …]; body is nil or a string. Any status is an answer; only a failed exchange raises.";
419    Network: "tcp_connect_ms" "tcp_connect_ms(host, port, timeout_ms)" "Open one TCP connection to host:port and close it at once: the milliseconds the connect took, the handshake only (name resolution is not counted). A failed connect is an answer, not a raise: [:error, why, detail], why one of :unresolved, :timeout, :refused, :unreachable or :failed. A bad port or timeout raises.";
420    Network: "pg_connect" "pg_connect(url[, timeout_ms])" "A PostgreSQL connection to postgres://user@host[:port]/database (a password, when the server asks for one, rides in the url's userinfo after a colon), speaking the wire protocol over plain TCP: trust, password and SCRAM-SHA-256 authentication; md5 and TLS are refused by name. Raises when it cannot connect.";
421    Network: "pg_query" "pg_query(conn, sql, params)" "One statement with $1… parameters (nil, true/false, numbers or text): {columns:, rows:, command:}, each row a map from column name to value, integers, floats and booleans typed, everything else text.";
422    Network: "pg_execute" "pg_execute(conn, sql)" "A script of statements, unparameterised: the list of their command tags. Raises naming the first failure.";
423    Network: "pg_close" "pg_close(conn)" "Close the connection; every later call on it raises. nil.";
424    Network: "resp_connect" "resp_connect(host, port, password, tls[, timeout_ms])" "A RESP (Valkey, Redis) connection, authenticated with password when it is not nil and confirmed by a PING. tls true is refused by name until the runtime has a TLS transport (docs/HTTP2.md P4); tls false is allowed only for 127.0.0.1. Raises when it cannot connect.";
425    Network: "resp_command" "resp_command(conn, args)" "Send one command, a list of text or numbers, and return its reply: text, an integer, nil or a list. A server error raises.";
426    Network: "resp_pipeline" "resp_pipeline(conn, commands)" "Send every command in one write and return their replies in order. Raises naming the first server error.";
427    Network: "resp_next" "resp_next(conn, timeout_ms)" "The next reply the server pushes (a subscribed connection's messages), waiting at most timeout_ms (0 only checks what has arrived); nil when none came.";
428    Network: "resp_close" "resp_close(conn)" "Close the connection; every later call on it raises. nil.";
429    Network: "http_listen" "http_listen(addr)" "A listening HTTP/1.1 server socket on host:port (port 0 picks a free one). Raises when the address cannot be bound.";
430    Network: "http_listener_port" "http_listener_port(listener)" "The TCP port the listener is bound to.";
431    Network: "http_next_request" "http_next_request(listener, timeout_ms)" "{method:, path:, query:, headers:, body:, exchange:} for the next request, waiting at most timeout_ms (0 only checks); nil when none came. A malformed request is answered 400 and skipped. Answer it with http_respond.";
432    Network: "http_respond" "http_respond(request, status, headers, body)" "Answer a request from http_next_request once and close its connection; headers as for http_request, body nil or a string. A second answer raises. nil.";
433    // ── crypto ────────────────────────────────────────────────────────────
434    Crypto: "blake3_hex" "blake3_hex(data)" "The BLAKE3-256 hash of a string or byte list, in lowercase hex. shomei's hash_message wraps it.";
435    Crypto: "digest_hex" "digest_hex(algorithm, data)" "The digest of a string or byte list in lowercase hex. The algorithm is \"sha1\", \"sha256\", \"sha512\" or \"blake3\" (a string or a keyword); any other name raises. shomei's hash wraps it.";
436    Crypto: "hmac_hex" "hmac_hex(algorithm, key, data)" "The HMAC of data under key in lowercase hex, for \"sha1\", \"sha256\" or \"sha512\"; key and data are a string or byte list, and an empty key is allowed. blake3 and unknown names raise. shomei's hash with key: wraps it.";
437    Crypto: "bytes_equal?" "bytes_equal?(a, b)" "Whether two strings or byte lists hold the same bytes. It reads every position of the longer side whether or not an earlier byte differs. A difference in length is false. shomei's same? wraps it.";
438    Crypto: "random_bytes" "random_bytes(n)" "A list of n random byte integers (0..=255) from the operating system, n from 0 to 1048576. Raises when n is not an integer in range or the source cannot be read. Needs the entropy capability.";
439    Crypto: "ed25519_keypair" "ed25519_keypair(seed_hex)" "[secret, public] in hex for a 32-byte seed. The caller supplies the seed; random_bytes draws one.";
440    Crypto: "ed25519_sign" "ed25519_sign(secret_hex, message)" "The Ed25519 signature of a message, in hex.";
441    Crypto: "ed25519_verify" "ed25519_verify(public_hex, message, signature_hex)" "Whether the signature is valid.";
442    // ── output ────────────────────────────────────────────────────────────
443    Output: "write_stdout" "write_stdout(text)" "Write text to stdout exactly, no newline added. The way a command prints.";
444    Output: "write_stderr" "write_stderr(text)" "Write text to stderr exactly.";
445    Output: "println" "println(xs...)" "Print values with a newline, strings with their quotes. For output a person reads, use write_stdout.";
446    Output: "print" "print(x)" "Print a value and a newline, a string with its quotes.";
447    Output: "display" "display(x)" "Print a value's Lisp rendering, no newline.";
448    Output: "newline" "newline" "Print a newline.";
449    // ── delayed values ────────────────────────────────────────────────────
450    Lazy: "force" "force(p)" "The value of a promise, computed once; a non-promise is returned as is.";
451    Lazy: "cycle" "cycle(xs)" "A tatara-lisp lazy sequence repeating xs. blue's list words do not force it: take(2, cycle(xs)) is not a plain list.";
452    Lazy: "realize" "realize(lazy)" "A finite tatara-lisp lazy sequence forced into a list. On anything built from cycle it does not terminate.";
453    // ── concurrency ───────────────────────────────────────────────────────
454    Concurrency: "chan" "chan([capacity])" "A tatara-lisp channel. Its put and take words (>! and <!) have no blue spelling.";
455    Concurrency: "close!" "close!(ch)" "Close a channel.";
456    Concurrency: "drain!" "drain!(ch)" "Every value waiting in a channel, as a list.";
457    Concurrency: "go" "go(f)" "A pending fiber for a zero-argument function. Running it (go-run) has no blue spelling.";
458    // ── blue processes ────────────────────────────────────────────────────
459    Actor: "spawn" "spawn(f[, args])" "Start a process running f(args...) and return its pid. f's code and copies of the data it reaches go with it; a host handle does not, so open it inside. Processes run while the program waits (receive); the program ends when it does.";
460    Actor: "spawn_link" "spawn_link(f[, args])" "spawn, linked to the caller: an abnormal exit of either ends the other, unless it traps exits.";
461    Actor: "spawn_server" "spawn_server(handler, state)" "A process that answers each message m with state = handler(m, state). Its state is what defmigrate transforms.";
462    Actor: "send" "send(pid, msg)" "Copy msg into pid's mailbox and return msg. A function or handle is refused; a send to a process that has exited is dropped.";
463    Actor: "receive" "receive([pattern][, timeout_ms][, on_timeout])" "The oldest message matching pattern (:_ matches anything, a list or map matches by shape), waiting for one. After timeout_ms, on_timeout (default nil). Waiting forever with no possible sender raises a deadlock.";
464    Actor: "self" "self" "This process's pid. The program itself is 0.";
465    Actor: "alive?" "alive?(pid)" "true while pid is running or waiting.";
466    Actor: "link" "link(pid)" "Link to pid: an abnormal exit of either ends the other. false when pid is not alive.";
467    Actor: "unlink" "unlink(pid)" "Remove a link; true.";
468    Actor: "monitor" "monitor(pid)" "Watch pid and return a ref; its exit arrives as [:down, ref, pid, reason] ([:down, ref, pid, :noproc] if it is gone).";
469    Actor: "demonitor" "demonitor(ref)" "Stop a monitor; true.";
470    Actor: "exit" "exit(pid, reason)" "Send pid an exit signal: it ends with reason unless it traps exits (:normal ends nothing, :kill cannot be trapped). exit(self, r) raises.";
471    Actor: "trap_exit" "trap_exit(flag)" "While true, exit signals arrive as [:exit, pid, reason] messages instead of ending this process. Returns the previous flag.";
472    Actor: "supervisor" "supervisor(strategy, children[, max_restarts])" "Start children (functions, [f, args] pairs, or [:server, handler, state]) under :one_for_one, :one_for_all or :rest_for_one and return the supervisor's pid. A crashed child restarts with an empty mailbox; past max_restarts (default 3) the supervisor gives up. Each crash writes a trace for blue replay.";
473    Actor: "children" "children(sup)" "The pids a supervisor started, in order.";
474    Actor: "defmigrate" "defmigrate(target, new_fn[, transform])" "Swap the code of process target, or of every process running that function, at its next boundary without dropping its mailbox; a server's state becomes transform(state). A migration that raises leaves the old code running. Returns [[pid, :ok or [:error, why]], …].";
475};
476
477/// The row for a name, if blue describes it.
478#[must_use]
479pub fn doc_of(name: &str) -> Option<&'static NameDoc> {
480    NAMES.iter().find(|d| d.name == name)
481}
482
483#[must_use]
484pub fn signatures() -> &'static blue_lang_check::contracts::Signatures {
485    static SIGNATURES: std::sync::OnceLock<blue_lang_check::contracts::Signatures> =
486        std::sync::OnceLock::new();
487    SIGNATURES.get_or_init(|| {
488        let rows: Vec<blue_lang_check::contracts::BuiltinRow> = NAMES
489            .iter()
490            .map(|d| blue_lang_check::contracts::BuiltinRow {
491                name: d.name,
492                signature: d.signature,
493                kinded: d.topic.kinded(),
494                guard: d.topic == Topic::Predicate && !matches!(d.name, "nil?" | "some?"),
495            })
496            .collect();
497        blue_lang_check::contracts::Signatures::new(&rows)
498    })
499}
500
501impl Topic {
502    #[must_use]
503    pub const fn kinded(self) -> bool {
504        matches!(
505            self,
506            Topic::Number | Topic::Text | Topic::List | Topic::Map | Topic::Function
507        )
508    }
509}