1#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
32pub enum Topic {
33 Form,
35 Lisp,
38 Number,
39 Text,
40 List,
41 Map,
42 Function,
44 Predicate,
46 Error,
47 Json,
48 Nix,
49 File,
50 Process,
51 Environment,
52 Clock,
53 Network,
55 Crypto,
56 Lazy,
58 Concurrency,
60 Actor,
62 Output,
64 Dispatch,
66}
67
68impl Topic {
69 pub const ALL: &'static [Topic] = &[
71 Topic::Form,
72 Topic::Number,
73 Topic::Text,
74 Topic::List,
75 Topic::Map,
76 Topic::Dispatch,
77 Topic::Function,
78 Topic::Predicate,
79 Topic::Error,
80 Topic::Json,
81 Topic::Nix,
82 Topic::File,
83 Topic::Process,
84 Topic::Environment,
85 Topic::Clock,
86 Topic::Network,
87 Topic::Crypto,
88 Topic::Output,
89 Topic::Lazy,
90 Topic::Concurrency,
91 Topic::Actor,
92 Topic::Lisp,
93 ];
94
95 #[must_use]
97 pub fn title(self) -> &'static str {
98 match self {
99 Topic::Form => "Forms",
100 Topic::Lisp => "tatara-lisp forms",
101 Topic::Number => "Numbers",
102 Topic::Text => "Strings",
103 Topic::List => "Lists",
104 Topic::Map => "Maps",
105 Topic::Function => "Functions",
106 Topic::Predicate => "Predicates",
107 Topic::Error => "Errors",
108 Topic::Json => "JSON",
109 Topic::Nix => "Nix source",
110 Topic::File => "Files and paths",
111 Topic::Process => "Processes",
112 Topic::Environment => "Environment and arguments",
113 Topic::Clock => "Time",
114 Topic::Network => "Network",
115 Topic::Crypto => "Hashing and signatures",
116 Topic::Lazy => "Delayed values",
117 Topic::Concurrency => "Channels and fibers",
118 Topic::Actor => "Blue processes and supervision",
119 Topic::Output => "Terminal output",
120 Topic::Dispatch => "Master verbs",
121 }
122 }
123}
124
125#[derive(Clone, Copy, Debug)]
127pub struct NameDoc {
128 pub name: &'static str,
129 pub topic: Topic,
130 pub signature: &'static str,
132 pub doc: &'static str,
134}
135
136macro_rules! names {
139 ($($topic:ident: $name:literal $sig:literal $doc:literal;)*) => {
140 &[$(NameDoc { name: $name, topic: Topic::$topic, signature: $sig, doc: $doc },)*]
141 };
142}
143
144pub const NAMES: &[NameDoc] = names! {
146 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.";
148 Form: "define" "define(name, value)" "Bind a name. Written name = value; the formatter prints that form.";
149 Form: "set!" "set!(name, value)" "Rebind an existing name. A name a forked process inherited is sealed and refuses it.";
150 Form: "lambda" "fn(x) … end" "The function form fn lowers to. Write fn.";
151 Form: "quasiquote" "quote … end" "What quote with unquote lowers to inside defmacro.";
152 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).";
153 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.";
154 Form: "comment" "comment(anything...)" "Ignores its arguments and is nil. Use # comments instead.";
155 Form: "eval" "eval(form)" "Evaluate a quoted form.";
156 Form: "macroexpand" "macroexpand(form)" "The expansion of a quoted macro call, for debugging a defmacro.";
157 Form: "delay" "delay(expr)" "A promise that evaluates expr once, on the first force.";
158 Lisp: "let" "(let ((x 1)) …)" "Lisp binding form; its binding list has no blue spelling. In blue, write x = 1.";
160 Lisp: "letrec" "(letrec ((f …)) …)" "Lisp recursive binding; its binding list has no blue spelling. In blue, use def.";
161 Lisp: "cond" "(cond (test expr) …)" "Lisp multi-way conditional; its clauses have no blue spelling. In blue, use if … elsif … end.";
162 Lisp: "require" "(require …)" "tatara-lisp's module loader. In blue, use(\"pkg\") imports a bidama.";
163 Lisp: "provide" "(provide …)" "tatara-lisp's module export. A bidama exports every def.";
164 Lisp: "dolist" "(dolist (x xs) …)" "Lisp loop; its binding list has no blue spelling. In blue, map(fn(x) … end, xs).";
165 Lisp: "doseq" "(doseq (x xs) …)" "Same as dolist. In blue, map(fn(x) … end, xs).";
166 Lisp: "dotimes" "(dotimes (i n) …)" "Lisp counted loop. In blue, map(fn(i) … end, range(n)).";
167 Lisp: "match" "(match v (pattern expr) …)" "Lisp pattern matching; its patterns have no blue spelling. blue's case compares values and does not destructure.";
168 Lisp: "defflow" "defflow(name, f, g, more...)" "Define name as a function that pipes its argument through f, then g: defflow(slug, trim, downcase).";
169 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.";
170 Lisp: "defstrategy" "defstrategy(name, :variant, f, …, :default, g)" "Define name(variant, args...) dispatching to the function under that keyword, or :default.";
171 Lisp: "defvisitor" "defvisitor(name, :tag, f, …, :default, g)" "Define name(tagged) dispatching on the first element of a tagged list.";
172 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.";
173 Lisp: "defcommand" "defcommand(bus, name, params, body)" "Register a command on a tatara-lisp command bus; params is a Lisp list.";
174 Lisp: "defquery" "defquery(bus, name, params, body)" "Register a query on a tatara-lisp command bus; params is a Lisp list.";
175 Number: "abs" "abs(x)" "Absolute value, keeping Int or Float.";
177 Number: "min" "min(x, more...)" "The smallest argument. Takes numbers, not a list: apply(min, xs).";
178 Number: "max" "max(x, more...)" "The largest argument. Takes numbers, not a list: apply(max, xs).";
179 Number: "inc" "inc(x)" "x + 1.";
180 Number: "dec" "dec(x)" "x - 1.";
181 Number: "floor" "floor(x)" "The largest Int not above x. floor(a / b) is integer division.";
182 Number: "ceiling" "ceiling(x)" "The smallest Int not below x.";
183 Number: "round" "round(x)" "The nearest Int, halves away from zero: round(2.5) is 3, round(-2.5) is -3.";
184 Number: "truncate" "truncate(x)" "The Int part of x, toward zero: truncate(-2.7) is -2.";
185 Number: "mod" "mod(a, b)" "Euclidean remainder, never negative; what % lowers to.";
186 Number: "modulo" "modulo(a, b)" "The same as mod.";
187 Number: "rem" "rem(a, b)" "The same as mod here: mod(-7, 3), modulo and rem are all 2.";
188 Number: "gcd" "gcd(xs...)" "Greatest common divisor of the arguments; 0 for none.";
189 Number: "lcm" "lcm(xs...)" "Least common multiple of the arguments.";
190 Number: "expt" "expt(base, power)" "base raised to power: expt(2, 10) is 1024.";
191 Number: "sqrt" "sqrt(x)" "Square root, always a Float: sqrt(16) is 4.0, and 4.0 == 4.";
192 Number: "exp" "exp(x)" "e to the x, a Float.";
193 Number: "log" "log(x[, base])" "Natural logarithm, or log to base: log(100, 10) is 2.0.";
194 Number: "sin" "sin(x)" "Sine of x radians.";
195 Number: "cos" "cos(x)" "Cosine of x radians.";
196 Number: "tan" "tan(x)" "Tangent of x radians.";
197 Number: "asin" "asin(x)" "Arcsine; refuses x outside [-1, 1] rather than returning NaN.";
198 Number: "acos" "acos(x)" "Arccosine; refuses x outside [-1, 1] rather than returning NaN.";
199 Number: "atan" "atan(x)" "Arctangent in radians.";
200 Number: "atan2" "atan2(y, x)" "The angle of the point (x, y); y comes first.";
201 Number: "hypot" "hypot(x, y)" "sqrt(x * x + y * y) without the overflow: hypot(3, 4) is 5.0.";
202 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).";
203 Number: "to_int!" "to_int!(x)" "Like to_int, but raises instead of returning nil.";
204 Number: "to_float" "to_float(x)" "A Float from a number or a numeric string.";
205 Number: "compare" "compare(a, b)" "-1, 0 or 1. Total over two numbers or two strings, which < is not; mixing them raises.";
206 Text: "concat" "concat(x, more...)" "The text of every argument joined, each rendered with to_s. What interpolation lowers to.";
208 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.";
209 Text: "string" "string(x)" "tatara-lisp's rendering: :a stays \":a\" and a list prints as (1). Use to_s.";
210 Text: "upcase" "upcase(s)" "The string in upper case. Raises on nil.";
211 Text: "downcase" "downcase(s)" "The string in lower case. Raises on nil.";
212 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.";
213 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.";
214 Text: "join" "join(xs, sep)" "The elements' text with sep between. The LIST comes first here, and the string first in split.";
215 Text: "replace" "replace(s, from, to)" "Every occurrence of from replaced by to.";
216 Text: "chars" "chars(s)" "The characters as one-character strings.";
217 Text: "starts_with?" "starts_with?(s, prefix)" "Whether s begins with prefix.";
218 Text: "ends_with?" "ends_with?(s, suffix)" "Whether s ends with suffix.";
219 Text: "contains?" "contains?(s, sub)" "Whether the STRING s contains sub. On a list it raises: use member?(x, xs).";
220 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.";
221 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.";
222 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].";
223 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.";
224 List: "list" "list(xs...)" "A list of the arguments. Written [a, b].";
226 List: "length" "length(seq)" "The length of a list or string. Raises on nil: retsu's size(xs) is total.";
227 List: "count" "count(x)" "The number of elements; 0 for nil.";
228 List: "nth" "nth(i, xs)" "The element at index i, from 0; nil past either end. The INDEX comes first.";
229 List: "first" "first(xs)" "The first element; raises on []. retsu's first answers nil.";
230 List: "second" "second(xs)" "The second element; raises when there is none.";
231 List: "third" "third(xs)" "The third element; raises when there is none.";
232 List: "fourth" "fourth(xs)" "The fourth element; raises when there is none.";
233 List: "last" "last(xs)" "The last element; raises on []. retsu's last answers nil.";
234 List: "rest" "rest(xs)" "All but the first; raises on []. retsu's rest answers [].";
235 List: "next" "next(xs)" "All but the first; raises on [].";
236 List: "butlast" "butlast(xs)" "All but the last; raises on [].";
237 List: "car" "car(xs)" "The first element. Prefer first.";
238 List: "cdr" "cdr(xs)" "All but the first ([] for a one-element list); raises on []. Prefer rest.";
239 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.";
240 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.";
241 List: "take" "take(n, xs)" "The first n elements, or all of them. The COUNT comes first.";
242 List: "drop" "drop(n, xs)" "All but the first n. The COUNT comes first.";
243 List: "reverse" "reverse(seq)" "The list or string backwards. Raises on nil.";
244 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.";
245 List: "flatten" "flatten(xs)" "Every nested list spliced in, to any depth.";
246 List: "distinct" "distinct(xs)" "The elements with repeats removed, first occurrence kept, compared with ==.";
247 List: "frequencies" "frequencies(xs)" "A map from each element to how many times it occurs.";
248 List: "zip" "zip(xs, ys)" "[[x1, y1], [x2, y2], …], as long as the shorter list.";
249 List: "interleave" "interleave(xs, ys)" "[x1, y1, x2, y2, …], as long as the shorter list allows.";
250 List: "intersperse" "intersperse(sep, xs)" "xs with sep between each pair. The SEPARATOR comes first.";
251 List: "position" "position(x, xs)" "The index of the first element == x, or -1.";
252 List: "member?" "member?(x, xs)" "Whether some element == x. The ELEMENT comes first.";
253 List: "partition" "partition(f, xs)" "[kept, rejected]: the elements f accepts, then the rest.";
254 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.";
255 List: "iterate" "iterate(f, x, n)" "[x, f(x), f(f(x)), …], n elements.";
256 List: "repeatedly" "repeatedly(f, n)" "A list of n calls to the zero-argument function f. The FUNCTION comes first.";
257 Map: "get" "get(m, k)" "The value at key k, or nil. Raises on nil and on a list.";
259 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.";
260 Map: "dissoc" "dissoc(m, k)" "A new map without k; unchanged when k is absent.";
261 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.";
262 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.";
263 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.";
264 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.";
265 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.";
266 Dispatch: "need" "need(store, key, more...)" "One entry, raising :not_found when absent. A plain map goes to jisho.";
267 Dispatch: "has?" "has?(store, key, more...)" "Whether the key is present. A plain map goes to jisho.";
268 Dispatch: "put" "put(store, key, value, more...)" "Create or replace an entry; a plain map goes to jisho, which returns a new map.";
269 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.";
270 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.";
271 Dispatch: "create" "create(store, key, value, more...)" "Add a new entry, refusing to overwrite; in_space(\"gitto\", repo) makes a branch or tag.";
272 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.";
273 Dispatch: "info" "info(subject, more...)" "The current state of the subject as a record: in_space(\"gitto\", repo) is the repository's state.";
274 Dispatch: "exists?" "exists?(subject, more...)" "Whether the subject is there; in_space(\"gitto\", repo) asks about a ref.";
275 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.";
276 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.";
277 Map: "zipmap" "zipmap(keys, values)" "A map pairing keys with values, as long as the shorter list.";
278 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.";
279 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 [].";
281 Function: "filter" "filter(f, xs)" "The elements f accepts. The FUNCTION comes first.";
282 Function: "remove" "remove(f, xs)" "The elements f rejects.";
283 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.";
284 Function: "foldl" "foldl(f, init, xs)" "reduce with an init: f(acc, x) from the left.";
285 Function: "foldr" "foldr(f, init, xs)" "Fold from the right: f(x, acc).";
286 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.";
287 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.";
288 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.";
289 Function: "some" "some(f, xs)" "true when f accepts an element, else nil (not false).";
290 Function: "any?" "any?(f, xs)" "Whether f accepts some element; false for [].";
291 Function: "every?" "every?(f, xs)" "Whether f accepts every element; true for [].";
292 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.";
293 Function: "identity" "identity(x)" "x.";
294 Function: "const" "const(x)" "A function that ignores its argument and returns x.";
295 Function: "comp" "comp(f, g)" "The function x -> f(g(x)).";
296 Function: "compose" "compose(fs...)" "Right to left: compose(f, g)(x) is f(g(x)).";
297 Function: "pipe" "pipe(fs...)" "Left to right: pipe(f, g)(x) is g(f(x)).";
298 Function: "partial" "partial(f, args...)" "f with its first arguments fixed: partial(f, 1)(2) is f(1, 2).";
299 Function: "flip" "flip(f)" "f with its two arguments swapped.";
300 Function: "juxt" "juxt(fs...)" "The function x -> [f(x), g(x), …].";
301 Function: "tap" "tap(f, x)" "Call f(x) for its effect and return x.";
302 Function: "memoize" "memoize(f)" "f, remembering each argument's answer.";
303 Function: "decorate" "decorate(f, keys-and-values...)" "f with metadata attached; it still calls as f.";
304 Function: "visit" "visit(f, x)" "f(x).";
305 Predicate: "nil?" "nil?(x)" "Whether x is nil (okite D0010). x.nil? says the same and is the spelling blue writes (B0047).";
307 Predicate: "bool?" "bool?(x)" "Whether x is true or false.";
308 Predicate: "boolean?" "boolean?(x)" "The same as bool?.";
309 Predicate: "integer?" "integer?(x)" "Whether x is an Int; integer?(1.0) is false.";
310 Predicate: "float?" "float?(x)" "Whether x is a Float. 6 / 2 is the Int 3, while 7 / 2 is 3.5.";
311 Predicate: "number?" "number?(x)" "Whether x is an Int or a Float.";
312 Predicate: "string?" "string?(x)" "Whether x is a string.";
313 Predicate: "keyword?" "keyword?(x)" "Whether x is a keyword such as :done.";
314 Predicate: "symbol?" "symbol?(x)" "Whether x is a Lisp symbol; false for keywords.";
315 Predicate: "list?" "list?(x)" "Whether x is a list. list?(nil) is true, so test x == nil first.";
316 Predicate: "map?" "map?(x)" "Whether x is a map. A parsed JSON object is not one.";
317 Predicate: "procedure?" "procedure?(x)" "Whether x can be called.";
318 Predicate: "empty?" "empty?(xs)" "Whether xs is nil or has no elements.";
319 Predicate: "null?" "null?(x)" "Whether x is nil or []. The Lisp word; prefer retsu's is_empty.";
320 Predicate: "pair?" "pair?(x)" "Whether x is a non-empty list.";
321 Predicate: "atom?" "atom?(x)" "Whether x is a scalar rather than a list; false for nil.";
322 Predicate: "some?" "some?(x)" "Whether x is not nil.";
323 Predicate: "even?" "even?(n)" "Whether n is even.";
324 Predicate: "odd?" "odd?(n)" "Whether n is odd.";
325 Predicate: "zero?" "zero?(n)" "Whether n is 0.";
326 Predicate: "positive?" "positive?(n)" "Whether n > 0.";
327 Predicate: "negative?" "negative?(n)" "Whether n < 0.";
328 Predicate: "equal?" "equal?(a, b)" "Structural equality; what == lowers to.";
329 Predicate: "eq?" "eq?(a, b)" "Identity: true for the same keyword or string, false for two equal lists. Use ==.";
330 Predicate: "is?" "is?(x, type)" "Whether x has the type named by a keyword: is?(5, :int). Value first.";
331 Predicate: "the" "the(type, x)" "x, after asserting its type: the(:int, 5). Type first.";
332 Predicate: "cast" "cast(type, x)" "x checked against, or narrowed to, the type a keyword names: cast(:float, 5) is 5.0. Type first.";
333 Predicate: "foreign?" "foreign?(x)" "Whether x is an opaque value a host handed in.";
334 Predicate: "promise?" "promise?(x)" "Whether x is a delayed value.";
335 Predicate: "chan?" "chan?(x)" "Whether x is a channel.";
336 Predicate: "go?" "go?(x)" "Whether x is a fiber.";
337 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.";
339 Error: "error" "error(kind, message[, data])" "An error VALUE, raising nothing. throw(error(k, m)) is written raise(k, m) (B0037).";
340 Error: "throw" "throw(err)" "Raise any value. Written raise(err) (B0037).";
341 Error: "error?" "error?(x)" "Whether x is an error value; true inside a catch handler.";
342 Error: "error_message" "error_message(e)" "The message of an error value. Written e.message (B0036).";
343 Error: "error_kind" "error_kind(e)" "The kind of an error value, a keyword. Written e.kind (B0036).";
344 Error: "gensym" "gensym([prefix])" "A fresh unique symbol, for macros that need a private name.";
345 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.";
346 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.";
347 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.";
349 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.";
350 Json: "json_stringify" "json_stringify(v)" "Compact JSON text, a map's keys sorted, a keyword as its name.";
351 Json: "json_get" "json_get(obj, key)" "The field of a parsed JSON object, or nil. Raises when obj is not an object.";
352 Json: "json_get_or" "json_get_or(obj, key, default)" "The field of a parsed JSON object, or default.";
353 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.";
354 File: "read_file" "read_file(path)" "The file's text. Raises when it cannot be read; shisutemu's read_or(path, default) does not.";
356 File: "write_file" "write_file(path, text)" "Replace the file's contents with text; nil.";
357 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).";
358 File: "append_file" "append_file(path, text)" "Add text to the end of the file, creating it; nil.";
359 File: "rename_file" "rename_file(from, to)" "Move a file, atomically on one filesystem: write a temporary beside to, then rename.";
360 File: "rm" "rm(path)" "Delete a file; raises when it is absent.";
361 File: "rm_rf" "rm_rf(path)" "Delete a file or a directory tree; nil when there is nothing.";
362 File: "mkdir" "mkdir(path)" "Create one directory; nil if it exists.";
363 File: "mkdir_p" "mkdir_p(path)" "Create a directory and its parents.";
364 File: "path_exists" "path_exists(path)" "Whether anything is at path.";
365 File: "is_file?" "is_file?(path)" "Whether path is a regular file.";
366 File: "is_dir?" "is_dir?(path)" "Whether path is a directory.";
367 File: "file_size" "file_size(path)" "The size in bytes.";
368 File: "file_mtime_ms" "file_mtime_ms(path)" "The last modification time, in Unix milliseconds.";
369 File: "ls" "ls(dir)" "The entries of a directory as full paths, sorted.";
370 File: "walk_dir" "walk_dir(root)" "Every file under root, as paths, in no fixed order: sort before comparing.";
371 File: "glob" "glob(pattern)" "Paths matching * and ** relative to the current directory. An absolute pattern finds nothing; walk_dir and filter instead.";
372 File: "path_join" "path_join(part, more...)" "The parts joined with /.";
373 File: "path_basename" "path_basename(path)" "The last component: \"b.txt\" of \"a/b.txt\".";
374 File: "path_dirname" "path_dirname(path)" "Everything before the last component.";
375 File: "path_extension" "path_extension(path)" "The extension without its dot, or nil.";
376 File: "cwd" "cwd" "The current directory.";
377 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.";
379 Process: "exec_check" "exec_check(cmd, args...)" "Run a command, no shell; its exit status as an Int. Output passes through.";
380 Process: "exec_ok?" "exec_ok?(cmd, args...)" "Whether a command exits 0.";
381 Process: "exec_with_stdin" "exec_with_stdin(input, cmd, args...)" "exec_capture, with input written to the command's stdin. The INPUT comes first.";
382 Process: "exec_with_env" "exec_with_env(env, cmd, args...)" "exec_capture with extra environment variables, given as [[name, value], …].";
383 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.";
384 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.";
385 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.";
386 Environment: "getenv" "getenv(name[, default])" "An environment variable, or default (nil when none is given).";
388 Environment: "env_required" "env_required(name)" "An environment variable; raises, naming it, when unset.";
389 Environment: "argv" "argv" "The program's own arguments: what follows the file in blue run file -- a b.";
390 Environment: "argv_get" "argv_get(i[, default])" "One argument by index, or default.";
391 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.";
392 Clock: "now" "now" "Unix time in seconds.";
394 Clock: "now_ms" "now_ms" "Unix time in milliseconds.";
395 Clock: "now_ns" "now_ns" "Unix time in nanoseconds.";
396 Clock: "now_rfc3339" "now_rfc3339" "The current UTC time as RFC 3339 text.";
397 Clock: "elapsed_since" "elapsed_since(start_ns)" "Nanoseconds since start_ns, a value of now_ns.";
398 Clock: "sleep" "sleep(seconds)" "Pause for whole seconds.";
399 Clock: "sleep_ms" "sleep_ms(ms)" "Pause for milliseconds.";
400 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.";
402 Network: "nats_publish" "nats_publish(conn, subject, payload)" "Publish a string to one subject (no wildcards); nil. Also answers the server's pings, so a publish-only loop stays connected.";
403 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).";
404 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.";
405 Network: "nats_next_message" "nats_next_message(sub, timeout_ms)" "{subject:, payload:, reply:} for the next message, waiting at most timeout_ms (0 only checks); nil when none came. reply is nil unless the sender wants an answer: publish to it.";
406 Network: "nats_unsubscribe" "nats_unsubscribe(sub)" "Stop a subscription; messages already queued for it are dropped. nil.";
407 Network: "nats_close" "nats_close(conn)" "Close the connection; every later call on it raises. nil.";
408 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.";
409 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.";
410 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.";
411 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.";
412 Network: "pg_execute" "pg_execute(conn, sql)" "A script of statements, unparameterised: the list of their command tags. Raises naming the first failure.";
413 Network: "pg_close" "pg_close(conn)" "Close the connection; every later call on it raises. nil.";
414 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.";
415 Network: "http_listener_port" "http_listener_port(listener)" "The TCP port the listener is bound to.";
416 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.";
417 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.";
418 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.";
420 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.";
421 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.";
422 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.";
423 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.";
424 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.";
425 Crypto: "ed25519_sign" "ed25519_sign(secret_hex, message)" "The Ed25519 signature of a message, in hex.";
426 Crypto: "ed25519_verify" "ed25519_verify(public_hex, message, signature_hex)" "Whether the signature is valid.";
427 Output: "write_stdout" "write_stdout(text)" "Write text to stdout exactly, no newline added. The way a command prints.";
429 Output: "write_stderr" "write_stderr(text)" "Write text to stderr exactly.";
430 Output: "println" "println(xs...)" "Print values with a newline, strings with their quotes. For output a person reads, use write_stdout.";
431 Output: "print" "print(x)" "Print a value and a newline, a string with its quotes.";
432 Output: "display" "display(x)" "Print a value's Lisp rendering, no newline.";
433 Output: "newline" "newline" "Print a newline.";
434 Lazy: "force" "force(p)" "The value of a promise, computed once; a non-promise is returned as is.";
436 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.";
437 Lazy: "realize" "realize(lazy)" "A finite tatara-lisp lazy sequence forced into a list. On anything built from cycle it does not terminate.";
438 Concurrency: "chan" "chan([capacity])" "A tatara-lisp channel. Its put and take words (>! and <!) have no blue spelling.";
440 Concurrency: "close!" "close!(ch)" "Close a channel.";
441 Concurrency: "drain!" "drain!(ch)" "Every value waiting in a channel, as a list.";
442 Concurrency: "go" "go(f)" "A pending fiber for a zero-argument function. Running it (go-run) has no blue spelling.";
443 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.";
445 Actor: "spawn_link" "spawn_link(f[, args])" "spawn, linked to the caller: an abnormal exit of either ends the other, unless it traps exits.";
446 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.";
447 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.";
448 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.";
449 Actor: "self" "self" "This process's pid. The program itself is 0.";
450 Actor: "alive?" "alive?(pid)" "true while pid is running or waiting.";
451 Actor: "link" "link(pid)" "Link to pid: an abnormal exit of either ends the other. false when pid is not alive.";
452 Actor: "unlink" "unlink(pid)" "Remove a link; true.";
453 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).";
454 Actor: "demonitor" "demonitor(ref)" "Stop a monitor; true.";
455 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.";
456 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.";
457 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.";
458 Actor: "children" "children(sup)" "The pids a supervisor started, in order.";
459 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]], …].";
460};
461
462#[must_use]
464pub fn doc_of(name: &str) -> Option<&'static NameDoc> {
465 NAMES.iter().find(|d| d.name == name)
466}
467
468#[must_use]
469pub fn signatures() -> &'static blue_lang_check::contracts::Signatures {
470 static SIGNATURES: std::sync::OnceLock<blue_lang_check::contracts::Signatures> =
471 std::sync::OnceLock::new();
472 SIGNATURES.get_or_init(|| {
473 let rows: Vec<blue_lang_check::contracts::BuiltinRow> = NAMES
474 .iter()
475 .map(|d| blue_lang_check::contracts::BuiltinRow {
476 name: d.name,
477 signature: d.signature,
478 kinded: d.topic.kinded(),
479 guard: d.topic == Topic::Predicate && !matches!(d.name, "nil?" | "some?"),
480 })
481 .collect();
482 blue_lang_check::contracts::Signatures::new(&rows)
483 })
484}
485
486impl Topic {
487 #[must_use]
488 pub const fn kinded(self) -> bool {
489 matches!(
490 self,
491 Topic::Number | Topic::Text | Topic::List | Topic::Map | Topic::Function
492 )
493 }
494}