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)); // Returns line + status (legacy)
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
301    // file.for-each-line+: Complex quotation type - defined manually
302    sigs.insert(
303        "file.for-each-line+".to_string(),
304        Effect::new(
305            StackType::RowVar("a".to_string())
306                .push(Type::String)
307                .push(Type::Quotation(Box::new(Effect::new(
308                    StackType::RowVar("a".to_string()).push(Type::String),
309                    StackType::RowVar("a".to_string()),
310                )))),
311            StackType::RowVar("a".to_string())
312                .push(Type::String)
313                .push(Type::Bool),
314        ),
315    );
316
317    // =========================================================================
318    // Type Conversions
319    // =========================================================================
320
321    builtin!(sigs, "int->string", (a Int -- a String));
322    builtin!(sigs, "int->float", (a Int -- a Float));
323    builtin!(sigs, "float->int", (a Float -- a Int));
324    builtin!(sigs, "float->string", (a Float -- a String));
325    builtin!(sigs, "string->int", (a String -- a Int Bool)); // value + success flag
326    builtin!(sigs, "string->float", (a String -- a Float Bool)); // value + success flag
327    builtin!(sigs, "char->string", (a Int -- a String));
328    builtin!(sigs, "symbol->string", (a Symbol -- a String));
329    builtin!(sigs, "string->symbol", (a String -- a Symbol));
330
331    // =========================================================================
332    // Integer Arithmetic ( a Int Int -- a Int )
333    // =========================================================================
334
335    builtins_int_int_to_int!(
336        sigs,
337        "i.add",
338        "i.subtract",
339        "i.multiply",
340        "i.divide",
341        "i.modulo"
342    );
343    builtins_int_int_to_int!(sigs, "i.+", "i.-", "i.*", "i./", "i.%");
344
345    // =========================================================================
346    // Integer Comparison ( a Int Int -- a Bool )
347    // =========================================================================
348
349    builtins_int_int_to_bool!(sigs, "i.=", "i.<", "i.>", "i.<=", "i.>=", "i.<>");
350    builtins_int_int_to_bool!(sigs, "i.eq", "i.lt", "i.gt", "i.lte", "i.gte", "i.neq");
351
352    // =========================================================================
353    // Boolean Operations ( a Bool Bool -- a Bool )
354    // =========================================================================
355
356    builtins_bool_bool_to_bool!(sigs, "and", "or");
357    builtin!(sigs, "not", (a Bool -- a Bool));
358
359    // =========================================================================
360    // Bitwise Operations
361    // =========================================================================
362
363    builtins_int_int_to_int!(sigs, "band", "bor", "bxor", "shl", "shr");
364    builtins_int_to_int!(sigs, "bnot", "popcount", "clz", "ctz");
365    builtin!(sigs, "int-bits", (a -- a Int));
366
367    // =========================================================================
368    // Stack Operations (Polymorphic)
369    // =========================================================================
370
371    builtin!(sigs, "dup", (a T -- a T T));
372    builtin!(sigs, "drop", (a T -- a));
373    builtin!(sigs, "swap", (a T U -- a U T));
374    builtin!(sigs, "over", (a T U -- a T U T));
375    builtin!(sigs, "rot", (a T U V -- a U V T));
376    builtin!(sigs, "nip", (a T U -- a U));
377    builtin!(sigs, "tuck", (a T U -- a U T U));
378    builtin!(sigs, "2dup", (a T U -- a T U T U));
379    builtin!(sigs, "3drop", (a T U V -- a));
380
381    // pick and roll: Type approximations (see detailed comments below)
382    // pick: ( ..a T Int -- ..a T T ) - copies value at depth n to top
383    builtin!(sigs, "pick", (a T Int -- a T T));
384    // roll: ( ..a T Int -- ..a T ) - rotates n+1 items, bringing depth n to top
385    builtin!(sigs, "roll", (a T Int -- a T));
386
387    // =========================================================================
388    // Channel Operations (CSP-style concurrency)
389    // Errors are values, not crashes - all ops return success flags
390    // =========================================================================
391
392    builtin!(sigs, "chan.make", (a -- a Channel));
393    builtin!(sigs, "chan.send", (a T Channel -- a Bool)); // returns success flag
394    builtin!(sigs, "chan.receive", (a Channel -- a T Bool)); // returns value and success flag
395    builtin!(sigs, "chan.close", (a Channel -- a));
396    builtin!(sigs, "chan.yield", (a - -a));
397
398    // =========================================================================
399    // Quotation/Control Flow Operations
400    // =========================================================================
401
402    // call: Polymorphic - accepts Quotation or Closure
403    // Uses type variable Q to represent "something callable"
404    sigs.insert(
405        "call".to_string(),
406        Effect::new(
407            StackType::RowVar("a".to_string()).push(Type::Var("Q".to_string())),
408            StackType::RowVar("b".to_string()),
409        ),
410    );
411
412    // cond: Multi-way conditional (variable arity)
413    sigs.insert(
414        "cond".to_string(),
415        Effect::new(
416            StackType::RowVar("a".to_string()),
417            StackType::RowVar("b".to_string()),
418        ),
419    );
420
421    // times: ( a Quotation Int -- a ) where Quotation has effect ( a -- a )
422    sigs.insert(
423        "times".to_string(),
424        Effect::new(
425            StackType::RowVar("a".to_string())
426                .push(Type::Quotation(Box::new(Effect::new(
427                    StackType::RowVar("a".to_string()),
428                    StackType::RowVar("a".to_string()),
429                ))))
430                .push(Type::Int),
431            StackType::RowVar("a".to_string()),
432        ),
433    );
434
435    // while: ( a CondQuot BodyQuot -- a )
436    // CondQuot: ( a -- a Bool ), BodyQuot: ( a -- a )
437    sigs.insert(
438        "while".to_string(),
439        Effect::new(
440            StackType::RowVar("a".to_string())
441                .push(Type::Quotation(Box::new(Effect::new(
442                    StackType::RowVar("a".to_string()),
443                    StackType::RowVar("a".to_string()).push(Type::Bool),
444                ))))
445                .push(Type::Quotation(Box::new(Effect::new(
446                    StackType::RowVar("a".to_string()),
447                    StackType::RowVar("a".to_string()),
448                )))),
449            StackType::RowVar("a".to_string()),
450        ),
451    );
452
453    // until: ( a BodyQuot CondQuot -- a )
454    // BodyQuot: ( a -- a ), CondQuot: ( a -- a Bool )
455    sigs.insert(
456        "until".to_string(),
457        Effect::new(
458            StackType::RowVar("a".to_string())
459                .push(Type::Quotation(Box::new(Effect::new(
460                    StackType::RowVar("a".to_string()),
461                    StackType::RowVar("a".to_string()),
462                ))))
463                .push(Type::Quotation(Box::new(Effect::new(
464                    StackType::RowVar("a".to_string()),
465                    StackType::RowVar("a".to_string()).push(Type::Bool),
466                )))),
467            StackType::RowVar("a".to_string()),
468        ),
469    );
470
471    // strand.spawn: ( a Quotation -- a Int ) - spawn a concurrent strand
472    // The quotation can have any stack effect - it runs independently
473    sigs.insert(
474        "strand.spawn".to_string(),
475        Effect::new(
476            StackType::RowVar("a".to_string()).push(Type::Quotation(Box::new(Effect::new(
477                StackType::RowVar("spawn_in".to_string()),
478                StackType::RowVar("spawn_out".to_string()),
479            )))),
480            StackType::RowVar("a".to_string()).push(Type::Int),
481        ),
482    );
483
484    // strand.weave: ( a Quotation -- a handle ) - create a woven strand (generator)
485    // The quotation receives (WeaveCtx, first_resume_value) and must thread WeaveCtx through.
486    // Returns a handle (WeaveCtx) for use with strand.resume.
487    sigs.insert(
488        "strand.weave".to_string(),
489        Effect::new(
490            StackType::RowVar("a".to_string()).push(Type::Quotation(Box::new(Effect::new(
491                StackType::RowVar("weave_in".to_string()),
492                StackType::RowVar("weave_out".to_string()),
493            )))),
494            StackType::RowVar("a".to_string()).push(Type::Var("handle".to_string())),
495        ),
496    );
497
498    // strand.resume: ( a handle b -- a handle b Bool ) - resume weave with value
499    // Takes handle and value to send, returns (handle, yielded_value, has_more)
500    sigs.insert(
501        "strand.resume".to_string(),
502        Effect::new(
503            StackType::RowVar("a".to_string())
504                .push(Type::Var("handle".to_string()))
505                .push(Type::Var("b".to_string())),
506            StackType::RowVar("a".to_string())
507                .push(Type::Var("handle".to_string()))
508                .push(Type::Var("b".to_string()))
509                .push(Type::Bool),
510        ),
511    );
512
513    // yield: ( a ctx b -- a ctx b | Yield b ) - yield value and receive resume value
514    // The WeaveCtx must be passed explicitly and threaded through.
515    // The Yield effect indicates this word produces yield semantics.
516    sigs.insert(
517        "yield".to_string(),
518        Effect::with_effects(
519            StackType::RowVar("a".to_string())
520                .push(Type::Var("ctx".to_string()))
521                .push(Type::Var("b".to_string())),
522            StackType::RowVar("a".to_string())
523                .push(Type::Var("ctx".to_string()))
524                .push(Type::Var("b".to_string())),
525            vec![SideEffect::Yield(Box::new(Type::Var("b".to_string())))],
526        ),
527    );
528
529    // strand.weave-cancel: ( a handle -- a ) - cancel a weave and release its resources
530    // Use this to clean up a weave that won't be resumed to completion.
531    // This prevents resource leaks from abandoned weaves.
532    sigs.insert(
533        "strand.weave-cancel".to_string(),
534        Effect::new(
535            StackType::RowVar("a".to_string()).push(Type::Var("handle".to_string())),
536            StackType::RowVar("a".to_string()),
537        ),
538    );
539
540    // =========================================================================
541    // TCP Operations
542    // =========================================================================
543
544    builtin!(sigs, "tcp.listen", (a Int -- a Int));
545    builtin!(sigs, "tcp.accept", (a Int -- a Int));
546    builtin!(sigs, "tcp.read", (a Int -- a String));
547    builtin!(sigs, "tcp.write", (a String Int -- a));
548    builtin!(sigs, "tcp.close", (a Int -- a));
549
550    // =========================================================================
551    // OS Operations
552    // =========================================================================
553
554    builtin!(sigs, "os.getenv", (a String -- a String Bool));
555    builtin!(sigs, "os.home-dir", (a -- a String Bool));
556    builtin!(sigs, "os.current-dir", (a -- a String Bool));
557    builtin!(sigs, "os.path-exists", (a String -- a Bool));
558    builtin!(sigs, "os.path-is-file", (a String -- a Bool));
559    builtin!(sigs, "os.path-is-dir", (a String -- a Bool));
560    builtin!(sigs, "os.path-join", (a String String -- a String));
561    builtin!(sigs, "os.path-parent", (a String -- a String Bool));
562    builtin!(sigs, "os.path-filename", (a String -- a String Bool));
563    builtin!(sigs, "os.exit", (a Int -- a)); // Never returns, but typed as identity
564    builtin!(sigs, "os.name", (a -- a String));
565    builtin!(sigs, "os.arch", (a -- a String));
566
567    // =========================================================================
568    // String Operations
569    // =========================================================================
570
571    builtin!(sigs, "string.concat", (a String String -- a String));
572    builtin!(sigs, "string.length", (a String -- a Int));
573    builtin!(sigs, "string.byte-length", (a String -- a Int));
574    builtin!(sigs, "string.char-at", (a String Int -- a Int));
575    builtin!(sigs, "string.substring", (a String Int Int -- a String));
576    builtin!(sigs, "string.find", (a String String -- a Int));
577    builtin!(sigs, "string.split", (a String String -- a V)); // Returns Variant (list)
578    builtin!(sigs, "string.contains", (a String String -- a Bool));
579    builtin!(sigs, "string.starts-with", (a String String -- a Bool));
580    builtin!(sigs, "string.empty?", (a String -- a Bool));
581    builtin!(sigs, "string.equal?", (a String String -- a Bool));
582
583    // Symbol operations
584    builtin!(sigs, "symbol.=", (a Symbol Symbol -- a Bool));
585
586    // String transformations
587    builtins_string_to_string!(
588        sigs,
589        "string.trim",
590        "string.chomp",
591        "string.to-upper",
592        "string.to-lower",
593        "string.json-escape"
594    );
595
596    // =========================================================================
597    // Variant Operations
598    // =========================================================================
599
600    builtin!(sigs, "variant.field-count", (a V -- a Int));
601    builtin!(sigs, "variant.tag", (a V -- a Symbol));
602    builtin!(sigs, "variant.field-at", (a V Int -- a T));
603    builtin!(sigs, "variant.append", (a V T -- a V2));
604    builtin!(sigs, "variant.last", (a V -- a T));
605    builtin!(sigs, "variant.init", (a V -- a V2));
606
607    // Type-safe variant constructors with fixed arity (symbol tags for SON support)
608    builtin!(sigs, "variant.make-0", (a Symbol -- a V));
609    builtin!(sigs, "variant.make-1", (a T1 Symbol -- a V));
610    builtin!(sigs, "variant.make-2", (a T1 T2 Symbol -- a V));
611    builtin!(sigs, "variant.make-3", (a T1 T2 T3 Symbol -- a V));
612    builtin!(sigs, "variant.make-4", (a T1 T2 T3 T4 Symbol -- a V));
613
614    // Aliases for dynamic variant construction (SON-friendly names)
615    builtin!(sigs, "wrap-0", (a Symbol -- a V));
616    builtin!(sigs, "wrap-1", (a T1 Symbol -- a V));
617    builtin!(sigs, "wrap-2", (a T1 T2 Symbol -- a V));
618    builtin!(sigs, "wrap-3", (a T1 T2 T3 Symbol -- a V));
619    builtin!(sigs, "wrap-4", (a T1 T2 T3 T4 Symbol -- a V));
620
621    // =========================================================================
622    // List Operations (Higher-order combinators for Variants)
623    // =========================================================================
624
625    // List construction and access
626    builtin!(sigs, "list.make", (a -- a V));
627    builtin!(sigs, "list.push", (a V T -- a V));
628    builtin!(sigs, "list.get", (a V Int -- a T Bool));
629    builtin!(sigs, "list.set", (a V Int T -- a V Bool));
630
631    builtin!(sigs, "list.length", (a V -- a Int));
632    builtin!(sigs, "list.empty?", (a V -- a Bool));
633
634    // list.map: ( a Variant Quotation -- a Variant )
635    // Quotation: ( b T -- b U )
636    sigs.insert(
637        "list.map".to_string(),
638        Effect::new(
639            StackType::RowVar("a".to_string())
640                .push(Type::Var("V".to_string()))
641                .push(Type::Quotation(Box::new(Effect::new(
642                    StackType::RowVar("b".to_string()).push(Type::Var("T".to_string())),
643                    StackType::RowVar("b".to_string()).push(Type::Var("U".to_string())),
644                )))),
645            StackType::RowVar("a".to_string()).push(Type::Var("V2".to_string())),
646        ),
647    );
648
649    // list.filter: ( a Variant Quotation -- a Variant )
650    // Quotation: ( b T -- b Bool )
651    sigs.insert(
652        "list.filter".to_string(),
653        Effect::new(
654            StackType::RowVar("a".to_string())
655                .push(Type::Var("V".to_string()))
656                .push(Type::Quotation(Box::new(Effect::new(
657                    StackType::RowVar("b".to_string()).push(Type::Var("T".to_string())),
658                    StackType::RowVar("b".to_string()).push(Type::Bool),
659                )))),
660            StackType::RowVar("a".to_string()).push(Type::Var("V2".to_string())),
661        ),
662    );
663
664    // list.fold: ( a Variant init Quotation -- a result )
665    // Quotation: ( b Acc T -- b Acc )
666    sigs.insert(
667        "list.fold".to_string(),
668        Effect::new(
669            StackType::RowVar("a".to_string())
670                .push(Type::Var("V".to_string()))
671                .push(Type::Var("Acc".to_string()))
672                .push(Type::Quotation(Box::new(Effect::new(
673                    StackType::RowVar("b".to_string())
674                        .push(Type::Var("Acc".to_string()))
675                        .push(Type::Var("T".to_string())),
676                    StackType::RowVar("b".to_string()).push(Type::Var("Acc".to_string())),
677                )))),
678            StackType::RowVar("a".to_string()).push(Type::Var("Acc".to_string())),
679        ),
680    );
681
682    // list.each: ( a Variant Quotation -- a )
683    // Quotation: ( b T -- b )
684    sigs.insert(
685        "list.each".to_string(),
686        Effect::new(
687            StackType::RowVar("a".to_string())
688                .push(Type::Var("V".to_string()))
689                .push(Type::Quotation(Box::new(Effect::new(
690                    StackType::RowVar("b".to_string()).push(Type::Var("T".to_string())),
691                    StackType::RowVar("b".to_string()),
692                )))),
693            StackType::RowVar("a".to_string()),
694        ),
695    );
696
697    // =========================================================================
698    // Map Operations (Dictionary with O(1) lookup)
699    // =========================================================================
700
701    builtin!(sigs, "map.make", (a -- a M));
702    builtin!(sigs, "map.get", (a M K -- a V Bool)); // returns (value success) - errors are values, not crashes
703    builtin!(sigs, "map.set", (a M K V -- a M2));
704    builtin!(sigs, "map.has?", (a M K -- a Bool));
705    builtin!(sigs, "map.remove", (a M K -- a M2));
706    builtin!(sigs, "map.keys", (a M -- a V));
707    builtin!(sigs, "map.values", (a M -- a V));
708    builtin!(sigs, "map.size", (a M -- a Int));
709    builtin!(sigs, "map.empty?", (a M -- a Bool));
710
711    // =========================================================================
712    // Float Arithmetic ( a Float Float -- a Float )
713    // =========================================================================
714
715    builtins_float_float_to_float!(sigs, "f.add", "f.subtract", "f.multiply", "f.divide");
716    builtins_float_float_to_float!(sigs, "f.+", "f.-", "f.*", "f./");
717
718    // =========================================================================
719    // Float Comparison ( a Float Float -- a Bool )
720    // =========================================================================
721
722    builtins_float_float_to_bool!(sigs, "f.=", "f.<", "f.>", "f.<=", "f.>=", "f.<>");
723    builtins_float_float_to_bool!(sigs, "f.eq", "f.lt", "f.gt", "f.lte", "f.gte", "f.neq");
724
725    // =========================================================================
726    // Test Framework
727    // =========================================================================
728
729    builtin!(sigs, "test.init", (a String -- a));
730    builtin!(sigs, "test.finish", (a - -a));
731    builtin!(sigs, "test.has-failures", (a -- a Bool));
732    builtin!(sigs, "test.assert", (a Bool -- a));
733    builtin!(sigs, "test.assert-not", (a Bool -- a));
734    builtin!(sigs, "test.assert-eq", (a Int Int -- a));
735    builtin!(sigs, "test.assert-eq-str", (a String String -- a));
736    builtin!(sigs, "test.fail", (a String -- a));
737    builtin!(sigs, "test.pass-count", (a -- a Int));
738    builtin!(sigs, "test.fail-count", (a -- a Int));
739
740    // Time operations
741    builtin!(sigs, "time.now", (a -- a Int));
742    builtin!(sigs, "time.nanos", (a -- a Int));
743    builtin!(sigs, "time.sleep-ms", (a Int -- a));
744
745    // SON serialization
746    builtin!(sigs, "son.dump", (a T -- a String));
747    builtin!(sigs, "son.dump-pretty", (a T -- a String));
748
749    // Stack introspection (for REPL)
750    // stack.dump prints all values and clears the stack
751    sigs.insert(
752        "stack.dump".to_string(),
753        Effect::new(
754            StackType::RowVar("a".to_string()), // Consumes any stack
755            StackType::RowVar("b".to_string()), // Returns empty stack (different row var)
756        ),
757    );
758
759    sigs
760}
761
762/// Get documentation for a built-in word
763pub fn builtin_doc(name: &str) -> Option<&'static str> {
764    BUILTIN_DOCS.get(name).copied()
765}
766
767/// Get all built-in word documentation (cached with LazyLock for performance)
768pub fn builtin_docs() -> &'static HashMap<&'static str, &'static str> {
769    &BUILTIN_DOCS
770}
771
772/// Lazily initialized documentation for all built-in words
773static BUILTIN_DOCS: LazyLock<HashMap<&'static str, &'static str>> = LazyLock::new(|| {
774    let mut docs = HashMap::new();
775
776    // I/O Operations
777    docs.insert(
778        "io.write",
779        "Write a string to stdout without a trailing newline.",
780    );
781    docs.insert(
782        "io.write-line",
783        "Write a string to stdout followed by a newline.",
784    );
785    docs.insert(
786        "io.read-line",
787        "Read a line from stdin. Returns (line, success).",
788    );
789    docs.insert(
790        "io.read-line+",
791        "Read a line from stdin. Returns (line, status_code).",
792    );
793    docs.insert(
794        "io.read-n",
795        "Read N bytes from stdin. Returns (bytes, status_code).",
796    );
797
798    // Command-line Arguments
799    docs.insert("args.count", "Get the number of command-line arguments.");
800    docs.insert("args.at", "Get the command-line argument at index N.");
801
802    // File Operations
803    docs.insert(
804        "file.slurp",
805        "Read entire file contents. Returns (content, success).",
806    );
807    docs.insert("file.exists?", "Check if a file exists at the given path.");
808    docs.insert(
809        "file.for-each-line+",
810        "Execute a quotation for each line in a file.",
811    );
812
813    // Type Conversions
814    docs.insert(
815        "int->string",
816        "Convert an integer to its string representation.",
817    );
818    docs.insert(
819        "int->float",
820        "Convert an integer to a floating-point number.",
821    );
822    docs.insert("float->int", "Truncate a float to an integer.");
823    docs.insert(
824        "float->string",
825        "Convert a float to its string representation.",
826    );
827    docs.insert(
828        "string->int",
829        "Parse a string as an integer. Returns (value, success).",
830    );
831    docs.insert(
832        "string->float",
833        "Parse a string as a float. Returns (value, success).",
834    );
835    docs.insert(
836        "char->string",
837        "Convert a Unicode codepoint to a single-character string.",
838    );
839    docs.insert(
840        "symbol->string",
841        "Convert a symbol to its string representation.",
842    );
843    docs.insert("string->symbol", "Intern a string as a symbol.");
844
845    // Integer Arithmetic
846    docs.insert("i.add", "Add two integers.");
847    docs.insert("i.subtract", "Subtract second integer from first.");
848    docs.insert("i.multiply", "Multiply two integers.");
849    docs.insert("i.divide", "Integer division (truncates toward zero).");
850    docs.insert("i.modulo", "Integer modulo (remainder after division).");
851    docs.insert("i.+", "Add two integers.");
852    docs.insert("i.-", "Subtract second integer from first.");
853    docs.insert("i.*", "Multiply two integers.");
854    docs.insert("i./", "Integer division (truncates toward zero).");
855    docs.insert("i.%", "Integer modulo (remainder after division).");
856
857    // Integer Comparison
858    docs.insert("i.=", "Test if two integers are equal.");
859    docs.insert("i.<", "Test if first integer is less than second.");
860    docs.insert("i.>", "Test if first integer is greater than second.");
861    docs.insert(
862        "i.<=",
863        "Test if first integer is less than or equal to second.",
864    );
865    docs.insert(
866        "i.>=",
867        "Test if first integer is greater than or equal to second.",
868    );
869    docs.insert("i.<>", "Test if two integers are not equal.");
870    docs.insert("i.eq", "Test if two integers are equal.");
871    docs.insert("i.lt", "Test if first integer is less than second.");
872    docs.insert("i.gt", "Test if first integer is greater than second.");
873    docs.insert(
874        "i.lte",
875        "Test if first integer is less than or equal to second.",
876    );
877    docs.insert(
878        "i.gte",
879        "Test if first integer is greater than or equal to second.",
880    );
881    docs.insert("i.neq", "Test if two integers are not equal.");
882
883    // Boolean Operations
884    docs.insert("and", "Logical AND of two booleans.");
885    docs.insert("or", "Logical OR of two booleans.");
886    docs.insert("not", "Logical NOT of a boolean.");
887
888    // Bitwise Operations
889    docs.insert("band", "Bitwise AND of two integers.");
890    docs.insert("bor", "Bitwise OR of two integers.");
891    docs.insert("bxor", "Bitwise XOR of two integers.");
892    docs.insert("bnot", "Bitwise NOT (complement) of an integer.");
893    docs.insert("shl", "Shift left by N bits.");
894    docs.insert("shr", "Shift right by N bits (arithmetic).");
895    docs.insert("popcount", "Count the number of set bits.");
896    docs.insert("clz", "Count leading zeros.");
897    docs.insert("ctz", "Count trailing zeros.");
898    docs.insert("int-bits", "Push the bit width of integers (64).");
899
900    // Stack Operations
901    docs.insert("dup", "Duplicate the top stack value.");
902    docs.insert("drop", "Remove the top stack value.");
903    docs.insert("swap", "Swap the top two stack values.");
904    docs.insert("over", "Copy the second value to the top.");
905    docs.insert("rot", "Rotate the top three values (third to top).");
906    docs.insert("nip", "Remove the second value from the stack.");
907    docs.insert("tuck", "Copy the top value below the second.");
908    docs.insert("2dup", "Duplicate the top two values.");
909    docs.insert("3drop", "Remove the top three values.");
910    docs.insert("pick", "Copy the value at depth N to the top.");
911    docs.insert("roll", "Rotate N+1 items, bringing depth N to top.");
912
913    // Channel Operations
914    docs.insert(
915        "chan.make",
916        "Create a new channel for inter-strand communication.",
917    );
918    docs.insert(
919        "chan.send",
920        "Send a value on a channel. Returns success flag.",
921    );
922    docs.insert(
923        "chan.receive",
924        "Receive a value from a channel. Returns (value, success).",
925    );
926    docs.insert("chan.close", "Close a channel.");
927    docs.insert("chan.yield", "Yield control to the scheduler.");
928
929    // Control Flow
930    docs.insert("call", "Call a quotation or closure.");
931    docs.insert(
932        "cond",
933        "Multi-way conditional: test clauses until one succeeds.",
934    );
935    docs.insert("times", "Execute a quotation N times.");
936    docs.insert("while", "Loop while condition is true: [cond] [body] while");
937    docs.insert("until", "Loop until condition is true: [body] [cond] until");
938
939    // Concurrency
940    docs.insert(
941        "strand.spawn",
942        "Spawn a concurrent strand. Returns strand ID.",
943    );
944    docs.insert(
945        "strand.weave",
946        "Create a generator/coroutine. Returns handle.",
947    );
948    docs.insert(
949        "strand.resume",
950        "Resume a weave with a value. Returns (handle, value, has_more).",
951    );
952    docs.insert(
953        "yield",
954        "Yield a value from a weave and receive resume value.",
955    );
956    docs.insert(
957        "strand.weave-cancel",
958        "Cancel a weave and release its resources.",
959    );
960
961    // TCP Operations
962    docs.insert(
963        "tcp.listen",
964        "Start listening on a port. Returns socket ID.",
965    );
966    docs.insert(
967        "tcp.accept",
968        "Accept a connection. Returns client socket ID.",
969    );
970    docs.insert("tcp.read", "Read data from a socket. Returns string.");
971    docs.insert("tcp.write", "Write data to a socket.");
972    docs.insert("tcp.close", "Close a socket.");
973
974    // OS Operations
975    docs.insert(
976        "os.getenv",
977        "Get environment variable. Returns (value, exists).",
978    );
979    docs.insert(
980        "os.home-dir",
981        "Get user's home directory. Returns (path, success).",
982    );
983    docs.insert(
984        "os.current-dir",
985        "Get current working directory. Returns (path, success).",
986    );
987    docs.insert("os.path-exists", "Check if a path exists.");
988    docs.insert("os.path-is-file", "Check if path is a regular file.");
989    docs.insert("os.path-is-dir", "Check if path is a directory.");
990    docs.insert("os.path-join", "Join two path components.");
991    docs.insert(
992        "os.path-parent",
993        "Get parent directory. Returns (path, success).",
994    );
995    docs.insert(
996        "os.path-filename",
997        "Get filename component. Returns (name, success).",
998    );
999    docs.insert("os.exit", "Exit the program with a status code.");
1000    docs.insert(
1001        "os.name",
1002        "Get the operating system name (e.g., \"macos\", \"linux\").",
1003    );
1004    docs.insert(
1005        "os.arch",
1006        "Get the CPU architecture (e.g., \"aarch64\", \"x86_64\").",
1007    );
1008
1009    // String Operations
1010    docs.insert("string.concat", "Concatenate two strings.");
1011    docs.insert("string.length", "Get the character length of a string.");
1012    docs.insert("string.byte-length", "Get the byte length of a string.");
1013    docs.insert(
1014        "string.char-at",
1015        "Get Unicode codepoint at character index.",
1016    );
1017    docs.insert(
1018        "string.substring",
1019        "Extract substring from start index with length.",
1020    );
1021    docs.insert(
1022        "string.find",
1023        "Find substring. Returns index or -1 if not found.",
1024    );
1025    docs.insert("string.split", "Split string by delimiter. Returns a list.");
1026    docs.insert("string.contains", "Check if string contains a substring.");
1027    docs.insert(
1028        "string.starts-with",
1029        "Check if string starts with a prefix.",
1030    );
1031    docs.insert("string.empty?", "Check if string is empty.");
1032    docs.insert("string.equal?", "Check if two strings are equal.");
1033    docs.insert("string.trim", "Remove leading and trailing whitespace.");
1034    docs.insert("string.chomp", "Remove trailing newline.");
1035    docs.insert("string.to-upper", "Convert to uppercase.");
1036    docs.insert("string.to-lower", "Convert to lowercase.");
1037    docs.insert("string.json-escape", "Escape special characters for JSON.");
1038    docs.insert("symbol.=", "Check if two symbols are equal.");
1039
1040    // Variant Operations
1041    docs.insert(
1042        "variant.field-count",
1043        "Get the number of fields in a variant.",
1044    );
1045    docs.insert(
1046        "variant.tag",
1047        "Get the tag (constructor name) of a variant.",
1048    );
1049    docs.insert("variant.field-at", "Get the field at index N.");
1050    docs.insert(
1051        "variant.append",
1052        "Append a value to a variant (creates new).",
1053    );
1054    docs.insert("variant.last", "Get the last field of a variant.");
1055    docs.insert("variant.init", "Get all fields except the last.");
1056    docs.insert("variant.make-0", "Create a variant with 0 fields.");
1057    docs.insert("variant.make-1", "Create a variant with 1 field.");
1058    docs.insert("variant.make-2", "Create a variant with 2 fields.");
1059    docs.insert("variant.make-3", "Create a variant with 3 fields.");
1060    docs.insert("variant.make-4", "Create a variant with 4 fields.");
1061    docs.insert("wrap-0", "Create a variant with 0 fields (alias).");
1062    docs.insert("wrap-1", "Create a variant with 1 field (alias).");
1063    docs.insert("wrap-2", "Create a variant with 2 fields (alias).");
1064    docs.insert("wrap-3", "Create a variant with 3 fields (alias).");
1065    docs.insert("wrap-4", "Create a variant with 4 fields (alias).");
1066
1067    // List Operations
1068    docs.insert("list.make", "Create an empty list.");
1069    docs.insert("list.push", "Push a value onto a list. Returns new list.");
1070    docs.insert("list.get", "Get value at index. Returns (value, success).");
1071    docs.insert("list.set", "Set value at index. Returns (list, success).");
1072    docs.insert("list.length", "Get the number of elements in a list.");
1073    docs.insert("list.empty?", "Check if a list is empty.");
1074    docs.insert(
1075        "list.map",
1076        "Apply quotation to each element. Returns new list.",
1077    );
1078    docs.insert("list.filter", "Keep elements where quotation returns true.");
1079    docs.insert("list.fold", "Reduce list with accumulator and quotation.");
1080    docs.insert(
1081        "list.each",
1082        "Execute quotation for each element (side effects).",
1083    );
1084
1085    // Map Operations
1086    docs.insert("map.make", "Create an empty map.");
1087    docs.insert("map.get", "Get value for key. Returns (value, success).");
1088    docs.insert("map.set", "Set key to value. Returns new map.");
1089    docs.insert("map.has?", "Check if map contains a key.");
1090    docs.insert("map.remove", "Remove a key. Returns new map.");
1091    docs.insert("map.keys", "Get all keys as a list.");
1092    docs.insert("map.values", "Get all values as a list.");
1093    docs.insert("map.size", "Get the number of key-value pairs.");
1094    docs.insert("map.empty?", "Check if map is empty.");
1095
1096    // Float Arithmetic
1097    docs.insert("f.add", "Add two floats.");
1098    docs.insert("f.subtract", "Subtract second float from first.");
1099    docs.insert("f.multiply", "Multiply two floats.");
1100    docs.insert("f.divide", "Divide first float by second.");
1101    docs.insert("f.+", "Add two floats.");
1102    docs.insert("f.-", "Subtract second float from first.");
1103    docs.insert("f.*", "Multiply two floats.");
1104    docs.insert("f./", "Divide first float by second.");
1105
1106    // Float Comparison
1107    docs.insert("f.=", "Test if two floats are equal.");
1108    docs.insert("f.<", "Test if first float is less than second.");
1109    docs.insert("f.>", "Test if first float is greater than second.");
1110    docs.insert("f.<=", "Test if first float is less than or equal.");
1111    docs.insert("f.>=", "Test if first float is greater than or equal.");
1112    docs.insert("f.<>", "Test if two floats are not equal.");
1113    docs.insert("f.eq", "Test if two floats are equal.");
1114    docs.insert("f.lt", "Test if first float is less than second.");
1115    docs.insert("f.gt", "Test if first float is greater than second.");
1116    docs.insert("f.lte", "Test if first float is less than or equal.");
1117    docs.insert("f.gte", "Test if first float is greater than or equal.");
1118    docs.insert("f.neq", "Test if two floats are not equal.");
1119
1120    // Test Framework
1121    docs.insert(
1122        "test.init",
1123        "Initialize the test framework with a test name.",
1124    );
1125    docs.insert("test.finish", "Finish testing and print results.");
1126    docs.insert("test.has-failures", "Check if any tests have failed.");
1127    docs.insert("test.assert", "Assert that a boolean is true.");
1128    docs.insert("test.assert-not", "Assert that a boolean is false.");
1129    docs.insert("test.assert-eq", "Assert that two integers are equal.");
1130    docs.insert("test.assert-eq-str", "Assert that two strings are equal.");
1131    docs.insert("test.fail", "Mark a test as failed with a message.");
1132    docs.insert("test.pass-count", "Get the number of passed assertions.");
1133    docs.insert("test.fail-count", "Get the number of failed assertions.");
1134
1135    // Time Operations
1136    docs.insert("time.now", "Get current Unix timestamp in seconds.");
1137    docs.insert(
1138        "time.nanos",
1139        "Get high-resolution monotonic time in nanoseconds.",
1140    );
1141    docs.insert("time.sleep-ms", "Sleep for N milliseconds.");
1142
1143    // Serialization
1144    docs.insert("son.dump", "Serialize any value to SON format (compact).");
1145    docs.insert(
1146        "son.dump-pretty",
1147        "Serialize any value to SON format (pretty-printed).",
1148    );
1149
1150    // Stack Introspection
1151    docs.insert(
1152        "stack.dump",
1153        "Print all stack values and clear the stack (REPL).",
1154    );
1155
1156    docs
1157});
1158
1159#[cfg(test)]
1160mod tests {
1161    use super::*;
1162
1163    #[test]
1164    fn test_builtin_signature_write_line() {
1165        let sig = builtin_signature("io.write-line").unwrap();
1166        // ( ..a String -- ..a )
1167        let (rest, top) = sig.inputs.clone().pop().unwrap();
1168        assert_eq!(top, Type::String);
1169        assert_eq!(rest, StackType::RowVar("a".to_string()));
1170        assert_eq!(sig.outputs, StackType::RowVar("a".to_string()));
1171    }
1172
1173    #[test]
1174    fn test_builtin_signature_i_add() {
1175        let sig = builtin_signature("i.add").unwrap();
1176        // ( ..a Int Int -- ..a Int )
1177        let (rest, top) = sig.inputs.clone().pop().unwrap();
1178        assert_eq!(top, Type::Int);
1179        let (rest2, top2) = rest.pop().unwrap();
1180        assert_eq!(top2, Type::Int);
1181        assert_eq!(rest2, StackType::RowVar("a".to_string()));
1182
1183        let (rest3, top3) = sig.outputs.clone().pop().unwrap();
1184        assert_eq!(top3, Type::Int);
1185        assert_eq!(rest3, StackType::RowVar("a".to_string()));
1186    }
1187
1188    #[test]
1189    fn test_builtin_signature_dup() {
1190        let sig = builtin_signature("dup").unwrap();
1191        // Input: ( ..a T )
1192        assert_eq!(
1193            sig.inputs,
1194            StackType::Cons {
1195                rest: Box::new(StackType::RowVar("a".to_string())),
1196                top: Type::Var("T".to_string())
1197            }
1198        );
1199        // Output: ( ..a T T )
1200        let (rest, top) = sig.outputs.clone().pop().unwrap();
1201        assert_eq!(top, Type::Var("T".to_string()));
1202        let (rest2, top2) = rest.pop().unwrap();
1203        assert_eq!(top2, Type::Var("T".to_string()));
1204        assert_eq!(rest2, StackType::RowVar("a".to_string()));
1205    }
1206
1207    #[test]
1208    fn test_all_builtins_have_signatures() {
1209        let sigs = builtin_signatures();
1210
1211        // Verify all expected builtins have signatures
1212        assert!(sigs.contains_key("io.write-line"));
1213        assert!(sigs.contains_key("io.read-line"));
1214        assert!(sigs.contains_key("int->string"));
1215        assert!(sigs.contains_key("i.add"));
1216        assert!(sigs.contains_key("dup"));
1217        assert!(sigs.contains_key("swap"));
1218        assert!(sigs.contains_key("chan.make"));
1219        assert!(sigs.contains_key("chan.send"));
1220        assert!(sigs.contains_key("chan.receive"));
1221        assert!(
1222            sigs.contains_key("string->float"),
1223            "string->float should be a builtin"
1224        );
1225    }
1226
1227    #[test]
1228    fn test_all_docs_have_signatures() {
1229        let sigs = builtin_signatures();
1230        let docs = builtin_docs();
1231
1232        for name in docs.keys() {
1233            assert!(
1234                sigs.contains_key(*name),
1235                "Builtin '{}' has documentation but no signature",
1236                name
1237            );
1238        }
1239    }
1240
1241    #[test]
1242    fn test_all_signatures_have_docs() {
1243        let sigs = builtin_signatures();
1244        let docs = builtin_docs();
1245
1246        for name in sigs.keys() {
1247            assert!(
1248                docs.contains_key(name.as_str()),
1249                "Builtin '{}' has signature but no documentation",
1250                name
1251            );
1252        }
1253    }
1254}