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 builtins_int_to_int!(sigs, "i.neg", "negate"); builtin!(sigs, "int-bits", (a -- a Int));
376
377 builtin!(sigs, "dup", (a T -- a T T));
382 builtin!(sigs, "drop", (a T -- a));
383 builtin!(sigs, "swap", (a T U -- a U T));
384 builtin!(sigs, "over", (a T U -- a T U T));
385 builtin!(sigs, "rot", (a T U V -- a U V T));
386 builtin!(sigs, "nip", (a T U -- a U));
387 builtin!(sigs, "tuck", (a T U -- a U T U));
388 builtin!(sigs, "2dup", (a T U -- a T U T U));
389 builtin!(sigs, "3drop", (a T U V -- a));
390
391 builtin!(sigs, "pick", (a T Int -- a T T));
394 builtin!(sigs, "roll", (a T Int -- a T));
396
397 builtin!(sigs, ">aux", (a T -- a));
404 builtin!(sigs, "aux>", (a -- a T));
405
406 builtin!(sigs, "chan.make", (a -- a Channel));
412 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));
415 builtin!(sigs, "chan.yield", (a - -a));
416
417 sigs.insert(
424 "call".to_string(),
425 Effect::new(
426 StackType::RowVar("a".to_string()).push(Type::Var("Q".to_string())),
427 StackType::RowVar("b".to_string()),
428 ),
429 );
430
431 sigs.insert(
440 "dip".to_string(),
441 Effect::new(
442 StackType::RowVar("a".to_string())
443 .push(Type::Var("T".to_string()))
444 .push(Type::Var("Q".to_string())),
445 StackType::RowVar("b".to_string()).push(Type::Var("T".to_string())),
446 ),
447 );
448
449 sigs.insert(
454 "keep".to_string(),
455 Effect::new(
456 StackType::RowVar("a".to_string())
457 .push(Type::Var("T".to_string()))
458 .push(Type::Var("Q".to_string())),
459 StackType::RowVar("b".to_string()).push(Type::Var("T".to_string())),
460 ),
461 );
462
463 sigs.insert(
468 "bi".to_string(),
469 Effect::new(
470 StackType::RowVar("a".to_string())
471 .push(Type::Var("T".to_string()))
472 .push(Type::Var("Q1".to_string()))
473 .push(Type::Var("Q2".to_string())),
474 StackType::RowVar("b".to_string()),
475 ),
476 );
477
478 sigs.insert(
480 "cond".to_string(),
481 Effect::new(
482 StackType::RowVar("a".to_string()),
483 StackType::RowVar("b".to_string()),
484 ),
485 );
486
487 sigs.insert(
490 "strand.spawn".to_string(),
491 Effect::new(
492 StackType::RowVar("a".to_string()).push(Type::Quotation(Box::new(Effect::new(
493 StackType::RowVar("spawn_in".to_string()),
494 StackType::RowVar("spawn_out".to_string()),
495 )))),
496 StackType::RowVar("a".to_string()).push(Type::Int),
497 ),
498 );
499
500 sigs.insert(
504 "strand.weave".to_string(),
505 Effect::new(
506 StackType::RowVar("a".to_string()).push(Type::Quotation(Box::new(Effect::new(
507 StackType::RowVar("weave_in".to_string()),
508 StackType::RowVar("weave_out".to_string()),
509 )))),
510 StackType::RowVar("a".to_string()).push(Type::Var("handle".to_string())),
511 ),
512 );
513
514 sigs.insert(
517 "strand.resume".to_string(),
518 Effect::new(
519 StackType::RowVar("a".to_string())
520 .push(Type::Var("handle".to_string()))
521 .push(Type::Var("b".to_string())),
522 StackType::RowVar("a".to_string())
523 .push(Type::Var("handle".to_string()))
524 .push(Type::Var("b".to_string()))
525 .push(Type::Bool),
526 ),
527 );
528
529 sigs.insert(
533 "yield".to_string(),
534 Effect::with_effects(
535 StackType::RowVar("a".to_string())
536 .push(Type::Var("ctx".to_string()))
537 .push(Type::Var("b".to_string())),
538 StackType::RowVar("a".to_string())
539 .push(Type::Var("ctx".to_string()))
540 .push(Type::Var("b".to_string())),
541 vec![SideEffect::Yield(Box::new(Type::Var("b".to_string())))],
542 ),
543 );
544
545 sigs.insert(
549 "strand.weave-cancel".to_string(),
550 Effect::new(
551 StackType::RowVar("a".to_string()).push(Type::Var("handle".to_string())),
552 StackType::RowVar("a".to_string()),
553 ),
554 );
555
556 builtin!(sigs, "tcp.listen", (a Int -- a Int Bool));
562 builtin!(sigs, "tcp.accept", (a Int -- a Int Bool));
563 builtin!(sigs, "tcp.read", (a Int -- a String Bool));
564 builtin!(sigs, "tcp.write", (a String Int -- a Bool));
565 builtin!(sigs, "tcp.close", (a Int -- a Bool));
566
567 builtin!(sigs, "os.getenv", (a String -- a String Bool));
572 builtin!(sigs, "os.home-dir", (a -- a String Bool));
573 builtin!(sigs, "os.current-dir", (a -- a String Bool));
574 builtin!(sigs, "os.path-exists", (a String -- a Bool));
575 builtin!(sigs, "os.path-is-file", (a String -- a Bool));
576 builtin!(sigs, "os.path-is-dir", (a String -- a Bool));
577 builtin!(sigs, "os.path-join", (a String String -- a String));
578 builtin!(sigs, "os.path-parent", (a String -- a String Bool));
579 builtin!(sigs, "os.path-filename", (a String -- a String Bool));
580 builtin!(sigs, "os.exit", (a Int -- a)); builtin!(sigs, "os.name", (a -- a String));
582 builtin!(sigs, "os.arch", (a -- a String));
583
584 builtin!(sigs, "signal.trap", (a Int -- a));
589 builtin!(sigs, "signal.received?", (a Int -- a Bool));
590 builtin!(sigs, "signal.pending?", (a Int -- a Bool));
591 builtin!(sigs, "signal.default", (a Int -- a));
592 builtin!(sigs, "signal.ignore", (a Int -- a));
593 builtin!(sigs, "signal.clear", (a Int -- a));
594 builtin!(sigs, "signal.SIGINT", (a -- a Int));
596 builtin!(sigs, "signal.SIGTERM", (a -- a Int));
597 builtin!(sigs, "signal.SIGHUP", (a -- a Int));
598 builtin!(sigs, "signal.SIGPIPE", (a -- a Int));
599 builtin!(sigs, "signal.SIGUSR1", (a -- a Int));
600 builtin!(sigs, "signal.SIGUSR2", (a -- a Int));
601 builtin!(sigs, "signal.SIGCHLD", (a -- a Int));
602 builtin!(sigs, "signal.SIGALRM", (a -- a Int));
603 builtin!(sigs, "signal.SIGCONT", (a -- a Int));
604
605 builtin!(sigs, "terminal.raw-mode", (a Bool -- a));
610 builtin!(sigs, "terminal.read-char", (a -- a Int));
611 builtin!(sigs, "terminal.read-char?", (a -- a Int));
612 builtin!(sigs, "terminal.width", (a -- a Int));
613 builtin!(sigs, "terminal.height", (a -- a Int));
614 builtin!(sigs, "terminal.flush", (a - -a));
615
616 builtin!(sigs, "string.concat", (a String String -- a String));
621 builtin!(sigs, "string.length", (a String -- a Int));
622 builtin!(sigs, "string.byte-length", (a String -- a Int));
623 builtin!(sigs, "string.char-at", (a String Int -- a Int));
624 builtin!(sigs, "string.substring", (a String Int Int -- a String));
625 builtin!(sigs, "string.find", (a String String -- a Int));
626 builtin!(sigs, "string.split", (a String String -- a V)); builtin!(sigs, "string.contains", (a String String -- a Bool));
628 builtin!(sigs, "string.starts-with", (a String String -- a Bool));
629 builtin!(sigs, "string.empty?", (a String -- a Bool));
630 builtin!(sigs, "string.equal?", (a String String -- a Bool));
631 builtin!(sigs, "string.join", (a V String -- a String)); builtin!(sigs, "symbol.=", (a Symbol Symbol -- a Bool));
635
636 builtins_string_to_string!(
638 sigs,
639 "string.trim",
640 "string.chomp",
641 "string.to-upper",
642 "string.to-lower",
643 "string.json-escape"
644 );
645
646 builtin!(sigs, "encoding.base64-encode", (a String -- a String));
651 builtin!(sigs, "encoding.base64-decode", (a String -- a String Bool));
652 builtin!(sigs, "encoding.base64url-encode", (a String -- a String));
653 builtin!(sigs, "encoding.base64url-decode", (a String -- a String Bool));
654 builtin!(sigs, "encoding.hex-encode", (a String -- a String));
655 builtin!(sigs, "encoding.hex-decode", (a String -- a String Bool));
656
657 builtin!(sigs, "crypto.sha256", (a String -- a String));
662 builtin!(sigs, "crypto.hmac-sha256", (a String String -- a String));
663 builtin!(sigs, "crypto.constant-time-eq", (a String String -- a Bool));
664 builtin!(sigs, "crypto.random-bytes", (a Int -- a String));
665 builtin!(sigs, "crypto.random-int", (a Int Int -- a Int));
666 builtin!(sigs, "crypto.uuid4", (a -- a String));
667 builtin!(sigs, "crypto.aes-gcm-encrypt", (a String String -- a String Bool));
668 builtin!(sigs, "crypto.aes-gcm-decrypt", (a String String -- a String Bool));
669 builtin!(sigs, "crypto.pbkdf2-sha256", (a String String Int -- a String Bool));
670 builtin!(sigs, "crypto.ed25519-keypair", (a -- a String String));
671 builtin!(sigs, "crypto.ed25519-sign", (a String String -- a String Bool));
672 builtin!(sigs, "crypto.ed25519-verify", (a String String String -- a Bool));
673
674 builtin!(sigs, "http.get", (a String -- a M));
679 builtin!(sigs, "http.post", (a String String String -- a M));
680 builtin!(sigs, "http.put", (a String String String -- a M));
681 builtin!(sigs, "http.delete", (a String -- a M));
682
683 builtin!(sigs, "regex.match?", (a String String -- a Bool));
689 builtin!(sigs, "regex.find", (a String String -- a String Bool));
690 builtin!(sigs, "regex.find-all", (a String String -- a V Bool));
691 builtin!(sigs, "regex.replace", (a String String String -- a String Bool));
692 builtin!(sigs, "regex.replace-all", (a String String String -- a String Bool));
693 builtin!(sigs, "regex.captures", (a String String -- a V Bool));
694 builtin!(sigs, "regex.split", (a String String -- a V Bool));
695 builtin!(sigs, "regex.valid?", (a String -- a Bool));
696
697 builtin!(sigs, "compress.gzip", (a String -- a String Bool));
702 builtin!(sigs, "compress.gzip-level", (a String Int -- a String Bool));
703 builtin!(sigs, "compress.gunzip", (a String -- a String Bool));
704 builtin!(sigs, "compress.zstd", (a String -- a String Bool));
705 builtin!(sigs, "compress.zstd-level", (a String Int -- a String Bool));
706 builtin!(sigs, "compress.unzstd", (a String -- a String Bool));
707
708 builtin!(sigs, "variant.field-count", (a V -- a Int));
713 builtin!(sigs, "variant.tag", (a V -- a Symbol));
714 builtin!(sigs, "variant.field-at", (a V Int -- a T));
715 builtin!(sigs, "variant.append", (a V T -- a V2));
716 builtin!(sigs, "variant.last", (a V -- a T));
717 builtin!(sigs, "variant.init", (a V -- a V2));
718
719 builtin!(sigs, "variant.make-0", (a Symbol -- a V));
721 builtin!(sigs, "variant.make-1", (a T1 Symbol -- a V));
722 builtin!(sigs, "variant.make-2", (a T1 T2 Symbol -- a V));
723 builtin!(sigs, "variant.make-3", (a T1 T2 T3 Symbol -- a V));
724 builtin!(sigs, "variant.make-4", (a T1 T2 T3 T4 Symbol -- a V));
725 for n in 5..=12 {
727 let mut input = StackType::RowVar("a".to_string());
728 for i in 1..=n {
729 input = input.push(Type::Var(format!("T{}", i)));
730 }
731 input = input.push(Type::Symbol);
732 let output = StackType::RowVar("a".to_string()).push(Type::Var("V".to_string()));
733 sigs.insert(format!("variant.make-{}", n), Effect::new(input, output));
734 }
735
736 builtin!(sigs, "wrap-0", (a Symbol -- a V));
738 builtin!(sigs, "wrap-1", (a T1 Symbol -- a V));
739 builtin!(sigs, "wrap-2", (a T1 T2 Symbol -- a V));
740 builtin!(sigs, "wrap-3", (a T1 T2 T3 Symbol -- a V));
741 builtin!(sigs, "wrap-4", (a T1 T2 T3 T4 Symbol -- a V));
742 for n in 5..=12 {
744 let mut input = StackType::RowVar("a".to_string());
745 for i in 1..=n {
746 input = input.push(Type::Var(format!("T{}", i)));
747 }
748 input = input.push(Type::Symbol);
749 let output = StackType::RowVar("a".to_string()).push(Type::Var("V".to_string()));
750 sigs.insert(format!("wrap-{}", n), Effect::new(input, output));
751 }
752
753 builtin!(sigs, "list.make", (a -- a V));
759 builtin!(sigs, "list.push", (a V T -- a V));
760 builtin!(sigs, "list.get", (a V Int -- a T Bool));
761 builtin!(sigs, "list.set", (a V Int T -- a V Bool));
762
763 builtin!(sigs, "list.length", (a V -- a Int));
764 builtin!(sigs, "list.empty?", (a V -- a Bool));
765 builtin!(sigs, "list.reverse", (a V -- a V));
766
767 sigs.insert(
770 "list.map".to_string(),
771 Effect::new(
772 StackType::RowVar("a".to_string())
773 .push(Type::Var("V".to_string()))
774 .push(Type::Quotation(Box::new(Effect::new(
775 StackType::RowVar("b".to_string()).push(Type::Var("T".to_string())),
776 StackType::RowVar("b".to_string()).push(Type::Var("U".to_string())),
777 )))),
778 StackType::RowVar("a".to_string()).push(Type::Var("V2".to_string())),
779 ),
780 );
781
782 sigs.insert(
785 "list.filter".to_string(),
786 Effect::new(
787 StackType::RowVar("a".to_string())
788 .push(Type::Var("V".to_string()))
789 .push(Type::Quotation(Box::new(Effect::new(
790 StackType::RowVar("b".to_string()).push(Type::Var("T".to_string())),
791 StackType::RowVar("b".to_string()).push(Type::Bool),
792 )))),
793 StackType::RowVar("a".to_string()).push(Type::Var("V2".to_string())),
794 ),
795 );
796
797 sigs.insert(
800 "list.fold".to_string(),
801 Effect::new(
802 StackType::RowVar("a".to_string())
803 .push(Type::Var("V".to_string()))
804 .push(Type::Var("Acc".to_string()))
805 .push(Type::Quotation(Box::new(Effect::new(
806 StackType::RowVar("b".to_string())
807 .push(Type::Var("Acc".to_string()))
808 .push(Type::Var("T".to_string())),
809 StackType::RowVar("b".to_string()).push(Type::Var("Acc".to_string())),
810 )))),
811 StackType::RowVar("a".to_string()).push(Type::Var("Acc".to_string())),
812 ),
813 );
814
815 sigs.insert(
818 "list.each".to_string(),
819 Effect::new(
820 StackType::RowVar("a".to_string())
821 .push(Type::Var("V".to_string()))
822 .push(Type::Quotation(Box::new(Effect::new(
823 StackType::RowVar("b".to_string()).push(Type::Var("T".to_string())),
824 StackType::RowVar("b".to_string()),
825 )))),
826 StackType::RowVar("a".to_string()),
827 ),
828 );
829
830 builtin!(sigs, "map.make", (a -- a M));
835 builtin!(sigs, "map.get", (a M K -- a V Bool)); builtin!(sigs, "map.set", (a M K V -- a M2));
837 builtin!(sigs, "map.has?", (a M K -- a Bool));
838 builtin!(sigs, "map.remove", (a M K -- a M2));
839 builtin!(sigs, "map.keys", (a M -- a V));
840 builtin!(sigs, "map.values", (a M -- a V));
841 builtin!(sigs, "map.size", (a M -- a Int));
842 builtin!(sigs, "map.empty?", (a M -- a Bool));
843
844 sigs.insert(
847 "map.each".to_string(),
848 Effect::new(
849 StackType::RowVar("a".to_string())
850 .push(Type::Var("M".to_string()))
851 .push(Type::Quotation(Box::new(Effect::new(
852 StackType::RowVar("b".to_string())
853 .push(Type::Var("K".to_string()))
854 .push(Type::Var("V".to_string())),
855 StackType::RowVar("b".to_string()),
856 )))),
857 StackType::RowVar("a".to_string()),
858 ),
859 );
860
861 sigs.insert(
864 "map.fold".to_string(),
865 Effect::new(
866 StackType::RowVar("a".to_string())
867 .push(Type::Var("M".to_string()))
868 .push(Type::Var("Acc".to_string()))
869 .push(Type::Quotation(Box::new(Effect::new(
870 StackType::RowVar("b".to_string())
871 .push(Type::Var("Acc".to_string()))
872 .push(Type::Var("K".to_string()))
873 .push(Type::Var("V".to_string())),
874 StackType::RowVar("b".to_string()).push(Type::Var("Acc".to_string())),
875 )))),
876 StackType::RowVar("a".to_string()).push(Type::Var("Acc".to_string())),
877 ),
878 );
879
880 builtins_float_float_to_float!(sigs, "f.add", "f.subtract", "f.multiply", "f.divide");
885 builtins_float_float_to_float!(sigs, "f.+", "f.-", "f.*", "f./");
886
887 builtins_float_float_to_bool!(sigs, "f.=", "f.<", "f.>", "f.<=", "f.>=", "f.<>");
892 builtins_float_float_to_bool!(sigs, "f.eq", "f.lt", "f.gt", "f.lte", "f.gte", "f.neq");
893
894 builtin!(sigs, "test.init", (a String -- a));
899 builtin!(sigs, "test.finish", (a - -a));
900 builtin!(sigs, "test.has-failures", (a -- a Bool));
901 builtin!(sigs, "test.assert", (a Bool -- a));
902 builtin!(sigs, "test.assert-not", (a Bool -- a));
903 builtin!(sigs, "test.assert-eq", (a Int Int -- a));
904 builtin!(sigs, "test.assert-eq-str", (a String String -- a));
905 builtin!(sigs, "test.fail", (a String -- a));
906 builtin!(sigs, "test.pass-count", (a -- a Int));
907 builtin!(sigs, "test.fail-count", (a -- a Int));
908
909 builtin!(sigs, "time.now", (a -- a Int));
911 builtin!(sigs, "time.nanos", (a -- a Int));
912 builtin!(sigs, "time.sleep-ms", (a Int -- a));
913
914 builtin!(sigs, "son.dump", (a T -- a String));
916 builtin!(sigs, "son.dump-pretty", (a T -- a String));
917
918 sigs.insert(
921 "stack.dump".to_string(),
922 Effect::new(
923 StackType::RowVar("a".to_string()), StackType::RowVar("b".to_string()), ),
926 );
927
928 sigs
929}
930
931pub fn builtin_doc(name: &str) -> Option<&'static str> {
933 BUILTIN_DOCS.get(name).copied()
934}
935
936pub fn builtin_docs() -> &'static HashMap<&'static str, &'static str> {
938 &BUILTIN_DOCS
939}
940
941static BUILTIN_DOCS: LazyLock<HashMap<&'static str, &'static str>> = LazyLock::new(|| {
943 let mut docs = HashMap::new();
944
945 docs.insert(
947 "io.write",
948 "Write a string to stdout without a trailing newline.",
949 );
950 docs.insert(
951 "io.write-line",
952 "Write a string to stdout followed by a newline.",
953 );
954 docs.insert(
955 "io.read-line",
956 "Read a line from stdin. Returns (String Bool) -- Bool is false on EOF or read error.",
957 );
958 docs.insert(
959 "io.read-line+",
960 "DEPRECATED: Use io.read-line instead. Read a line from stdin. Returns (line, status_code).",
961 );
962 docs.insert(
963 "io.read-n",
964 "Read N bytes from stdin. Returns (bytes, status_code).",
965 );
966
967 docs.insert("args.count", "Get the number of command-line arguments.");
969 docs.insert("args.at", "Get the command-line argument at index N.");
970
971 docs.insert(
973 "file.slurp",
974 "Read entire file contents. Returns (String Bool) -- Bool is false if file not found or unreadable.",
975 );
976 docs.insert("file.exists?", "Check if a file exists at the given path.");
977 docs.insert(
978 "file.spit",
979 "Write string to file (creates or overwrites). Returns Bool -- false on write failure.",
980 );
981 docs.insert(
982 "file.append",
983 "Append string to file (creates if needed). Returns Bool -- false on write failure.",
984 );
985 docs.insert(
986 "file.delete",
987 "Delete a file. Returns Bool -- false on failure.",
988 );
989 docs.insert(
990 "file.size",
991 "Get file size in bytes. Returns (Int Bool) -- Bool is false if file not found.",
992 );
993 docs.insert(
994 "file.for-each-line+",
995 "Execute a quotation for each line in a file.",
996 );
997
998 docs.insert(
1000 "dir.exists?",
1001 "Check if a directory exists at the given path.",
1002 );
1003 docs.insert(
1004 "dir.make",
1005 "Create a directory (and parent directories if needed). Returns Bool -- false on failure.",
1006 );
1007 docs.insert(
1008 "dir.delete",
1009 "Delete an empty directory. Returns Bool -- false on failure.",
1010 );
1011 docs.insert(
1012 "dir.list",
1013 "List directory contents. Returns (List Bool) -- Bool is false if directory not found.",
1014 );
1015
1016 docs.insert(
1018 "int->string",
1019 "Convert an integer to its string representation.",
1020 );
1021 docs.insert(
1022 "int->float",
1023 "Convert an integer to a floating-point number.",
1024 );
1025 docs.insert("float->int", "Truncate a float to an integer.");
1026 docs.insert(
1027 "float->string",
1028 "Convert a float to its string representation.",
1029 );
1030 docs.insert(
1031 "string->int",
1032 "Parse a string as an integer. Returns (Int Bool) -- Bool is false if string is not a valid integer.",
1033 );
1034 docs.insert(
1035 "string->float",
1036 "Parse a string as a float. Returns (Float Bool) -- Bool is false if string is not a valid number.",
1037 );
1038 docs.insert(
1039 "char->string",
1040 "Convert a Unicode codepoint to a single-character string.",
1041 );
1042 docs.insert(
1043 "symbol->string",
1044 "Convert a symbol to its string representation.",
1045 );
1046 docs.insert("string->symbol", "Intern a string as a symbol.");
1047
1048 docs.insert("i.add", "Add two integers.");
1050 docs.insert("i.subtract", "Subtract second integer from first.");
1051 docs.insert("i.multiply", "Multiply two integers.");
1052 docs.insert(
1053 "i.divide",
1054 "Integer division. Returns (result Bool) -- Bool is false on division by zero.",
1055 );
1056 docs.insert(
1057 "i.modulo",
1058 "Integer modulo. Returns (result Bool) -- Bool is false on division by zero.",
1059 );
1060 docs.insert("i.+", "Add two integers.");
1061 docs.insert("i.-", "Subtract second integer from first.");
1062 docs.insert("i.*", "Multiply two integers.");
1063 docs.insert(
1064 "i./",
1065 "Integer division. Returns (result Bool) -- Bool is false on division by zero.",
1066 );
1067 docs.insert(
1068 "i.%",
1069 "Integer modulo. Returns (result Bool) -- Bool is false on division by zero.",
1070 );
1071
1072 docs.insert("i.=", "Test if two integers are equal.");
1074 docs.insert("i.<", "Test if first integer is less than second.");
1075 docs.insert("i.>", "Test if first integer is greater than second.");
1076 docs.insert(
1077 "i.<=",
1078 "Test if first integer is less than or equal to second.",
1079 );
1080 docs.insert(
1081 "i.>=",
1082 "Test if first integer is greater than or equal to second.",
1083 );
1084 docs.insert("i.<>", "Test if two integers are not equal.");
1085 docs.insert("i.eq", "Test if two integers are equal.");
1086 docs.insert("i.lt", "Test if first integer is less than second.");
1087 docs.insert("i.gt", "Test if first integer is greater than second.");
1088 docs.insert(
1089 "i.lte",
1090 "Test if first integer is less than or equal to second.",
1091 );
1092 docs.insert(
1093 "i.gte",
1094 "Test if first integer is greater than or equal to second.",
1095 );
1096 docs.insert("i.neq", "Test if two integers are not equal.");
1097
1098 docs.insert("and", "Logical AND of two booleans.");
1100 docs.insert("or", "Logical OR of two booleans.");
1101 docs.insert("not", "Logical NOT of a boolean.");
1102
1103 docs.insert("band", "Bitwise AND of two integers.");
1105 docs.insert("bor", "Bitwise OR of two integers.");
1106 docs.insert("bxor", "Bitwise XOR of two integers.");
1107 docs.insert("bnot", "Bitwise NOT (complement) of an integer.");
1108 docs.insert("shl", "Shift left by N bits.");
1109 docs.insert("shr", "Shift right by N bits (arithmetic).");
1110 docs.insert(
1111 "i.neg",
1112 "Negate an integer (0 - n). Canonical name; `negate` is an alias.",
1113 );
1114 docs.insert(
1115 "negate",
1116 "Negate an integer (0 - n). Ergonomic alias for `i.neg`.",
1117 );
1118 docs.insert("popcount", "Count the number of set bits.");
1119 docs.insert("clz", "Count leading zeros.");
1120 docs.insert("ctz", "Count trailing zeros.");
1121 docs.insert("int-bits", "Push the bit width of integers (64).");
1122
1123 docs.insert("dup", "Duplicate the top stack value.");
1125 docs.insert("drop", "Remove the top stack value.");
1126 docs.insert("swap", "Swap the top two stack values.");
1127 docs.insert("over", "Copy the second value to the top.");
1128 docs.insert("rot", "Rotate the top three values (third to top).");
1129 docs.insert("nip", "Remove the second value from the stack.");
1130 docs.insert("tuck", "Copy the top value below the second.");
1131 docs.insert("2dup", "Duplicate the top two values.");
1132 docs.insert("3drop", "Remove the top three values.");
1133 docs.insert("pick", "Copy the value at depth N to the top.");
1134 docs.insert("roll", "Rotate N+1 items, bringing depth N to top.");
1135
1136 docs.insert(
1138 ">aux",
1139 "Move top of stack to word-local aux stack. Must be balanced with aux> before word returns.",
1140 );
1141 docs.insert(
1142 "aux>",
1143 "Move top of aux stack back to main stack. Requires a matching >aux.",
1144 );
1145
1146 docs.insert(
1148 "chan.make",
1149 "Create a new channel for inter-strand communication.",
1150 );
1151 docs.insert(
1152 "chan.send",
1153 "Send a value on a channel. Returns Bool -- false if channel is closed.",
1154 );
1155 docs.insert(
1156 "chan.receive",
1157 "Receive a value from a channel. Returns (value Bool) -- Bool is false if channel is closed.",
1158 );
1159 docs.insert("chan.close", "Close a channel.");
1160 docs.insert("chan.yield", "Yield control to the scheduler.");
1161
1162 docs.insert("call", "Call a quotation or closure.");
1164 docs.insert(
1165 "cond",
1166 "Multi-way conditional: test clauses until one succeeds.",
1167 );
1168
1169 docs.insert(
1171 "dip",
1172 "Hide top value, run quotation on rest of stack, restore value. ( ..a x [..a -- ..b] -- ..b x )",
1173 );
1174 docs.insert(
1175 "keep",
1176 "Run quotation on top value, but preserve the original. ( ..a x [..a x -- ..b] -- ..b x )",
1177 );
1178 docs.insert(
1179 "bi",
1180 "Apply two quotations to the same value. ( ..a x [q1] [q2] -- ..c )",
1181 );
1182
1183 docs.insert(
1185 "strand.spawn",
1186 "Spawn a concurrent strand. Returns strand ID.",
1187 );
1188 docs.insert(
1189 "strand.weave",
1190 "Create a generator/coroutine. Returns handle.",
1191 );
1192 docs.insert(
1193 "strand.resume",
1194 "Resume a weave with a value. Returns (handle, value, has_more).",
1195 );
1196 docs.insert(
1197 "yield",
1198 "Yield a value from a weave and receive resume value.",
1199 );
1200 docs.insert(
1201 "strand.weave-cancel",
1202 "Cancel a weave and release its resources.",
1203 );
1204
1205 docs.insert(
1207 "tcp.listen",
1208 "Start listening on a port. Returns (socket_id, success).",
1209 );
1210 docs.insert(
1211 "tcp.accept",
1212 "Accept a connection. Returns (client_id, success).",
1213 );
1214 docs.insert(
1215 "tcp.read",
1216 "Read data from a socket. Returns (string, success).",
1217 );
1218 docs.insert("tcp.write", "Write data to a socket. Returns success.");
1219 docs.insert("tcp.close", "Close a socket. Returns success.");
1220
1221 docs.insert(
1223 "os.getenv",
1224 "Get environment variable. Returns (value, exists).",
1225 );
1226 docs.insert(
1227 "os.home-dir",
1228 "Get user's home directory. Returns (path, success).",
1229 );
1230 docs.insert(
1231 "os.current-dir",
1232 "Get current working directory. Returns (path, success).",
1233 );
1234 docs.insert("os.path-exists", "Check if a path exists.");
1235 docs.insert("os.path-is-file", "Check if path is a regular file.");
1236 docs.insert("os.path-is-dir", "Check if path is a directory.");
1237 docs.insert("os.path-join", "Join two path components.");
1238 docs.insert(
1239 "os.path-parent",
1240 "Get parent directory. Returns (path, success).",
1241 );
1242 docs.insert(
1243 "os.path-filename",
1244 "Get filename component. Returns (name, success).",
1245 );
1246 docs.insert("os.exit", "Exit the program with a status code.");
1247 docs.insert(
1248 "os.name",
1249 "Get the operating system name (e.g., \"macos\", \"linux\").",
1250 );
1251 docs.insert(
1252 "os.arch",
1253 "Get the CPU architecture (e.g., \"aarch64\", \"x86_64\").",
1254 );
1255
1256 docs.insert(
1258 "signal.trap",
1259 "Trap a signal: set internal flag on receipt instead of default action.",
1260 );
1261 docs.insert(
1262 "signal.received?",
1263 "Check if signal was received and clear the flag. Returns Bool.",
1264 );
1265 docs.insert(
1266 "signal.pending?",
1267 "Check if signal is pending without clearing the flag. Returns Bool.",
1268 );
1269 docs.insert(
1270 "signal.default",
1271 "Restore the default handler for a signal.",
1272 );
1273 docs.insert(
1274 "signal.ignore",
1275 "Ignore a signal entirely (useful for SIGPIPE in servers).",
1276 );
1277 docs.insert(
1278 "signal.clear",
1279 "Clear the pending flag for a signal without checking it.",
1280 );
1281 docs.insert("signal.SIGINT", "SIGINT constant (Ctrl+C interrupt).");
1282 docs.insert("signal.SIGTERM", "SIGTERM constant (termination request).");
1283 docs.insert("signal.SIGHUP", "SIGHUP constant (hangup detected).");
1284 docs.insert("signal.SIGPIPE", "SIGPIPE constant (broken pipe).");
1285 docs.insert(
1286 "signal.SIGUSR1",
1287 "SIGUSR1 constant (user-defined signal 1).",
1288 );
1289 docs.insert(
1290 "signal.SIGUSR2",
1291 "SIGUSR2 constant (user-defined signal 2).",
1292 );
1293 docs.insert("signal.SIGCHLD", "SIGCHLD constant (child status changed).");
1294 docs.insert("signal.SIGALRM", "SIGALRM constant (alarm clock).");
1295 docs.insert("signal.SIGCONT", "SIGCONT constant (continue if stopped).");
1296
1297 docs.insert(
1299 "terminal.raw-mode",
1300 "Enable/disable raw terminal mode. In raw mode: no line buffering, no echo, Ctrl+C read as byte 3.",
1301 );
1302 docs.insert(
1303 "terminal.read-char",
1304 "Read a single byte from stdin (blocking). Returns 0-255 on success, -1 on EOF/error.",
1305 );
1306 docs.insert(
1307 "terminal.read-char?",
1308 "Read a single byte from stdin (non-blocking). Returns 0-255 if available, -1 otherwise.",
1309 );
1310 docs.insert(
1311 "terminal.width",
1312 "Get terminal width in columns. Returns 80 if unknown.",
1313 );
1314 docs.insert(
1315 "terminal.height",
1316 "Get terminal height in rows. Returns 24 if unknown.",
1317 );
1318 docs.insert(
1319 "terminal.flush",
1320 "Flush stdout. Use after writing escape sequences or partial lines.",
1321 );
1322
1323 docs.insert("string.concat", "Concatenate two strings.");
1325 docs.insert("string.length", "Get the character length of a string.");
1326 docs.insert("string.byte-length", "Get the byte length of a string.");
1327 docs.insert(
1328 "string.char-at",
1329 "Get Unicode codepoint at character index.",
1330 );
1331 docs.insert(
1332 "string.substring",
1333 "Extract substring from start index with length.",
1334 );
1335 docs.insert(
1336 "string.find",
1337 "Find substring. Returns index or -1 if not found.",
1338 );
1339 docs.insert("string.split", "Split string by delimiter. Returns a list.");
1340 docs.insert("string.contains", "Check if string contains a substring.");
1341 docs.insert(
1342 "string.starts-with",
1343 "Check if string starts with a prefix.",
1344 );
1345 docs.insert("string.empty?", "Check if string is empty.");
1346 docs.insert("string.equal?", "Check if two strings are equal.");
1347 docs.insert(
1348 "string.join",
1349 "Join a list of values with a separator string. ( list sep -- string )",
1350 );
1351 docs.insert("string.trim", "Remove leading and trailing whitespace.");
1352 docs.insert("string.chomp", "Remove trailing newline.");
1353 docs.insert("string.to-upper", "Convert to uppercase.");
1354 docs.insert("string.to-lower", "Convert to lowercase.");
1355 docs.insert("string.json-escape", "Escape special characters for JSON.");
1356 docs.insert("symbol.=", "Check if two symbols are equal.");
1357
1358 docs.insert(
1360 "encoding.base64-encode",
1361 "Encode a string to Base64 (standard alphabet with padding).",
1362 );
1363 docs.insert(
1364 "encoding.base64-decode",
1365 "Decode a Base64 string. Returns (decoded, success).",
1366 );
1367 docs.insert(
1368 "encoding.base64url-encode",
1369 "Encode to URL-safe Base64 (no padding). Suitable for JWTs and URLs.",
1370 );
1371 docs.insert(
1372 "encoding.base64url-decode",
1373 "Decode URL-safe Base64. Returns (decoded, success).",
1374 );
1375 docs.insert(
1376 "encoding.hex-encode",
1377 "Encode a string to lowercase hexadecimal.",
1378 );
1379 docs.insert(
1380 "encoding.hex-decode",
1381 "Decode a hexadecimal string. Returns (decoded, success).",
1382 );
1383
1384 docs.insert(
1386 "crypto.sha256",
1387 "Compute SHA-256 hash of a string. Returns 64-char hex digest.",
1388 );
1389 docs.insert(
1390 "crypto.hmac-sha256",
1391 "Compute HMAC-SHA256 signature. ( message key -- signature )",
1392 );
1393 docs.insert(
1394 "crypto.constant-time-eq",
1395 "Timing-safe string comparison. Use for comparing signatures/tokens.",
1396 );
1397 docs.insert(
1398 "crypto.random-bytes",
1399 "Generate N cryptographically secure random bytes as hex string.",
1400 );
1401 docs.insert(
1402 "crypto.random-int",
1403 "Generate uniform random integer in [min, max). ( min max -- Int ) Uses rejection sampling to avoid modulo bias.",
1404 );
1405 docs.insert("crypto.uuid4", "Generate a random UUID v4 string.");
1406 docs.insert(
1407 "crypto.aes-gcm-encrypt",
1408 "Encrypt with AES-256-GCM. ( plaintext hex-key -- ciphertext success )",
1409 );
1410 docs.insert(
1411 "crypto.aes-gcm-decrypt",
1412 "Decrypt AES-256-GCM ciphertext. ( ciphertext hex-key -- plaintext success )",
1413 );
1414 docs.insert(
1415 "crypto.pbkdf2-sha256",
1416 "Derive key from password. ( password salt iterations -- hex-key success ) Min 1000 iterations, 100000+ recommended.",
1417 );
1418 docs.insert(
1419 "crypto.ed25519-keypair",
1420 "Generate Ed25519 keypair. ( -- public-key private-key ) Both as 64-char hex strings.",
1421 );
1422 docs.insert(
1423 "crypto.ed25519-sign",
1424 "Sign message with Ed25519 private key. ( message private-key -- signature success ) Signature is 128-char hex.",
1425 );
1426 docs.insert(
1427 "crypto.ed25519-verify",
1428 "Verify Ed25519 signature. ( message signature public-key -- valid )",
1429 );
1430
1431 docs.insert(
1433 "http.get",
1434 "HTTP GET request. ( url -- response-map ) Map has status, body, ok, error.",
1435 );
1436 docs.insert(
1437 "http.post",
1438 "HTTP POST request. ( url body content-type -- response-map )",
1439 );
1440 docs.insert(
1441 "http.put",
1442 "HTTP PUT request. ( url body content-type -- response-map )",
1443 );
1444 docs.insert(
1445 "http.delete",
1446 "HTTP DELETE request. ( url -- response-map )",
1447 );
1448
1449 docs.insert(
1451 "regex.match?",
1452 "Check if pattern matches anywhere in string. ( text pattern -- bool )",
1453 );
1454 docs.insert(
1455 "regex.find",
1456 "Find first match. ( text pattern -- matched success )",
1457 );
1458 docs.insert(
1459 "regex.find-all",
1460 "Find all matches. ( text pattern -- list success )",
1461 );
1462 docs.insert(
1463 "regex.replace",
1464 "Replace first match. ( text pattern replacement -- result success )",
1465 );
1466 docs.insert(
1467 "regex.replace-all",
1468 "Replace all matches. ( text pattern replacement -- result success )",
1469 );
1470 docs.insert(
1471 "regex.captures",
1472 "Extract capture groups. ( text pattern -- groups success )",
1473 );
1474 docs.insert(
1475 "regex.split",
1476 "Split string by pattern. ( text pattern -- list success )",
1477 );
1478 docs.insert(
1479 "regex.valid?",
1480 "Check if pattern is valid regex. ( pattern -- bool )",
1481 );
1482
1483 docs.insert(
1485 "compress.gzip",
1486 "Compress string with gzip. Returns base64-encoded data. ( data -- compressed success )",
1487 );
1488 docs.insert(
1489 "compress.gzip-level",
1490 "Compress with gzip at level 1-9. ( data level -- compressed success )",
1491 );
1492 docs.insert(
1493 "compress.gunzip",
1494 "Decompress gzip data. ( base64-data -- decompressed success )",
1495 );
1496 docs.insert(
1497 "compress.zstd",
1498 "Compress string with zstd. Returns base64-encoded data. ( data -- compressed success )",
1499 );
1500 docs.insert(
1501 "compress.zstd-level",
1502 "Compress with zstd at level 1-22. ( data level -- compressed success )",
1503 );
1504 docs.insert(
1505 "compress.unzstd",
1506 "Decompress zstd data. ( base64-data -- decompressed success )",
1507 );
1508
1509 docs.insert(
1511 "variant.field-count",
1512 "Get the number of fields in a variant.",
1513 );
1514 docs.insert(
1515 "variant.tag",
1516 "Get the tag (constructor name) of a variant.",
1517 );
1518 docs.insert("variant.field-at", "Get the field at index N.");
1519 docs.insert(
1520 "variant.append",
1521 "Append a value to a variant (creates new).",
1522 );
1523 docs.insert("variant.last", "Get the last field of a variant.");
1524 docs.insert("variant.init", "Get all fields except the last.");
1525 docs.insert("variant.make-0", "Create a variant with 0 fields.");
1526 docs.insert("variant.make-1", "Create a variant with 1 field.");
1527 docs.insert("variant.make-2", "Create a variant with 2 fields.");
1528 docs.insert("variant.make-3", "Create a variant with 3 fields.");
1529 docs.insert("variant.make-4", "Create a variant with 4 fields.");
1530 docs.insert("variant.make-5", "Create a variant with 5 fields.");
1531 docs.insert("variant.make-6", "Create a variant with 6 fields.");
1532 docs.insert("variant.make-7", "Create a variant with 7 fields.");
1533 docs.insert("variant.make-8", "Create a variant with 8 fields.");
1534 docs.insert("variant.make-9", "Create a variant with 9 fields.");
1535 docs.insert("variant.make-10", "Create a variant with 10 fields.");
1536 docs.insert("variant.make-11", "Create a variant with 11 fields.");
1537 docs.insert("variant.make-12", "Create a variant with 12 fields.");
1538 docs.insert("wrap-0", "Create a variant with 0 fields (alias).");
1539 docs.insert("wrap-1", "Create a variant with 1 field (alias).");
1540 docs.insert("wrap-2", "Create a variant with 2 fields (alias).");
1541 docs.insert("wrap-3", "Create a variant with 3 fields (alias).");
1542 docs.insert("wrap-4", "Create a variant with 4 fields (alias).");
1543 docs.insert("wrap-5", "Create a variant with 5 fields (alias).");
1544 docs.insert("wrap-6", "Create a variant with 6 fields (alias).");
1545 docs.insert("wrap-7", "Create a variant with 7 fields (alias).");
1546 docs.insert("wrap-8", "Create a variant with 8 fields (alias).");
1547 docs.insert("wrap-9", "Create a variant with 9 fields (alias).");
1548 docs.insert("wrap-10", "Create a variant with 10 fields (alias).");
1549 docs.insert("wrap-11", "Create a variant with 11 fields (alias).");
1550 docs.insert("wrap-12", "Create a variant with 12 fields (alias).");
1551
1552 docs.insert("list.make", "Create an empty list.");
1554 docs.insert("list.push", "Push a value onto a list. Returns new list.");
1555 docs.insert(
1556 "list.get",
1557 "Get value at index. Returns (value Bool) -- Bool is false if index out of bounds.",
1558 );
1559 docs.insert(
1560 "list.set",
1561 "Set value at index. Returns (List Bool) -- Bool is false if index out of bounds.",
1562 );
1563 docs.insert("list.length", "Get the number of elements in a list.");
1564 docs.insert("list.empty?", "Check if a list is empty.");
1565 docs.insert("list.reverse", "Reverse the elements of a list.");
1566 docs.insert(
1567 "list.map",
1568 "Apply quotation to each element. Returns new list.",
1569 );
1570 docs.insert("list.filter", "Keep elements where quotation returns true.");
1571 docs.insert("list.fold", "Reduce list with accumulator and quotation.");
1572 docs.insert(
1573 "list.each",
1574 "Execute quotation for each element (side effects).",
1575 );
1576
1577 docs.insert("map.make", "Create an empty map.");
1579 docs.insert(
1580 "map.get",
1581 "Get value for key. Returns (value Bool) -- Bool is false if key not found.",
1582 );
1583 docs.insert("map.set", "Set key to value. Returns new map.");
1584 docs.insert("map.has?", "Check if map contains a key.");
1585 docs.insert("map.remove", "Remove a key. Returns new map.");
1586 docs.insert("map.keys", "Get all keys as a list.");
1587 docs.insert("map.values", "Get all values as a list.");
1588 docs.insert("map.size", "Get the number of key-value pairs.");
1589 docs.insert("map.empty?", "Check if map is empty.");
1590 docs.insert(
1591 "map.each",
1592 "Iterate key-value pairs. Quotation: ( key value -- ).",
1593 );
1594 docs.insert(
1595 "map.fold",
1596 "Fold over key-value pairs with accumulator. Quotation: ( acc key value -- acc' ).",
1597 );
1598
1599 docs.insert(
1601 "tcp.listen",
1602 "Start listening on a port. Returns (fd Bool) -- Bool is false on failure.",
1603 );
1604 docs.insert(
1605 "tcp.accept",
1606 "Accept a connection. Returns (fd Bool) -- Bool is false on failure.",
1607 );
1608 docs.insert(
1609 "tcp.read",
1610 "Read from a connection. Returns (String Bool) -- Bool is false on failure.",
1611 );
1612 docs.insert(
1613 "tcp.write",
1614 "Write to a connection. Returns Bool -- false on failure.",
1615 );
1616 docs.insert(
1617 "tcp.close",
1618 "Close a connection. Returns Bool -- false on failure.",
1619 );
1620
1621 docs.insert(
1623 "os.getenv",
1624 "Get environment variable. Returns (String Bool) -- Bool is false if not set.",
1625 );
1626 docs.insert(
1627 "os.home-dir",
1628 "Get home directory. Returns (String Bool) -- Bool is false if unavailable.",
1629 );
1630 docs.insert(
1631 "os.current-dir",
1632 "Get current directory. Returns (String Bool) -- Bool is false if unavailable.",
1633 );
1634 docs.insert("os.path-exists", "Check if a path exists.");
1635 docs.insert("os.path-is-file", "Check if a path is a file.");
1636 docs.insert("os.path-is-dir", "Check if a path is a directory.");
1637 docs.insert("os.path-join", "Join two path segments.");
1638 docs.insert(
1639 "os.path-parent",
1640 "Get parent directory. Returns (String Bool) -- Bool is false for root.",
1641 );
1642 docs.insert(
1643 "os.path-filename",
1644 "Get filename component. Returns (String Bool) -- Bool is false if none.",
1645 );
1646 docs.insert("os.exit", "Exit the process with given exit code.");
1647 docs.insert("os.name", "Get OS name (e.g., \"macos\", \"linux\").");
1648 docs.insert(
1649 "os.arch",
1650 "Get CPU architecture (e.g., \"aarch64\", \"x86_64\").",
1651 );
1652
1653 docs.insert("regex.match?", "Test if string matches regex pattern.");
1655 docs.insert(
1656 "regex.find",
1657 "Find first match. Returns (String Bool) -- Bool is false if no match or invalid regex.",
1658 );
1659 docs.insert(
1660 "regex.find-all",
1661 "Find all matches. Returns (List Bool) -- Bool is false if invalid regex.",
1662 );
1663 docs.insert(
1664 "regex.replace",
1665 "Replace first match. Returns (String Bool) -- Bool is false if invalid regex.",
1666 );
1667 docs.insert(
1668 "regex.replace-all",
1669 "Replace all matches. Returns (String Bool) -- Bool is false if invalid regex.",
1670 );
1671 docs.insert(
1672 "regex.captures",
1673 "Get capture groups. Returns (List Bool) -- Bool is false if invalid regex.",
1674 );
1675 docs.insert(
1676 "regex.split",
1677 "Split by regex. Returns (List Bool) -- Bool is false if invalid regex.",
1678 );
1679 docs.insert("regex.valid?", "Check if a regex pattern is valid.");
1680
1681 docs.insert("encoding.base64-encode", "Encode string as base64.");
1683 docs.insert(
1684 "encoding.base64-decode",
1685 "Decode base64 string. Returns (String Bool) -- Bool is false if invalid.",
1686 );
1687 docs.insert("encoding.base64url-encode", "Encode string as base64url.");
1688 docs.insert(
1689 "encoding.base64url-decode",
1690 "Decode base64url string. Returns (String Bool) -- Bool is false if invalid.",
1691 );
1692 docs.insert("encoding.hex-encode", "Encode string as hexadecimal.");
1693 docs.insert(
1694 "encoding.hex-decode",
1695 "Decode hex string. Returns (String Bool) -- Bool is false if invalid.",
1696 );
1697
1698 docs.insert("crypto.sha256", "Compute SHA-256 hash of a string.");
1700 docs.insert(
1701 "crypto.hmac-sha256",
1702 "Compute HMAC-SHA256. ( message key -- hash )",
1703 );
1704 docs.insert(
1705 "crypto.constant-time-eq",
1706 "Constant-time string equality comparison.",
1707 );
1708 docs.insert(
1709 "crypto.random-bytes",
1710 "Generate N random bytes as a string.",
1711 );
1712 docs.insert(
1713 "crypto.random-int",
1714 "Generate random integer in range [min, max).",
1715 );
1716 docs.insert("crypto.uuid4", "Generate a random UUID v4 string.");
1717 docs.insert(
1718 "crypto.aes-gcm-encrypt",
1719 "AES-GCM encrypt. Returns (String Bool) -- Bool is false on failure.",
1720 );
1721 docs.insert("crypto.aes-gcm-decrypt", "AES-GCM decrypt. Returns (String Bool) -- Bool is false on failure (wrong key or tampered data).");
1722 docs.insert(
1723 "crypto.pbkdf2-sha256",
1724 "Derive key with PBKDF2. Returns (String Bool) -- Bool is false on failure.",
1725 );
1726 docs.insert(
1727 "crypto.ed25519-keypair",
1728 "Generate Ed25519 keypair. Returns (public private).",
1729 );
1730 docs.insert(
1731 "crypto.ed25519-sign",
1732 "Sign with Ed25519. Returns (String Bool) -- Bool is false on failure.",
1733 );
1734 docs.insert(
1735 "crypto.ed25519-verify",
1736 "Verify Ed25519 signature. Returns Bool -- true if valid.",
1737 );
1738
1739 docs.insert(
1741 "compress.gzip",
1742 "Gzip compress. Returns (String Bool) -- Bool is false on failure.",
1743 );
1744 docs.insert(
1745 "compress.gzip-level",
1746 "Gzip compress at level N. Returns (String Bool) -- Bool is false on failure.",
1747 );
1748 docs.insert(
1749 "compress.gunzip",
1750 "Gzip decompress. Returns (String Bool) -- Bool is false on failure.",
1751 );
1752 docs.insert(
1753 "compress.zstd",
1754 "Zstd compress. Returns (String Bool) -- Bool is false on failure.",
1755 );
1756 docs.insert(
1757 "compress.zstd-level",
1758 "Zstd compress at level N. Returns (String Bool) -- Bool is false on failure.",
1759 );
1760 docs.insert(
1761 "compress.unzstd",
1762 "Zstd decompress. Returns (String Bool) -- Bool is false on failure.",
1763 );
1764
1765 docs.insert(
1767 "signal.trap",
1768 "Register a signal handler for the given signal number.",
1769 );
1770 docs.insert("signal.received?", "Check if a signal has been received.");
1771 docs.insert("signal.pending?", "Check if a signal is pending.");
1772 docs.insert("signal.default", "Reset signal to default handler.");
1773 docs.insert("signal.ignore", "Ignore a signal.");
1774 docs.insert("signal.clear", "Clear pending signal.");
1775
1776 docs.insert("terminal.raw-mode", "Enable/disable raw terminal mode.");
1778 docs.insert("terminal.read-char", "Read a single character (blocking).");
1779 docs.insert(
1780 "terminal.read-char?",
1781 "Read a character if available (non-blocking). Returns 0 if none.",
1782 );
1783 docs.insert("terminal.width", "Get terminal width in columns.");
1784 docs.insert("terminal.height", "Get terminal height in rows.");
1785 docs.insert("terminal.flush", "Flush stdout.");
1786
1787 docs.insert("f.add", "Add two floats.");
1789 docs.insert("f.subtract", "Subtract second float from first.");
1790 docs.insert("f.multiply", "Multiply two floats.");
1791 docs.insert("f.divide", "Divide first float by second.");
1792 docs.insert("f.+", "Add two floats.");
1793 docs.insert("f.-", "Subtract second float from first.");
1794 docs.insert("f.*", "Multiply two floats.");
1795 docs.insert("f./", "Divide first float by second.");
1796
1797 docs.insert("f.=", "Test if two floats are equal.");
1799 docs.insert("f.<", "Test if first float is less than second.");
1800 docs.insert("f.>", "Test if first float is greater than second.");
1801 docs.insert("f.<=", "Test if first float is less than or equal.");
1802 docs.insert("f.>=", "Test if first float is greater than or equal.");
1803 docs.insert("f.<>", "Test if two floats are not equal.");
1804 docs.insert("f.eq", "Test if two floats are equal.");
1805 docs.insert("f.lt", "Test if first float is less than second.");
1806 docs.insert("f.gt", "Test if first float is greater than second.");
1807 docs.insert("f.lte", "Test if first float is less than or equal.");
1808 docs.insert("f.gte", "Test if first float is greater than or equal.");
1809 docs.insert("f.neq", "Test if two floats are not equal.");
1810
1811 docs.insert(
1813 "test.init",
1814 "Initialize the test framework with a test name.",
1815 );
1816 docs.insert("test.finish", "Finish testing and print results.");
1817 docs.insert("test.has-failures", "Check if any tests have failed.");
1818 docs.insert("test.assert", "Assert that a boolean is true.");
1819 docs.insert("test.assert-not", "Assert that a boolean is false.");
1820 docs.insert("test.assert-eq", "Assert that two integers are equal.");
1821 docs.insert("test.assert-eq-str", "Assert that two strings are equal.");
1822 docs.insert("test.fail", "Mark a test as failed with a message.");
1823 docs.insert("test.pass-count", "Get the number of passed assertions.");
1824 docs.insert("test.fail-count", "Get the number of failed assertions.");
1825
1826 docs.insert("time.now", "Get current Unix timestamp in seconds.");
1828 docs.insert(
1829 "time.nanos",
1830 "Get high-resolution monotonic time in nanoseconds.",
1831 );
1832 docs.insert("time.sleep-ms", "Sleep for N milliseconds.");
1833
1834 docs.insert("son.dump", "Serialize any value to SON format (compact).");
1836 docs.insert(
1837 "son.dump-pretty",
1838 "Serialize any value to SON format (pretty-printed).",
1839 );
1840
1841 docs.insert(
1843 "stack.dump",
1844 "Print all stack values and clear the stack (REPL).",
1845 );
1846
1847 docs
1848});
1849
1850#[cfg(test)]
1851mod tests {
1852 use super::*;
1853
1854 #[test]
1855 fn test_builtin_signature_write_line() {
1856 let sig = builtin_signature("io.write-line").unwrap();
1857 let (rest, top) = sig.inputs.clone().pop().unwrap();
1859 assert_eq!(top, Type::String);
1860 assert_eq!(rest, StackType::RowVar("a".to_string()));
1861 assert_eq!(sig.outputs, StackType::RowVar("a".to_string()));
1862 }
1863
1864 #[test]
1865 fn test_builtin_signature_i_add() {
1866 let sig = builtin_signature("i.add").unwrap();
1867 let (rest, top) = sig.inputs.clone().pop().unwrap();
1869 assert_eq!(top, Type::Int);
1870 let (rest2, top2) = rest.pop().unwrap();
1871 assert_eq!(top2, Type::Int);
1872 assert_eq!(rest2, StackType::RowVar("a".to_string()));
1873
1874 let (rest3, top3) = sig.outputs.clone().pop().unwrap();
1875 assert_eq!(top3, Type::Int);
1876 assert_eq!(rest3, StackType::RowVar("a".to_string()));
1877 }
1878
1879 #[test]
1880 fn test_builtin_signature_dup() {
1881 let sig = builtin_signature("dup").unwrap();
1882 assert_eq!(
1884 sig.inputs,
1885 StackType::Cons {
1886 rest: Box::new(StackType::RowVar("a".to_string())),
1887 top: Type::Var("T".to_string())
1888 }
1889 );
1890 let (rest, top) = sig.outputs.clone().pop().unwrap();
1892 assert_eq!(top, Type::Var("T".to_string()));
1893 let (rest2, top2) = rest.pop().unwrap();
1894 assert_eq!(top2, Type::Var("T".to_string()));
1895 assert_eq!(rest2, StackType::RowVar("a".to_string()));
1896 }
1897
1898 #[test]
1899 fn test_all_builtins_have_signatures() {
1900 let sigs = builtin_signatures();
1901
1902 assert!(sigs.contains_key("io.write-line"));
1904 assert!(sigs.contains_key("io.read-line"));
1905 assert!(sigs.contains_key("int->string"));
1906 assert!(sigs.contains_key("i.add"));
1907 assert!(sigs.contains_key("dup"));
1908 assert!(sigs.contains_key("swap"));
1909 assert!(sigs.contains_key("chan.make"));
1910 assert!(sigs.contains_key("chan.send"));
1911 assert!(sigs.contains_key("chan.receive"));
1912 assert!(
1913 sigs.contains_key("string->float"),
1914 "string->float should be a builtin"
1915 );
1916 assert!(
1917 sigs.contains_key("signal.trap"),
1918 "signal.trap should be a builtin"
1919 );
1920 }
1921
1922 #[test]
1923 fn test_all_docs_have_signatures() {
1924 let sigs = builtin_signatures();
1925 let docs = builtin_docs();
1926
1927 for name in docs.keys() {
1928 assert!(
1929 sigs.contains_key(*name),
1930 "Builtin '{}' has documentation but no signature",
1931 name
1932 );
1933 }
1934 }
1935
1936 #[test]
1937 fn test_all_signatures_have_docs() {
1938 let sigs = builtin_signatures();
1939 let docs = builtin_docs();
1940
1941 for name in sigs.keys() {
1942 assert!(
1943 docs.contains_key(name.as_str()),
1944 "Builtin '{}' has signature but no documentation",
1945 name
1946 );
1947 }
1948 }
1949}