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