Skip to main content

seq_runtime/
combinators.rs

1//! Dataflow combinators for Seq
2//!
3//! Higher-order words that manage value flow on the stack,
4//! reducing the need for explicit stack shuffling (swap/rot/pick)
5//! or auxiliary stack usage (>aux / aux>).
6//!
7//! These follow the concatenative tradition from Factor/Joy:
8//! - `dip`  — hide top value, run quotation, restore value
9//! - `keep` — run quotation on top value, but preserve the original
10//! - `bi`   — apply two quotations to the same value
11//! - `if`   — branch on a Bool, invoking one of two quotations
12
13use crate::quotations::invoke_callable;
14use crate::stack::{Stack, pop, push};
15use crate::value::Value;
16
17/// `dip`: Hide top value, run quotation on the rest, restore value.
18///
19/// Stack effect: ( ..a x quot -- ..b x )
20///   where quot : ( ..a -- ..b )
21///
22/// Equivalent to: `swap >aux call aux>`
23///
24/// # Safety
25/// - Stack must have at least 2 values (quotation on top, preserved value below)
26/// - Top of stack must be a Quotation or Closure
27#[unsafe(no_mangle)]
28pub unsafe extern "C" fn patch_seq_dip(stack: Stack) -> Stack {
29    // SAFETY: Caller guarantees stack has quotation on top and a value below.
30    // invoke_callable's safety is documented in quotations.rs.
31    unsafe {
32        let (stack, quot) = pop(stack); // pop quotation
33        let (stack, x) = pop(stack); // pop preserved value
34        let stack = invoke_callable(stack, &quot); // run quotation on remaining stack
35        push(stack, x) // restore preserved value
36    }
37}
38
39/// `keep`: Run quotation on top value, but preserve the original.
40///
41/// Stack effect: ( ..a x quot -- ..b x )
42///   where quot : ( ..a x -- ..b )
43///
44/// Like `dip`, but the quotation also receives the preserved value.
45/// Equivalent to: `over >aux call aux>`
46///
47/// # Safety
48/// - Stack must have at least 2 values (quotation on top, value below)
49/// - Top of stack must be a Quotation or Closure
50#[unsafe(no_mangle)]
51pub unsafe extern "C" fn patch_seq_keep(stack: Stack) -> Stack {
52    // SAFETY: Caller guarantees stack has quotation on top and a value below.
53    // x is cloned so both the quotation and the restore get valid values.
54    unsafe {
55        let (stack, quot) = pop(stack); // pop quotation
56        let (stack, x) = pop(stack); // pop value to preserve
57        let stack = push(stack, x.clone()); // push copy for quotation to consume
58        let stack = invoke_callable(stack, &quot); // run quotation (consumes the copy)
59        push(stack, x) // restore original value
60    }
61}
62
63/// `bi`: Apply two quotations to the same value.
64///
65/// Stack effect: ( ..a x quot1 quot2 -- ..c )
66///   where quot1 : ( ..a x -- ..b )
67///         quot2 : ( ..b x -- ..c )
68///
69/// Equivalent to: `>aux keep aux> call`
70///
71/// # Safety
72/// - Stack must have at least 3 values (quot2 on top, quot1 below, value below that)
73/// - Top two stack values must be Quotations or Closures
74#[unsafe(no_mangle)]
75pub unsafe extern "C" fn patch_seq_bi(stack: Stack) -> Stack {
76    // SAFETY: Caller guarantees stack layout. x is cloned so both
77    // quotations receive a valid copy.
78    unsafe {
79        let (stack, quot2) = pop(stack); // pop second quotation
80        let (stack, quot1) = pop(stack); // pop first quotation
81        let (stack, x) = pop(stack); // pop value
82        let stack = push(stack, x.clone()); // push copy for quot1
83        let stack = invoke_callable(stack, &quot1); // run first quotation
84        let stack = push(stack, x); // push original for quot2
85        invoke_callable(stack, &quot2) // run second quotation
86    }
87}
88
89/// `if`: Branch on a Bool, invoking one of two quotations.
90///
91/// Stack effect: ( ..a Bool [..a -- ..b] [..a -- ..b] -- ..b )
92///   The two quotations must have identical effects (the typechecker
93///   enforces this); whichever runs leaves the stack in the same shape.
94///
95/// Layout at entry (top → bottom): else-quot, then-quot, cond.
96///
97/// # Safety
98/// - Stack must have at least 3 values (else-quot on top, then-quot below,
99///   Bool below that).
100/// - The top two values must be Quotations or Closures.
101/// - The third value must be a Bool.
102#[unsafe(no_mangle)]
103pub unsafe extern "C" fn patch_seq_if(stack: Stack) -> Stack {
104    // SAFETY: Caller guarantees the stack layout above. invoke_callable's
105    // safety contract is documented in quotations.rs.
106    unsafe {
107        let (stack, else_quot) = pop(stack);
108        let (stack, then_quot) = pop(stack);
109        let (stack, cond) = pop(stack);
110        match cond {
111            Value::Bool(true) => invoke_callable(stack, &then_quot),
112            Value::Bool(false) => invoke_callable(stack, &else_quot),
113            // Defense-in-depth: the typechecker enforces that `if`'s
114            // condition is Bool, so this arm is unreachable from
115            // type-correct programs. We panic rather than silently
116            // dispatch in case a runtime path slips past — analogous
117            // to how `dip`/`keep`/`bi` handle their own invariants.
118            other => panic!("if: expected Bool condition, got {:?}", other),
119        }
120    }
121}
122
123// Public re-exports with short names for internal use.
124// `if_combinator` rather than `if` because `if` is a Rust keyword.
125pub use patch_seq_bi as bi;
126pub use patch_seq_dip as dip;
127pub use patch_seq_if as if_combinator;
128pub use patch_seq_keep as keep;