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