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    builtin!(sigs, "int-bits", (a -- a Int));
375
376    // =========================================================================
377    // Stack Operations (Polymorphic)
378    // =========================================================================
379
380    builtin!(sigs, "dup", (a T -- a T T));
381    builtin!(sigs, "drop", (a T -- a));
382    builtin!(sigs, "swap", (a T U -- a U T));
383    builtin!(sigs, "over", (a T U -- a T U T));
384    builtin!(sigs, "rot", (a T U V -- a U V T));
385    builtin!(sigs, "nip", (a T U -- a U));
386    builtin!(sigs, "tuck", (a T U -- a U T U));
387    builtin!(sigs, "2dup", (a T U -- a T U T U));
388    builtin!(sigs, "3drop", (a T U V -- a));
389
390    // pick and roll: Type approximations (see detailed comments below)
391    // pick: ( ..a T Int -- ..a T T ) - copies value at depth n to top
392    builtin!(sigs, "pick", (a T Int -- a T T));
393    // roll: ( ..a T Int -- ..a T ) - rotates n+1 items, bringing depth n to top
394    builtin!(sigs, "roll", (a T Int -- a T));
395
396    // =========================================================================
397    // Channel Operations (CSP-style concurrency)
398    // Errors are values, not crashes - all ops return success flags
399    // =========================================================================
400
401    builtin!(sigs, "chan.make", (a -- a Channel));
402    builtin!(sigs, "chan.send", (a T Channel -- a Bool)); // returns success flag
403    builtin!(sigs, "chan.receive", (a Channel -- a T Bool)); // returns value and success flag
404    builtin!(sigs, "chan.close", (a Channel -- a));
405    builtin!(sigs, "chan.yield", (a - -a));
406
407    // =========================================================================
408    // Quotation/Control Flow Operations
409    // =========================================================================
410
411    // call: Polymorphic - accepts Quotation or Closure
412    // Uses type variable Q to represent "something callable"
413    sigs.insert(
414        "call".to_string(),
415        Effect::new(
416            StackType::RowVar("a".to_string()).push(Type::Var("Q".to_string())),
417            StackType::RowVar("b".to_string()),
418        ),
419    );
420
421    // cond: Multi-way conditional (variable arity)
422    sigs.insert(
423        "cond".to_string(),
424        Effect::new(
425            StackType::RowVar("a".to_string()),
426            StackType::RowVar("b".to_string()),
427        ),
428    );
429
430    // strand.spawn: ( a Quotation -- a Int ) - spawn a concurrent strand
431    // The quotation can have any stack effect - it runs independently
432    sigs.insert(
433        "strand.spawn".to_string(),
434        Effect::new(
435            StackType::RowVar("a".to_string()).push(Type::Quotation(Box::new(Effect::new(
436                StackType::RowVar("spawn_in".to_string()),
437                StackType::RowVar("spawn_out".to_string()),
438            )))),
439            StackType::RowVar("a".to_string()).push(Type::Int),
440        ),
441    );
442
443    // strand.weave: ( a Quotation -- a handle ) - create a woven strand (generator)
444    // The quotation receives (WeaveCtx, first_resume_value) and must thread WeaveCtx through.
445    // Returns a handle (WeaveCtx) for use with strand.resume.
446    sigs.insert(
447        "strand.weave".to_string(),
448        Effect::new(
449            StackType::RowVar("a".to_string()).push(Type::Quotation(Box::new(Effect::new(
450                StackType::RowVar("weave_in".to_string()),
451                StackType::RowVar("weave_out".to_string()),
452            )))),
453            StackType::RowVar("a".to_string()).push(Type::Var("handle".to_string())),
454        ),
455    );
456
457    // strand.resume: ( a handle b -- a handle b Bool ) - resume weave with value
458    // Takes handle and value to send, returns (handle, yielded_value, has_more)
459    sigs.insert(
460        "strand.resume".to_string(),
461        Effect::new(
462            StackType::RowVar("a".to_string())
463                .push(Type::Var("handle".to_string()))
464                .push(Type::Var("b".to_string())),
465            StackType::RowVar("a".to_string())
466                .push(Type::Var("handle".to_string()))
467                .push(Type::Var("b".to_string()))
468                .push(Type::Bool),
469        ),
470    );
471
472    // yield: ( a ctx b -- a ctx b | Yield b ) - yield value and receive resume value
473    // The WeaveCtx must be passed explicitly and threaded through.
474    // The Yield effect indicates this word produces yield semantics.
475    sigs.insert(
476        "yield".to_string(),
477        Effect::with_effects(
478            StackType::RowVar("a".to_string())
479                .push(Type::Var("ctx".to_string()))
480                .push(Type::Var("b".to_string())),
481            StackType::RowVar("a".to_string())
482                .push(Type::Var("ctx".to_string()))
483                .push(Type::Var("b".to_string())),
484            vec![SideEffect::Yield(Box::new(Type::Var("b".to_string())))],
485        ),
486    );
487
488    // strand.weave-cancel: ( a handle -- a ) - cancel a weave and release its resources
489    // Use this to clean up a weave that won't be resumed to completion.
490    // This prevents resource leaks from abandoned weaves.
491    sigs.insert(
492        "strand.weave-cancel".to_string(),
493        Effect::new(
494            StackType::RowVar("a".to_string()).push(Type::Var("handle".to_string())),
495            StackType::RowVar("a".to_string()),
496        ),
497    );
498
499    // =========================================================================
500    // TCP Operations
501    // =========================================================================
502
503    // TCP operations return Bool for error handling
504    builtin!(sigs, "tcp.listen", (a Int -- a Int Bool));
505    builtin!(sigs, "tcp.accept", (a Int -- a Int Bool));
506    builtin!(sigs, "tcp.read", (a Int -- a String Bool));
507    builtin!(sigs, "tcp.write", (a String Int -- a Bool));
508    builtin!(sigs, "tcp.close", (a Int -- a Bool));
509
510    // =========================================================================
511    // OS Operations
512    // =========================================================================
513
514    builtin!(sigs, "os.getenv", (a String -- a String Bool));
515    builtin!(sigs, "os.home-dir", (a -- a String Bool));
516    builtin!(sigs, "os.current-dir", (a -- a String Bool));
517    builtin!(sigs, "os.path-exists", (a String -- a Bool));
518    builtin!(sigs, "os.path-is-file", (a String -- a Bool));
519    builtin!(sigs, "os.path-is-dir", (a String -- a Bool));
520    builtin!(sigs, "os.path-join", (a String String -- a String));
521    builtin!(sigs, "os.path-parent", (a String -- a String Bool));
522    builtin!(sigs, "os.path-filename", (a String -- a String Bool));
523    builtin!(sigs, "os.exit", (a Int -- a)); // Never returns, but typed as identity
524    builtin!(sigs, "os.name", (a -- a String));
525    builtin!(sigs, "os.arch", (a -- a String));
526
527    // =========================================================================
528    // Signal Handling (Unix signals)
529    // =========================================================================
530
531    builtin!(sigs, "signal.trap", (a Int -- a));
532    builtin!(sigs, "signal.received?", (a Int -- a Bool));
533    builtin!(sigs, "signal.pending?", (a Int -- a Bool));
534    builtin!(sigs, "signal.default", (a Int -- a));
535    builtin!(sigs, "signal.ignore", (a Int -- a));
536    builtin!(sigs, "signal.clear", (a Int -- a));
537    // Signal constants (platform-correct values)
538    builtin!(sigs, "signal.SIGINT", (a -- a Int));
539    builtin!(sigs, "signal.SIGTERM", (a -- a Int));
540    builtin!(sigs, "signal.SIGHUP", (a -- a Int));
541    builtin!(sigs, "signal.SIGPIPE", (a -- a Int));
542    builtin!(sigs, "signal.SIGUSR1", (a -- a Int));
543    builtin!(sigs, "signal.SIGUSR2", (a -- a Int));
544    builtin!(sigs, "signal.SIGCHLD", (a -- a Int));
545    builtin!(sigs, "signal.SIGALRM", (a -- a Int));
546    builtin!(sigs, "signal.SIGCONT", (a -- a Int));
547
548    // =========================================================================
549    // Terminal Operations (raw mode, character I/O, dimensions)
550    // =========================================================================
551
552    builtin!(sigs, "terminal.raw-mode", (a Bool -- a));
553    builtin!(sigs, "terminal.read-char", (a -- a Int));
554    builtin!(sigs, "terminal.read-char?", (a -- a Int));
555    builtin!(sigs, "terminal.width", (a -- a Int));
556    builtin!(sigs, "terminal.height", (a -- a Int));
557    builtin!(sigs, "terminal.flush", (a - -a));
558
559    // =========================================================================
560    // String Operations
561    // =========================================================================
562
563    builtin!(sigs, "string.concat", (a String String -- a String));
564    builtin!(sigs, "string.length", (a String -- a Int));
565    builtin!(sigs, "string.byte-length", (a String -- a Int));
566    builtin!(sigs, "string.char-at", (a String Int -- a Int));
567    builtin!(sigs, "string.substring", (a String Int Int -- a String));
568    builtin!(sigs, "string.find", (a String String -- a Int));
569    builtin!(sigs, "string.split", (a String String -- a V)); // Returns Variant (list)
570    builtin!(sigs, "string.contains", (a String String -- a Bool));
571    builtin!(sigs, "string.starts-with", (a String String -- a Bool));
572    builtin!(sigs, "string.empty?", (a String -- a Bool));
573    builtin!(sigs, "string.equal?", (a String String -- a Bool));
574
575    // Symbol operations
576    builtin!(sigs, "symbol.=", (a Symbol Symbol -- a Bool));
577
578    // String transformations
579    builtins_string_to_string!(
580        sigs,
581        "string.trim",
582        "string.chomp",
583        "string.to-upper",
584        "string.to-lower",
585        "string.json-escape"
586    );
587
588    // =========================================================================
589    // Encoding Operations
590    // =========================================================================
591
592    builtin!(sigs, "encoding.base64-encode", (a String -- a String));
593    builtin!(sigs, "encoding.base64-decode", (a String -- a String Bool));
594    builtin!(sigs, "encoding.base64url-encode", (a String -- a String));
595    builtin!(sigs, "encoding.base64url-decode", (a String -- a String Bool));
596    builtin!(sigs, "encoding.hex-encode", (a String -- a String));
597    builtin!(sigs, "encoding.hex-decode", (a String -- a String Bool));
598
599    // =========================================================================
600    // Crypto Operations
601    // =========================================================================
602
603    builtin!(sigs, "crypto.sha256", (a String -- a String));
604    builtin!(sigs, "crypto.hmac-sha256", (a String String -- a String));
605    builtin!(sigs, "crypto.constant-time-eq", (a String String -- a Bool));
606    builtin!(sigs, "crypto.random-bytes", (a Int -- a String));
607    builtin!(sigs, "crypto.random-int", (a Int Int -- a Int));
608    builtin!(sigs, "crypto.uuid4", (a -- a String));
609    builtin!(sigs, "crypto.aes-gcm-encrypt", (a String String -- a String Bool));
610    builtin!(sigs, "crypto.aes-gcm-decrypt", (a String String -- a String Bool));
611    builtin!(sigs, "crypto.pbkdf2-sha256", (a String String Int -- a String Bool));
612    builtin!(sigs, "crypto.ed25519-keypair", (a -- a String String));
613    builtin!(sigs, "crypto.ed25519-sign", (a String String -- a String Bool));
614    builtin!(sigs, "crypto.ed25519-verify", (a String String String -- a Bool));
615
616    // =========================================================================
617    // HTTP Client Operations
618    // =========================================================================
619
620    builtin!(sigs, "http.get", (a String -- a M));
621    builtin!(sigs, "http.post", (a String String String -- a M));
622    builtin!(sigs, "http.put", (a String String String -- a M));
623    builtin!(sigs, "http.delete", (a String -- a M));
624
625    // =========================================================================
626    // Regular Expression Operations
627    // =========================================================================
628
629    // Regex operations return Bool for error handling (invalid regex)
630    builtin!(sigs, "regex.match?", (a String String -- a Bool));
631    builtin!(sigs, "regex.find", (a String String -- a String Bool));
632    builtin!(sigs, "regex.find-all", (a String String -- a V Bool));
633    builtin!(sigs, "regex.replace", (a String String String -- a String Bool));
634    builtin!(sigs, "regex.replace-all", (a String String String -- a String Bool));
635    builtin!(sigs, "regex.captures", (a String String -- a V Bool));
636    builtin!(sigs, "regex.split", (a String String -- a V Bool));
637    builtin!(sigs, "regex.valid?", (a String -- a Bool));
638
639    // =========================================================================
640    // Compression Operations
641    // =========================================================================
642
643    builtin!(sigs, "compress.gzip", (a String -- a String Bool));
644    builtin!(sigs, "compress.gzip-level", (a String Int -- a String Bool));
645    builtin!(sigs, "compress.gunzip", (a String -- a String Bool));
646    builtin!(sigs, "compress.zstd", (a String -- a String Bool));
647    builtin!(sigs, "compress.zstd-level", (a String Int -- a String Bool));
648    builtin!(sigs, "compress.unzstd", (a String -- a String Bool));
649
650    // =========================================================================
651    // Variant Operations
652    // =========================================================================
653
654    builtin!(sigs, "variant.field-count", (a V -- a Int));
655    builtin!(sigs, "variant.tag", (a V -- a Symbol));
656    builtin!(sigs, "variant.field-at", (a V Int -- a T));
657    builtin!(sigs, "variant.append", (a V T -- a V2));
658    builtin!(sigs, "variant.last", (a V -- a T));
659    builtin!(sigs, "variant.init", (a V -- a V2));
660
661    // Type-safe variant constructors with fixed arity (symbol tags for SON support)
662    builtin!(sigs, "variant.make-0", (a Symbol -- a V));
663    builtin!(sigs, "variant.make-1", (a T1 Symbol -- a V));
664    builtin!(sigs, "variant.make-2", (a T1 T2 Symbol -- a V));
665    builtin!(sigs, "variant.make-3", (a T1 T2 T3 Symbol -- a V));
666    builtin!(sigs, "variant.make-4", (a T1 T2 T3 T4 Symbol -- a V));
667    // variant.make-5 through variant.make-12 defined manually (macro only supports up to 5 inputs)
668    for n in 5..=12 {
669        let mut input = StackType::RowVar("a".to_string());
670        for i in 1..=n {
671            input = input.push(Type::Var(format!("T{}", i)));
672        }
673        input = input.push(Type::Symbol);
674        let output = StackType::RowVar("a".to_string()).push(Type::Var("V".to_string()));
675        sigs.insert(format!("variant.make-{}", n), Effect::new(input, output));
676    }
677
678    // Aliases for dynamic variant construction (SON-friendly names)
679    builtin!(sigs, "wrap-0", (a Symbol -- a V));
680    builtin!(sigs, "wrap-1", (a T1 Symbol -- a V));
681    builtin!(sigs, "wrap-2", (a T1 T2 Symbol -- a V));
682    builtin!(sigs, "wrap-3", (a T1 T2 T3 Symbol -- a V));
683    builtin!(sigs, "wrap-4", (a T1 T2 T3 T4 Symbol -- a V));
684    // wrap-5 through wrap-12 defined manually
685    for n in 5..=12 {
686        let mut input = StackType::RowVar("a".to_string());
687        for i in 1..=n {
688            input = input.push(Type::Var(format!("T{}", i)));
689        }
690        input = input.push(Type::Symbol);
691        let output = StackType::RowVar("a".to_string()).push(Type::Var("V".to_string()));
692        sigs.insert(format!("wrap-{}", n), Effect::new(input, output));
693    }
694
695    // =========================================================================
696    // List Operations (Higher-order combinators for Variants)
697    // =========================================================================
698
699    // List construction and access
700    builtin!(sigs, "list.make", (a -- a V));
701    builtin!(sigs, "list.push", (a V T -- a V));
702    builtin!(sigs, "list.get", (a V Int -- a T Bool));
703    builtin!(sigs, "list.set", (a V Int T -- a V Bool));
704
705    builtin!(sigs, "list.length", (a V -- a Int));
706    builtin!(sigs, "list.empty?", (a V -- a Bool));
707
708    // list.map: ( a Variant Quotation -- a Variant )
709    // Quotation: ( b T -- b U )
710    sigs.insert(
711        "list.map".to_string(),
712        Effect::new(
713            StackType::RowVar("a".to_string())
714                .push(Type::Var("V".to_string()))
715                .push(Type::Quotation(Box::new(Effect::new(
716                    StackType::RowVar("b".to_string()).push(Type::Var("T".to_string())),
717                    StackType::RowVar("b".to_string()).push(Type::Var("U".to_string())),
718                )))),
719            StackType::RowVar("a".to_string()).push(Type::Var("V2".to_string())),
720        ),
721    );
722
723    // list.filter: ( a Variant Quotation -- a Variant )
724    // Quotation: ( b T -- b Bool )
725    sigs.insert(
726        "list.filter".to_string(),
727        Effect::new(
728            StackType::RowVar("a".to_string())
729                .push(Type::Var("V".to_string()))
730                .push(Type::Quotation(Box::new(Effect::new(
731                    StackType::RowVar("b".to_string()).push(Type::Var("T".to_string())),
732                    StackType::RowVar("b".to_string()).push(Type::Bool),
733                )))),
734            StackType::RowVar("a".to_string()).push(Type::Var("V2".to_string())),
735        ),
736    );
737
738    // list.fold: ( a Variant init Quotation -- a result )
739    // Quotation: ( b Acc T -- b Acc )
740    sigs.insert(
741        "list.fold".to_string(),
742        Effect::new(
743            StackType::RowVar("a".to_string())
744                .push(Type::Var("V".to_string()))
745                .push(Type::Var("Acc".to_string()))
746                .push(Type::Quotation(Box::new(Effect::new(
747                    StackType::RowVar("b".to_string())
748                        .push(Type::Var("Acc".to_string()))
749                        .push(Type::Var("T".to_string())),
750                    StackType::RowVar("b".to_string()).push(Type::Var("Acc".to_string())),
751                )))),
752            StackType::RowVar("a".to_string()).push(Type::Var("Acc".to_string())),
753        ),
754    );
755
756    // list.each: ( a Variant Quotation -- a )
757    // Quotation: ( b T -- b )
758    sigs.insert(
759        "list.each".to_string(),
760        Effect::new(
761            StackType::RowVar("a".to_string())
762                .push(Type::Var("V".to_string()))
763                .push(Type::Quotation(Box::new(Effect::new(
764                    StackType::RowVar("b".to_string()).push(Type::Var("T".to_string())),
765                    StackType::RowVar("b".to_string()),
766                )))),
767            StackType::RowVar("a".to_string()),
768        ),
769    );
770
771    // =========================================================================
772    // Map Operations (Dictionary with O(1) lookup)
773    // =========================================================================
774
775    builtin!(sigs, "map.make", (a -- a M));
776    builtin!(sigs, "map.get", (a M K -- a V Bool)); // returns (value success) - errors are values, not crashes
777    builtin!(sigs, "map.set", (a M K V -- a M2));
778    builtin!(sigs, "map.has?", (a M K -- a Bool));
779    builtin!(sigs, "map.remove", (a M K -- a M2));
780    builtin!(sigs, "map.keys", (a M -- a V));
781    builtin!(sigs, "map.values", (a M -- a V));
782    builtin!(sigs, "map.size", (a M -- a Int));
783    builtin!(sigs, "map.empty?", (a M -- a Bool));
784
785    // =========================================================================
786    // Float Arithmetic ( a Float Float -- a Float )
787    // =========================================================================
788
789    builtins_float_float_to_float!(sigs, "f.add", "f.subtract", "f.multiply", "f.divide");
790    builtins_float_float_to_float!(sigs, "f.+", "f.-", "f.*", "f./");
791
792    // =========================================================================
793    // Float Comparison ( a Float Float -- a Bool )
794    // =========================================================================
795
796    builtins_float_float_to_bool!(sigs, "f.=", "f.<", "f.>", "f.<=", "f.>=", "f.<>");
797    builtins_float_float_to_bool!(sigs, "f.eq", "f.lt", "f.gt", "f.lte", "f.gte", "f.neq");
798
799    // =========================================================================
800    // Test Framework
801    // =========================================================================
802
803    builtin!(sigs, "test.init", (a String -- a));
804    builtin!(sigs, "test.finish", (a - -a));
805    builtin!(sigs, "test.has-failures", (a -- a Bool));
806    builtin!(sigs, "test.assert", (a Bool -- a));
807    builtin!(sigs, "test.assert-not", (a Bool -- a));
808    builtin!(sigs, "test.assert-eq", (a Int Int -- a));
809    builtin!(sigs, "test.assert-eq-str", (a String String -- a));
810    builtin!(sigs, "test.fail", (a String -- a));
811    builtin!(sigs, "test.pass-count", (a -- a Int));
812    builtin!(sigs, "test.fail-count", (a -- a Int));
813
814    // Time operations
815    builtin!(sigs, "time.now", (a -- a Int));
816    builtin!(sigs, "time.nanos", (a -- a Int));
817    builtin!(sigs, "time.sleep-ms", (a Int -- a));
818
819    // SON serialization
820    builtin!(sigs, "son.dump", (a T -- a String));
821    builtin!(sigs, "son.dump-pretty", (a T -- a String));
822
823    // Stack introspection (for REPL)
824    // stack.dump prints all values and clears the stack
825    sigs.insert(
826        "stack.dump".to_string(),
827        Effect::new(
828            StackType::RowVar("a".to_string()), // Consumes any stack
829            StackType::RowVar("b".to_string()), // Returns empty stack (different row var)
830        ),
831    );
832
833    sigs
834}
835
836/// Get documentation for a built-in word
837pub fn builtin_doc(name: &str) -> Option<&'static str> {
838    BUILTIN_DOCS.get(name).copied()
839}
840
841/// Get all built-in word documentation (cached with LazyLock for performance)
842pub fn builtin_docs() -> &'static HashMap<&'static str, &'static str> {
843    &BUILTIN_DOCS
844}
845
846/// Lazily initialized documentation for all built-in words
847static BUILTIN_DOCS: LazyLock<HashMap<&'static str, &'static str>> = LazyLock::new(|| {
848    let mut docs = HashMap::new();
849
850    // I/O Operations
851    docs.insert(
852        "io.write",
853        "Write a string to stdout without a trailing newline.",
854    );
855    docs.insert(
856        "io.write-line",
857        "Write a string to stdout followed by a newline.",
858    );
859    docs.insert(
860        "io.read-line",
861        "Read a line from stdin. Returns (line, success).",
862    );
863    docs.insert(
864        "io.read-line+",
865        "DEPRECATED: Use io.read-line instead. Read a line from stdin. Returns (line, status_code).",
866    );
867    docs.insert(
868        "io.read-n",
869        "Read N bytes from stdin. Returns (bytes, status_code).",
870    );
871
872    // Command-line Arguments
873    docs.insert("args.count", "Get the number of command-line arguments.");
874    docs.insert("args.at", "Get the command-line argument at index N.");
875
876    // File Operations
877    docs.insert(
878        "file.slurp",
879        "Read entire file contents. Returns (content, success).",
880    );
881    docs.insert("file.exists?", "Check if a file exists at the given path.");
882    docs.insert(
883        "file.spit",
884        "Write string to file (creates or overwrites). Returns success.",
885    );
886    docs.insert(
887        "file.append",
888        "Append string to file (creates if needed). Returns success.",
889    );
890    docs.insert("file.delete", "Delete a file. Returns success.");
891    docs.insert(
892        "file.size",
893        "Get file size in bytes. Returns (size, success).",
894    );
895    docs.insert(
896        "file.for-each-line+",
897        "Execute a quotation for each line in a file.",
898    );
899
900    // Directory Operations
901    docs.insert(
902        "dir.exists?",
903        "Check if a directory exists at the given path.",
904    );
905    docs.insert(
906        "dir.make",
907        "Create a directory (and parent directories if needed). Returns success.",
908    );
909    docs.insert("dir.delete", "Delete an empty directory. Returns success.");
910    docs.insert(
911        "dir.list",
912        "List directory contents. Returns (list-of-names, success).",
913    );
914
915    // Type Conversions
916    docs.insert(
917        "int->string",
918        "Convert an integer to its string representation.",
919    );
920    docs.insert(
921        "int->float",
922        "Convert an integer to a floating-point number.",
923    );
924    docs.insert("float->int", "Truncate a float to an integer.");
925    docs.insert(
926        "float->string",
927        "Convert a float to its string representation.",
928    );
929    docs.insert(
930        "string->int",
931        "Parse a string as an integer. Returns (value, success).",
932    );
933    docs.insert(
934        "string->float",
935        "Parse a string as a float. Returns (value, success).",
936    );
937    docs.insert(
938        "char->string",
939        "Convert a Unicode codepoint to a single-character string.",
940    );
941    docs.insert(
942        "symbol->string",
943        "Convert a symbol to its string representation.",
944    );
945    docs.insert("string->symbol", "Intern a string as a symbol.");
946
947    // Integer Arithmetic
948    docs.insert("i.add", "Add two integers.");
949    docs.insert("i.subtract", "Subtract second integer from first.");
950    docs.insert("i.multiply", "Multiply two integers.");
951    docs.insert("i.divide", "Integer division (truncates toward zero).");
952    docs.insert("i.modulo", "Integer modulo (remainder after division).");
953    docs.insert("i.+", "Add two integers.");
954    docs.insert("i.-", "Subtract second integer from first.");
955    docs.insert("i.*", "Multiply two integers.");
956    docs.insert("i./", "Integer division (truncates toward zero).");
957    docs.insert("i.%", "Integer modulo (remainder after division).");
958
959    // Integer Comparison
960    docs.insert("i.=", "Test if two integers are equal.");
961    docs.insert("i.<", "Test if first integer is less than second.");
962    docs.insert("i.>", "Test if first integer is greater than second.");
963    docs.insert(
964        "i.<=",
965        "Test if first integer is less than or equal to second.",
966    );
967    docs.insert(
968        "i.>=",
969        "Test if first integer is greater than or equal to second.",
970    );
971    docs.insert("i.<>", "Test if two integers are not equal.");
972    docs.insert("i.eq", "Test if two integers are equal.");
973    docs.insert("i.lt", "Test if first integer is less than second.");
974    docs.insert("i.gt", "Test if first integer is greater than second.");
975    docs.insert(
976        "i.lte",
977        "Test if first integer is less than or equal to second.",
978    );
979    docs.insert(
980        "i.gte",
981        "Test if first integer is greater than or equal to second.",
982    );
983    docs.insert("i.neq", "Test if two integers are not equal.");
984
985    // Boolean Operations
986    docs.insert("and", "Logical AND of two booleans.");
987    docs.insert("or", "Logical OR of two booleans.");
988    docs.insert("not", "Logical NOT of a boolean.");
989
990    // Bitwise Operations
991    docs.insert("band", "Bitwise AND of two integers.");
992    docs.insert("bor", "Bitwise OR of two integers.");
993    docs.insert("bxor", "Bitwise XOR of two integers.");
994    docs.insert("bnot", "Bitwise NOT (complement) of an integer.");
995    docs.insert("shl", "Shift left by N bits.");
996    docs.insert("shr", "Shift right by N bits (arithmetic).");
997    docs.insert("popcount", "Count the number of set bits.");
998    docs.insert("clz", "Count leading zeros.");
999    docs.insert("ctz", "Count trailing zeros.");
1000    docs.insert("int-bits", "Push the bit width of integers (64).");
1001
1002    // Stack Operations
1003    docs.insert("dup", "Duplicate the top stack value.");
1004    docs.insert("drop", "Remove the top stack value.");
1005    docs.insert("swap", "Swap the top two stack values.");
1006    docs.insert("over", "Copy the second value to the top.");
1007    docs.insert("rot", "Rotate the top three values (third to top).");
1008    docs.insert("nip", "Remove the second value from the stack.");
1009    docs.insert("tuck", "Copy the top value below the second.");
1010    docs.insert("2dup", "Duplicate the top two values.");
1011    docs.insert("3drop", "Remove the top three values.");
1012    docs.insert("pick", "Copy the value at depth N to the top.");
1013    docs.insert("roll", "Rotate N+1 items, bringing depth N to top.");
1014
1015    // Channel Operations
1016    docs.insert(
1017        "chan.make",
1018        "Create a new channel for inter-strand communication.",
1019    );
1020    docs.insert(
1021        "chan.send",
1022        "Send a value on a channel. Returns success flag.",
1023    );
1024    docs.insert(
1025        "chan.receive",
1026        "Receive a value from a channel. Returns (value, success).",
1027    );
1028    docs.insert("chan.close", "Close a channel.");
1029    docs.insert("chan.yield", "Yield control to the scheduler.");
1030
1031    // Control Flow
1032    docs.insert("call", "Call a quotation or closure.");
1033    docs.insert(
1034        "cond",
1035        "Multi-way conditional: test clauses until one succeeds.",
1036    );
1037
1038    // Concurrency
1039    docs.insert(
1040        "strand.spawn",
1041        "Spawn a concurrent strand. Returns strand ID.",
1042    );
1043    docs.insert(
1044        "strand.weave",
1045        "Create a generator/coroutine. Returns handle.",
1046    );
1047    docs.insert(
1048        "strand.resume",
1049        "Resume a weave with a value. Returns (handle, value, has_more).",
1050    );
1051    docs.insert(
1052        "yield",
1053        "Yield a value from a weave and receive resume value.",
1054    );
1055    docs.insert(
1056        "strand.weave-cancel",
1057        "Cancel a weave and release its resources.",
1058    );
1059
1060    // TCP Operations
1061    docs.insert(
1062        "tcp.listen",
1063        "Start listening on a port. Returns (socket_id, success).",
1064    );
1065    docs.insert(
1066        "tcp.accept",
1067        "Accept a connection. Returns (client_id, success).",
1068    );
1069    docs.insert(
1070        "tcp.read",
1071        "Read data from a socket. Returns (string, success).",
1072    );
1073    docs.insert("tcp.write", "Write data to a socket. Returns success.");
1074    docs.insert("tcp.close", "Close a socket. Returns success.");
1075
1076    // OS Operations
1077    docs.insert(
1078        "os.getenv",
1079        "Get environment variable. Returns (value, exists).",
1080    );
1081    docs.insert(
1082        "os.home-dir",
1083        "Get user's home directory. Returns (path, success).",
1084    );
1085    docs.insert(
1086        "os.current-dir",
1087        "Get current working directory. Returns (path, success).",
1088    );
1089    docs.insert("os.path-exists", "Check if a path exists.");
1090    docs.insert("os.path-is-file", "Check if path is a regular file.");
1091    docs.insert("os.path-is-dir", "Check if path is a directory.");
1092    docs.insert("os.path-join", "Join two path components.");
1093    docs.insert(
1094        "os.path-parent",
1095        "Get parent directory. Returns (path, success).",
1096    );
1097    docs.insert(
1098        "os.path-filename",
1099        "Get filename component. Returns (name, success).",
1100    );
1101    docs.insert("os.exit", "Exit the program with a status code.");
1102    docs.insert(
1103        "os.name",
1104        "Get the operating system name (e.g., \"macos\", \"linux\").",
1105    );
1106    docs.insert(
1107        "os.arch",
1108        "Get the CPU architecture (e.g., \"aarch64\", \"x86_64\").",
1109    );
1110
1111    // Signal Handling
1112    docs.insert(
1113        "signal.trap",
1114        "Trap a signal: set internal flag on receipt instead of default action.",
1115    );
1116    docs.insert(
1117        "signal.received?",
1118        "Check if signal was received and clear the flag. Returns Bool.",
1119    );
1120    docs.insert(
1121        "signal.pending?",
1122        "Check if signal is pending without clearing the flag. Returns Bool.",
1123    );
1124    docs.insert(
1125        "signal.default",
1126        "Restore the default handler for a signal.",
1127    );
1128    docs.insert(
1129        "signal.ignore",
1130        "Ignore a signal entirely (useful for SIGPIPE in servers).",
1131    );
1132    docs.insert(
1133        "signal.clear",
1134        "Clear the pending flag for a signal without checking it.",
1135    );
1136    docs.insert("signal.SIGINT", "SIGINT constant (Ctrl+C interrupt).");
1137    docs.insert("signal.SIGTERM", "SIGTERM constant (termination request).");
1138    docs.insert("signal.SIGHUP", "SIGHUP constant (hangup detected).");
1139    docs.insert("signal.SIGPIPE", "SIGPIPE constant (broken pipe).");
1140    docs.insert(
1141        "signal.SIGUSR1",
1142        "SIGUSR1 constant (user-defined signal 1).",
1143    );
1144    docs.insert(
1145        "signal.SIGUSR2",
1146        "SIGUSR2 constant (user-defined signal 2).",
1147    );
1148    docs.insert("signal.SIGCHLD", "SIGCHLD constant (child status changed).");
1149    docs.insert("signal.SIGALRM", "SIGALRM constant (alarm clock).");
1150    docs.insert("signal.SIGCONT", "SIGCONT constant (continue if stopped).");
1151
1152    // Terminal Operations
1153    docs.insert(
1154        "terminal.raw-mode",
1155        "Enable/disable raw terminal mode. In raw mode: no line buffering, no echo, Ctrl+C read as byte 3.",
1156    );
1157    docs.insert(
1158        "terminal.read-char",
1159        "Read a single byte from stdin (blocking). Returns 0-255 on success, -1 on EOF/error.",
1160    );
1161    docs.insert(
1162        "terminal.read-char?",
1163        "Read a single byte from stdin (non-blocking). Returns 0-255 if available, -1 otherwise.",
1164    );
1165    docs.insert(
1166        "terminal.width",
1167        "Get terminal width in columns. Returns 80 if unknown.",
1168    );
1169    docs.insert(
1170        "terminal.height",
1171        "Get terminal height in rows. Returns 24 if unknown.",
1172    );
1173    docs.insert(
1174        "terminal.flush",
1175        "Flush stdout. Use after writing escape sequences or partial lines.",
1176    );
1177
1178    // String Operations
1179    docs.insert("string.concat", "Concatenate two strings.");
1180    docs.insert("string.length", "Get the character length of a string.");
1181    docs.insert("string.byte-length", "Get the byte length of a string.");
1182    docs.insert(
1183        "string.char-at",
1184        "Get Unicode codepoint at character index.",
1185    );
1186    docs.insert(
1187        "string.substring",
1188        "Extract substring from start index with length.",
1189    );
1190    docs.insert(
1191        "string.find",
1192        "Find substring. Returns index or -1 if not found.",
1193    );
1194    docs.insert("string.split", "Split string by delimiter. Returns a list.");
1195    docs.insert("string.contains", "Check if string contains a substring.");
1196    docs.insert(
1197        "string.starts-with",
1198        "Check if string starts with a prefix.",
1199    );
1200    docs.insert("string.empty?", "Check if string is empty.");
1201    docs.insert("string.equal?", "Check if two strings are equal.");
1202    docs.insert("string.trim", "Remove leading and trailing whitespace.");
1203    docs.insert("string.chomp", "Remove trailing newline.");
1204    docs.insert("string.to-upper", "Convert to uppercase.");
1205    docs.insert("string.to-lower", "Convert to lowercase.");
1206    docs.insert("string.json-escape", "Escape special characters for JSON.");
1207    docs.insert("symbol.=", "Check if two symbols are equal.");
1208
1209    // Encoding Operations
1210    docs.insert(
1211        "encoding.base64-encode",
1212        "Encode a string to Base64 (standard alphabet with padding).",
1213    );
1214    docs.insert(
1215        "encoding.base64-decode",
1216        "Decode a Base64 string. Returns (decoded, success).",
1217    );
1218    docs.insert(
1219        "encoding.base64url-encode",
1220        "Encode to URL-safe Base64 (no padding). Suitable for JWTs and URLs.",
1221    );
1222    docs.insert(
1223        "encoding.base64url-decode",
1224        "Decode URL-safe Base64. Returns (decoded, success).",
1225    );
1226    docs.insert(
1227        "encoding.hex-encode",
1228        "Encode a string to lowercase hexadecimal.",
1229    );
1230    docs.insert(
1231        "encoding.hex-decode",
1232        "Decode a hexadecimal string. Returns (decoded, success).",
1233    );
1234
1235    // Crypto Operations
1236    docs.insert(
1237        "crypto.sha256",
1238        "Compute SHA-256 hash of a string. Returns 64-char hex digest.",
1239    );
1240    docs.insert(
1241        "crypto.hmac-sha256",
1242        "Compute HMAC-SHA256 signature. ( message key -- signature )",
1243    );
1244    docs.insert(
1245        "crypto.constant-time-eq",
1246        "Timing-safe string comparison. Use for comparing signatures/tokens.",
1247    );
1248    docs.insert(
1249        "crypto.random-bytes",
1250        "Generate N cryptographically secure random bytes as hex string.",
1251    );
1252    docs.insert(
1253        "crypto.random-int",
1254        "Generate uniform random integer in [min, max). ( min max -- Int ) Uses rejection sampling to avoid modulo bias.",
1255    );
1256    docs.insert("crypto.uuid4", "Generate a random UUID v4 string.");
1257    docs.insert(
1258        "crypto.aes-gcm-encrypt",
1259        "Encrypt with AES-256-GCM. ( plaintext hex-key -- ciphertext success )",
1260    );
1261    docs.insert(
1262        "crypto.aes-gcm-decrypt",
1263        "Decrypt AES-256-GCM ciphertext. ( ciphertext hex-key -- plaintext success )",
1264    );
1265    docs.insert(
1266        "crypto.pbkdf2-sha256",
1267        "Derive key from password. ( password salt iterations -- hex-key success ) Min 1000 iterations, 100000+ recommended.",
1268    );
1269    docs.insert(
1270        "crypto.ed25519-keypair",
1271        "Generate Ed25519 keypair. ( -- public-key private-key ) Both as 64-char hex strings.",
1272    );
1273    docs.insert(
1274        "crypto.ed25519-sign",
1275        "Sign message with Ed25519 private key. ( message private-key -- signature success ) Signature is 128-char hex.",
1276    );
1277    docs.insert(
1278        "crypto.ed25519-verify",
1279        "Verify Ed25519 signature. ( message signature public-key -- valid )",
1280    );
1281
1282    // HTTP Client Operations
1283    docs.insert(
1284        "http.get",
1285        "HTTP GET request. ( url -- response-map ) Map has status, body, ok, error.",
1286    );
1287    docs.insert(
1288        "http.post",
1289        "HTTP POST request. ( url body content-type -- response-map )",
1290    );
1291    docs.insert(
1292        "http.put",
1293        "HTTP PUT request. ( url body content-type -- response-map )",
1294    );
1295    docs.insert(
1296        "http.delete",
1297        "HTTP DELETE request. ( url -- response-map )",
1298    );
1299
1300    // Regular Expression Operations
1301    docs.insert(
1302        "regex.match?",
1303        "Check if pattern matches anywhere in string. ( text pattern -- bool )",
1304    );
1305    docs.insert(
1306        "regex.find",
1307        "Find first match. ( text pattern -- matched success )",
1308    );
1309    docs.insert(
1310        "regex.find-all",
1311        "Find all matches. ( text pattern -- list success )",
1312    );
1313    docs.insert(
1314        "regex.replace",
1315        "Replace first match. ( text pattern replacement -- result success )",
1316    );
1317    docs.insert(
1318        "regex.replace-all",
1319        "Replace all matches. ( text pattern replacement -- result success )",
1320    );
1321    docs.insert(
1322        "regex.captures",
1323        "Extract capture groups. ( text pattern -- groups success )",
1324    );
1325    docs.insert(
1326        "regex.split",
1327        "Split string by pattern. ( text pattern -- list success )",
1328    );
1329    docs.insert(
1330        "regex.valid?",
1331        "Check if pattern is valid regex. ( pattern -- bool )",
1332    );
1333
1334    // Compression Operations
1335    docs.insert(
1336        "compress.gzip",
1337        "Compress string with gzip. Returns base64-encoded data. ( data -- compressed success )",
1338    );
1339    docs.insert(
1340        "compress.gzip-level",
1341        "Compress with gzip at level 1-9. ( data level -- compressed success )",
1342    );
1343    docs.insert(
1344        "compress.gunzip",
1345        "Decompress gzip data. ( base64-data -- decompressed success )",
1346    );
1347    docs.insert(
1348        "compress.zstd",
1349        "Compress string with zstd. Returns base64-encoded data. ( data -- compressed success )",
1350    );
1351    docs.insert(
1352        "compress.zstd-level",
1353        "Compress with zstd at level 1-22. ( data level -- compressed success )",
1354    );
1355    docs.insert(
1356        "compress.unzstd",
1357        "Decompress zstd data. ( base64-data -- decompressed success )",
1358    );
1359
1360    // Variant Operations
1361    docs.insert(
1362        "variant.field-count",
1363        "Get the number of fields in a variant.",
1364    );
1365    docs.insert(
1366        "variant.tag",
1367        "Get the tag (constructor name) of a variant.",
1368    );
1369    docs.insert("variant.field-at", "Get the field at index N.");
1370    docs.insert(
1371        "variant.append",
1372        "Append a value to a variant (creates new).",
1373    );
1374    docs.insert("variant.last", "Get the last field of a variant.");
1375    docs.insert("variant.init", "Get all fields except the last.");
1376    docs.insert("variant.make-0", "Create a variant with 0 fields.");
1377    docs.insert("variant.make-1", "Create a variant with 1 field.");
1378    docs.insert("variant.make-2", "Create a variant with 2 fields.");
1379    docs.insert("variant.make-3", "Create a variant with 3 fields.");
1380    docs.insert("variant.make-4", "Create a variant with 4 fields.");
1381    docs.insert("variant.make-5", "Create a variant with 5 fields.");
1382    docs.insert("variant.make-6", "Create a variant with 6 fields.");
1383    docs.insert("variant.make-7", "Create a variant with 7 fields.");
1384    docs.insert("variant.make-8", "Create a variant with 8 fields.");
1385    docs.insert("variant.make-9", "Create a variant with 9 fields.");
1386    docs.insert("variant.make-10", "Create a variant with 10 fields.");
1387    docs.insert("variant.make-11", "Create a variant with 11 fields.");
1388    docs.insert("variant.make-12", "Create a variant with 12 fields.");
1389    docs.insert("wrap-0", "Create a variant with 0 fields (alias).");
1390    docs.insert("wrap-1", "Create a variant with 1 field (alias).");
1391    docs.insert("wrap-2", "Create a variant with 2 fields (alias).");
1392    docs.insert("wrap-3", "Create a variant with 3 fields (alias).");
1393    docs.insert("wrap-4", "Create a variant with 4 fields (alias).");
1394    docs.insert("wrap-5", "Create a variant with 5 fields (alias).");
1395    docs.insert("wrap-6", "Create a variant with 6 fields (alias).");
1396    docs.insert("wrap-7", "Create a variant with 7 fields (alias).");
1397    docs.insert("wrap-8", "Create a variant with 8 fields (alias).");
1398    docs.insert("wrap-9", "Create a variant with 9 fields (alias).");
1399    docs.insert("wrap-10", "Create a variant with 10 fields (alias).");
1400    docs.insert("wrap-11", "Create a variant with 11 fields (alias).");
1401    docs.insert("wrap-12", "Create a variant with 12 fields (alias).");
1402
1403    // List Operations
1404    docs.insert("list.make", "Create an empty list.");
1405    docs.insert("list.push", "Push a value onto a list. Returns new list.");
1406    docs.insert("list.get", "Get value at index. Returns (value, success).");
1407    docs.insert("list.set", "Set value at index. Returns (list, success).");
1408    docs.insert("list.length", "Get the number of elements in a list.");
1409    docs.insert("list.empty?", "Check if a list is empty.");
1410    docs.insert(
1411        "list.map",
1412        "Apply quotation to each element. Returns new list.",
1413    );
1414    docs.insert("list.filter", "Keep elements where quotation returns true.");
1415    docs.insert("list.fold", "Reduce list with accumulator and quotation.");
1416    docs.insert(
1417        "list.each",
1418        "Execute quotation for each element (side effects).",
1419    );
1420
1421    // Map Operations
1422    docs.insert("map.make", "Create an empty map.");
1423    docs.insert("map.get", "Get value for key. Returns (value, success).");
1424    docs.insert("map.set", "Set key to value. Returns new map.");
1425    docs.insert("map.has?", "Check if map contains a key.");
1426    docs.insert("map.remove", "Remove a key. Returns new map.");
1427    docs.insert("map.keys", "Get all keys as a list.");
1428    docs.insert("map.values", "Get all values as a list.");
1429    docs.insert("map.size", "Get the number of key-value pairs.");
1430    docs.insert("map.empty?", "Check if map is empty.");
1431
1432    // Float Arithmetic
1433    docs.insert("f.add", "Add two floats.");
1434    docs.insert("f.subtract", "Subtract second float from first.");
1435    docs.insert("f.multiply", "Multiply two floats.");
1436    docs.insert("f.divide", "Divide first float by second.");
1437    docs.insert("f.+", "Add two floats.");
1438    docs.insert("f.-", "Subtract second float from first.");
1439    docs.insert("f.*", "Multiply two floats.");
1440    docs.insert("f./", "Divide first float by second.");
1441
1442    // Float Comparison
1443    docs.insert("f.=", "Test if two floats are equal.");
1444    docs.insert("f.<", "Test if first float is less than second.");
1445    docs.insert("f.>", "Test if first float is greater than second.");
1446    docs.insert("f.<=", "Test if first float is less than or equal.");
1447    docs.insert("f.>=", "Test if first float is greater than or equal.");
1448    docs.insert("f.<>", "Test if two floats are not equal.");
1449    docs.insert("f.eq", "Test if two floats are equal.");
1450    docs.insert("f.lt", "Test if first float is less than second.");
1451    docs.insert("f.gt", "Test if first float is greater than second.");
1452    docs.insert("f.lte", "Test if first float is less than or equal.");
1453    docs.insert("f.gte", "Test if first float is greater than or equal.");
1454    docs.insert("f.neq", "Test if two floats are not equal.");
1455
1456    // Test Framework
1457    docs.insert(
1458        "test.init",
1459        "Initialize the test framework with a test name.",
1460    );
1461    docs.insert("test.finish", "Finish testing and print results.");
1462    docs.insert("test.has-failures", "Check if any tests have failed.");
1463    docs.insert("test.assert", "Assert that a boolean is true.");
1464    docs.insert("test.assert-not", "Assert that a boolean is false.");
1465    docs.insert("test.assert-eq", "Assert that two integers are equal.");
1466    docs.insert("test.assert-eq-str", "Assert that two strings are equal.");
1467    docs.insert("test.fail", "Mark a test as failed with a message.");
1468    docs.insert("test.pass-count", "Get the number of passed assertions.");
1469    docs.insert("test.fail-count", "Get the number of failed assertions.");
1470
1471    // Time Operations
1472    docs.insert("time.now", "Get current Unix timestamp in seconds.");
1473    docs.insert(
1474        "time.nanos",
1475        "Get high-resolution monotonic time in nanoseconds.",
1476    );
1477    docs.insert("time.sleep-ms", "Sleep for N milliseconds.");
1478
1479    // Serialization
1480    docs.insert("son.dump", "Serialize any value to SON format (compact).");
1481    docs.insert(
1482        "son.dump-pretty",
1483        "Serialize any value to SON format (pretty-printed).",
1484    );
1485
1486    // Stack Introspection
1487    docs.insert(
1488        "stack.dump",
1489        "Print all stack values and clear the stack (REPL).",
1490    );
1491
1492    docs
1493});
1494
1495#[cfg(test)]
1496mod tests {
1497    use super::*;
1498
1499    #[test]
1500    fn test_builtin_signature_write_line() {
1501        let sig = builtin_signature("io.write-line").unwrap();
1502        // ( ..a String -- ..a )
1503        let (rest, top) = sig.inputs.clone().pop().unwrap();
1504        assert_eq!(top, Type::String);
1505        assert_eq!(rest, StackType::RowVar("a".to_string()));
1506        assert_eq!(sig.outputs, StackType::RowVar("a".to_string()));
1507    }
1508
1509    #[test]
1510    fn test_builtin_signature_i_add() {
1511        let sig = builtin_signature("i.add").unwrap();
1512        // ( ..a Int Int -- ..a Int )
1513        let (rest, top) = sig.inputs.clone().pop().unwrap();
1514        assert_eq!(top, Type::Int);
1515        let (rest2, top2) = rest.pop().unwrap();
1516        assert_eq!(top2, Type::Int);
1517        assert_eq!(rest2, StackType::RowVar("a".to_string()));
1518
1519        let (rest3, top3) = sig.outputs.clone().pop().unwrap();
1520        assert_eq!(top3, Type::Int);
1521        assert_eq!(rest3, StackType::RowVar("a".to_string()));
1522    }
1523
1524    #[test]
1525    fn test_builtin_signature_dup() {
1526        let sig = builtin_signature("dup").unwrap();
1527        // Input: ( ..a T )
1528        assert_eq!(
1529            sig.inputs,
1530            StackType::Cons {
1531                rest: Box::new(StackType::RowVar("a".to_string())),
1532                top: Type::Var("T".to_string())
1533            }
1534        );
1535        // Output: ( ..a T T )
1536        let (rest, top) = sig.outputs.clone().pop().unwrap();
1537        assert_eq!(top, Type::Var("T".to_string()));
1538        let (rest2, top2) = rest.pop().unwrap();
1539        assert_eq!(top2, Type::Var("T".to_string()));
1540        assert_eq!(rest2, StackType::RowVar("a".to_string()));
1541    }
1542
1543    #[test]
1544    fn test_all_builtins_have_signatures() {
1545        let sigs = builtin_signatures();
1546
1547        // Verify all expected builtins have signatures
1548        assert!(sigs.contains_key("io.write-line"));
1549        assert!(sigs.contains_key("io.read-line"));
1550        assert!(sigs.contains_key("int->string"));
1551        assert!(sigs.contains_key("i.add"));
1552        assert!(sigs.contains_key("dup"));
1553        assert!(sigs.contains_key("swap"));
1554        assert!(sigs.contains_key("chan.make"));
1555        assert!(sigs.contains_key("chan.send"));
1556        assert!(sigs.contains_key("chan.receive"));
1557        assert!(
1558            sigs.contains_key("string->float"),
1559            "string->float should be a builtin"
1560        );
1561        assert!(
1562            sigs.contains_key("signal.trap"),
1563            "signal.trap should be a builtin"
1564        );
1565    }
1566
1567    #[test]
1568    fn test_all_docs_have_signatures() {
1569        let sigs = builtin_signatures();
1570        let docs = builtin_docs();
1571
1572        for name in docs.keys() {
1573            assert!(
1574                sigs.contains_key(*name),
1575                "Builtin '{}' has documentation but no signature",
1576                name
1577            );
1578        }
1579    }
1580
1581    #[test]
1582    fn test_all_signatures_have_docs() {
1583        let sigs = builtin_signatures();
1584        let docs = builtin_docs();
1585
1586        for name in sigs.keys() {
1587            assert!(
1588                docs.contains_key(name.as_str()),
1589                "Builtin '{}' has signature but no documentation",
1590                name
1591            );
1592        }
1593    }
1594}