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    File,
49    Process,
50    Environment,
51    Clock,
52    Crypto,
53    /// Delayed values.
54    Lazy,
55    /// tatara-lisp's channels and fibers.
56    Concurrency,
57    /// Printing to the terminal.
58    Output,
59}
60
61impl Topic {
62    /// Every topic, in the order a reference presents them.
63    pub const ALL: &'static [Topic] = &[
64        Topic::Form,
65        Topic::Number,
66        Topic::Text,
67        Topic::List,
68        Topic::Map,
69        Topic::Function,
70        Topic::Predicate,
71        Topic::Error,
72        Topic::Json,
73        Topic::File,
74        Topic::Process,
75        Topic::Environment,
76        Topic::Clock,
77        Topic::Crypto,
78        Topic::Output,
79        Topic::Lazy,
80        Topic::Concurrency,
81        Topic::Lisp,
82    ];
83
84    /// The heading a reference gives the topic.
85    #[must_use]
86    pub fn title(self) -> &'static str {
87        match self {
88            Topic::Form => "Forms",
89            Topic::Lisp => "tatara-lisp forms",
90            Topic::Number => "Numbers",
91            Topic::Text => "Strings",
92            Topic::List => "Lists",
93            Topic::Map => "Maps",
94            Topic::Function => "Functions",
95            Topic::Predicate => "Predicates",
96            Topic::Error => "Errors",
97            Topic::Json => "JSON",
98            Topic::File => "Files and paths",
99            Topic::Process => "Processes",
100            Topic::Environment => "Environment and arguments",
101            Topic::Clock => "Time",
102            Topic::Crypto => "Hashing and signatures",
103            Topic::Lazy => "Delayed values",
104            Topic::Concurrency => "Channels and fibers",
105            Topic::Output => "Terminal output",
106        }
107    }
108}
109
110/// One bound name, described.
111#[derive(Clone, Copy, Debug)]
112pub struct NameDoc {
113    pub name: &'static str,
114    pub topic: Topic,
115    /// How a blue program calls it. See the module docs for the grammar.
116    pub signature: &'static str,
117    /// One line: what it returns, and the trap in it when there is one.
118    pub doc: &'static str,
119}
120
121/// `names! { topic: "name" "signature" "doc"; … }` — one row per name, so the
122/// table reads as a table.
123macro_rules! names {
124    ($($topic:ident: $name:literal $sig:literal $doc:literal;)*) => {
125        &[$(NameDoc { name: $name, topic: Topic::$topic, signature: $sig, doc: $doc },)*]
126    };
127}
128
129/// Every name the full interpreter binds that a blue identifier can spell.
130pub const NAMES: &[NameDoc] = names! {
131    // ── forms ─────────────────────────────────────────────────────────────
132    Form: "and" "and(a, b, more...)" "The last value if every one is truthy, else the first falsy one; stops early. The operator form is a && b.";
133    Form: "or" "or(a, b, more...)" "The first truthy value, else the last; stops early. The operator form is a || b.";
134    Form: "not" "not(x)" "true when x is false or nil. Written !x.";
135    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.";
136    Form: "define" "define(name, value)" "Bind a name. Written name = value; the formatter prints that form.";
137    Form: "set!" "set!(name, value)" "Rebind an existing name. A name a forked process inherited is sealed and refuses it.";
138    Form: "begin" "begin(a, b, more...)" "Evaluate in order; the value is the last. A def body already does this.";
139    Form: "lambda" "fn(x) … end" "The function form fn lowers to. Write fn.";
140    Form: "quasiquote" "quote … end" "What quote with unquote lowers to inside defmacro.";
141    Form: "try" "try(expr, catch(e(), handler))" "The value of expr, or of handler when expr throws. The binding is written e(). The kind and message of e cannot be read.";
142    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.";
143    Form: "comment" "comment(anything...)" "Ignores its arguments and is nil. Use # comments instead.";
144    Form: "eval" "eval(form)" "Evaluate a quoted form.";
145    Form: "macroexpand" "macroexpand(form)" "The expansion of a quoted macro call, for debugging a defmacro.";
146    Form: "delay" "delay(expr)" "A promise that evaluates expr once, on the first force.";
147    // ── tatara-lisp forms ─────────────────────────────────────────────────
148    Lisp: "let" "(let ((x 1)) …)" "Lisp binding form; its binding list has no blue spelling. In blue, write x = 1.";
149    Lisp: "letrec" "(letrec ((f …)) …)" "Lisp recursive binding; its binding list has no blue spelling. In blue, use def.";
150    Lisp: "cond" "(cond (test expr) …)" "Lisp multi-way conditional; its clauses have no blue spelling. In blue, use if … elsif … end.";
151    Lisp: "require" "(require …)" "tatara-lisp's module loader. In blue, use(\"pkg\") imports a bidama.";
152    Lisp: "provide" "(provide …)" "tatara-lisp's module export. A bidama exports every def.";
153    Lisp: "dolist" "(dolist (x xs) …)" "Lisp loop; its binding list has no blue spelling. In blue, map(fn(x) … end, xs).";
154    Lisp: "doseq" "(doseq (x xs) …)" "Same as dolist. In blue, map(fn(x) … end, xs).";
155    Lisp: "dotimes" "(dotimes (i n) …)" "Lisp counted loop. In blue, map(fn(i) … end, range(n)).";
156    Lisp: "match" "(match v (pattern expr) …)" "Lisp pattern matching; its patterns have no blue spelling. blue's case compares values and does not destructure.";
157    Lisp: "defflow" "defflow(name, f, g, more...)" "Define name as a function that pipes its argument through f, then g: defflow(slug, trim, downcase).";
158    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.";
159    Lisp: "defstrategy" "defstrategy(name, :variant, f, …, :default, g)" "Define name(variant, args...) dispatching to the function under that keyword, or :default.";
160    Lisp: "defvisitor" "defvisitor(name, :tag, f, …, :default, g)" "Define name(tagged) dispatching on the first element of a tagged list.";
161    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.";
162    Lisp: "defcommand" "defcommand(bus, name, params, body)" "Register a command on a tatara-lisp command bus; params is a Lisp list.";
163    Lisp: "defquery" "defquery(bus, name, params, body)" "Register a query on a tatara-lisp command bus; params is a Lisp list.";
164    // ── numbers ───────────────────────────────────────────────────────────
165    Number: "abs" "abs(x)" "Absolute value, keeping Int or Float.";
166    Number: "min" "min(x, more...)" "The smallest argument. Takes numbers, not a list: apply(min, xs).";
167    Number: "max" "max(x, more...)" "The largest argument. Takes numbers, not a list: apply(max, xs).";
168    Number: "inc" "inc(x)" "x + 1.";
169    Number: "dec" "dec(x)" "x - 1.";
170    Number: "floor" "floor(x)" "The largest Int not above x. floor(a / b) is integer division.";
171    Number: "ceiling" "ceiling(x)" "The smallest Int not below x.";
172    Number: "round" "round(x)" "The nearest Int, halves away from zero: round(2.5) is 3, round(-2.5) is -3.";
173    Number: "truncate" "truncate(x)" "The Int part of x, toward zero: truncate(-2.7) is -2.";
174    Number: "mod" "mod(a, b)" "Euclidean remainder, never negative; what % lowers to.";
175    Number: "modulo" "modulo(a, b)" "The same as mod.";
176    Number: "rem" "rem(a, b)" "The same as mod here: mod(-7, 3), modulo and rem are all 2.";
177    Number: "gcd" "gcd(xs...)" "Greatest common divisor of the arguments; 0 for none.";
178    Number: "lcm" "lcm(xs...)" "Least common multiple of the arguments.";
179    Number: "expt" "expt(base, power)" "base raised to power: expt(2, 10) is 1024.";
180    Number: "sqrt" "sqrt(x)" "Square root, always a Float: sqrt(16) is 4.0, and 4.0 == 4.";
181    Number: "exp" "exp(x)" "e to the x, a Float.";
182    Number: "log" "log(x[, base])" "Natural logarithm, or log to base: log(100, 10) is 2.0.";
183    Number: "sin" "sin(x)" "Sine of x radians.";
184    Number: "cos" "cos(x)" "Cosine of x radians.";
185    Number: "tan" "tan(x)" "Tangent of x radians.";
186    Number: "asin" "asin(x)" "Arcsine; refuses x outside [-1, 1] rather than returning NaN.";
187    Number: "acos" "acos(x)" "Arccosine; refuses x outside [-1, 1] rather than returning NaN.";
188    Number: "atan" "atan(x)" "Arctangent in radians.";
189    Number: "atan2" "atan2(y, x)" "The angle of the point (x, y); y comes first.";
190    Number: "hypot" "hypot(x, y)" "sqrt(x * x + y * y) without the overflow: hypot(3, 4) is 5.0.";
191    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).";
192    Number: "to_int!" "to_int!(x)" "Like to_int, but raises instead of returning nil.";
193    Number: "to_float" "to_float(x)" "A Float from a number or a numeric string.";
194    Number: "compare" "compare(a, b)" "-1, 0 or 1. Total over two numbers or two strings, which < is not; mixing them raises.";
195    // ── strings ───────────────────────────────────────────────────────────
196    Text: "concat" "concat(x, more...)" "The text of every argument joined, each rendered with to_s. What interpolation lowers to.";
197    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.";
198    Text: "string" "string(x)" "tatara-lisp's rendering: :a stays \":a\" and a list prints as (1). Use to_s.";
199    Text: "upcase" "upcase(s)" "The string in upper case. Raises on nil.";
200    Text: "downcase" "downcase(s)" "The string in lower case. Raises on nil.";
201    Text: "trim" "trim(s)" "The string without leading and trailing whitespace.";
202    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.";
203    Text: "join" "join(xs, sep)" "The elements' text with sep between. The LIST comes first here, and the string first in split.";
204    Text: "replace" "replace(s, from, to)" "Every occurrence of from replaced by to.";
205    Text: "chars" "chars(s)" "The characters as one-character strings.";
206    Text: "starts_with?" "starts_with?(s, prefix)" "Whether s begins with prefix.";
207    Text: "ends_with?" "ends_with?(s, suffix)" "Whether s ends with suffix.";
208    Text: "contains?" "contains?(s, sub)" "Whether the STRING s contains sub. On a list it raises: use member?(x, xs).";
209    // ── lists ─────────────────────────────────────────────────────────────
210    List: "list" "list(xs...)" "A list of the arguments. Written [a, b].";
211    List: "length" "length(xs)" "The length of a list or string. Raises on nil: retsu's size(xs) is total.";
212    List: "count" "count(xs)" "The number of elements; 0 for nil.";
213    List: "nth" "nth(i, xs)" "The element at index i, from 0; nil past either end. The INDEX comes first.";
214    List: "first" "first(xs)" "The first element; raises on []. retsu's first answers nil.";
215    List: "second" "second(xs)" "The second element; raises when there is none.";
216    List: "third" "third(xs)" "The third element; raises when there is none.";
217    List: "fourth" "fourth(xs)" "The fourth element; raises when there is none.";
218    List: "last" "last(xs)" "The last element; raises on []. retsu's last answers nil.";
219    List: "rest" "rest(xs)" "All but the first; raises on []. retsu's rest answers [].";
220    List: "next" "next(xs)" "All but the first; raises on [].";
221    List: "butlast" "butlast(xs)" "All but the last; raises on [].";
222    List: "car" "car(xs)" "The first element. Prefer first.";
223    List: "cdr" "cdr(xs)" "All but the first ([] for a one-element list); raises on []. Prefer rest.";
224    List: "cons" "cons(x, xs)" "A new list with x in front. Copies xs.";
225    List: "append" "append(lists...)" "The lists joined, in order; nil counts as []. Copies, so growing a list one element at a time is quadratic.";
226    List: "take" "take(n, xs)" "The first n elements, or all of them. The COUNT comes first.";
227    List: "drop" "drop(n, xs)" "All but the first n. The COUNT comes first.";
228    List: "reverse" "reverse(xs)" "The list or string backwards. Raises on nil.";
229    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.";
230    List: "flatten" "flatten(xs)" "Every nested list spliced in, to any depth.";
231    List: "distinct" "distinct(xs)" "The elements with repeats removed, first occurrence kept, compared with ==.";
232    List: "frequencies" "frequencies(xs)" "A map from each element to how many times it occurs.";
233    List: "zip" "zip(xs, ys)" "[[x1, y1], [x2, y2], …], as long as the shorter list.";
234    List: "interleave" "interleave(xs, ys)" "[x1, y1, x2, y2, …], as long as the shorter list allows.";
235    List: "intersperse" "intersperse(sep, xs)" "xs with sep between each pair. The SEPARATOR comes first.";
236    List: "position" "position(x, xs)" "The index of the first element == x, or -1.";
237    List: "member?" "member?(x, xs)" "Whether some element == x. The ELEMENT comes first.";
238    List: "partition" "partition(f, xs)" "[kept, rejected]: the elements f accepts, then the rest.";
239    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.";
240    List: "iterate" "iterate(f, x, n)" "[x, f(x), f(f(x)), …], n elements.";
241    List: "repeatedly" "repeatedly(f, n)" "A list of n calls to the zero-argument function f. The FUNCTION comes first.";
242    // ── maps ──────────────────────────────────────────────────────────────
243    Map: "get" "get(m, k)" "The value at key k, or nil. Raises on nil and on a list.";
244    Map: "assoc" "assoc(m, k, v)" "A new map with k set to v. Copies m; raises on nil.";
245    Map: "dissoc" "dissoc(m, k)" "A new map without k; unchanged when k is absent.";
246    Map: "zipmap" "zipmap(keys, values)" "A map pairing keys with values, as long as the shorter list.";
247    // ── functions ─────────────────────────────────────────────────────────
248    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 [].";
249    Function: "filter" "filter(f, xs)" "The elements f accepts. The FUNCTION comes first.";
250    Function: "remove" "remove(f, xs)" "The elements f rejects.";
251    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.";
252    Function: "foldl" "foldl(f, init, xs)" "reduce with an init: f(acc, x) from the left.";
253    Function: "foldr" "foldr(f, init, xs)" "Fold from the right: f(x, acc).";
254    Function: "find" "find(f, xs)" "The first element f accepts, or nil.";
255    Function: "some" "some(f, xs)" "true when f accepts an element, else nil (not false).";
256    Function: "any?" "any?(f, xs)" "Whether f accepts some element; false for [].";
257    Function: "every?" "every?(f, xs)" "Whether f accepts every element; true for [].";
258    Function: "apply" "apply(f, args..., xs)" "Call f with the elements of the last list as its arguments: apply(max, [3, 1]).";
259    Function: "identity" "identity(x)" "x.";
260    Function: "const" "const(x)" "A function that ignores its argument and returns x.";
261    Function: "comp" "comp(f, g)" "The function x -> f(g(x)).";
262    Function: "compose" "compose(fs...)" "Right to left: compose(f, g)(x) is f(g(x)).";
263    Function: "pipe" "pipe(fs...)" "Left to right: pipe(f, g)(x) is g(f(x)).";
264    Function: "partial" "partial(f, args...)" "f with its first arguments fixed: partial(f, 1)(2) is f(1, 2).";
265    Function: "flip" "flip(f)" "f with its two arguments swapped.";
266    Function: "juxt" "juxt(fs...)" "The function x -> [f(x), g(x), …].";
267    Function: "tap" "tap(f, x)" "Call f(x) for its effect and return x.";
268    Function: "memoize" "memoize(f)" "f, remembering each argument's answer.";
269    Function: "decorate" "decorate(f, keys-and-values...)" "f with metadata attached; it still calls as f.";
270    Function: "visit" "visit(f, x)" "f(x).";
271    // ── predicates ────────────────────────────────────────────────────────
272    Predicate: "nil?" "nil?(x)" "Whether x is nil (okite D0010). x == nil says the same.";
273    Predicate: "bool?" "bool?(x)" "Whether x is true or false.";
274    Predicate: "boolean?" "boolean?(x)" "The same as bool?.";
275    Predicate: "integer?" "integer?(x)" "Whether x is an Int; integer?(1.0) is false.";
276    Predicate: "float?" "float?(x)" "Whether x is a Float. 6 / 2 is the Int 3, while 7 / 2 is 3.5.";
277    Predicate: "number?" "number?(x)" "Whether x is an Int or a Float.";
278    Predicate: "string?" "string?(x)" "Whether x is a string.";
279    Predicate: "keyword?" "keyword?(x)" "Whether x is a keyword such as :done.";
280    Predicate: "symbol?" "symbol?(x)" "Whether x is a Lisp symbol; false for keywords.";
281    Predicate: "list?" "list?(x)" "Whether x is a list. list?(nil) is true, so test x == nil first.";
282    Predicate: "map?" "map?(x)" "Whether x is a map. A parsed JSON object is not one.";
283    Predicate: "procedure?" "procedure?(x)" "Whether x can be called.";
284    Predicate: "empty?" "empty?(xs)" "Whether xs is nil or has no elements.";
285    Predicate: "null?" "null?(x)" "Whether x is nil or []. The Lisp word; prefer retsu's is_empty.";
286    Predicate: "pair?" "pair?(x)" "Whether x is a non-empty list.";
287    Predicate: "atom?" "atom?(x)" "Whether x is a scalar rather than a list; false for nil.";
288    Predicate: "some?" "some?(x)" "Whether x is not nil.";
289    Predicate: "even?" "even?(n)" "Whether n is even.";
290    Predicate: "odd?" "odd?(n)" "Whether n is odd.";
291    Predicate: "zero?" "zero?(n)" "Whether n is 0.";
292    Predicate: "positive?" "positive?(n)" "Whether n > 0.";
293    Predicate: "negative?" "negative?(n)" "Whether n < 0.";
294    Predicate: "equal?" "equal?(a, b)" "Structural equality; what == lowers to.";
295    Predicate: "eq?" "eq?(a, b)" "Identity: true for the same keyword or string, false for two equal lists. Use ==.";
296    Predicate: "is?" "is?(x, type)" "Whether x has the type named by a keyword: is?(5, :int). Value first.";
297    Predicate: "the" "the(type, x)" "x, after asserting its type: the(:int, 5). Type first.";
298    Predicate: "cast" "cast(type, x)" "x checked against, or narrowed to, the type a keyword names: cast(:float, 5) is 5.0. Type first.";
299    Predicate: "foreign?" "foreign?(x)" "Whether x is an opaque value a host handed in.";
300    Predicate: "promise?" "promise?(x)" "Whether x is a delayed value.";
301    Predicate: "chan?" "chan?(x)" "Whether x is a channel.";
302    Predicate: "go?" "go?(x)" "Whether x is a fiber.";
303    // ── errors ────────────────────────────────────────────────────────────
304    Error: "error" "error(kind, message[, data])" "An error VALUE. It raises nothing until throw(error(…)).";
305    Error: "throw" "throw(err)" "Raise err. Uncaught, the run fails with its kind and message.";
306    Error: "error?" "error?(x)" "Whether x is an error value; true inside a catch handler.";
307    Error: "gensym" "gensym([prefix])" "A fresh unique symbol, for macros that need a private name.";
308    // ── JSON ──────────────────────────────────────────────────────────────
309    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.";
310    Json: "json_stringify" "json_stringify(v)" "Compact JSON text, a map's keys sorted, a keyword as its name.";
311    Json: "json_get" "json_get(obj, key)" "The field of a parsed JSON object, or nil. Raises when obj is not an object.";
312    Json: "json_get_or" "json_get_or(obj, key, default)" "The field of a parsed JSON object, or default.";
313    // ── files ─────────────────────────────────────────────────────────────
314    File: "read_file" "read_file(path)" "The file's text. Raises when it cannot be read; shisutemu's read_or(path, default) does not.";
315    File: "write_file" "write_file(path, text)" "Replace the file's contents with text; nil.";
316    File: "append_file" "append_file(path, text)" "Add text to the end of the file, creating it; nil.";
317    File: "rename_file" "rename_file(from, to)" "Move a file, atomically on one filesystem: write a temporary beside to, then rename.";
318    File: "rm" "rm(path)" "Delete a file; raises when it is absent.";
319    File: "rm_rf" "rm_rf(path)" "Delete a file or a directory tree; nil when there is nothing.";
320    File: "mkdir" "mkdir(path)" "Create one directory; nil if it exists.";
321    File: "mkdir_p" "mkdir_p(path)" "Create a directory and its parents.";
322    File: "path_exists" "path_exists(path)" "Whether anything is at path.";
323    File: "is_file?" "is_file?(path)" "Whether path is a regular file.";
324    File: "is_dir?" "is_dir?(path)" "Whether path is a directory.";
325    File: "file_size" "file_size(path)" "The size in bytes.";
326    File: "file_mtime_ms" "file_mtime_ms(path)" "The last modification time, in Unix milliseconds.";
327    File: "ls" "ls(dir)" "The entries of a directory as full paths, sorted.";
328    File: "walk_dir" "walk_dir(root)" "Every file under root, as paths, in no fixed order: sort before comparing.";
329    File: "glob" "glob(pattern)" "Paths matching * and ** relative to the current directory. An absolute pattern finds nothing; walk_dir and filter instead.";
330    File: "path_join" "path_join(part, more...)" "The parts joined with /.";
331    File: "path_basename" "path_basename(path)" "The last component: \"b.txt\" of \"a/b.txt\".";
332    File: "path_dirname" "path_dirname(path)" "Everything before the last component.";
333    File: "path_extension" "path_extension(path)" "The extension without its dot, or nil.";
334    File: "cwd" "cwd()" "The current directory.";
335    // ── processes ─────────────────────────────────────────────────────────
336    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.";
337    Process: "exec_check" "exec_check(cmd, args...)" "Run a command, no shell; its exit status as an Int. Output passes through.";
338    Process: "exec_ok?" "exec_ok?(cmd, args...)" "Whether a command exits 0.";
339    Process: "exec_with_stdin" "exec_with_stdin(input, cmd, args...)" "exec_capture, with input written to the command's stdin. The INPUT comes first.";
340    Process: "exec_with_env" "exec_with_env(env, cmd, args...)" "exec_capture with extra environment variables, given as [[name, value], …].";
341    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.";
342    // ── environment ───────────────────────────────────────────────────────
343    Environment: "getenv" "getenv(name[, default])" "An environment variable, or default (nil when none is given).";
344    Environment: "env_required" "env_required(name)" "An environment variable; raises, naming it, when unset.";
345    Environment: "argv" "argv()" "The program's own arguments: what follows the file in blue run file -- a b.";
346    Environment: "argv_get" "argv_get(i[, default])" "One argument by index, or default.";
347    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.";
348    // ── time ──────────────────────────────────────────────────────────────
349    Clock: "now" "now()" "Unix time in seconds.";
350    Clock: "now_ms" "now_ms()" "Unix time in milliseconds.";
351    Clock: "now_ns" "now_ns()" "Unix time in nanoseconds.";
352    Clock: "now_rfc3339" "now_rfc3339()" "The current UTC time as RFC 3339 text.";
353    Clock: "elapsed_since" "elapsed_since(start_ns)" "Nanoseconds since start_ns, a value of now_ns().";
354    Clock: "sleep" "sleep(seconds)" "Pause for whole seconds.";
355    Clock: "sleep_ms" "sleep_ms(ms)" "Pause for milliseconds.";
356    // ── crypto ────────────────────────────────────────────────────────────
357    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.";
358    Crypto: "ed25519_keypair" "ed25519_keypair(seed_hex)" "[secret, public] in hex for a 32-byte seed. There is no entropy source: the caller supplies the seed.";
359    Crypto: "ed25519_sign" "ed25519_sign(secret_hex, message)" "The Ed25519 signature of a message, in hex.";
360    Crypto: "ed25519_verify" "ed25519_verify(public_hex, message, signature_hex)" "Whether the signature is valid.";
361    // ── output ────────────────────────────────────────────────────────────
362    Output: "write_stdout" "write_stdout(text)" "Write text to stdout exactly, no newline added. The way a command prints.";
363    Output: "write_stderr" "write_stderr(text)" "Write text to stderr exactly.";
364    Output: "println" "println(xs...)" "Print values with a newline, strings with their quotes. For output a person reads, use write_stdout.";
365    Output: "print" "print(x)" "Print a value and a newline, a string with its quotes.";
366    Output: "display" "display(x)" "Print a value's Lisp rendering, no newline.";
367    Output: "newline" "newline()" "Print a newline.";
368    // ── delayed values ────────────────────────────────────────────────────
369    Lazy: "force" "force(p)" "The value of a promise, computed once; a non-promise is returned as is.";
370    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.";
371    Lazy: "realize" "realize(lazy)" "A finite tatara-lisp lazy sequence forced into a list. On anything built from cycle it does not terminate.";
372    // ── concurrency ───────────────────────────────────────────────────────
373    Concurrency: "chan" "chan([capacity])" "A tatara-lisp channel. Its put and take words (>! and <!) have no blue spelling.";
374    Concurrency: "close!" "close!(ch)" "Close a channel.";
375    Concurrency: "drain!" "drain!(ch)" "Every value waiting in a channel, as a list.";
376    Concurrency: "go" "go(f)" "A pending fiber for a zero-argument function. Running it (go-run) has no blue spelling.";
377};
378
379/// The row for a name, if blue describes it.
380#[must_use]
381pub fn doc_of(name: &str) -> Option<&'static NameDoc> {
382    NAMES.iter().find(|d| d.name == name)
383}