Skip to main content

seqc/
builtins.rs

1//! Built-in word signatures for Seq
2//!
3//! Defines the stack effects for all runtime built-in operations.
4//!
5//! Uses declarative macros to minimize boilerplate. The `builtin!` macro
6//! supports a Forth-like notation: `(a Type1 Type2 -- a Type3)` where:
7//! - `a` is the row variable (representing "rest of stack")
8//! - Concrete types: `Int`, `String`, `Float`
9//! - Type variables: single uppercase letters like `T`, `U`, `V`
10
11use crate::types::{Effect, SideEffect, StackType, Type};
12use std::collections::HashMap;
13use std::sync::LazyLock;
14
15/// Convert a type token to a Type expression
16macro_rules! ty {
17    (Int) => {
18        Type::Int
19    };
20    (Bool) => {
21        Type::Bool
22    };
23    (String) => {
24        Type::String
25    };
26    (Float) => {
27        Type::Float
28    };
29    (Symbol) => {
30        Type::Symbol
31    };
32    (Channel) => {
33        Type::Channel
34    };
35    // Single uppercase letter = type variable
36    (T) => {
37        Type::Var("T".to_string())
38    };
39    (U) => {
40        Type::Var("U".to_string())
41    };
42    (V) => {
43        Type::Var("V".to_string())
44    };
45    (W) => {
46        Type::Var("W".to_string())
47    };
48    (K) => {
49        Type::Var("K".to_string())
50    };
51    (M) => {
52        Type::Var("M".to_string())
53    };
54    (Q) => {
55        Type::Var("Q".to_string())
56    };
57    // Multi-char type variables (T1, T2, etc.)
58    (T1) => {
59        Type::Var("T1".to_string())
60    };
61    (T2) => {
62        Type::Var("T2".to_string())
63    };
64    (T3) => {
65        Type::Var("T3".to_string())
66    };
67    (T4) => {
68        Type::Var("T4".to_string())
69    };
70    (V2) => {
71        Type::Var("V2".to_string())
72    };
73    (M2) => {
74        Type::Var("M2".to_string())
75    };
76    (Acc) => {
77        Type::Var("Acc".to_string())
78    };
79}
80
81/// Build a stack type from row variable 'a' plus pushed types
82macro_rules! stack {
83    // Just the row variable
84    (a) => {
85        StackType::RowVar("a".to_string())
86    };
87    // Row variable with one type pushed
88    (a $t1:tt) => {
89        StackType::RowVar("a".to_string()).push(ty!($t1))
90    };
91    // Row variable with two types pushed
92    (a $t1:tt $t2:tt) => {
93        StackType::RowVar("a".to_string())
94            .push(ty!($t1))
95            .push(ty!($t2))
96    };
97    // Row variable with three types pushed
98    (a $t1:tt $t2:tt $t3:tt) => {
99        StackType::RowVar("a".to_string())
100            .push(ty!($t1))
101            .push(ty!($t2))
102            .push(ty!($t3))
103    };
104    // Row variable with four types pushed
105    (a $t1:tt $t2:tt $t3:tt $t4:tt) => {
106        StackType::RowVar("a".to_string())
107            .push(ty!($t1))
108            .push(ty!($t2))
109            .push(ty!($t3))
110            .push(ty!($t4))
111    };
112    // Row variable with five types pushed
113    (a $t1:tt $t2:tt $t3:tt $t4:tt $t5:tt) => {
114        StackType::RowVar("a".to_string())
115            .push(ty!($t1))
116            .push(ty!($t2))
117            .push(ty!($t3))
118            .push(ty!($t4))
119            .push(ty!($t5))
120    };
121    // Row variable 'b' (used in some signatures)
122    (b) => {
123        StackType::RowVar("b".to_string())
124    };
125    (b $t1:tt) => {
126        StackType::RowVar("b".to_string()).push(ty!($t1))
127    };
128    (b $t1:tt $t2:tt) => {
129        StackType::RowVar("b".to_string())
130            .push(ty!($t1))
131            .push(ty!($t2))
132    };
133}
134
135/// Define a builtin signature with Forth-like stack effect notation
136///
137/// Usage: `builtin!(sigs, "name", (a Type1 Type2 -- a Type3));`
138macro_rules! builtin {
139    // (a -- a)
140    ($sigs:ident, $name:expr, (a -- a)) => {
141        $sigs.insert($name.to_string(), Effect::new(stack!(a), stack!(a)));
142    };
143    // (a -- a T)
144    ($sigs:ident, $name:expr, (a -- a $o1:tt)) => {
145        $sigs.insert($name.to_string(), Effect::new(stack!(a), stack!(a $o1)));
146    };
147    // (a -- a T U)
148    ($sigs:ident, $name:expr, (a -- a $o1:tt $o2:tt)) => {
149        $sigs.insert($name.to_string(), Effect::new(stack!(a), stack!(a $o1 $o2)));
150    };
151    // (a T -- a)
152    ($sigs:ident, $name:expr, (a $i1:tt -- a)) => {
153        $sigs.insert($name.to_string(), Effect::new(stack!(a $i1), stack!(a)));
154    };
155    // (a T -- a U)
156    ($sigs:ident, $name:expr, (a $i1:tt -- a $o1:tt)) => {
157        $sigs.insert($name.to_string(), Effect::new(stack!(a $i1), stack!(a $o1)));
158    };
159    // (a T -- a U V)
160    ($sigs:ident, $name:expr, (a $i1:tt -- a $o1:tt $o2:tt)) => {
161        $sigs.insert($name.to_string(), Effect::new(stack!(a $i1), stack!(a $o1 $o2)));
162    };
163    // (a T U -- a)
164    ($sigs:ident, $name:expr, (a $i1:tt $i2:tt -- a)) => {
165        $sigs.insert($name.to_string(), Effect::new(stack!(a $i1 $i2), stack!(a)));
166    };
167    // (a T U -- a V)
168    ($sigs:ident, $name:expr, (a $i1:tt $i2:tt -- a $o1:tt)) => {
169        $sigs.insert($name.to_string(), Effect::new(stack!(a $i1 $i2), stack!(a $o1)));
170    };
171    // (a T U -- a V W)
172    ($sigs:ident, $name:expr, (a $i1:tt $i2:tt -- a $o1:tt $o2:tt)) => {
173        $sigs.insert($name.to_string(), Effect::new(stack!(a $i1 $i2), stack!(a $o1 $o2)));
174    };
175    // (a T U -- a V W X)
176    ($sigs:ident, $name:expr, (a $i1:tt $i2:tt -- a $o1:tt $o2:tt $o3:tt)) => {
177        $sigs.insert($name.to_string(), Effect::new(stack!(a $i1 $i2), stack!(a $o1 $o2 $o3)));
178    };
179    // (a T U -- a V W X Y)
180    ($sigs:ident, $name:expr, (a $i1:tt $i2:tt -- a $o1:tt $o2:tt $o3:tt $o4:tt)) => {
181        $sigs.insert($name.to_string(), Effect::new(stack!(a $i1 $i2), stack!(a $o1 $o2 $o3 $o4)));
182    };
183    // (a T U V -- a)
184    ($sigs:ident, $name:expr, (a $i1:tt $i2:tt $i3:tt -- a)) => {
185        $sigs.insert($name.to_string(), Effect::new(stack!(a $i1 $i2 $i3), stack!(a)));
186    };
187    // (a T U V -- a W)
188    ($sigs:ident, $name:expr, (a $i1:tt $i2:tt $i3:tt -- a $o1:tt)) => {
189        $sigs.insert($name.to_string(), Effect::new(stack!(a $i1 $i2 $i3), stack!(a $o1)));
190    };
191    // (a T U V -- a W X)
192    ($sigs:ident, $name:expr, (a $i1:tt $i2:tt $i3:tt -- a $o1:tt $o2:tt)) => {
193        $sigs.insert($name.to_string(), Effect::new(stack!(a $i1 $i2 $i3), stack!(a $o1 $o2)));
194    };
195    // (a T U V -- a W X Y)
196    ($sigs:ident, $name:expr, (a $i1:tt $i2:tt $i3:tt -- a $o1:tt $o2:tt $o3:tt)) => {
197        $sigs.insert($name.to_string(), Effect::new(stack!(a $i1 $i2 $i3), stack!(a $o1 $o2 $o3)));
198    };
199    // (a T U V W -- a X)
200    ($sigs:ident, $name:expr, (a $i1:tt $i2:tt $i3:tt $i4:tt -- a $o1:tt)) => {
201        $sigs.insert($name.to_string(), Effect::new(stack!(a $i1 $i2 $i3 $i4), stack!(a $o1)));
202    };
203    // (a T U V W X -- a Y)
204    ($sigs:ident, $name:expr, (a $i1:tt $i2:tt $i3:tt $i4:tt $i5:tt -- a $o1:tt)) => {
205        $sigs.insert($name.to_string(), Effect::new(stack!(a $i1 $i2 $i3 $i4 $i5), stack!(a $o1)));
206    };
207}
208
209/// Define multiple builtins with the same signature
210/// Note: Can't use a generic macro due to tt repetition issues, so we use specific helpers
211macro_rules! builtins_int_int_to_int {
212    ($sigs:ident, $($name:expr),+ $(,)?) => {
213        $(
214            builtin!($sigs, $name, (a Int Int -- a Int));
215        )+
216    };
217}
218
219macro_rules! builtins_int_int_to_bool {
220    ($sigs:ident, $($name:expr),+ $(,)?) => {
221        $(
222            builtin!($sigs, $name, (a Int Int -- a Bool));
223        )+
224    };
225}
226
227macro_rules! builtins_bool_bool_to_bool {
228    ($sigs:ident, $($name:expr),+ $(,)?) => {
229        $(
230            builtin!($sigs, $name, (a Bool Bool -- a Bool));
231        )+
232    };
233}
234
235macro_rules! builtins_int_to_int {
236    ($sigs:ident, $($name:expr),+ $(,)?) => {
237        $(
238            builtin!($sigs, $name, (a Int -- a Int));
239        )+
240    };
241}
242
243macro_rules! builtins_string_to_string {
244    ($sigs:ident, $($name:expr),+ $(,)?) => {
245        $(
246            builtin!($sigs, $name, (a String -- a String));
247        )+
248    };
249}
250
251macro_rules! builtins_float_float_to_float {
252    ($sigs:ident, $($name:expr),+ $(,)?) => {
253        $(
254            builtin!($sigs, $name, (a Float Float -- a Float));
255        )+
256    };
257}
258
259macro_rules! builtins_float_float_to_bool {
260    ($sigs:ident, $($name:expr),+ $(,)?) => {
261        $(
262            builtin!($sigs, $name, (a Float Float -- a Bool));
263        )+
264    };
265}
266
267/// Get the stack effect signature for a built-in word
268pub fn builtin_signature(name: &str) -> Option<Effect> {
269    let signatures = builtin_signatures();
270    signatures.get(name).cloned()
271}
272
273/// Get all built-in word signatures
274pub fn builtin_signatures() -> HashMap<String, Effect> {
275    let mut sigs = HashMap::new();
276
277    // =========================================================================
278    // I/O Operations
279    // =========================================================================
280
281    builtin!(sigs, "io.write", (a String -- a)); // Write without newline
282    builtin!(sigs, "io.write-line", (a String -- a));
283    builtin!(sigs, "io.read-line", (a -- a String Bool)); // Returns line + success flag
284    builtin!(sigs, "io.read-line+", (a -- a String Int)); // DEPRECATED: use io.read-line instead
285    builtin!(sigs, "io.read-n", (a Int -- a String Int)); // Read N bytes, returns bytes + status
286
287    // =========================================================================
288    // Command-line Arguments
289    // =========================================================================
290
291    builtin!(sigs, "args.count", (a -- a Int));
292    builtin!(sigs, "args.at", (a Int -- a String));
293
294    // =========================================================================
295    // File Operations
296    // =========================================================================
297
298    builtin!(sigs, "file.slurp", (a String -- a String Bool)); // returns (content success) - errors are values
299    builtin!(sigs, "file.exists?", (a String -- a Bool));
300    builtin!(sigs, "file.spit", (a String String -- a Bool)); // (content path -- success)
301    builtin!(sigs, "file.append", (a String String -- a Bool)); // (content path -- success)
302    builtin!(sigs, "file.delete", (a String -- a Bool));
303    builtin!(sigs, "file.size", (a String -- a Int Bool)); // (path -- size success)
304
305    // Directory operations
306    builtin!(sigs, "dir.exists?", (a String -- a Bool));
307    builtin!(sigs, "dir.make", (a String -- a Bool));
308    builtin!(sigs, "dir.delete", (a String -- a Bool));
309    builtin!(sigs, "dir.list", (a String -- a V Bool)); // V = List variant
310
311    // file.for-each-line+: Complex quotation type - defined manually
312    sigs.insert(
313        "file.for-each-line+".to_string(),
314        Effect::new(
315            StackType::RowVar("a".to_string())
316                .push(Type::String)
317                .push(Type::Quotation(Box::new(Effect::new(
318                    StackType::RowVar("a".to_string()).push(Type::String),
319                    StackType::RowVar("a".to_string()),
320                )))),
321            StackType::RowVar("a".to_string())
322                .push(Type::String)
323                .push(Type::Bool),
324        ),
325    );
326
327    // =========================================================================
328    // Type Conversions
329    // =========================================================================
330
331    builtin!(sigs, "int->string", (a Int -- a String));
332    builtin!(sigs, "int->float", (a Int -- a Float));
333    builtin!(sigs, "float->int", (a Float -- a Int));
334    builtin!(sigs, "float->string", (a Float -- a String));
335    builtin!(sigs, "string->int", (a String -- a Int Bool)); // value + success flag
336    builtin!(sigs, "string->float", (a String -- a Float Bool)); // value + success flag
337    builtin!(sigs, "char->string", (a Int -- a String));
338    builtin!(sigs, "symbol->string", (a Symbol -- a String));
339    builtin!(sigs, "string->symbol", (a String -- a Symbol));
340
341    // =========================================================================
342    // Integer Arithmetic ( a Int Int -- a Int )
343    // =========================================================================
344
345    builtins_int_int_to_int!(sigs, "i.add", "i.subtract", "i.multiply");
346    builtins_int_int_to_int!(sigs, "i.+", "i.-", "i.*");
347
348    // Division operations return ( a Int Int -- a Int Bool ) for error handling
349    builtin!(sigs, "i.divide", (a Int Int -- a Int Bool));
350    builtin!(sigs, "i.modulo", (a Int Int -- a Int Bool));
351    builtin!(sigs, "i./", (a Int Int -- a Int Bool));
352    builtin!(sigs, "i.%", (a Int Int -- a Int Bool));
353
354    // =========================================================================
355    // Integer Comparison ( a Int Int -- a Bool )
356    // =========================================================================
357
358    builtins_int_int_to_bool!(sigs, "i.=", "i.<", "i.>", "i.<=", "i.>=", "i.<>");
359    builtins_int_int_to_bool!(sigs, "i.eq", "i.lt", "i.gt", "i.lte", "i.gte", "i.neq");
360
361    // =========================================================================
362    // Boolean Operations ( a Bool Bool -- a Bool )
363    // =========================================================================
364
365    builtins_bool_bool_to_bool!(sigs, "and", "or");
366    builtin!(sigs, "not", (a Bool -- a Bool));
367
368    // =========================================================================
369    // Bitwise Operations
370    // =========================================================================
371
372    builtins_int_int_to_int!(sigs, "band", "bor", "bxor", "shl", "shr");
373    builtins_int_to_int!(sigs, "bnot", "popcount", "clz", "ctz");
374    builtins_int_to_int!(sigs, "i.neg", "negate"); // Integer negation (inline)
375    builtin!(sigs, "int-bits", (a -- a Int));
376
377    // =========================================================================
378    // Stack Operations (Polymorphic)
379    // =========================================================================
380
381    builtin!(sigs, "dup", (a T -- a T T));
382    builtin!(sigs, "drop", (a T -- a));
383    builtin!(sigs, "swap", (a T U -- a U T));
384    builtin!(sigs, "over", (a T U -- a T U T));
385    builtin!(sigs, "rot", (a T U V -- a U V T));
386    builtin!(sigs, "nip", (a T U -- a U));
387    builtin!(sigs, "tuck", (a T U -- a U T U));
388    builtin!(sigs, "2dup", (a T U -- a T U T U));
389    builtin!(sigs, "3drop", (a T U V -- a));
390
391    // pick and roll: Type approximations (see detailed comments below)
392    // pick: ( ..a T Int -- ..a T T ) - copies value at depth n to top
393    builtin!(sigs, "pick", (a T Int -- a T T));
394    // roll: ( ..a T Int -- ..a T ) - rotates n+1 items, bringing depth n to top
395    builtin!(sigs, "roll", (a T Int -- a T));
396
397    // =========================================================================
398    // Aux Stack Operations (word-local temporary storage)
399    // Note: actual aux stack effects are handled specially by the typechecker.
400    // These signatures describe only the main stack effects.
401    // =========================================================================
402
403    builtin!(sigs, ">aux", (a T -- a));
404    builtin!(sigs, "aux>", (a -- a T));
405
406    // =========================================================================
407    // Channel Operations (CSP-style concurrency)
408    // Errors are values, not crashes - all ops return success flags
409    // =========================================================================
410
411    builtin!(sigs, "chan.make", (a -- a Channel));
412    builtin!(sigs, "chan.send", (a T Channel -- a Bool)); // returns success flag
413    builtin!(sigs, "chan.receive", (a Channel -- a T Bool)); // returns value and success flag
414    builtin!(sigs, "chan.close", (a Channel -- a));
415    builtin!(sigs, "chan.yield", (a - -a));
416
417    // =========================================================================
418    // Quotation/Control Flow Operations
419    // =========================================================================
420
421    // call: Polymorphic - accepts Quotation or Closure
422    // Uses type variable Q to represent "something callable"
423    sigs.insert(
424        "call".to_string(),
425        Effect::new(
426            StackType::RowVar("a".to_string()).push(Type::Var("Q".to_string())),
427            StackType::RowVar("b".to_string()),
428        ),
429    );
430
431    // =========================================================================
432    // Dataflow Combinators
433    // =========================================================================
434
435    // dip: ( ..a x Quotation[..a -- ..b] -- ..b x )
436    // Hide top value, run quotation on rest, restore value.
437    // Type-checked specially in typechecker (like `call`); this is a placeholder.
438    // Same placeholder shape as keep — both take (value, quotation) and preserve value.
439    sigs.insert(
440        "dip".to_string(),
441        Effect::new(
442            StackType::RowVar("a".to_string())
443                .push(Type::Var("T".to_string()))
444                .push(Type::Var("Q".to_string())),
445            StackType::RowVar("b".to_string()).push(Type::Var("T".to_string())),
446        ),
447    );
448
449    // keep: ( ..a x Quotation[..a x -- ..b] -- ..b x )
450    // Run quotation on value, but preserve the original.
451    // Type-checked specially in typechecker (like `call`); this is a placeholder.
452    // Same placeholder shape as dip — both take (value, quotation) and preserve value.
453    sigs.insert(
454        "keep".to_string(),
455        Effect::new(
456            StackType::RowVar("a".to_string())
457                .push(Type::Var("T".to_string()))
458                .push(Type::Var("Q".to_string())),
459            StackType::RowVar("b".to_string()).push(Type::Var("T".to_string())),
460        ),
461    );
462
463    // bi: ( ..a x Quotation[..a x -- ..b] Quotation[..b x -- ..c] -- ..c )
464    // Apply two quotations to the same value.
465    // Type-checked specially in typechecker (like `call`); this is a placeholder.
466    // Q1/Q2 are distinct type vars — the two quotations may have different types.
467    sigs.insert(
468        "bi".to_string(),
469        Effect::new(
470            StackType::RowVar("a".to_string())
471                .push(Type::Var("T".to_string()))
472                .push(Type::Var("Q1".to_string()))
473                .push(Type::Var("Q2".to_string())),
474            StackType::RowVar("b".to_string()),
475        ),
476    );
477
478    // cond: Multi-way conditional (variable arity)
479    sigs.insert(
480        "cond".to_string(),
481        Effect::new(
482            StackType::RowVar("a".to_string()),
483            StackType::RowVar("b".to_string()),
484        ),
485    );
486
487    // strand.spawn: ( a Quotation -- a Int ) - spawn a concurrent strand
488    // The quotation can have any stack effect - it runs independently
489    sigs.insert(
490        "strand.spawn".to_string(),
491        Effect::new(
492            StackType::RowVar("a".to_string()).push(Type::Quotation(Box::new(Effect::new(
493                StackType::RowVar("spawn_in".to_string()),
494                StackType::RowVar("spawn_out".to_string()),
495            )))),
496            StackType::RowVar("a".to_string()).push(Type::Int),
497        ),
498    );
499
500    // strand.weave: ( a Quotation -- a handle ) - create a woven strand (generator)
501    // The quotation receives (WeaveCtx, first_resume_value) and must thread WeaveCtx through.
502    // Returns a handle (WeaveCtx) for use with strand.resume.
503    sigs.insert(
504        "strand.weave".to_string(),
505        Effect::new(
506            StackType::RowVar("a".to_string()).push(Type::Quotation(Box::new(Effect::new(
507                StackType::RowVar("weave_in".to_string()),
508                StackType::RowVar("weave_out".to_string()),
509            )))),
510            StackType::RowVar("a".to_string()).push(Type::Var("handle".to_string())),
511        ),
512    );
513
514    // strand.resume: ( a handle b -- a handle b Bool ) - resume weave with value
515    // Takes handle and value to send, returns (handle, yielded_value, has_more)
516    sigs.insert(
517        "strand.resume".to_string(),
518        Effect::new(
519            StackType::RowVar("a".to_string())
520                .push(Type::Var("handle".to_string()))
521                .push(Type::Var("b".to_string())),
522            StackType::RowVar("a".to_string())
523                .push(Type::Var("handle".to_string()))
524                .push(Type::Var("b".to_string()))
525                .push(Type::Bool),
526        ),
527    );
528
529    // yield: ( a ctx b -- a ctx b | Yield b ) - yield value and receive resume value
530    // The WeaveCtx must be passed explicitly and threaded through.
531    // The Yield effect indicates this word produces yield semantics.
532    sigs.insert(
533        "yield".to_string(),
534        Effect::with_effects(
535            StackType::RowVar("a".to_string())
536                .push(Type::Var("ctx".to_string()))
537                .push(Type::Var("b".to_string())),
538            StackType::RowVar("a".to_string())
539                .push(Type::Var("ctx".to_string()))
540                .push(Type::Var("b".to_string())),
541            vec![SideEffect::Yield(Box::new(Type::Var("b".to_string())))],
542        ),
543    );
544
545    // strand.weave-cancel: ( a handle -- a ) - cancel a weave and release its resources
546    // Use this to clean up a weave that won't be resumed to completion.
547    // This prevents resource leaks from abandoned weaves.
548    sigs.insert(
549        "strand.weave-cancel".to_string(),
550        Effect::new(
551            StackType::RowVar("a".to_string()).push(Type::Var("handle".to_string())),
552            StackType::RowVar("a".to_string()),
553        ),
554    );
555
556    // =========================================================================
557    // TCP Operations
558    // =========================================================================
559
560    // TCP operations return Bool for error handling
561    builtin!(sigs, "tcp.listen", (a Int -- a Int Bool));
562    builtin!(sigs, "tcp.accept", (a Int -- a Int Bool));
563    builtin!(sigs, "tcp.read", (a Int -- a String Bool));
564    builtin!(sigs, "tcp.write", (a String Int -- a Bool));
565    builtin!(sigs, "tcp.close", (a Int -- a Bool));
566
567    // =========================================================================
568    // OS Operations
569    // =========================================================================
570
571    builtin!(sigs, "os.getenv", (a String -- a String Bool));
572    builtin!(sigs, "os.home-dir", (a -- a String Bool));
573    builtin!(sigs, "os.current-dir", (a -- a String Bool));
574    builtin!(sigs, "os.path-exists", (a String -- a Bool));
575    builtin!(sigs, "os.path-is-file", (a String -- a Bool));
576    builtin!(sigs, "os.path-is-dir", (a String -- a Bool));
577    builtin!(sigs, "os.path-join", (a String String -- a String));
578    builtin!(sigs, "os.path-parent", (a String -- a String Bool));
579    builtin!(sigs, "os.path-filename", (a String -- a String Bool));
580    builtin!(sigs, "os.exit", (a Int -- a)); // Never returns, but typed as identity
581    builtin!(sigs, "os.name", (a -- a String));
582    builtin!(sigs, "os.arch", (a -- a String));
583
584    // =========================================================================
585    // Signal Handling (Unix signals)
586    // =========================================================================
587
588    builtin!(sigs, "signal.trap", (a Int -- a));
589    builtin!(sigs, "signal.received?", (a Int -- a Bool));
590    builtin!(sigs, "signal.pending?", (a Int -- a Bool));
591    builtin!(sigs, "signal.default", (a Int -- a));
592    builtin!(sigs, "signal.ignore", (a Int -- a));
593    builtin!(sigs, "signal.clear", (a Int -- a));
594    // Signal constants (platform-correct values)
595    builtin!(sigs, "signal.SIGINT", (a -- a Int));
596    builtin!(sigs, "signal.SIGTERM", (a -- a Int));
597    builtin!(sigs, "signal.SIGHUP", (a -- a Int));
598    builtin!(sigs, "signal.SIGPIPE", (a -- a Int));
599    builtin!(sigs, "signal.SIGUSR1", (a -- a Int));
600    builtin!(sigs, "signal.SIGUSR2", (a -- a Int));
601    builtin!(sigs, "signal.SIGCHLD", (a -- a Int));
602    builtin!(sigs, "signal.SIGALRM", (a -- a Int));
603    builtin!(sigs, "signal.SIGCONT", (a -- a Int));
604
605    // =========================================================================
606    // Terminal Operations (raw mode, character I/O, dimensions)
607    // =========================================================================
608
609    builtin!(sigs, "terminal.raw-mode", (a Bool -- a));
610    builtin!(sigs, "terminal.read-char", (a -- a Int));
611    builtin!(sigs, "terminal.read-char?", (a -- a Int));
612    builtin!(sigs, "terminal.width", (a -- a Int));
613    builtin!(sigs, "terminal.height", (a -- a Int));
614    builtin!(sigs, "terminal.flush", (a - -a));
615
616    // =========================================================================
617    // String Operations
618    // =========================================================================
619
620    builtin!(sigs, "string.concat", (a String String -- a String));
621    builtin!(sigs, "string.length", (a String -- a Int));
622    builtin!(sigs, "string.byte-length", (a String -- a Int));
623    builtin!(sigs, "string.char-at", (a String Int -- a Int));
624    builtin!(sigs, "string.substring", (a String Int Int -- a String));
625    builtin!(sigs, "string.find", (a String String -- a Int));
626    builtin!(sigs, "string.split", (a String String -- a V)); // Returns Variant (list)
627    builtin!(sigs, "string.contains", (a String String -- a Bool));
628    builtin!(sigs, "string.starts-with", (a String String -- a Bool));
629    builtin!(sigs, "string.empty?", (a String -- a Bool));
630    builtin!(sigs, "string.equal?", (a String String -- a Bool));
631    builtin!(sigs, "string.join", (a V String -- a String)); // ( list separator -- joined )
632
633    // Symbol operations
634    builtin!(sigs, "symbol.=", (a Symbol Symbol -- a Bool));
635
636    // String transformations
637    builtins_string_to_string!(
638        sigs,
639        "string.trim",
640        "string.chomp",
641        "string.to-upper",
642        "string.to-lower",
643        "string.json-escape"
644    );
645
646    // =========================================================================
647    // Encoding Operations
648    // =========================================================================
649
650    builtin!(sigs, "encoding.base64-encode", (a String -- a String));
651    builtin!(sigs, "encoding.base64-decode", (a String -- a String Bool));
652    builtin!(sigs, "encoding.base64url-encode", (a String -- a String));
653    builtin!(sigs, "encoding.base64url-decode", (a String -- a String Bool));
654    builtin!(sigs, "encoding.hex-encode", (a String -- a String));
655    builtin!(sigs, "encoding.hex-decode", (a String -- a String Bool));
656
657    // =========================================================================
658    // Crypto Operations
659    // =========================================================================
660
661    builtin!(sigs, "crypto.sha256", (a String -- a String));
662    builtin!(sigs, "crypto.hmac-sha256", (a String String -- a String));
663    builtin!(sigs, "crypto.constant-time-eq", (a String String -- a Bool));
664    builtin!(sigs, "crypto.random-bytes", (a Int -- a String));
665    builtin!(sigs, "crypto.random-int", (a Int Int -- a Int));
666    builtin!(sigs, "crypto.uuid4", (a -- a String));
667    builtin!(sigs, "crypto.aes-gcm-encrypt", (a String String -- a String Bool));
668    builtin!(sigs, "crypto.aes-gcm-decrypt", (a String String -- a String Bool));
669    builtin!(sigs, "crypto.pbkdf2-sha256", (a String String Int -- a String Bool));
670    builtin!(sigs, "crypto.ed25519-keypair", (a -- a String String));
671    builtin!(sigs, "crypto.ed25519-sign", (a String String -- a String Bool));
672    builtin!(sigs, "crypto.ed25519-verify", (a String String String -- a Bool));
673
674    // =========================================================================
675    // HTTP Client Operations
676    // =========================================================================
677
678    builtin!(sigs, "http.get", (a String -- a M));
679    builtin!(sigs, "http.post", (a String String String -- a M));
680    builtin!(sigs, "http.put", (a String String String -- a M));
681    builtin!(sigs, "http.delete", (a String -- a M));
682
683    // =========================================================================
684    // Regular Expression Operations
685    // =========================================================================
686
687    // Regex operations return Bool for error handling (invalid regex)
688    builtin!(sigs, "regex.match?", (a String String -- a Bool));
689    builtin!(sigs, "regex.find", (a String String -- a String Bool));
690    builtin!(sigs, "regex.find-all", (a String String -- a V Bool));
691    builtin!(sigs, "regex.replace", (a String String String -- a String Bool));
692    builtin!(sigs, "regex.replace-all", (a String String String -- a String Bool));
693    builtin!(sigs, "regex.captures", (a String String -- a V Bool));
694    builtin!(sigs, "regex.split", (a String String -- a V Bool));
695    builtin!(sigs, "regex.valid?", (a String -- a Bool));
696
697    // =========================================================================
698    // Compression Operations
699    // =========================================================================
700
701    builtin!(sigs, "compress.gzip", (a String -- a String Bool));
702    builtin!(sigs, "compress.gzip-level", (a String Int -- a String Bool));
703    builtin!(sigs, "compress.gunzip", (a String -- a String Bool));
704    builtin!(sigs, "compress.zstd", (a String -- a String Bool));
705    builtin!(sigs, "compress.zstd-level", (a String Int -- a String Bool));
706    builtin!(sigs, "compress.unzstd", (a String -- a String Bool));
707
708    // =========================================================================
709    // Variant Operations
710    // =========================================================================
711
712    builtin!(sigs, "variant.field-count", (a V -- a Int));
713    builtin!(sigs, "variant.tag", (a V -- a Symbol));
714    builtin!(sigs, "variant.field-at", (a V Int -- a T));
715    builtin!(sigs, "variant.append", (a V T -- a V2));
716    builtin!(sigs, "variant.last", (a V -- a T));
717    builtin!(sigs, "variant.init", (a V -- a V2));
718
719    // Type-safe variant constructors with fixed arity (symbol tags for SON support)
720    builtin!(sigs, "variant.make-0", (a Symbol -- a V));
721    builtin!(sigs, "variant.make-1", (a T1 Symbol -- a V));
722    builtin!(sigs, "variant.make-2", (a T1 T2 Symbol -- a V));
723    builtin!(sigs, "variant.make-3", (a T1 T2 T3 Symbol -- a V));
724    builtin!(sigs, "variant.make-4", (a T1 T2 T3 T4 Symbol -- a V));
725    // variant.make-5 through variant.make-12 defined manually (macro only supports up to 5 inputs)
726    for n in 5..=12 {
727        let mut input = StackType::RowVar("a".to_string());
728        for i in 1..=n {
729            input = input.push(Type::Var(format!("T{}", i)));
730        }
731        input = input.push(Type::Symbol);
732        let output = StackType::RowVar("a".to_string()).push(Type::Var("V".to_string()));
733        sigs.insert(format!("variant.make-{}", n), Effect::new(input, output));
734    }
735
736    // Aliases for dynamic variant construction (SON-friendly names)
737    builtin!(sigs, "wrap-0", (a Symbol -- a V));
738    builtin!(sigs, "wrap-1", (a T1 Symbol -- a V));
739    builtin!(sigs, "wrap-2", (a T1 T2 Symbol -- a V));
740    builtin!(sigs, "wrap-3", (a T1 T2 T3 Symbol -- a V));
741    builtin!(sigs, "wrap-4", (a T1 T2 T3 T4 Symbol -- a V));
742    // wrap-5 through wrap-12 defined manually
743    for n in 5..=12 {
744        let mut input = StackType::RowVar("a".to_string());
745        for i in 1..=n {
746            input = input.push(Type::Var(format!("T{}", i)));
747        }
748        input = input.push(Type::Symbol);
749        let output = StackType::RowVar("a".to_string()).push(Type::Var("V".to_string()));
750        sigs.insert(format!("wrap-{}", n), Effect::new(input, output));
751    }
752
753    // =========================================================================
754    // List Operations (Higher-order combinators for Variants)
755    // =========================================================================
756
757    // List construction and access
758    builtin!(sigs, "list.make", (a -- a V));
759    builtin!(sigs, "list.push", (a V T -- a V));
760    builtin!(sigs, "list.get", (a V Int -- a T Bool));
761    builtin!(sigs, "list.set", (a V Int T -- a V Bool));
762
763    builtin!(sigs, "list.length", (a V -- a Int));
764    builtin!(sigs, "list.empty?", (a V -- a Bool));
765    builtin!(sigs, "list.reverse", (a V -- a V));
766
767    // list.map: ( a Variant Quotation -- a Variant )
768    // Quotation: ( b T -- b U )
769    sigs.insert(
770        "list.map".to_string(),
771        Effect::new(
772            StackType::RowVar("a".to_string())
773                .push(Type::Var("V".to_string()))
774                .push(Type::Quotation(Box::new(Effect::new(
775                    StackType::RowVar("b".to_string()).push(Type::Var("T".to_string())),
776                    StackType::RowVar("b".to_string()).push(Type::Var("U".to_string())),
777                )))),
778            StackType::RowVar("a".to_string()).push(Type::Var("V2".to_string())),
779        ),
780    );
781
782    // list.filter: ( a Variant Quotation -- a Variant )
783    // Quotation: ( b T -- b Bool )
784    sigs.insert(
785        "list.filter".to_string(),
786        Effect::new(
787            StackType::RowVar("a".to_string())
788                .push(Type::Var("V".to_string()))
789                .push(Type::Quotation(Box::new(Effect::new(
790                    StackType::RowVar("b".to_string()).push(Type::Var("T".to_string())),
791                    StackType::RowVar("b".to_string()).push(Type::Bool),
792                )))),
793            StackType::RowVar("a".to_string()).push(Type::Var("V2".to_string())),
794        ),
795    );
796
797    // list.fold: ( a Variant init Quotation -- a result )
798    // Quotation: ( b Acc T -- b Acc )
799    sigs.insert(
800        "list.fold".to_string(),
801        Effect::new(
802            StackType::RowVar("a".to_string())
803                .push(Type::Var("V".to_string()))
804                .push(Type::Var("Acc".to_string()))
805                .push(Type::Quotation(Box::new(Effect::new(
806                    StackType::RowVar("b".to_string())
807                        .push(Type::Var("Acc".to_string()))
808                        .push(Type::Var("T".to_string())),
809                    StackType::RowVar("b".to_string()).push(Type::Var("Acc".to_string())),
810                )))),
811            StackType::RowVar("a".to_string()).push(Type::Var("Acc".to_string())),
812        ),
813    );
814
815    // list.each: ( a Variant Quotation -- a )
816    // Quotation: ( b T -- b )
817    sigs.insert(
818        "list.each".to_string(),
819        Effect::new(
820            StackType::RowVar("a".to_string())
821                .push(Type::Var("V".to_string()))
822                .push(Type::Quotation(Box::new(Effect::new(
823                    StackType::RowVar("b".to_string()).push(Type::Var("T".to_string())),
824                    StackType::RowVar("b".to_string()),
825                )))),
826            StackType::RowVar("a".to_string()),
827        ),
828    );
829
830    // =========================================================================
831    // Map Operations (Dictionary with O(1) lookup)
832    // =========================================================================
833
834    builtin!(sigs, "map.make", (a -- a M));
835    builtin!(sigs, "map.get", (a M K -- a V Bool)); // returns (value success) - errors are values, not crashes
836    builtin!(sigs, "map.set", (a M K V -- a M2));
837    builtin!(sigs, "map.has?", (a M K -- a Bool));
838    builtin!(sigs, "map.remove", (a M K -- a M2));
839    builtin!(sigs, "map.keys", (a M -- a V));
840    builtin!(sigs, "map.values", (a M -- a V));
841    builtin!(sigs, "map.size", (a M -- a Int));
842    builtin!(sigs, "map.empty?", (a M -- a Bool));
843
844    // map.each: ( a Map Quotation -- a )
845    // Quotation: ( b K V -- b )
846    sigs.insert(
847        "map.each".to_string(),
848        Effect::new(
849            StackType::RowVar("a".to_string())
850                .push(Type::Var("M".to_string()))
851                .push(Type::Quotation(Box::new(Effect::new(
852                    StackType::RowVar("b".to_string())
853                        .push(Type::Var("K".to_string()))
854                        .push(Type::Var("V".to_string())),
855                    StackType::RowVar("b".to_string()),
856                )))),
857            StackType::RowVar("a".to_string()),
858        ),
859    );
860
861    // map.fold: ( a Map Acc Quotation -- a Acc )
862    // Quotation: ( b Acc K V -- b Acc )
863    sigs.insert(
864        "map.fold".to_string(),
865        Effect::new(
866            StackType::RowVar("a".to_string())
867                .push(Type::Var("M".to_string()))
868                .push(Type::Var("Acc".to_string()))
869                .push(Type::Quotation(Box::new(Effect::new(
870                    StackType::RowVar("b".to_string())
871                        .push(Type::Var("Acc".to_string()))
872                        .push(Type::Var("K".to_string()))
873                        .push(Type::Var("V".to_string())),
874                    StackType::RowVar("b".to_string()).push(Type::Var("Acc".to_string())),
875                )))),
876            StackType::RowVar("a".to_string()).push(Type::Var("Acc".to_string())),
877        ),
878    );
879
880    // =========================================================================
881    // Float Arithmetic ( a Float Float -- a Float )
882    // =========================================================================
883
884    builtins_float_float_to_float!(sigs, "f.add", "f.subtract", "f.multiply", "f.divide");
885    builtins_float_float_to_float!(sigs, "f.+", "f.-", "f.*", "f./");
886
887    // =========================================================================
888    // Float Comparison ( a Float Float -- a Bool )
889    // =========================================================================
890
891    builtins_float_float_to_bool!(sigs, "f.=", "f.<", "f.>", "f.<=", "f.>=", "f.<>");
892    builtins_float_float_to_bool!(sigs, "f.eq", "f.lt", "f.gt", "f.lte", "f.gte", "f.neq");
893
894    // =========================================================================
895    // Test Framework
896    // =========================================================================
897
898    builtin!(sigs, "test.init", (a String -- a));
899    builtin!(sigs, "test.finish", (a - -a));
900    builtin!(sigs, "test.has-failures", (a -- a Bool));
901    builtin!(sigs, "test.assert", (a Bool -- a));
902    builtin!(sigs, "test.assert-not", (a Bool -- a));
903    builtin!(sigs, "test.assert-eq", (a Int Int -- a));
904    builtin!(sigs, "test.assert-eq-str", (a String String -- a));
905    builtin!(sigs, "test.fail", (a String -- a));
906    builtin!(sigs, "test.pass-count", (a -- a Int));
907    builtin!(sigs, "test.fail-count", (a -- a Int));
908
909    // Time operations
910    builtin!(sigs, "time.now", (a -- a Int));
911    builtin!(sigs, "time.nanos", (a -- a Int));
912    builtin!(sigs, "time.sleep-ms", (a Int -- a));
913
914    // SON serialization
915    builtin!(sigs, "son.dump", (a T -- a String));
916    builtin!(sigs, "son.dump-pretty", (a T -- a String));
917
918    // Stack introspection (for REPL)
919    // stack.dump prints all values and clears the stack
920    sigs.insert(
921        "stack.dump".to_string(),
922        Effect::new(
923            StackType::RowVar("a".to_string()), // Consumes any stack
924            StackType::RowVar("b".to_string()), // Returns empty stack (different row var)
925        ),
926    );
927
928    sigs
929}
930
931/// Get documentation for a built-in word
932pub fn builtin_doc(name: &str) -> Option<&'static str> {
933    BUILTIN_DOCS.get(name).copied()
934}
935
936/// Get all built-in word documentation (cached with LazyLock for performance)
937pub fn builtin_docs() -> &'static HashMap<&'static str, &'static str> {
938    &BUILTIN_DOCS
939}
940
941/// Lazily initialized documentation for all built-in words
942static BUILTIN_DOCS: LazyLock<HashMap<&'static str, &'static str>> = LazyLock::new(|| {
943    let mut docs = HashMap::new();
944
945    // I/O Operations
946    docs.insert(
947        "io.write",
948        "Write a string to stdout without a trailing newline.",
949    );
950    docs.insert(
951        "io.write-line",
952        "Write a string to stdout followed by a newline.",
953    );
954    docs.insert(
955        "io.read-line",
956        "Read a line from stdin. Returns (String Bool) -- Bool is false on EOF or read error.",
957    );
958    docs.insert(
959        "io.read-line+",
960        "DEPRECATED: Use io.read-line instead. Read a line from stdin. Returns (line, status_code).",
961    );
962    docs.insert(
963        "io.read-n",
964        "Read N bytes from stdin. Returns (bytes, status_code).",
965    );
966
967    // Command-line Arguments
968    docs.insert("args.count", "Get the number of command-line arguments.");
969    docs.insert("args.at", "Get the command-line argument at index N.");
970
971    // File Operations
972    docs.insert(
973        "file.slurp",
974        "Read entire file contents. Returns (String Bool) -- Bool is false if file not found or unreadable.",
975    );
976    docs.insert("file.exists?", "Check if a file exists at the given path.");
977    docs.insert(
978        "file.spit",
979        "Write string to file (creates or overwrites). Returns Bool -- false on write failure.",
980    );
981    docs.insert(
982        "file.append",
983        "Append string to file (creates if needed). Returns Bool -- false on write failure.",
984    );
985    docs.insert(
986        "file.delete",
987        "Delete a file. Returns Bool -- false on failure.",
988    );
989    docs.insert(
990        "file.size",
991        "Get file size in bytes. Returns (Int Bool) -- Bool is false if file not found.",
992    );
993    docs.insert(
994        "file.for-each-line+",
995        "Execute a quotation for each line in a file.",
996    );
997
998    // Directory Operations
999    docs.insert(
1000        "dir.exists?",
1001        "Check if a directory exists at the given path.",
1002    );
1003    docs.insert(
1004        "dir.make",
1005        "Create a directory (and parent directories if needed). Returns Bool -- false on failure.",
1006    );
1007    docs.insert(
1008        "dir.delete",
1009        "Delete an empty directory. Returns Bool -- false on failure.",
1010    );
1011    docs.insert(
1012        "dir.list",
1013        "List directory contents. Returns (List Bool) -- Bool is false if directory not found.",
1014    );
1015
1016    // Type Conversions
1017    docs.insert(
1018        "int->string",
1019        "Convert an integer to its string representation.",
1020    );
1021    docs.insert(
1022        "int->float",
1023        "Convert an integer to a floating-point number.",
1024    );
1025    docs.insert("float->int", "Truncate a float to an integer.");
1026    docs.insert(
1027        "float->string",
1028        "Convert a float to its string representation.",
1029    );
1030    docs.insert(
1031        "string->int",
1032        "Parse a string as an integer. Returns (Int Bool) -- Bool is false if string is not a valid integer.",
1033    );
1034    docs.insert(
1035        "string->float",
1036        "Parse a string as a float. Returns (Float Bool) -- Bool is false if string is not a valid number.",
1037    );
1038    docs.insert(
1039        "char->string",
1040        "Convert a Unicode codepoint to a single-character string.",
1041    );
1042    docs.insert(
1043        "symbol->string",
1044        "Convert a symbol to its string representation.",
1045    );
1046    docs.insert("string->symbol", "Intern a string as a symbol.");
1047
1048    // Integer Arithmetic
1049    docs.insert("i.add", "Add two integers.");
1050    docs.insert("i.subtract", "Subtract second integer from first.");
1051    docs.insert("i.multiply", "Multiply two integers.");
1052    docs.insert(
1053        "i.divide",
1054        "Integer division. Returns (result Bool) -- Bool is false on division by zero.",
1055    );
1056    docs.insert(
1057        "i.modulo",
1058        "Integer modulo. Returns (result Bool) -- Bool is false on division by zero.",
1059    );
1060    docs.insert("i.+", "Add two integers.");
1061    docs.insert("i.-", "Subtract second integer from first.");
1062    docs.insert("i.*", "Multiply two integers.");
1063    docs.insert(
1064        "i./",
1065        "Integer division. Returns (result Bool) -- Bool is false on division by zero.",
1066    );
1067    docs.insert(
1068        "i.%",
1069        "Integer modulo. Returns (result Bool) -- Bool is false on division by zero.",
1070    );
1071
1072    // Integer Comparison
1073    docs.insert("i.=", "Test if two integers are equal.");
1074    docs.insert("i.<", "Test if first integer is less than second.");
1075    docs.insert("i.>", "Test if first integer is greater than second.");
1076    docs.insert(
1077        "i.<=",
1078        "Test if first integer is less than or equal to second.",
1079    );
1080    docs.insert(
1081        "i.>=",
1082        "Test if first integer is greater than or equal to second.",
1083    );
1084    docs.insert("i.<>", "Test if two integers are not equal.");
1085    docs.insert("i.eq", "Test if two integers are equal.");
1086    docs.insert("i.lt", "Test if first integer is less than second.");
1087    docs.insert("i.gt", "Test if first integer is greater than second.");
1088    docs.insert(
1089        "i.lte",
1090        "Test if first integer is less than or equal to second.",
1091    );
1092    docs.insert(
1093        "i.gte",
1094        "Test if first integer is greater than or equal to second.",
1095    );
1096    docs.insert("i.neq", "Test if two integers are not equal.");
1097
1098    // Boolean Operations
1099    docs.insert("and", "Logical AND of two booleans.");
1100    docs.insert("or", "Logical OR of two booleans.");
1101    docs.insert("not", "Logical NOT of a boolean.");
1102
1103    // Bitwise Operations
1104    docs.insert("band", "Bitwise AND of two integers.");
1105    docs.insert("bor", "Bitwise OR of two integers.");
1106    docs.insert("bxor", "Bitwise XOR of two integers.");
1107    docs.insert("bnot", "Bitwise NOT (complement) of an integer.");
1108    docs.insert("shl", "Shift left by N bits.");
1109    docs.insert("shr", "Shift right by N bits (arithmetic).");
1110    docs.insert(
1111        "i.neg",
1112        "Negate an integer (0 - n). Canonical name; `negate` is an alias.",
1113    );
1114    docs.insert(
1115        "negate",
1116        "Negate an integer (0 - n). Ergonomic alias for `i.neg`.",
1117    );
1118    docs.insert("popcount", "Count the number of set bits.");
1119    docs.insert("clz", "Count leading zeros.");
1120    docs.insert("ctz", "Count trailing zeros.");
1121    docs.insert("int-bits", "Push the bit width of integers (64).");
1122
1123    // Stack Operations
1124    docs.insert("dup", "Duplicate the top stack value.");
1125    docs.insert("drop", "Remove the top stack value.");
1126    docs.insert("swap", "Swap the top two stack values.");
1127    docs.insert("over", "Copy the second value to the top.");
1128    docs.insert("rot", "Rotate the top three values (third to top).");
1129    docs.insert("nip", "Remove the second value from the stack.");
1130    docs.insert("tuck", "Copy the top value below the second.");
1131    docs.insert("2dup", "Duplicate the top two values.");
1132    docs.insert("3drop", "Remove the top three values.");
1133    docs.insert("pick", "Copy the value at depth N to the top.");
1134    docs.insert("roll", "Rotate N+1 items, bringing depth N to top.");
1135
1136    // Aux Stack Operations
1137    docs.insert(
1138        ">aux",
1139        "Move top of stack to word-local aux stack. Must be balanced with aux> before word returns.",
1140    );
1141    docs.insert(
1142        "aux>",
1143        "Move top of aux stack back to main stack. Requires a matching >aux.",
1144    );
1145
1146    // Channel Operations
1147    docs.insert(
1148        "chan.make",
1149        "Create a new channel for inter-strand communication.",
1150    );
1151    docs.insert(
1152        "chan.send",
1153        "Send a value on a channel. Returns Bool -- false if channel is closed.",
1154    );
1155    docs.insert(
1156        "chan.receive",
1157        "Receive a value from a channel. Returns (value Bool) -- Bool is false if channel is closed.",
1158    );
1159    docs.insert("chan.close", "Close a channel.");
1160    docs.insert("chan.yield", "Yield control to the scheduler.");
1161
1162    // Control Flow
1163    docs.insert("call", "Call a quotation or closure.");
1164    docs.insert(
1165        "cond",
1166        "Multi-way conditional: test clauses until one succeeds.",
1167    );
1168
1169    // Dataflow Combinators
1170    docs.insert(
1171        "dip",
1172        "Hide top value, run quotation on rest of stack, restore value. ( ..a x [..a -- ..b] -- ..b x )",
1173    );
1174    docs.insert(
1175        "keep",
1176        "Run quotation on top value, but preserve the original. ( ..a x [..a x -- ..b] -- ..b x )",
1177    );
1178    docs.insert(
1179        "bi",
1180        "Apply two quotations to the same value. ( ..a x [q1] [q2] -- ..c )",
1181    );
1182
1183    // Concurrency
1184    docs.insert(
1185        "strand.spawn",
1186        "Spawn a concurrent strand. Returns strand ID.",
1187    );
1188    docs.insert(
1189        "strand.weave",
1190        "Create a generator/coroutine. Returns handle.",
1191    );
1192    docs.insert(
1193        "strand.resume",
1194        "Resume a weave with a value. Returns (handle, value, has_more).",
1195    );
1196    docs.insert(
1197        "yield",
1198        "Yield a value from a weave and receive resume value.",
1199    );
1200    docs.insert(
1201        "strand.weave-cancel",
1202        "Cancel a weave and release its resources.",
1203    );
1204
1205    // TCP Operations
1206    docs.insert(
1207        "tcp.listen",
1208        "Start listening on a port. Returns (socket_id, success).",
1209    );
1210    docs.insert(
1211        "tcp.accept",
1212        "Accept a connection. Returns (client_id, success).",
1213    );
1214    docs.insert(
1215        "tcp.read",
1216        "Read data from a socket. Returns (string, success).",
1217    );
1218    docs.insert("tcp.write", "Write data to a socket. Returns success.");
1219    docs.insert("tcp.close", "Close a socket. Returns success.");
1220
1221    // OS Operations
1222    docs.insert(
1223        "os.getenv",
1224        "Get environment variable. Returns (value, exists).",
1225    );
1226    docs.insert(
1227        "os.home-dir",
1228        "Get user's home directory. Returns (path, success).",
1229    );
1230    docs.insert(
1231        "os.current-dir",
1232        "Get current working directory. Returns (path, success).",
1233    );
1234    docs.insert("os.path-exists", "Check if a path exists.");
1235    docs.insert("os.path-is-file", "Check if path is a regular file.");
1236    docs.insert("os.path-is-dir", "Check if path is a directory.");
1237    docs.insert("os.path-join", "Join two path components.");
1238    docs.insert(
1239        "os.path-parent",
1240        "Get parent directory. Returns (path, success).",
1241    );
1242    docs.insert(
1243        "os.path-filename",
1244        "Get filename component. Returns (name, success).",
1245    );
1246    docs.insert("os.exit", "Exit the program with a status code.");
1247    docs.insert(
1248        "os.name",
1249        "Get the operating system name (e.g., \"macos\", \"linux\").",
1250    );
1251    docs.insert(
1252        "os.arch",
1253        "Get the CPU architecture (e.g., \"aarch64\", \"x86_64\").",
1254    );
1255
1256    // Signal Handling
1257    docs.insert(
1258        "signal.trap",
1259        "Trap a signal: set internal flag on receipt instead of default action.",
1260    );
1261    docs.insert(
1262        "signal.received?",
1263        "Check if signal was received and clear the flag. Returns Bool.",
1264    );
1265    docs.insert(
1266        "signal.pending?",
1267        "Check if signal is pending without clearing the flag. Returns Bool.",
1268    );
1269    docs.insert(
1270        "signal.default",
1271        "Restore the default handler for a signal.",
1272    );
1273    docs.insert(
1274        "signal.ignore",
1275        "Ignore a signal entirely (useful for SIGPIPE in servers).",
1276    );
1277    docs.insert(
1278        "signal.clear",
1279        "Clear the pending flag for a signal without checking it.",
1280    );
1281    docs.insert("signal.SIGINT", "SIGINT constant (Ctrl+C interrupt).");
1282    docs.insert("signal.SIGTERM", "SIGTERM constant (termination request).");
1283    docs.insert("signal.SIGHUP", "SIGHUP constant (hangup detected).");
1284    docs.insert("signal.SIGPIPE", "SIGPIPE constant (broken pipe).");
1285    docs.insert(
1286        "signal.SIGUSR1",
1287        "SIGUSR1 constant (user-defined signal 1).",
1288    );
1289    docs.insert(
1290        "signal.SIGUSR2",
1291        "SIGUSR2 constant (user-defined signal 2).",
1292    );
1293    docs.insert("signal.SIGCHLD", "SIGCHLD constant (child status changed).");
1294    docs.insert("signal.SIGALRM", "SIGALRM constant (alarm clock).");
1295    docs.insert("signal.SIGCONT", "SIGCONT constant (continue if stopped).");
1296
1297    // Terminal Operations
1298    docs.insert(
1299        "terminal.raw-mode",
1300        "Enable/disable raw terminal mode. In raw mode: no line buffering, no echo, Ctrl+C read as byte 3.",
1301    );
1302    docs.insert(
1303        "terminal.read-char",
1304        "Read a single byte from stdin (blocking). Returns 0-255 on success, -1 on EOF/error.",
1305    );
1306    docs.insert(
1307        "terminal.read-char?",
1308        "Read a single byte from stdin (non-blocking). Returns 0-255 if available, -1 otherwise.",
1309    );
1310    docs.insert(
1311        "terminal.width",
1312        "Get terminal width in columns. Returns 80 if unknown.",
1313    );
1314    docs.insert(
1315        "terminal.height",
1316        "Get terminal height in rows. Returns 24 if unknown.",
1317    );
1318    docs.insert(
1319        "terminal.flush",
1320        "Flush stdout. Use after writing escape sequences or partial lines.",
1321    );
1322
1323    // String Operations
1324    docs.insert("string.concat", "Concatenate two strings.");
1325    docs.insert("string.length", "Get the character length of a string.");
1326    docs.insert("string.byte-length", "Get the byte length of a string.");
1327    docs.insert(
1328        "string.char-at",
1329        "Get Unicode codepoint at character index.",
1330    );
1331    docs.insert(
1332        "string.substring",
1333        "Extract substring from start index with length.",
1334    );
1335    docs.insert(
1336        "string.find",
1337        "Find substring. Returns index or -1 if not found.",
1338    );
1339    docs.insert("string.split", "Split string by delimiter. Returns a list.");
1340    docs.insert("string.contains", "Check if string contains a substring.");
1341    docs.insert(
1342        "string.starts-with",
1343        "Check if string starts with a prefix.",
1344    );
1345    docs.insert("string.empty?", "Check if string is empty.");
1346    docs.insert("string.equal?", "Check if two strings are equal.");
1347    docs.insert(
1348        "string.join",
1349        "Join a list of values with a separator string. ( list sep -- string )",
1350    );
1351    docs.insert("string.trim", "Remove leading and trailing whitespace.");
1352    docs.insert("string.chomp", "Remove trailing newline.");
1353    docs.insert("string.to-upper", "Convert to uppercase.");
1354    docs.insert("string.to-lower", "Convert to lowercase.");
1355    docs.insert("string.json-escape", "Escape special characters for JSON.");
1356    docs.insert("symbol.=", "Check if two symbols are equal.");
1357
1358    // Encoding Operations
1359    docs.insert(
1360        "encoding.base64-encode",
1361        "Encode a string to Base64 (standard alphabet with padding).",
1362    );
1363    docs.insert(
1364        "encoding.base64-decode",
1365        "Decode a Base64 string. Returns (decoded, success).",
1366    );
1367    docs.insert(
1368        "encoding.base64url-encode",
1369        "Encode to URL-safe Base64 (no padding). Suitable for JWTs and URLs.",
1370    );
1371    docs.insert(
1372        "encoding.base64url-decode",
1373        "Decode URL-safe Base64. Returns (decoded, success).",
1374    );
1375    docs.insert(
1376        "encoding.hex-encode",
1377        "Encode a string to lowercase hexadecimal.",
1378    );
1379    docs.insert(
1380        "encoding.hex-decode",
1381        "Decode a hexadecimal string. Returns (decoded, success).",
1382    );
1383
1384    // Crypto Operations
1385    docs.insert(
1386        "crypto.sha256",
1387        "Compute SHA-256 hash of a string. Returns 64-char hex digest.",
1388    );
1389    docs.insert(
1390        "crypto.hmac-sha256",
1391        "Compute HMAC-SHA256 signature. ( message key -- signature )",
1392    );
1393    docs.insert(
1394        "crypto.constant-time-eq",
1395        "Timing-safe string comparison. Use for comparing signatures/tokens.",
1396    );
1397    docs.insert(
1398        "crypto.random-bytes",
1399        "Generate N cryptographically secure random bytes as hex string.",
1400    );
1401    docs.insert(
1402        "crypto.random-int",
1403        "Generate uniform random integer in [min, max). ( min max -- Int ) Uses rejection sampling to avoid modulo bias.",
1404    );
1405    docs.insert("crypto.uuid4", "Generate a random UUID v4 string.");
1406    docs.insert(
1407        "crypto.aes-gcm-encrypt",
1408        "Encrypt with AES-256-GCM. ( plaintext hex-key -- ciphertext success )",
1409    );
1410    docs.insert(
1411        "crypto.aes-gcm-decrypt",
1412        "Decrypt AES-256-GCM ciphertext. ( ciphertext hex-key -- plaintext success )",
1413    );
1414    docs.insert(
1415        "crypto.pbkdf2-sha256",
1416        "Derive key from password. ( password salt iterations -- hex-key success ) Min 1000 iterations, 100000+ recommended.",
1417    );
1418    docs.insert(
1419        "crypto.ed25519-keypair",
1420        "Generate Ed25519 keypair. ( -- public-key private-key ) Both as 64-char hex strings.",
1421    );
1422    docs.insert(
1423        "crypto.ed25519-sign",
1424        "Sign message with Ed25519 private key. ( message private-key -- signature success ) Signature is 128-char hex.",
1425    );
1426    docs.insert(
1427        "crypto.ed25519-verify",
1428        "Verify Ed25519 signature. ( message signature public-key -- valid )",
1429    );
1430
1431    // HTTP Client Operations
1432    docs.insert(
1433        "http.get",
1434        "HTTP GET request. ( url -- response-map ) Map has status, body, ok, error.",
1435    );
1436    docs.insert(
1437        "http.post",
1438        "HTTP POST request. ( url body content-type -- response-map )",
1439    );
1440    docs.insert(
1441        "http.put",
1442        "HTTP PUT request. ( url body content-type -- response-map )",
1443    );
1444    docs.insert(
1445        "http.delete",
1446        "HTTP DELETE request. ( url -- response-map )",
1447    );
1448
1449    // Regular Expression Operations
1450    docs.insert(
1451        "regex.match?",
1452        "Check if pattern matches anywhere in string. ( text pattern -- bool )",
1453    );
1454    docs.insert(
1455        "regex.find",
1456        "Find first match. ( text pattern -- matched success )",
1457    );
1458    docs.insert(
1459        "regex.find-all",
1460        "Find all matches. ( text pattern -- list success )",
1461    );
1462    docs.insert(
1463        "regex.replace",
1464        "Replace first match. ( text pattern replacement -- result success )",
1465    );
1466    docs.insert(
1467        "regex.replace-all",
1468        "Replace all matches. ( text pattern replacement -- result success )",
1469    );
1470    docs.insert(
1471        "regex.captures",
1472        "Extract capture groups. ( text pattern -- groups success )",
1473    );
1474    docs.insert(
1475        "regex.split",
1476        "Split string by pattern. ( text pattern -- list success )",
1477    );
1478    docs.insert(
1479        "regex.valid?",
1480        "Check if pattern is valid regex. ( pattern -- bool )",
1481    );
1482
1483    // Compression Operations
1484    docs.insert(
1485        "compress.gzip",
1486        "Compress string with gzip. Returns base64-encoded data. ( data -- compressed success )",
1487    );
1488    docs.insert(
1489        "compress.gzip-level",
1490        "Compress with gzip at level 1-9. ( data level -- compressed success )",
1491    );
1492    docs.insert(
1493        "compress.gunzip",
1494        "Decompress gzip data. ( base64-data -- decompressed success )",
1495    );
1496    docs.insert(
1497        "compress.zstd",
1498        "Compress string with zstd. Returns base64-encoded data. ( data -- compressed success )",
1499    );
1500    docs.insert(
1501        "compress.zstd-level",
1502        "Compress with zstd at level 1-22. ( data level -- compressed success )",
1503    );
1504    docs.insert(
1505        "compress.unzstd",
1506        "Decompress zstd data. ( base64-data -- decompressed success )",
1507    );
1508
1509    // Variant Operations
1510    docs.insert(
1511        "variant.field-count",
1512        "Get the number of fields in a variant.",
1513    );
1514    docs.insert(
1515        "variant.tag",
1516        "Get the tag (constructor name) of a variant.",
1517    );
1518    docs.insert("variant.field-at", "Get the field at index N.");
1519    docs.insert(
1520        "variant.append",
1521        "Append a value to a variant (creates new).",
1522    );
1523    docs.insert("variant.last", "Get the last field of a variant.");
1524    docs.insert("variant.init", "Get all fields except the last.");
1525    docs.insert("variant.make-0", "Create a variant with 0 fields.");
1526    docs.insert("variant.make-1", "Create a variant with 1 field.");
1527    docs.insert("variant.make-2", "Create a variant with 2 fields.");
1528    docs.insert("variant.make-3", "Create a variant with 3 fields.");
1529    docs.insert("variant.make-4", "Create a variant with 4 fields.");
1530    docs.insert("variant.make-5", "Create a variant with 5 fields.");
1531    docs.insert("variant.make-6", "Create a variant with 6 fields.");
1532    docs.insert("variant.make-7", "Create a variant with 7 fields.");
1533    docs.insert("variant.make-8", "Create a variant with 8 fields.");
1534    docs.insert("variant.make-9", "Create a variant with 9 fields.");
1535    docs.insert("variant.make-10", "Create a variant with 10 fields.");
1536    docs.insert("variant.make-11", "Create a variant with 11 fields.");
1537    docs.insert("variant.make-12", "Create a variant with 12 fields.");
1538    docs.insert("wrap-0", "Create a variant with 0 fields (alias).");
1539    docs.insert("wrap-1", "Create a variant with 1 field (alias).");
1540    docs.insert("wrap-2", "Create a variant with 2 fields (alias).");
1541    docs.insert("wrap-3", "Create a variant with 3 fields (alias).");
1542    docs.insert("wrap-4", "Create a variant with 4 fields (alias).");
1543    docs.insert("wrap-5", "Create a variant with 5 fields (alias).");
1544    docs.insert("wrap-6", "Create a variant with 6 fields (alias).");
1545    docs.insert("wrap-7", "Create a variant with 7 fields (alias).");
1546    docs.insert("wrap-8", "Create a variant with 8 fields (alias).");
1547    docs.insert("wrap-9", "Create a variant with 9 fields (alias).");
1548    docs.insert("wrap-10", "Create a variant with 10 fields (alias).");
1549    docs.insert("wrap-11", "Create a variant with 11 fields (alias).");
1550    docs.insert("wrap-12", "Create a variant with 12 fields (alias).");
1551
1552    // List Operations
1553    docs.insert("list.make", "Create an empty list.");
1554    docs.insert("list.push", "Push a value onto a list. Returns new list.");
1555    docs.insert(
1556        "list.get",
1557        "Get value at index. Returns (value Bool) -- Bool is false if index out of bounds.",
1558    );
1559    docs.insert(
1560        "list.set",
1561        "Set value at index. Returns (List Bool) -- Bool is false if index out of bounds.",
1562    );
1563    docs.insert("list.length", "Get the number of elements in a list.");
1564    docs.insert("list.empty?", "Check if a list is empty.");
1565    docs.insert("list.reverse", "Reverse the elements of a list.");
1566    docs.insert(
1567        "list.map",
1568        "Apply quotation to each element. Returns new list.",
1569    );
1570    docs.insert("list.filter", "Keep elements where quotation returns true.");
1571    docs.insert("list.fold", "Reduce list with accumulator and quotation.");
1572    docs.insert(
1573        "list.each",
1574        "Execute quotation for each element (side effects).",
1575    );
1576
1577    // Map Operations
1578    docs.insert("map.make", "Create an empty map.");
1579    docs.insert(
1580        "map.get",
1581        "Get value for key. Returns (value Bool) -- Bool is false if key not found.",
1582    );
1583    docs.insert("map.set", "Set key to value. Returns new map.");
1584    docs.insert("map.has?", "Check if map contains a key.");
1585    docs.insert("map.remove", "Remove a key. Returns new map.");
1586    docs.insert("map.keys", "Get all keys as a list.");
1587    docs.insert("map.values", "Get all values as a list.");
1588    docs.insert("map.size", "Get the number of key-value pairs.");
1589    docs.insert("map.empty?", "Check if map is empty.");
1590    docs.insert(
1591        "map.each",
1592        "Iterate key-value pairs. Quotation: ( key value -- ).",
1593    );
1594    docs.insert(
1595        "map.fold",
1596        "Fold over key-value pairs with accumulator. Quotation: ( acc key value -- acc' ).",
1597    );
1598
1599    // TCP Operations
1600    docs.insert(
1601        "tcp.listen",
1602        "Start listening on a port. Returns (fd Bool) -- Bool is false on failure.",
1603    );
1604    docs.insert(
1605        "tcp.accept",
1606        "Accept a connection. Returns (fd Bool) -- Bool is false on failure.",
1607    );
1608    docs.insert(
1609        "tcp.read",
1610        "Read from a connection. Returns (String Bool) -- Bool is false on failure.",
1611    );
1612    docs.insert(
1613        "tcp.write",
1614        "Write to a connection. Returns Bool -- false on failure.",
1615    );
1616    docs.insert(
1617        "tcp.close",
1618        "Close a connection. Returns Bool -- false on failure.",
1619    );
1620
1621    // OS Operations
1622    docs.insert(
1623        "os.getenv",
1624        "Get environment variable. Returns (String Bool) -- Bool is false if not set.",
1625    );
1626    docs.insert(
1627        "os.home-dir",
1628        "Get home directory. Returns (String Bool) -- Bool is false if unavailable.",
1629    );
1630    docs.insert(
1631        "os.current-dir",
1632        "Get current directory. Returns (String Bool) -- Bool is false if unavailable.",
1633    );
1634    docs.insert("os.path-exists", "Check if a path exists.");
1635    docs.insert("os.path-is-file", "Check if a path is a file.");
1636    docs.insert("os.path-is-dir", "Check if a path is a directory.");
1637    docs.insert("os.path-join", "Join two path segments.");
1638    docs.insert(
1639        "os.path-parent",
1640        "Get parent directory. Returns (String Bool) -- Bool is false for root.",
1641    );
1642    docs.insert(
1643        "os.path-filename",
1644        "Get filename component. Returns (String Bool) -- Bool is false if none.",
1645    );
1646    docs.insert("os.exit", "Exit the process with given exit code.");
1647    docs.insert("os.name", "Get OS name (e.g., \"macos\", \"linux\").");
1648    docs.insert(
1649        "os.arch",
1650        "Get CPU architecture (e.g., \"aarch64\", \"x86_64\").",
1651    );
1652
1653    // Regex Operations
1654    docs.insert("regex.match?", "Test if string matches regex pattern.");
1655    docs.insert(
1656        "regex.find",
1657        "Find first match. Returns (String Bool) -- Bool is false if no match or invalid regex.",
1658    );
1659    docs.insert(
1660        "regex.find-all",
1661        "Find all matches. Returns (List Bool) -- Bool is false if invalid regex.",
1662    );
1663    docs.insert(
1664        "regex.replace",
1665        "Replace first match. Returns (String Bool) -- Bool is false if invalid regex.",
1666    );
1667    docs.insert(
1668        "regex.replace-all",
1669        "Replace all matches. Returns (String Bool) -- Bool is false if invalid regex.",
1670    );
1671    docs.insert(
1672        "regex.captures",
1673        "Get capture groups. Returns (List Bool) -- Bool is false if invalid regex.",
1674    );
1675    docs.insert(
1676        "regex.split",
1677        "Split by regex. Returns (List Bool) -- Bool is false if invalid regex.",
1678    );
1679    docs.insert("regex.valid?", "Check if a regex pattern is valid.");
1680
1681    // Encoding Operations
1682    docs.insert("encoding.base64-encode", "Encode string as base64.");
1683    docs.insert(
1684        "encoding.base64-decode",
1685        "Decode base64 string. Returns (String Bool) -- Bool is false if invalid.",
1686    );
1687    docs.insert("encoding.base64url-encode", "Encode string as base64url.");
1688    docs.insert(
1689        "encoding.base64url-decode",
1690        "Decode base64url string. Returns (String Bool) -- Bool is false if invalid.",
1691    );
1692    docs.insert("encoding.hex-encode", "Encode string as hexadecimal.");
1693    docs.insert(
1694        "encoding.hex-decode",
1695        "Decode hex string. Returns (String Bool) -- Bool is false if invalid.",
1696    );
1697
1698    // Crypto Operations
1699    docs.insert("crypto.sha256", "Compute SHA-256 hash of a string.");
1700    docs.insert(
1701        "crypto.hmac-sha256",
1702        "Compute HMAC-SHA256. ( message key -- hash )",
1703    );
1704    docs.insert(
1705        "crypto.constant-time-eq",
1706        "Constant-time string equality comparison.",
1707    );
1708    docs.insert(
1709        "crypto.random-bytes",
1710        "Generate N random bytes as a string.",
1711    );
1712    docs.insert(
1713        "crypto.random-int",
1714        "Generate random integer in range [min, max).",
1715    );
1716    docs.insert("crypto.uuid4", "Generate a random UUID v4 string.");
1717    docs.insert(
1718        "crypto.aes-gcm-encrypt",
1719        "AES-GCM encrypt. Returns (String Bool) -- Bool is false on failure.",
1720    );
1721    docs.insert("crypto.aes-gcm-decrypt", "AES-GCM decrypt. Returns (String Bool) -- Bool is false on failure (wrong key or tampered data).");
1722    docs.insert(
1723        "crypto.pbkdf2-sha256",
1724        "Derive key with PBKDF2. Returns (String Bool) -- Bool is false on failure.",
1725    );
1726    docs.insert(
1727        "crypto.ed25519-keypair",
1728        "Generate Ed25519 keypair. Returns (public private).",
1729    );
1730    docs.insert(
1731        "crypto.ed25519-sign",
1732        "Sign with Ed25519. Returns (String Bool) -- Bool is false on failure.",
1733    );
1734    docs.insert(
1735        "crypto.ed25519-verify",
1736        "Verify Ed25519 signature. Returns Bool -- true if valid.",
1737    );
1738
1739    // Compression Operations
1740    docs.insert(
1741        "compress.gzip",
1742        "Gzip compress. Returns (String Bool) -- Bool is false on failure.",
1743    );
1744    docs.insert(
1745        "compress.gzip-level",
1746        "Gzip compress at level N. Returns (String Bool) -- Bool is false on failure.",
1747    );
1748    docs.insert(
1749        "compress.gunzip",
1750        "Gzip decompress. Returns (String Bool) -- Bool is false on failure.",
1751    );
1752    docs.insert(
1753        "compress.zstd",
1754        "Zstd compress. Returns (String Bool) -- Bool is false on failure.",
1755    );
1756    docs.insert(
1757        "compress.zstd-level",
1758        "Zstd compress at level N. Returns (String Bool) -- Bool is false on failure.",
1759    );
1760    docs.insert(
1761        "compress.unzstd",
1762        "Zstd decompress. Returns (String Bool) -- Bool is false on failure.",
1763    );
1764
1765    // Signal Operations
1766    docs.insert(
1767        "signal.trap",
1768        "Register a signal handler for the given signal number.",
1769    );
1770    docs.insert("signal.received?", "Check if a signal has been received.");
1771    docs.insert("signal.pending?", "Check if a signal is pending.");
1772    docs.insert("signal.default", "Reset signal to default handler.");
1773    docs.insert("signal.ignore", "Ignore a signal.");
1774    docs.insert("signal.clear", "Clear pending signal.");
1775
1776    // Terminal Operations
1777    docs.insert("terminal.raw-mode", "Enable/disable raw terminal mode.");
1778    docs.insert("terminal.read-char", "Read a single character (blocking).");
1779    docs.insert(
1780        "terminal.read-char?",
1781        "Read a character if available (non-blocking). Returns 0 if none.",
1782    );
1783    docs.insert("terminal.width", "Get terminal width in columns.");
1784    docs.insert("terminal.height", "Get terminal height in rows.");
1785    docs.insert("terminal.flush", "Flush stdout.");
1786
1787    // Float Arithmetic
1788    docs.insert("f.add", "Add two floats.");
1789    docs.insert("f.subtract", "Subtract second float from first.");
1790    docs.insert("f.multiply", "Multiply two floats.");
1791    docs.insert("f.divide", "Divide first float by second.");
1792    docs.insert("f.+", "Add two floats.");
1793    docs.insert("f.-", "Subtract second float from first.");
1794    docs.insert("f.*", "Multiply two floats.");
1795    docs.insert("f./", "Divide first float by second.");
1796
1797    // Float Comparison
1798    docs.insert("f.=", "Test if two floats are equal.");
1799    docs.insert("f.<", "Test if first float is less than second.");
1800    docs.insert("f.>", "Test if first float is greater than second.");
1801    docs.insert("f.<=", "Test if first float is less than or equal.");
1802    docs.insert("f.>=", "Test if first float is greater than or equal.");
1803    docs.insert("f.<>", "Test if two floats are not equal.");
1804    docs.insert("f.eq", "Test if two floats are equal.");
1805    docs.insert("f.lt", "Test if first float is less than second.");
1806    docs.insert("f.gt", "Test if first float is greater than second.");
1807    docs.insert("f.lte", "Test if first float is less than or equal.");
1808    docs.insert("f.gte", "Test if first float is greater than or equal.");
1809    docs.insert("f.neq", "Test if two floats are not equal.");
1810
1811    // Test Framework
1812    docs.insert(
1813        "test.init",
1814        "Initialize the test framework with a test name.",
1815    );
1816    docs.insert("test.finish", "Finish testing and print results.");
1817    docs.insert("test.has-failures", "Check if any tests have failed.");
1818    docs.insert("test.assert", "Assert that a boolean is true.");
1819    docs.insert("test.assert-not", "Assert that a boolean is false.");
1820    docs.insert("test.assert-eq", "Assert that two integers are equal.");
1821    docs.insert("test.assert-eq-str", "Assert that two strings are equal.");
1822    docs.insert("test.fail", "Mark a test as failed with a message.");
1823    docs.insert("test.pass-count", "Get the number of passed assertions.");
1824    docs.insert("test.fail-count", "Get the number of failed assertions.");
1825
1826    // Time Operations
1827    docs.insert("time.now", "Get current Unix timestamp in seconds.");
1828    docs.insert(
1829        "time.nanos",
1830        "Get high-resolution monotonic time in nanoseconds.",
1831    );
1832    docs.insert("time.sleep-ms", "Sleep for N milliseconds.");
1833
1834    // Serialization
1835    docs.insert("son.dump", "Serialize any value to SON format (compact).");
1836    docs.insert(
1837        "son.dump-pretty",
1838        "Serialize any value to SON format (pretty-printed).",
1839    );
1840
1841    // Stack Introspection
1842    docs.insert(
1843        "stack.dump",
1844        "Print all stack values and clear the stack (REPL).",
1845    );
1846
1847    docs
1848});
1849
1850#[cfg(test)]
1851mod tests {
1852    use super::*;
1853
1854    #[test]
1855    fn test_builtin_signature_write_line() {
1856        let sig = builtin_signature("io.write-line").unwrap();
1857        // ( ..a String -- ..a )
1858        let (rest, top) = sig.inputs.clone().pop().unwrap();
1859        assert_eq!(top, Type::String);
1860        assert_eq!(rest, StackType::RowVar("a".to_string()));
1861        assert_eq!(sig.outputs, StackType::RowVar("a".to_string()));
1862    }
1863
1864    #[test]
1865    fn test_builtin_signature_i_add() {
1866        let sig = builtin_signature("i.add").unwrap();
1867        // ( ..a Int Int -- ..a Int )
1868        let (rest, top) = sig.inputs.clone().pop().unwrap();
1869        assert_eq!(top, Type::Int);
1870        let (rest2, top2) = rest.pop().unwrap();
1871        assert_eq!(top2, Type::Int);
1872        assert_eq!(rest2, StackType::RowVar("a".to_string()));
1873
1874        let (rest3, top3) = sig.outputs.clone().pop().unwrap();
1875        assert_eq!(top3, Type::Int);
1876        assert_eq!(rest3, StackType::RowVar("a".to_string()));
1877    }
1878
1879    #[test]
1880    fn test_builtin_signature_dup() {
1881        let sig = builtin_signature("dup").unwrap();
1882        // Input: ( ..a T )
1883        assert_eq!(
1884            sig.inputs,
1885            StackType::Cons {
1886                rest: Box::new(StackType::RowVar("a".to_string())),
1887                top: Type::Var("T".to_string())
1888            }
1889        );
1890        // Output: ( ..a T T )
1891        let (rest, top) = sig.outputs.clone().pop().unwrap();
1892        assert_eq!(top, Type::Var("T".to_string()));
1893        let (rest2, top2) = rest.pop().unwrap();
1894        assert_eq!(top2, Type::Var("T".to_string()));
1895        assert_eq!(rest2, StackType::RowVar("a".to_string()));
1896    }
1897
1898    #[test]
1899    fn test_all_builtins_have_signatures() {
1900        let sigs = builtin_signatures();
1901
1902        // Verify all expected builtins have signatures
1903        assert!(sigs.contains_key("io.write-line"));
1904        assert!(sigs.contains_key("io.read-line"));
1905        assert!(sigs.contains_key("int->string"));
1906        assert!(sigs.contains_key("i.add"));
1907        assert!(sigs.contains_key("dup"));
1908        assert!(sigs.contains_key("swap"));
1909        assert!(sigs.contains_key("chan.make"));
1910        assert!(sigs.contains_key("chan.send"));
1911        assert!(sigs.contains_key("chan.receive"));
1912        assert!(
1913            sigs.contains_key("string->float"),
1914            "string->float should be a builtin"
1915        );
1916        assert!(
1917            sigs.contains_key("signal.trap"),
1918            "signal.trap should be a builtin"
1919        );
1920    }
1921
1922    #[test]
1923    fn test_all_docs_have_signatures() {
1924        let sigs = builtin_signatures();
1925        let docs = builtin_docs();
1926
1927        for name in docs.keys() {
1928            assert!(
1929                sigs.contains_key(*name),
1930                "Builtin '{}' has documentation but no signature",
1931                name
1932            );
1933        }
1934    }
1935
1936    #[test]
1937    fn test_all_signatures_have_docs() {
1938        let sigs = builtin_signatures();
1939        let docs = builtin_docs();
1940
1941        for name in sigs.keys() {
1942            assert!(
1943                docs.contains_key(name.as_str()),
1944                "Builtin '{}' has signature but no documentation",
1945                name
1946            );
1947        }
1948    }
1949}