seqc/
builtins.rs

1//! Built-in word signatures for Seq
2//!
3//! Defines the stack effects for all runtime built-in operations.
4
5use crate::types::{Effect, StackType, Type};
6use std::collections::HashMap;
7
8/// Get the stack effect signature for a built-in word
9pub fn builtin_signature(name: &str) -> Option<Effect> {
10    // Build the map lazily
11    let signatures = builtin_signatures();
12    signatures.get(name).cloned()
13}
14
15/// Get all built-in word signatures
16pub fn builtin_signatures() -> HashMap<String, Effect> {
17    let mut sigs = HashMap::new();
18
19    // I/O operations with row polymorphism
20    sigs.insert(
21        "write_line".to_string(),
22        Effect::new(
23            StackType::RowVar("a".to_string()).push(Type::String), // ( ..a String -- ..a )
24            StackType::RowVar("a".to_string()),
25        ),
26    );
27
28    sigs.insert(
29        "read_line".to_string(),
30        Effect::new(
31            StackType::RowVar("a".to_string()), // ( ..a -- ..a String )
32            StackType::RowVar("a".to_string()).push(Type::String),
33        ),
34    );
35
36    // Command-line argument operations
37    // arg-count: ( ..a -- ..a Int ) returns number of arguments including program name
38    sigs.insert(
39        "arg-count".to_string(),
40        Effect::new(
41            StackType::RowVar("a".to_string()),
42            StackType::RowVar("a".to_string()).push(Type::Int),
43        ),
44    );
45
46    // arg: ( ..a Int -- ..a String ) returns argument at index (0 = program name)
47    sigs.insert(
48        "arg".to_string(),
49        Effect::new(
50            StackType::RowVar("a".to_string()).push(Type::Int),
51            StackType::RowVar("a".to_string()).push(Type::String),
52        ),
53    );
54
55    // File operations
56    // file-slurp: ( ..a String -- ..a String ) reads entire file contents
57    sigs.insert(
58        "file-slurp".to_string(),
59        Effect::new(
60            StackType::RowVar("a".to_string()).push(Type::String),
61            StackType::RowVar("a".to_string()).push(Type::String),
62        ),
63    );
64
65    // file-exists?: ( ..a String -- ..a Int ) returns 1 if file exists, 0 otherwise
66    sigs.insert(
67        "file-exists?".to_string(),
68        Effect::new(
69            StackType::RowVar("a".to_string()).push(Type::String),
70            StackType::RowVar("a".to_string()).push(Type::Int),
71        ),
72    );
73
74    sigs.insert(
75        "int->string".to_string(),
76        Effect::new(
77            StackType::RowVar("a".to_string()).push(Type::Int), // ( ..a Int -- ..a String )
78            StackType::RowVar("a".to_string()).push(Type::String),
79        ),
80    );
81
82    // Arithmetic operations ( ..a Int Int -- ..a Int )
83    for op in &["add", "subtract", "multiply", "divide"] {
84        sigs.insert(
85            op.to_string(),
86            Effect::new(
87                StackType::RowVar("a".to_string())
88                    .push(Type::Int)
89                    .push(Type::Int),
90                StackType::RowVar("a".to_string()).push(Type::Int),
91            ),
92        );
93    }
94
95    // Comparison operations ( ..a Int Int -- ..a Int )
96    // Note: Comparisons return Int (0 or 1), not Bool, for Forth compatibility
97    for op in &["=", "<", ">", "<=", ">=", "<>"] {
98        sigs.insert(
99            op.to_string(),
100            Effect::new(
101                StackType::RowVar("a".to_string())
102                    .push(Type::Int)
103                    .push(Type::Int),
104                StackType::RowVar("a".to_string()).push(Type::Int),
105            ),
106        );
107    }
108
109    // Boolean operations ( ..a Int Int -- ..a Int )
110    // Forth-style: 0 is false, non-zero is true
111    // and: ( a b -- result ) returns 1 if both non-zero, 0 otherwise
112    sigs.insert(
113        "and".to_string(),
114        Effect::new(
115            StackType::RowVar("a".to_string())
116                .push(Type::Int)
117                .push(Type::Int),
118            StackType::RowVar("a".to_string()).push(Type::Int),
119        ),
120    );
121
122    // or: ( a b -- result ) returns 1 if either non-zero, 0 otherwise
123    sigs.insert(
124        "or".to_string(),
125        Effect::new(
126            StackType::RowVar("a".to_string())
127                .push(Type::Int)
128                .push(Type::Int),
129            StackType::RowVar("a".to_string()).push(Type::Int),
130        ),
131    );
132
133    // not: ( a -- result ) returns 1 if zero, 0 if non-zero
134    sigs.insert(
135        "not".to_string(),
136        Effect::new(
137            StackType::RowVar("a".to_string()).push(Type::Int),
138            StackType::RowVar("a".to_string()).push(Type::Int),
139        ),
140    );
141
142    // Stack operations with row polymorphism
143    // dup: ( ..a T -- ..a T T )
144    sigs.insert(
145        "dup".to_string(),
146        Effect::new(
147            StackType::RowVar("a".to_string()).push(Type::Var("T".to_string())),
148            StackType::RowVar("a".to_string())
149                .push(Type::Var("T".to_string()))
150                .push(Type::Var("T".to_string())),
151        ),
152    );
153
154    // drop: ( ..a T -- ..a )
155    sigs.insert(
156        "drop".to_string(),
157        Effect::new(
158            StackType::RowVar("a".to_string()).push(Type::Var("T".to_string())),
159            StackType::RowVar("a".to_string()),
160        ),
161    );
162
163    // swap: ( ..a T U -- ..a U T )
164    sigs.insert(
165        "swap".to_string(),
166        Effect::new(
167            StackType::RowVar("a".to_string())
168                .push(Type::Var("T".to_string()))
169                .push(Type::Var("U".to_string())),
170            StackType::RowVar("a".to_string())
171                .push(Type::Var("U".to_string()))
172                .push(Type::Var("T".to_string())),
173        ),
174    );
175
176    // over: ( ..a T U -- ..a T U T )
177    sigs.insert(
178        "over".to_string(),
179        Effect::new(
180            StackType::RowVar("a".to_string())
181                .push(Type::Var("T".to_string()))
182                .push(Type::Var("U".to_string())),
183            StackType::RowVar("a".to_string())
184                .push(Type::Var("T".to_string()))
185                .push(Type::Var("U".to_string()))
186                .push(Type::Var("T".to_string())),
187        ),
188    );
189
190    // rot: ( ..a T U V -- ..a U V T )
191    sigs.insert(
192        "rot".to_string(),
193        Effect::new(
194            StackType::RowVar("a".to_string())
195                .push(Type::Var("T".to_string()))
196                .push(Type::Var("U".to_string()))
197                .push(Type::Var("V".to_string())),
198            StackType::RowVar("a".to_string())
199                .push(Type::Var("U".to_string()))
200                .push(Type::Var("V".to_string()))
201                .push(Type::Var("T".to_string())),
202        ),
203    );
204
205    // nip: ( ..a T U -- ..a U )
206    sigs.insert(
207        "nip".to_string(),
208        Effect::new(
209            StackType::RowVar("a".to_string())
210                .push(Type::Var("T".to_string()))
211                .push(Type::Var("U".to_string())),
212            StackType::RowVar("a".to_string()).push(Type::Var("U".to_string())),
213        ),
214    );
215
216    // tuck: ( ..a T U -- ..a U T U )
217    sigs.insert(
218        "tuck".to_string(),
219        Effect::new(
220            StackType::RowVar("a".to_string())
221                .push(Type::Var("T".to_string()))
222                .push(Type::Var("U".to_string())),
223            StackType::RowVar("a".to_string())
224                .push(Type::Var("U".to_string()))
225                .push(Type::Var("T".to_string()))
226                .push(Type::Var("U".to_string())),
227        ),
228    );
229
230    // pick: ( ..a T Int -- ..a T T )
231    // Copies value at depth n to top of stack
232    // pick(0) = dup, pick(1) = over, pick(2) = third value, etc.
233    //
234    // TODO: This type signature is a known limitation of the current type system.
235    // The signature claims pick copies the top value T, but pick actually copies
236    // the value at depth n, which could be any type within the row variable ..a.
237    // For example:
238    //   - pick(0) on ( Int String -- ) copies the String (type T)
239    //   - pick(1) on ( Int String -- ) copies the Int (type U, not T)
240    //
241    // A proper signature would require dependent types or indexed row variables:
242    //   pick: ∀a, n. ( ..a[T_0, ..., T_n, ...] Int(n) -- ..a[T_0, ..., T_n, ...] T_n )
243    //
244    // The runtime validates stack depth at runtime (see pick_op in runtime/src/stack.rs),
245    // but the type system cannot statically verify the copied value's type matches
246    // how it's used. This is acceptable for now as pick is primarily used for
247    // building known-safe utilities (third, fourth, 3dup, etc.).
248    sigs.insert(
249        "pick".to_string(),
250        Effect::new(
251            StackType::RowVar("a".to_string())
252                .push(Type::Var("T".to_string()))
253                .push(Type::Int),
254            StackType::RowVar("a".to_string())
255                .push(Type::Var("T".to_string()))
256                .push(Type::Var("T".to_string())),
257        ),
258    );
259
260    // roll: ( ..a T_n ... T_1 T_0 Int(n) -- ..a T_(n-1) ... T_1 T_0 T_n )
261    // Rotates n+1 items, bringing the item at depth n to the top.
262    // roll(0) = no-op, roll(1) = swap, roll(2) = rot
263    //
264    // Like pick, the true type requires dependent types. The signature below
265    // is a simplified approximation that validates the depth parameter is Int
266    // and that the stack has at least one item to rotate.
267    //
268    // Runtime validates stack depth (see roll in runtime/src/stack.rs).
269    sigs.insert(
270        "roll".to_string(),
271        Effect::new(
272            StackType::RowVar("a".to_string())
273                .push(Type::Var("T".to_string()))
274                .push(Type::Int),
275            StackType::RowVar("a".to_string()).push(Type::Var("T".to_string())),
276        ),
277    );
278
279    // Concurrency operations with row polymorphism
280    // make-channel: ( ..a -- ..a Int )
281    // Returns channel ID as Int
282    sigs.insert(
283        "make-channel".to_string(),
284        Effect::new(
285            StackType::RowVar("a".to_string()),
286            StackType::RowVar("a".to_string()).push(Type::Int),
287        ),
288    );
289
290    // send: ( ..a T Int -- ..a )
291    // Takes value T and channel Int
292    sigs.insert(
293        "send".to_string(),
294        Effect::new(
295            StackType::RowVar("a".to_string())
296                .push(Type::Var("T".to_string()))
297                .push(Type::Int),
298            StackType::RowVar("a".to_string()),
299        ),
300    );
301
302    // receive: ( ..a Int -- ..a T )
303    // Takes channel Int, returns value T
304    sigs.insert(
305        "receive".to_string(),
306        Effect::new(
307            StackType::RowVar("a".to_string()).push(Type::Int),
308            StackType::RowVar("a".to_string()).push(Type::Var("T".to_string())),
309        ),
310    );
311
312    // close-channel: ( ..a Int -- ..a )
313    sigs.insert(
314        "close-channel".to_string(),
315        Effect::new(
316            StackType::RowVar("a".to_string()).push(Type::Int),
317            StackType::RowVar("a".to_string()),
318        ),
319    );
320
321    // yield: ( ..a -- ..a )
322    sigs.insert(
323        "yield".to_string(),
324        Effect::new(
325            StackType::RowVar("a".to_string()),
326            StackType::RowVar("a".to_string()),
327        ),
328    );
329
330    // Quotation operations
331    // call: ( ..a Callable -- ..b )
332    // Note: Accepts both Quotation and Closure types
333    // The actual effect depends on the quotation/closure's type,
334    // but we use ..a and ..b to indicate this is polymorphic
335    // A more precise type would require dependent types
336    //
337    // Using type variable Q to represent "something callable"
338    // This allows both Quotation and Closure to unify with Q
339    sigs.insert(
340        "call".to_string(),
341        Effect::new(
342            StackType::RowVar("a".to_string()).push(Type::Var("Q".to_string())),
343            StackType::RowVar("b".to_string()),
344        ),
345    );
346
347    // times: ( ..a Quotation Int -- ..a )
348    // Executes quotation n times. Quotation must have effect ( ..a -- ..a )
349    sigs.insert(
350        "times".to_string(),
351        Effect::new(
352            StackType::RowVar("a".to_string())
353                .push(Type::Quotation(Box::new(Effect::new(
354                    StackType::RowVar("a".to_string()),
355                    StackType::RowVar("a".to_string()),
356                ))))
357                .push(Type::Int),
358            StackType::RowVar("a".to_string()),
359        ),
360    );
361
362    // while: ( ..a Quotation Quotation -- ..a )
363    // First quotation is condition ( ..a -- ..a Int ), second is body ( ..a -- ..a )
364    sigs.insert(
365        "while".to_string(),
366        Effect::new(
367            StackType::RowVar("a".to_string())
368                .push(Type::Quotation(Box::new(Effect::new(
369                    StackType::RowVar("a".to_string()),
370                    StackType::RowVar("a".to_string()).push(Type::Int),
371                ))))
372                .push(Type::Quotation(Box::new(Effect::new(
373                    StackType::RowVar("a".to_string()),
374                    StackType::RowVar("a".to_string()),
375                )))),
376            StackType::RowVar("a".to_string()),
377        ),
378    );
379
380    // until: ( ..a Quotation Quotation -- ..a )
381    // First quotation is body ( ..a -- ..a ), second is condition ( ..a -- ..a Int )
382    // Executes body, then checks condition; repeats until condition is true
383    sigs.insert(
384        "until".to_string(),
385        Effect::new(
386            StackType::RowVar("a".to_string())
387                .push(Type::Quotation(Box::new(Effect::new(
388                    StackType::RowVar("a".to_string()),
389                    StackType::RowVar("a".to_string()),
390                ))))
391                .push(Type::Quotation(Box::new(Effect::new(
392                    StackType::RowVar("a".to_string()),
393                    StackType::RowVar("a".to_string()).push(Type::Int),
394                )))),
395            StackType::RowVar("a".to_string()),
396        ),
397    );
398
399    // forever: ( ..a Quotation -- ..a )
400    // Executes quotation infinitely. Quotation must have effect ( ..a -- ..a )
401    // Note: This never returns in practice, but type-wise it has same stack effect
402    sigs.insert(
403        "forever".to_string(),
404        Effect::new(
405            StackType::RowVar("a".to_string()).push(Type::Quotation(Box::new(Effect::new(
406                StackType::RowVar("a".to_string()),
407                StackType::RowVar("a".to_string()),
408            )))),
409            StackType::RowVar("a".to_string()),
410        ),
411    );
412
413    // spawn: ( ..a Quotation -- ..a Int )
414    // Spawns a quotation as a new strand, returns strand ID
415    // The quotation should have effect ( -- ) (empty stack in, empty stack out)
416    sigs.insert(
417        "spawn".to_string(),
418        Effect::new(
419            StackType::RowVar("a".to_string()).push(Type::Quotation(Box::new(Effect::new(
420                StackType::Empty,
421                StackType::Empty,
422            )))),
423            StackType::RowVar("a".to_string()).push(Type::Int),
424        ),
425    );
426
427    // cond: ( ..a -- ..b )
428    // Multi-way conditional combinator
429    // Actual stack effect: ( value [pred1] [body1] [pred2] [body2] ... [predN] [bodyN] count -- result )
430    // Each predicate quotation has effect: ( T -- T Int )
431    // Each body quotation has effect: ( T -- U )
432    // Note: Variable-arity makes precise typing difficult; using row polymorphism as approximation
433    sigs.insert(
434        "cond".to_string(),
435        Effect::new(
436            StackType::RowVar("a".to_string()),
437            StackType::RowVar("b".to_string()),
438        ),
439    );
440
441    // TCP operations with row polymorphism
442    // tcp-listen: ( ..a Int -- ..a Int )
443    // Takes port number, returns listener ID
444    sigs.insert(
445        "tcp-listen".to_string(),
446        Effect::new(
447            StackType::RowVar("a".to_string()).push(Type::Int),
448            StackType::RowVar("a".to_string()).push(Type::Int),
449        ),
450    );
451
452    // tcp-accept: ( ..a Int -- ..a Int )
453    // Takes listener ID, returns client socket ID
454    sigs.insert(
455        "tcp-accept".to_string(),
456        Effect::new(
457            StackType::RowVar("a".to_string()).push(Type::Int),
458            StackType::RowVar("a".to_string()).push(Type::Int),
459        ),
460    );
461
462    // tcp-read: ( ..a Int -- ..a String )
463    // Takes socket ID, returns data read as String
464    sigs.insert(
465        "tcp-read".to_string(),
466        Effect::new(
467            StackType::RowVar("a".to_string()).push(Type::Int),
468            StackType::RowVar("a".to_string()).push(Type::String),
469        ),
470    );
471
472    // tcp-write: ( ..a String Int -- ..a )
473    // Takes data String and socket ID, writes to socket
474    sigs.insert(
475        "tcp-write".to_string(),
476        Effect::new(
477            StackType::RowVar("a".to_string())
478                .push(Type::String)
479                .push(Type::Int),
480            StackType::RowVar("a".to_string()),
481        ),
482    );
483
484    // tcp-close: ( ..a Int -- ..a )
485    // Takes socket ID, closes the socket
486    sigs.insert(
487        "tcp-close".to_string(),
488        Effect::new(
489            StackType::RowVar("a".to_string()).push(Type::Int),
490            StackType::RowVar("a".to_string()),
491        ),
492    );
493
494    // String operations
495    // string-concat: ( ..a String String -- ..a String )
496    // Concatenate two strings
497    sigs.insert(
498        "string-concat".to_string(),
499        Effect::new(
500            StackType::RowVar("a".to_string())
501                .push(Type::String)
502                .push(Type::String),
503            StackType::RowVar("a".to_string()).push(Type::String),
504        ),
505    );
506
507    // string-length: ( ..a String -- ..a Int )
508    // Get string length
509    sigs.insert(
510        "string-length".to_string(),
511        Effect::new(
512            StackType::RowVar("a".to_string()).push(Type::String),
513            StackType::RowVar("a".to_string()).push(Type::Int),
514        ),
515    );
516
517    // string-split: ( ..a String String -- ..a Variant )
518    // Split string by delimiter, returns a Variant containing the parts
519    sigs.insert(
520        "string-split".to_string(),
521        Effect::new(
522            StackType::RowVar("a".to_string())
523                .push(Type::String)
524                .push(Type::String),
525            StackType::RowVar("a".to_string()).push(Type::Var("V".to_string())),
526        ),
527    );
528
529    // string-contains: ( ..a String String -- ..a Int )
530    // Check if string contains substring (returns 0 or 1)
531    sigs.insert(
532        "string-contains".to_string(),
533        Effect::new(
534            StackType::RowVar("a".to_string())
535                .push(Type::String)
536                .push(Type::String),
537            StackType::RowVar("a".to_string()).push(Type::Int),
538        ),
539    );
540
541    // string-starts-with: ( ..a String String -- ..a Int )
542    // Check if string starts with prefix (returns 0 or 1)
543    sigs.insert(
544        "string-starts-with".to_string(),
545        Effect::new(
546            StackType::RowVar("a".to_string())
547                .push(Type::String)
548                .push(Type::String),
549            StackType::RowVar("a".to_string()).push(Type::Int),
550        ),
551    );
552
553    // string-empty: ( ..a String -- ..a Int )
554    // Check if string is empty (returns 0 or 1)
555    sigs.insert(
556        "string-empty".to_string(),
557        Effect::new(
558            StackType::RowVar("a".to_string()).push(Type::String),
559            StackType::RowVar("a".to_string()).push(Type::Int),
560        ),
561    );
562
563    // string-trim: ( ..a String -- ..a String )
564    // Trim whitespace from both ends
565    sigs.insert(
566        "string-trim".to_string(),
567        Effect::new(
568            StackType::RowVar("a".to_string()).push(Type::String),
569            StackType::RowVar("a".to_string()).push(Type::String),
570        ),
571    );
572
573    // string-to-upper: ( ..a String -- ..a String )
574    // Convert to uppercase
575    sigs.insert(
576        "string-to-upper".to_string(),
577        Effect::new(
578            StackType::RowVar("a".to_string()).push(Type::String),
579            StackType::RowVar("a".to_string()).push(Type::String),
580        ),
581    );
582
583    // string-to-lower: ( ..a String -- ..a String )
584    // Convert to lowercase
585    sigs.insert(
586        "string-to-lower".to_string(),
587        Effect::new(
588            StackType::RowVar("a".to_string()).push(Type::String),
589            StackType::RowVar("a".to_string()).push(Type::String),
590        ),
591    );
592
593    // string-equal: ( ..a String String -- ..a Int )
594    // Check if two strings are equal (returns 0 or 1)
595    sigs.insert(
596        "string-equal".to_string(),
597        Effect::new(
598            StackType::RowVar("a".to_string())
599                .push(Type::String)
600                .push(Type::String),
601            StackType::RowVar("a".to_string()).push(Type::Int),
602        ),
603    );
604
605    // string-byte-length: ( ..a String -- ..a Int )
606    // Get byte length (for HTTP Content-Length, buffer allocation)
607    sigs.insert(
608        "string-byte-length".to_string(),
609        Effect::new(
610            StackType::RowVar("a".to_string()).push(Type::String),
611            StackType::RowVar("a".to_string()).push(Type::Int),
612        ),
613    );
614
615    // string-char-at: ( ..a String Int -- ..a Int )
616    // Get Unicode code point at character index
617    sigs.insert(
618        "string-char-at".to_string(),
619        Effect::new(
620            StackType::RowVar("a".to_string())
621                .push(Type::String)
622                .push(Type::Int),
623            StackType::RowVar("a".to_string()).push(Type::Int),
624        ),
625    );
626
627    // string-substring: ( ..a String Int Int -- ..a String )
628    // Extract substring by character indices (string, start, length)
629    sigs.insert(
630        "string-substring".to_string(),
631        Effect::new(
632            StackType::RowVar("a".to_string())
633                .push(Type::String)
634                .push(Type::Int)
635                .push(Type::Int),
636            StackType::RowVar("a".to_string()).push(Type::String),
637        ),
638    );
639
640    // char->string: ( ..a Int -- ..a String )
641    // Convert Unicode code point to single-character string
642    sigs.insert(
643        "char->string".to_string(),
644        Effect::new(
645            StackType::RowVar("a".to_string()).push(Type::Int),
646            StackType::RowVar("a".to_string()).push(Type::String),
647        ),
648    );
649
650    // string-find: ( ..a String String -- ..a Int )
651    // Find first occurrence of substring, returns character index or -1
652    sigs.insert(
653        "string-find".to_string(),
654        Effect::new(
655            StackType::RowVar("a".to_string())
656                .push(Type::String)
657                .push(Type::String),
658            StackType::RowVar("a".to_string()).push(Type::Int),
659        ),
660    );
661
662    // Variant operations
663    // variant-field-count: ( ..a Variant -- ..a Int )
664    // Get number of fields in a variant
665    sigs.insert(
666        "variant-field-count".to_string(),
667        Effect::new(
668            StackType::RowVar("a".to_string()).push(Type::Var("V".to_string())),
669            StackType::RowVar("a".to_string()).push(Type::Int),
670        ),
671    );
672
673    // variant-tag: ( ..a Variant -- ..a Int )
674    // Get tag of a variant
675    sigs.insert(
676        "variant-tag".to_string(),
677        Effect::new(
678            StackType::RowVar("a".to_string()).push(Type::Var("V".to_string())),
679            StackType::RowVar("a".to_string()).push(Type::Int),
680        ),
681    );
682
683    // variant-field-at: ( ..a Variant Int -- ..a Value )
684    // Get field at index from variant
685    sigs.insert(
686        "variant-field-at".to_string(),
687        Effect::new(
688            StackType::RowVar("a".to_string())
689                .push(Type::Var("V".to_string()))
690                .push(Type::Int),
691            StackType::RowVar("a".to_string()).push(Type::Var("T".to_string())),
692        ),
693    );
694
695    // make-variant: ( ..a field1 ... fieldN count tag -- ..a Variant )
696    // Create a variant with given tag and N fields (count specifies N)
697    // Type signature only validates count and tag are Ints; runtime validates field count
698    sigs.insert(
699        "make-variant".to_string(),
700        Effect::new(
701            StackType::RowVar("a".to_string())
702                .push(Type::Int) // count
703                .push(Type::Int), // tag
704            StackType::RowVar("a".to_string()).push(Type::Var("V".to_string())),
705        ),
706    );
707
708    // variant-append: ( ..a Variant Value -- ..a Variant' )
709    // Append a value to a variant, returning a new variant with the value added
710    // Functional style - original variant is not modified
711    sigs.insert(
712        "variant-append".to_string(),
713        Effect::new(
714            StackType::RowVar("a".to_string())
715                .push(Type::Var("V".to_string()))
716                .push(Type::Var("T".to_string())),
717            StackType::RowVar("a".to_string()).push(Type::Var("V2".to_string())),
718        ),
719    );
720
721    // variant-last: ( ..a Variant -- ..a Value )
722    // Get the last field from a variant (peek for stack-like usage)
723    sigs.insert(
724        "variant-last".to_string(),
725        Effect::new(
726            StackType::RowVar("a".to_string()).push(Type::Var("V".to_string())),
727            StackType::RowVar("a".to_string()).push(Type::Var("T".to_string())),
728        ),
729    );
730
731    // variant-init: ( ..a Variant -- ..a Variant' )
732    // Get all but the last field from a variant (pop without returning value)
733    sigs.insert(
734        "variant-init".to_string(),
735        Effect::new(
736            StackType::RowVar("a".to_string()).push(Type::Var("V".to_string())),
737            StackType::RowVar("a".to_string()).push(Type::Var("V2".to_string())),
738        ),
739    );
740
741    // Float arithmetic operations ( ..a Float Float -- ..a Float )
742    for op in &["f.add", "f.subtract", "f.multiply", "f.divide"] {
743        sigs.insert(
744            op.to_string(),
745            Effect::new(
746                StackType::RowVar("a".to_string())
747                    .push(Type::Float)
748                    .push(Type::Float),
749                StackType::RowVar("a".to_string()).push(Type::Float),
750            ),
751        );
752    }
753
754    // Float comparison operations ( ..a Float Float -- ..a Int )
755    // Comparisons return Int (0 or 1) like integer comparisons
756    for op in &["f.=", "f.<", "f.>", "f.<=", "f.>=", "f.<>"] {
757        sigs.insert(
758            op.to_string(),
759            Effect::new(
760                StackType::RowVar("a".to_string())
761                    .push(Type::Float)
762                    .push(Type::Float),
763                StackType::RowVar("a".to_string()).push(Type::Int),
764            ),
765        );
766    }
767
768    // int->float: ( ..a Int -- ..a Float )
769    // Convert integer to floating-point
770    sigs.insert(
771        "int->float".to_string(),
772        Effect::new(
773            StackType::RowVar("a".to_string()).push(Type::Int),
774            StackType::RowVar("a".to_string()).push(Type::Float),
775        ),
776    );
777
778    // float->int: ( ..a Float -- ..a Int )
779    // Truncate floating-point to integer (toward zero)
780    sigs.insert(
781        "float->int".to_string(),
782        Effect::new(
783            StackType::RowVar("a".to_string()).push(Type::Float),
784            StackType::RowVar("a".to_string()).push(Type::Int),
785        ),
786    );
787
788    // float->string: ( ..a Float -- ..a String )
789    // Convert floating-point to string representation
790    sigs.insert(
791        "float->string".to_string(),
792        Effect::new(
793            StackType::RowVar("a".to_string()).push(Type::Float),
794            StackType::RowVar("a".to_string()).push(Type::String),
795        ),
796    );
797
798    // string->float: ( ..a String -- ..a Float Int )
799    // Parse string as float, returns value and success flag (1 on success, 0 on failure)
800    sigs.insert(
801        "string->float".to_string(),
802        Effect::new(
803            StackType::RowVar("a".to_string()).push(Type::String),
804            StackType::RowVar("a".to_string())
805                .push(Type::Float)
806                .push(Type::Int),
807        ),
808    );
809
810    sigs
811}
812
813#[cfg(test)]
814mod tests {
815    use super::*;
816
817    #[test]
818    fn test_builtin_signature_write_line() {
819        let sig = builtin_signature("write_line").unwrap();
820        // ( ..a String -- ..a )
821        let (rest, top) = sig.inputs.clone().pop().unwrap();
822        assert_eq!(top, Type::String);
823        assert_eq!(rest, StackType::RowVar("a".to_string()));
824        assert_eq!(sig.outputs, StackType::RowVar("a".to_string()));
825    }
826
827    #[test]
828    fn test_builtin_signature_add() {
829        let sig = builtin_signature("add").unwrap();
830        // ( ..a Int Int -- ..a Int )
831        let (rest, top) = sig.inputs.clone().pop().unwrap();
832        assert_eq!(top, Type::Int);
833        let (rest2, top2) = rest.pop().unwrap();
834        assert_eq!(top2, Type::Int);
835        assert_eq!(rest2, StackType::RowVar("a".to_string()));
836
837        let (rest3, top3) = sig.outputs.clone().pop().unwrap();
838        assert_eq!(top3, Type::Int);
839        assert_eq!(rest3, StackType::RowVar("a".to_string()));
840    }
841
842    #[test]
843    fn test_builtin_signature_dup() {
844        let sig = builtin_signature("dup").unwrap();
845        // Input: ( ..a T )
846        assert_eq!(
847            sig.inputs,
848            StackType::Cons {
849                rest: Box::new(StackType::RowVar("a".to_string())),
850                top: Type::Var("T".to_string())
851            }
852        );
853        // Output: ( ..a T T )
854        let (rest, top) = sig.outputs.clone().pop().unwrap();
855        assert_eq!(top, Type::Var("T".to_string()));
856        let (rest2, top2) = rest.pop().unwrap();
857        assert_eq!(top2, Type::Var("T".to_string()));
858        assert_eq!(rest2, StackType::RowVar("a".to_string()));
859    }
860
861    #[test]
862    fn test_all_builtins_have_signatures() {
863        let sigs = builtin_signatures();
864
865        // Verify all expected builtins have signatures
866        assert!(sigs.contains_key("write_line"));
867        assert!(sigs.contains_key("read_line"));
868        assert!(sigs.contains_key("int->string"));
869        assert!(sigs.contains_key("add"));
870        assert!(sigs.contains_key("dup"));
871        assert!(sigs.contains_key("swap"));
872        assert!(sigs.contains_key("make-channel"));
873        assert!(sigs.contains_key("send"));
874        assert!(sigs.contains_key("receive"));
875        assert!(
876            sigs.contains_key("string->float"),
877            "string->float should be a builtin"
878        );
879    }
880}