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