seq_runtime/
list_ops.rs

1//! List operations for Seq
2//!
3//! Higher-order combinators for working with lists (Variants).
4//! These provide idiomatic concatenative-style list processing.
5//!
6//! # Examples
7//!
8//! ```seq
9//! # Map: double each element
10//! my-list [ 2 * ] list-map
11//!
12//! # Filter: keep positive numbers
13//! my-list [ 0 > ] list-filter
14//!
15//! # Fold: sum all elements
16//! my-list 0 [ + ] list-fold
17//!
18//! # Each: print each element
19//! my-list [ write_line ] list-each
20//! ```
21
22use crate::stack::{Stack, drop_stack_value, pop, pop_sv, push};
23use crate::value::{Value, VariantData};
24use std::sync::Arc;
25
26/// Helper to drain any remaining stack values back to the base
27///
28/// This ensures no memory is leaked if a quotation misbehaves
29/// by leaving extra values on the stack.
30unsafe fn drain_stack_to_base(mut stack: Stack, base: Stack) {
31    unsafe {
32        while stack > base {
33            let (rest, sv) = pop_sv(stack);
34            drop_stack_value(sv);
35            stack = rest;
36        }
37    }
38}
39
40/// Helper to call a quotation or closure with a value on the stack
41///
42/// Pushes `value` onto a fresh stack, calls the callable, and returns (result_stack, base).
43/// The caller can compare result_stack to base to check if there are extra values.
44unsafe fn call_with_value(base: Stack, value: Value, callable: &Value) -> Stack {
45    unsafe {
46        let stack = push(base, value);
47
48        match callable {
49            Value::Quotation { wrapper, .. } => {
50                let fn_ref: unsafe extern "C" fn(Stack) -> Stack = std::mem::transmute(*wrapper);
51                fn_ref(stack)
52            }
53            Value::Closure { fn_ptr, env } => {
54                let fn_ref: unsafe extern "C" fn(Stack, *const Value, usize) -> Stack =
55                    std::mem::transmute(*fn_ptr);
56                fn_ref(stack, env.as_ptr(), env.len())
57            }
58            _ => panic!("list operation: expected Quotation or Closure"),
59        }
60    }
61}
62
63/// Map a quotation over a list, returning a new list
64///
65/// Stack effect: ( Variant Quotation -- Variant )
66///
67/// The quotation should have effect ( elem -- elem' )
68/// Each element is transformed by the quotation.
69///
70/// # Safety
71/// Stack must have a Quotation/Closure on top and a Variant below
72#[unsafe(no_mangle)]
73pub unsafe extern "C" fn patch_seq_list_map(stack: Stack) -> Stack {
74    unsafe {
75        // Pop quotation
76        let (stack, callable) = pop(stack);
77
78        // Validate callable
79        match &callable {
80            Value::Quotation { .. } | Value::Closure { .. } => {}
81            _ => panic!(
82                "list-map: expected Quotation or Closure, got {:?}",
83                callable
84            ),
85        }
86
87        // Pop variant (list)
88        let (stack, list_val) = pop(stack);
89
90        let variant_data = match list_val {
91            Value::Variant(v) => v,
92            _ => panic!("list-map: expected Variant (list), got {:?}", list_val),
93        };
94
95        // Map over each element
96        let mut results = Vec::with_capacity(variant_data.fields.len());
97
98        for field in variant_data.fields.iter() {
99            // Create a fresh temp stack for this call
100            let temp_base = crate::stack::alloc_stack();
101            let temp_stack = call_with_value(temp_base, field.clone(), &callable);
102
103            // Pop result - quotation should have effect ( elem -- elem' )
104            if temp_stack <= temp_base {
105                panic!("list-map: quotation consumed element without producing result");
106            }
107            let (remaining, result) = pop(temp_stack);
108            results.push(result);
109
110            // Stack hygiene: drain any extra values left by misbehaving quotation
111            if remaining > temp_base {
112                drain_stack_to_base(remaining, temp_base);
113            }
114        }
115
116        // Create new variant with same tag
117        let new_variant = Value::Variant(Arc::new(VariantData::new(variant_data.tag, results)));
118
119        push(stack, new_variant)
120    }
121}
122
123/// Filter a list, keeping elements where quotation returns true
124///
125/// Stack effect: ( Variant Quotation -- Variant )
126///
127/// The quotation should have effect ( elem -- Bool )
128/// Elements are kept if the quotation returns true.
129///
130/// # Safety
131/// Stack must have a Quotation/Closure on top and a Variant below
132#[unsafe(no_mangle)]
133pub unsafe extern "C" fn patch_seq_list_filter(stack: Stack) -> Stack {
134    unsafe {
135        // Pop quotation
136        let (stack, callable) = pop(stack);
137
138        // Validate callable
139        match &callable {
140            Value::Quotation { .. } | Value::Closure { .. } => {}
141            _ => panic!(
142                "list-filter: expected Quotation or Closure, got {:?}",
143                callable
144            ),
145        }
146
147        // Pop variant (list)
148        let (stack, list_val) = pop(stack);
149
150        let variant_data = match list_val {
151            Value::Variant(v) => v,
152            _ => panic!("list-filter: expected Variant (list), got {:?}", list_val),
153        };
154
155        // Filter elements
156        let mut results = Vec::new();
157
158        for field in variant_data.fields.iter() {
159            // Create a fresh temp stack for this call
160            let temp_base = crate::stack::alloc_stack();
161            let temp_stack = call_with_value(temp_base, field.clone(), &callable);
162
163            // Pop result - quotation should have effect ( elem -- Bool )
164            if temp_stack <= temp_base {
165                panic!("list-filter: quotation consumed element without producing result");
166            }
167            let (remaining, result) = pop(temp_stack);
168
169            let keep = match result {
170                Value::Bool(b) => b,
171                _ => panic!("list-filter: quotation must return Bool, got {:?}", result),
172            };
173
174            if keep {
175                results.push(field.clone());
176            }
177
178            // Stack hygiene: drain any extra values left by misbehaving quotation
179            if remaining > temp_base {
180                drain_stack_to_base(remaining, temp_base);
181            }
182        }
183
184        // Create new variant with same tag
185        let new_variant = Value::Variant(Arc::new(VariantData::new(variant_data.tag, results)));
186
187        push(stack, new_variant)
188    }
189}
190
191/// Fold a list with an accumulator and quotation
192///
193/// Stack effect: ( Variant init Quotation -- result )
194///
195/// The quotation should have effect ( acc elem -- acc' )
196/// Starts with init as accumulator, folds left through the list.
197///
198/// # Safety
199/// Stack must have Quotation on top, init below, and Variant below that
200#[unsafe(no_mangle)]
201pub unsafe extern "C" fn patch_seq_list_fold(stack: Stack) -> Stack {
202    unsafe {
203        // Pop quotation
204        let (stack, callable) = pop(stack);
205
206        // Validate callable
207        match &callable {
208            Value::Quotation { .. } | Value::Closure { .. } => {}
209            _ => panic!(
210                "list-fold: expected Quotation or Closure, got {:?}",
211                callable
212            ),
213        }
214
215        // Pop initial accumulator
216        let (stack, init) = pop(stack);
217
218        // Pop variant (list)
219        let (stack, list_val) = pop(stack);
220
221        let variant_data = match list_val {
222            Value::Variant(v) => v,
223            _ => panic!("list-fold: expected Variant (list), got {:?}", list_val),
224        };
225
226        // Fold over elements
227        let mut acc = init;
228
229        for field in variant_data.fields.iter() {
230            // Create a fresh temp stack and push acc, then element, then call quotation
231            let temp_base = crate::stack::alloc_stack();
232            let temp_stack = push(temp_base, acc);
233            let temp_stack = push(temp_stack, field.clone());
234
235            let temp_stack = match &callable {
236                Value::Quotation { wrapper, .. } => {
237                    let fn_ref: unsafe extern "C" fn(Stack) -> Stack =
238                        std::mem::transmute(*wrapper);
239                    fn_ref(temp_stack)
240                }
241                Value::Closure { fn_ptr, env } => {
242                    let fn_ref: unsafe extern "C" fn(Stack, *const Value, usize) -> Stack =
243                        std::mem::transmute(*fn_ptr);
244                    fn_ref(temp_stack, env.as_ptr(), env.len())
245                }
246                _ => unreachable!(),
247            };
248
249            // Pop new accumulator - quotation should have effect ( acc elem -- acc' )
250            if temp_stack <= temp_base {
251                panic!("list-fold: quotation consumed inputs without producing result");
252            }
253            let (remaining, new_acc) = pop(temp_stack);
254            acc = new_acc;
255
256            // Stack hygiene: drain any extra values left by misbehaving quotation
257            if remaining > temp_base {
258                drain_stack_to_base(remaining, temp_base);
259            }
260        }
261
262        push(stack, acc)
263    }
264}
265
266/// Apply a quotation to each element of a list (for side effects)
267///
268/// Stack effect: ( Variant Quotation -- )
269///
270/// The quotation should have effect ( elem -- )
271/// Each element is passed to the quotation; results are discarded.
272///
273/// # Safety
274/// Stack must have a Quotation/Closure on top and a Variant below
275#[unsafe(no_mangle)]
276pub unsafe extern "C" fn patch_seq_list_each(stack: Stack) -> Stack {
277    unsafe {
278        // Pop quotation
279        let (stack, callable) = pop(stack);
280
281        // Validate callable
282        match &callable {
283            Value::Quotation { .. } | Value::Closure { .. } => {}
284            _ => panic!(
285                "list-each: expected Quotation or Closure, got {:?}",
286                callable
287            ),
288        }
289
290        // Pop variant (list)
291        let (stack, list_val) = pop(stack);
292
293        let variant_data = match list_val {
294            Value::Variant(v) => v,
295            _ => panic!("list-each: expected Variant (list), got {:?}", list_val),
296        };
297
298        // Call quotation for each element (for side effects)
299        for field in variant_data.fields.iter() {
300            let temp_base = crate::stack::alloc_stack();
301            let temp_stack = call_with_value(temp_base, field.clone(), &callable);
302            // Stack hygiene: drain any values left by quotation (effect should be ( elem -- ))
303            if temp_stack > temp_base {
304                drain_stack_to_base(temp_stack, temp_base);
305            }
306        }
307
308        stack
309    }
310}
311
312/// Get the length of a list
313///
314/// Stack effect: ( Variant -- Int )
315///
316/// Returns the number of elements in the list.
317/// This is an alias for variant-field-count, provided for semantic clarity.
318///
319/// # Safety
320/// Stack must have a Variant on top
321#[unsafe(no_mangle)]
322pub unsafe extern "C" fn patch_seq_list_length(stack: Stack) -> Stack {
323    unsafe { crate::variant_ops::patch_seq_variant_field_count(stack) }
324}
325
326/// Check if a list is empty
327///
328/// Stack effect: ( Variant -- Bool )
329///
330/// Returns true if the list has no elements, false otherwise.
331///
332/// # Safety
333/// Stack must have a Variant on top
334#[unsafe(no_mangle)]
335pub unsafe extern "C" fn patch_seq_list_empty(stack: Stack) -> Stack {
336    unsafe {
337        let (stack, list_val) = pop(stack);
338
339        let is_empty = match list_val {
340            Value::Variant(v) => v.fields.is_empty(),
341            _ => panic!("list-empty?: expected Variant (list), got {:?}", list_val),
342        };
343
344        push(stack, Value::Bool(is_empty))
345    }
346}
347
348// Public re-exports
349pub use patch_seq_list_each as list_each;
350pub use patch_seq_list_empty as list_empty;
351pub use patch_seq_list_filter as list_filter;
352pub use patch_seq_list_fold as list_fold;
353pub use patch_seq_list_length as list_length;
354pub use patch_seq_list_map as list_map;
355
356#[cfg(test)]
357mod tests {
358    use super::*;
359
360    // Helper quotation: double an integer
361    unsafe extern "C" fn double_quot(stack: Stack) -> Stack {
362        unsafe {
363            let (stack, val) = pop(stack);
364            match val {
365                Value::Int(n) => push(stack, Value::Int(n * 2)),
366                _ => panic!("Expected Int"),
367            }
368        }
369    }
370
371    // Helper quotation: check if positive
372    unsafe extern "C" fn is_positive_quot(stack: Stack) -> Stack {
373        unsafe {
374            let (stack, val) = pop(stack);
375            match val {
376                Value::Int(n) => push(stack, Value::Bool(n > 0)),
377                _ => panic!("Expected Int"),
378            }
379        }
380    }
381
382    // Helper quotation: add two integers
383    unsafe extern "C" fn add_quot(stack: Stack) -> Stack {
384        unsafe {
385            let (stack, b) = pop(stack);
386            let (stack, a) = pop(stack);
387            match (a, b) {
388                (Value::Int(x), Value::Int(y)) => push(stack, Value::Int(x + y)),
389                _ => panic!("Expected two Ints"),
390            }
391        }
392    }
393
394    #[test]
395    fn test_list_map_double() {
396        unsafe {
397            // Create list [1, 2, 3]
398            let list = Value::Variant(Arc::new(VariantData::new(
399                0,
400                vec![Value::Int(1), Value::Int(2), Value::Int(3)],
401            )));
402
403            let stack = crate::stack::alloc_test_stack();
404            let stack = push(stack, list);
405            let fn_ptr = double_quot as usize;
406            let stack = push(
407                stack,
408                Value::Quotation {
409                    wrapper: fn_ptr,
410                    impl_: fn_ptr,
411                },
412            );
413            let stack = list_map(stack);
414
415            let (_stack, result) = pop(stack);
416            match result {
417                Value::Variant(v) => {
418                    assert_eq!(v.fields.len(), 3);
419                    assert_eq!(v.fields[0], Value::Int(2));
420                    assert_eq!(v.fields[1], Value::Int(4));
421                    assert_eq!(v.fields[2], Value::Int(6));
422                }
423                _ => panic!("Expected Variant"),
424            }
425        }
426    }
427
428    #[test]
429    fn test_list_filter_positive() {
430        unsafe {
431            // Create list [-1, 2, -3, 4, 0]
432            let list = Value::Variant(Arc::new(VariantData::new(
433                0,
434                vec![
435                    Value::Int(-1),
436                    Value::Int(2),
437                    Value::Int(-3),
438                    Value::Int(4),
439                    Value::Int(0),
440                ],
441            )));
442
443            let stack = crate::stack::alloc_test_stack();
444            let stack = push(stack, list);
445            let fn_ptr = is_positive_quot as usize;
446            let stack = push(
447                stack,
448                Value::Quotation {
449                    wrapper: fn_ptr,
450                    impl_: fn_ptr,
451                },
452            );
453            let stack = list_filter(stack);
454
455            let (_stack, result) = pop(stack);
456            match result {
457                Value::Variant(v) => {
458                    assert_eq!(v.fields.len(), 2);
459                    assert_eq!(v.fields[0], Value::Int(2));
460                    assert_eq!(v.fields[1], Value::Int(4));
461                }
462                _ => panic!("Expected Variant"),
463            }
464        }
465    }
466
467    #[test]
468    fn test_list_fold_sum() {
469        unsafe {
470            // Create list [1, 2, 3, 4, 5]
471            let list = Value::Variant(Arc::new(VariantData::new(
472                0,
473                vec![
474                    Value::Int(1),
475                    Value::Int(2),
476                    Value::Int(3),
477                    Value::Int(4),
478                    Value::Int(5),
479                ],
480            )));
481
482            let stack = crate::stack::alloc_test_stack();
483            let stack = push(stack, list);
484            let stack = push(stack, Value::Int(0)); // initial accumulator
485            let fn_ptr = add_quot as usize;
486            let stack = push(
487                stack,
488                Value::Quotation {
489                    wrapper: fn_ptr,
490                    impl_: fn_ptr,
491                },
492            );
493            let stack = list_fold(stack);
494
495            let (_stack, result) = pop(stack);
496            assert_eq!(result, Value::Int(15)); // 1+2+3+4+5 = 15
497        }
498    }
499
500    #[test]
501    fn test_list_fold_empty() {
502        unsafe {
503            // Create empty list
504            let list = Value::Variant(Arc::new(VariantData::new(0, vec![])));
505
506            let stack = crate::stack::alloc_test_stack();
507            let stack = push(stack, list);
508            let stack = push(stack, Value::Int(42)); // initial accumulator
509            let fn_ptr = add_quot as usize;
510            let stack = push(
511                stack,
512                Value::Quotation {
513                    wrapper: fn_ptr,
514                    impl_: fn_ptr,
515                },
516            );
517            let stack = list_fold(stack);
518
519            let (_stack, result) = pop(stack);
520            assert_eq!(result, Value::Int(42)); // Should return initial value
521        }
522    }
523
524    #[test]
525    fn test_list_length() {
526        unsafe {
527            let list = Value::Variant(Arc::new(VariantData::new(
528                0,
529                vec![Value::Int(1), Value::Int(2), Value::Int(3)],
530            )));
531
532            let stack = crate::stack::alloc_test_stack();
533            let stack = push(stack, list);
534            let stack = list_length(stack);
535
536            let (_stack, result) = pop(stack);
537            assert_eq!(result, Value::Int(3));
538        }
539    }
540
541    #[test]
542    fn test_list_empty_true() {
543        unsafe {
544            let list = Value::Variant(Arc::new(VariantData::new(0, vec![])));
545
546            let stack = crate::stack::alloc_test_stack();
547            let stack = push(stack, list);
548            let stack = list_empty(stack);
549
550            let (_stack, result) = pop(stack);
551            assert_eq!(result, Value::Bool(true));
552        }
553    }
554
555    #[test]
556    fn test_list_empty_false() {
557        unsafe {
558            let list = Value::Variant(Arc::new(VariantData::new(0, vec![Value::Int(1)])));
559
560            let stack = crate::stack::alloc_test_stack();
561            let stack = push(stack, list);
562            let stack = list_empty(stack);
563
564            let (_stack, result) = pop(stack);
565            assert_eq!(result, Value::Bool(false));
566        }
567    }
568
569    #[test]
570    fn test_list_map_empty() {
571        unsafe {
572            let list = Value::Variant(Arc::new(VariantData::new(0, vec![])));
573
574            let stack = crate::stack::alloc_test_stack();
575            let stack = push(stack, list);
576            let fn_ptr = double_quot as usize;
577            let stack = push(
578                stack,
579                Value::Quotation {
580                    wrapper: fn_ptr,
581                    impl_: fn_ptr,
582                },
583            );
584            let stack = list_map(stack);
585
586            let (_stack, result) = pop(stack);
587            match result {
588                Value::Variant(v) => {
589                    assert_eq!(v.fields.len(), 0);
590                }
591                _ => panic!("Expected Variant"),
592            }
593        }
594    }
595
596    #[test]
597    fn test_list_map_preserves_tag() {
598        unsafe {
599            // Create list with custom tag (e.g., 42)
600            let list = Value::Variant(Arc::new(VariantData::new(
601                42,
602                vec![Value::Int(1), Value::Int(2)],
603            )));
604
605            let stack = crate::stack::alloc_test_stack();
606            let stack = push(stack, list);
607            let fn_ptr = double_quot as usize;
608            let stack = push(
609                stack,
610                Value::Quotation {
611                    wrapper: fn_ptr,
612                    impl_: fn_ptr,
613                },
614            );
615            let stack = list_map(stack);
616
617            let (_stack, result) = pop(stack);
618            match result {
619                Value::Variant(v) => {
620                    assert_eq!(v.tag, 42); // Tag preserved
621                    assert_eq!(v.fields[0], Value::Int(2));
622                    assert_eq!(v.fields[1], Value::Int(4));
623                }
624                _ => panic!("Expected Variant"),
625            }
626        }
627    }
628
629    // Helper closure function: adds captured value to element
630    // Closure receives: stack with element, env with [captured_value]
631    unsafe extern "C" fn add_captured_closure(
632        stack: Stack,
633        env: *const Value,
634        _env_len: usize,
635    ) -> Stack {
636        unsafe {
637            let (stack, val) = pop(stack);
638            let captured = &*env; // First (and only) captured value
639            match (val, captured) {
640                (Value::Int(n), Value::Int(c)) => push(stack, Value::Int(n + c)),
641                _ => panic!("Expected Int"),
642            }
643        }
644    }
645
646    #[test]
647    fn test_list_map_with_closure() {
648        unsafe {
649            // Create list [1, 2, 3]
650            let list = Value::Variant(Arc::new(VariantData::new(
651                0,
652                vec![Value::Int(1), Value::Int(2), Value::Int(3)],
653            )));
654
655            // Create closure that adds 10 to each element
656            let env: std::sync::Arc<[Value]> =
657                std::sync::Arc::from(vec![Value::Int(10)].into_boxed_slice());
658            let closure = Value::Closure {
659                fn_ptr: add_captured_closure as usize,
660                env,
661            };
662
663            let stack = crate::stack::alloc_test_stack();
664            let stack = push(stack, list);
665            let stack = push(stack, closure);
666            let stack = list_map(stack);
667
668            let (_stack, result) = pop(stack);
669            match result {
670                Value::Variant(v) => {
671                    assert_eq!(v.fields.len(), 3);
672                    assert_eq!(v.fields[0], Value::Int(11)); // 1 + 10
673                    assert_eq!(v.fields[1], Value::Int(12)); // 2 + 10
674                    assert_eq!(v.fields[2], Value::Int(13)); // 3 + 10
675                }
676                _ => panic!("Expected Variant"),
677            }
678        }
679    }
680}