Skip to main content

seq_runtime/list_ops/
combinators.rs

1//! Higher-order list combinators: `list_map`, `list_filter`, `list_fold`,
2//! `list_each`. Each invokes a Quotation/Closure on the caller's stack
3//! via the shared `invoke_callable` helper from `quotations`.
4
5use crate::stack::{Stack, drop_stack_value, get_stack_base, pop, pop_sv, push, stack_value_size};
6use crate::value::{Value, VariantData};
7use std::sync::Arc;
8
9/// Check if the stack has at least `n` values
10#[inline]
11pub(super) fn stack_depth(stack: Stack) -> usize {
12    if stack.is_null() {
13        return 0;
14    }
15    let base = get_stack_base();
16    if base.is_null() {
17        return 0;
18    }
19    (stack as usize - base as usize) / stack_value_size()
20}
21
22/// Helper to drain any remaining stack values back to the base
23///
24/// This ensures no memory is leaked if a quotation misbehaves
25/// by leaving extra values on the stack.
26unsafe fn drain_stack_to_base(mut stack: Stack, base: Stack) {
27    unsafe {
28        while stack > base {
29            let (rest, sv) = pop_sv(stack);
30            drop_stack_value(sv);
31            stack = rest;
32        }
33    }
34}
35
36/// Helper to call a quotation or closure with a value on the stack.
37///
38/// Pushes `value` onto the base stack, then invokes the callable.
39/// Uses the shared `invoke_callable` from quotations.rs.
40unsafe fn call_with_value(base: Stack, value: Value, callable: &Value) -> Stack {
41    unsafe {
42        let stack = push(base, value);
43        crate::quotations::invoke_callable(stack, callable)
44    }
45}
46
47/// Map a quotation over a list, returning a new list
48///
49/// Stack effect: ( Variant Quotation -- Variant )
50///
51/// The quotation should have effect ( elem -- elem' )
52/// Each element is transformed by the quotation.
53///
54/// # Safety
55/// Stack must have a Quotation/Closure on top and a Variant below
56#[unsafe(no_mangle)]
57pub unsafe extern "C" fn patch_seq_list_map(stack: Stack) -> Stack {
58    unsafe {
59        // Pop quotation
60        let (stack, callable) = pop(stack);
61
62        // Validate callable
63        match &callable {
64            Value::Quotation { .. } | Value::Closure { .. } => {}
65            _ => panic!(
66                "list-map: expected Quotation or Closure, got {:?}",
67                callable
68            ),
69        }
70
71        // Pop variant (list)
72        let (stack, list_val) = pop(stack);
73
74        let variant_data = match list_val {
75            Value::Variant(v) => v,
76            _ => panic!("list-map: expected Variant (list), got {:?}", list_val),
77        };
78
79        // Map over each element
80        let mut results = Vec::with_capacity(variant_data.fields.len());
81
82        for field in variant_data.fields.iter() {
83            // Create a fresh temp stack for this call
84            let temp_base = crate::stack::alloc_stack();
85            let temp_stack = call_with_value(temp_base, field.clone(), &callable);
86
87            // Pop result - quotation should have effect ( elem -- elem' )
88            if temp_stack <= temp_base {
89                panic!("list-map: quotation consumed element without producing result");
90            }
91            let (remaining, result) = pop(temp_stack);
92            results.push(result);
93
94            // Stack hygiene: drain any extra values left by misbehaving quotation
95            if remaining > temp_base {
96                drain_stack_to_base(remaining, temp_base);
97            }
98        }
99
100        // Create new variant with same tag
101        let new_variant = Value::Variant(Arc::new(VariantData::new(
102            variant_data.tag.clone(),
103            results,
104        )));
105
106        push(stack, new_variant)
107    }
108}
109
110/// Filter a list, keeping elements where quotation returns true
111///
112/// Stack effect: ( Variant Quotation -- Variant )
113///
114/// The quotation should have effect ( elem -- Bool )
115/// Elements are kept if the quotation returns true.
116///
117/// # Safety
118/// Stack must have a Quotation/Closure on top and a Variant below
119#[unsafe(no_mangle)]
120pub unsafe extern "C" fn patch_seq_list_filter(stack: Stack) -> Stack {
121    unsafe {
122        // Pop quotation
123        let (stack, callable) = pop(stack);
124
125        // Validate callable
126        match &callable {
127            Value::Quotation { .. } | Value::Closure { .. } => {}
128            _ => panic!(
129                "list-filter: expected Quotation or Closure, got {:?}",
130                callable
131            ),
132        }
133
134        // Pop variant (list)
135        let (stack, list_val) = pop(stack);
136
137        let variant_data = match list_val {
138            Value::Variant(v) => v,
139            _ => panic!("list-filter: expected Variant (list), got {:?}", list_val),
140        };
141
142        // Filter elements
143        let mut results = Vec::new();
144
145        for field in variant_data.fields.iter() {
146            // Create a fresh temp stack for this call
147            let temp_base = crate::stack::alloc_stack();
148            let temp_stack = call_with_value(temp_base, field.clone(), &callable);
149
150            // Pop result - quotation should have effect ( elem -- Bool )
151            if temp_stack <= temp_base {
152                panic!("list-filter: quotation consumed element without producing result");
153            }
154            let (remaining, result) = pop(temp_stack);
155
156            let keep = match result {
157                Value::Bool(b) => b,
158                _ => panic!("list-filter: quotation must return Bool, got {:?}", result),
159            };
160
161            if keep {
162                results.push(field.clone());
163            }
164
165            // Stack hygiene: drain any extra values left by misbehaving quotation
166            if remaining > temp_base {
167                drain_stack_to_base(remaining, temp_base);
168            }
169        }
170
171        // Create new variant with same tag
172        let new_variant = Value::Variant(Arc::new(VariantData::new(
173            variant_data.tag.clone(),
174            results,
175        )));
176
177        push(stack, new_variant)
178    }
179}
180
181/// Fold a list with an accumulator and quotation
182///
183/// Stack effect: ( Variant init Quotation -- result )
184///
185/// The quotation should have effect ( acc elem -- acc' )
186/// Starts with init as accumulator, folds left through the list.
187///
188/// # Safety
189/// Stack must have Quotation on top, init below, and Variant below that
190#[unsafe(no_mangle)]
191pub unsafe extern "C" fn patch_seq_list_fold(stack: Stack) -> Stack {
192    unsafe {
193        // Pop quotation
194        let (stack, callable) = pop(stack);
195
196        // Validate callable
197        match &callable {
198            Value::Quotation { .. } | Value::Closure { .. } => {}
199            _ => panic!(
200                "list-fold: expected Quotation or Closure, got {:?}",
201                callable
202            ),
203        }
204
205        // Pop initial accumulator
206        let (stack, init) = pop(stack);
207
208        // Pop variant (list)
209        let (stack, list_val) = pop(stack);
210
211        let variant_data = match list_val {
212            Value::Variant(v) => v,
213            _ => panic!("list-fold: expected Variant (list), got {:?}", list_val),
214        };
215
216        // Fold over elements
217        let mut acc = init;
218
219        for field in variant_data.fields.iter() {
220            // Create a fresh temp stack and push acc, then element, then call quotation
221            let temp_base = crate::stack::alloc_stack();
222            let temp_stack = push(temp_base, acc);
223            let temp_stack = push(temp_stack, field.clone());
224
225            let temp_stack = match &callable {
226                Value::Quotation { wrapper, .. } => {
227                    let fn_ref: unsafe extern "C" fn(Stack) -> Stack =
228                        std::mem::transmute(*wrapper);
229                    fn_ref(temp_stack)
230                }
231                Value::Closure { fn_ptr, env } => {
232                    let fn_ref: unsafe extern "C" fn(Stack, *const Value, usize) -> Stack =
233                        std::mem::transmute(*fn_ptr);
234                    fn_ref(temp_stack, env.as_ptr(), env.len())
235                }
236                _ => unreachable!(),
237            };
238
239            // Pop new accumulator - quotation should have effect ( acc elem -- acc' )
240            if temp_stack <= temp_base {
241                panic!("list-fold: quotation consumed inputs without producing result");
242            }
243            let (remaining, new_acc) = pop(temp_stack);
244            acc = new_acc;
245
246            // Stack hygiene: drain any extra values left by misbehaving quotation
247            if remaining > temp_base {
248                drain_stack_to_base(remaining, temp_base);
249            }
250        }
251
252        push(stack, acc)
253    }
254}
255
256/// Apply a quotation to each element of a list (for side effects)
257///
258/// Stack effect: ( Variant Quotation -- )
259///
260/// The quotation should have effect ( elem -- )
261/// Each element is passed to the quotation; results are discarded.
262///
263/// # Safety
264/// Stack must have a Quotation/Closure on top and a Variant below
265#[unsafe(no_mangle)]
266pub unsafe extern "C" fn patch_seq_list_each(stack: Stack) -> Stack {
267    unsafe {
268        // Pop quotation
269        let (stack, callable) = pop(stack);
270
271        // Validate callable
272        match &callable {
273            Value::Quotation { .. } | Value::Closure { .. } => {}
274            _ => panic!(
275                "list-each: expected Quotation or Closure, got {:?}",
276                callable
277            ),
278        }
279
280        // Pop variant (list)
281        let (stack, list_val) = pop(stack);
282
283        let variant_data = match list_val {
284            Value::Variant(v) => v,
285            _ => panic!("list-each: expected Variant (list), got {:?}", list_val),
286        };
287
288        // Call quotation for each element (for side effects)
289        for field in variant_data.fields.iter() {
290            let temp_base = crate::stack::alloc_stack();
291            let temp_stack = call_with_value(temp_base, field.clone(), &callable);
292            // Stack hygiene: drain any values left by quotation (effect should be ( elem -- ))
293            if temp_stack > temp_base {
294                drain_stack_to_base(temp_stack, temp_base);
295            }
296        }
297
298        stack
299    }
300}