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 non-zero
124///
125/// Stack effect: ( Variant Quotation -- Variant )
126///
127/// The quotation should have effect ( elem -- Int )
128/// Elements are kept if the quotation returns non-zero.
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 -- Int )
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::Int(n) => n != 0,
171                _ => panic!("list-filter: quotation must return Int, 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 -- Int )
329///
330/// Returns 1 if the list has no elements, 0 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) => {
341                if v.fields.is_empty() {
342                    1i64
343                } else {
344                    0i64
345                }
346            }
347            _ => panic!("list-empty?: expected Variant (list), got {:?}", list_val),
348        };
349
350        push(stack, Value::Int(is_empty))
351    }
352}
353
354// Public re-exports
355pub use patch_seq_list_each as list_each;
356pub use patch_seq_list_empty as list_empty;
357pub use patch_seq_list_filter as list_filter;
358pub use patch_seq_list_fold as list_fold;
359pub use patch_seq_list_length as list_length;
360pub use patch_seq_list_map as list_map;
361
362#[cfg(test)]
363mod tests {
364    use super::*;
365
366    // Helper quotation: double an integer
367    unsafe extern "C" fn double_quot(stack: Stack) -> Stack {
368        unsafe {
369            let (stack, val) = pop(stack);
370            match val {
371                Value::Int(n) => push(stack, Value::Int(n * 2)),
372                _ => panic!("Expected Int"),
373            }
374        }
375    }
376
377    // Helper quotation: check if positive
378    unsafe extern "C" fn is_positive_quot(stack: Stack) -> Stack {
379        unsafe {
380            let (stack, val) = pop(stack);
381            match val {
382                Value::Int(n) => push(stack, Value::Int(if n > 0 { 1 } else { 0 })),
383                _ => panic!("Expected Int"),
384            }
385        }
386    }
387
388    // Helper quotation: add two integers
389    unsafe extern "C" fn add_quot(stack: Stack) -> Stack {
390        unsafe {
391            let (stack, b) = pop(stack);
392            let (stack, a) = pop(stack);
393            match (a, b) {
394                (Value::Int(x), Value::Int(y)) => push(stack, Value::Int(x + y)),
395                _ => panic!("Expected two Ints"),
396            }
397        }
398    }
399
400    #[test]
401    fn test_list_map_double() {
402        unsafe {
403            // Create list [1, 2, 3]
404            let list = Value::Variant(Arc::new(VariantData::new(
405                0,
406                vec![Value::Int(1), Value::Int(2), Value::Int(3)],
407            )));
408
409            let stack = crate::stack::alloc_test_stack();
410            let stack = push(stack, list);
411            let fn_ptr = double_quot as usize;
412            let stack = push(
413                stack,
414                Value::Quotation {
415                    wrapper: fn_ptr,
416                    impl_: fn_ptr,
417                },
418            );
419            let stack = list_map(stack);
420
421            let (_stack, result) = pop(stack);
422            match result {
423                Value::Variant(v) => {
424                    assert_eq!(v.fields.len(), 3);
425                    assert_eq!(v.fields[0], Value::Int(2));
426                    assert_eq!(v.fields[1], Value::Int(4));
427                    assert_eq!(v.fields[2], Value::Int(6));
428                }
429                _ => panic!("Expected Variant"),
430            }
431        }
432    }
433
434    #[test]
435    fn test_list_filter_positive() {
436        unsafe {
437            // Create list [-1, 2, -3, 4, 0]
438            let list = Value::Variant(Arc::new(VariantData::new(
439                0,
440                vec![
441                    Value::Int(-1),
442                    Value::Int(2),
443                    Value::Int(-3),
444                    Value::Int(4),
445                    Value::Int(0),
446                ],
447            )));
448
449            let stack = crate::stack::alloc_test_stack();
450            let stack = push(stack, list);
451            let fn_ptr = is_positive_quot as usize;
452            let stack = push(
453                stack,
454                Value::Quotation {
455                    wrapper: fn_ptr,
456                    impl_: fn_ptr,
457                },
458            );
459            let stack = list_filter(stack);
460
461            let (_stack, result) = pop(stack);
462            match result {
463                Value::Variant(v) => {
464                    assert_eq!(v.fields.len(), 2);
465                    assert_eq!(v.fields[0], Value::Int(2));
466                    assert_eq!(v.fields[1], Value::Int(4));
467                }
468                _ => panic!("Expected Variant"),
469            }
470        }
471    }
472
473    #[test]
474    fn test_list_fold_sum() {
475        unsafe {
476            // Create list [1, 2, 3, 4, 5]
477            let list = Value::Variant(Arc::new(VariantData::new(
478                0,
479                vec![
480                    Value::Int(1),
481                    Value::Int(2),
482                    Value::Int(3),
483                    Value::Int(4),
484                    Value::Int(5),
485                ],
486            )));
487
488            let stack = crate::stack::alloc_test_stack();
489            let stack = push(stack, list);
490            let stack = push(stack, Value::Int(0)); // initial accumulator
491            let fn_ptr = add_quot as usize;
492            let stack = push(
493                stack,
494                Value::Quotation {
495                    wrapper: fn_ptr,
496                    impl_: fn_ptr,
497                },
498            );
499            let stack = list_fold(stack);
500
501            let (_stack, result) = pop(stack);
502            assert_eq!(result, Value::Int(15)); // 1+2+3+4+5 = 15
503        }
504    }
505
506    #[test]
507    fn test_list_fold_empty() {
508        unsafe {
509            // Create empty list
510            let list = Value::Variant(Arc::new(VariantData::new(0, vec![])));
511
512            let stack = crate::stack::alloc_test_stack();
513            let stack = push(stack, list);
514            let stack = push(stack, Value::Int(42)); // initial accumulator
515            let fn_ptr = add_quot as usize;
516            let stack = push(
517                stack,
518                Value::Quotation {
519                    wrapper: fn_ptr,
520                    impl_: fn_ptr,
521                },
522            );
523            let stack = list_fold(stack);
524
525            let (_stack, result) = pop(stack);
526            assert_eq!(result, Value::Int(42)); // Should return initial value
527        }
528    }
529
530    #[test]
531    fn test_list_length() {
532        unsafe {
533            let list = Value::Variant(Arc::new(VariantData::new(
534                0,
535                vec![Value::Int(1), Value::Int(2), Value::Int(3)],
536            )));
537
538            let stack = crate::stack::alloc_test_stack();
539            let stack = push(stack, list);
540            let stack = list_length(stack);
541
542            let (_stack, result) = pop(stack);
543            assert_eq!(result, Value::Int(3));
544        }
545    }
546
547    #[test]
548    fn test_list_empty_true() {
549        unsafe {
550            let list = Value::Variant(Arc::new(VariantData::new(0, vec![])));
551
552            let stack = crate::stack::alloc_test_stack();
553            let stack = push(stack, list);
554            let stack = list_empty(stack);
555
556            let (_stack, result) = pop(stack);
557            assert_eq!(result, Value::Int(1));
558        }
559    }
560
561    #[test]
562    fn test_list_empty_false() {
563        unsafe {
564            let list = Value::Variant(Arc::new(VariantData::new(0, vec![Value::Int(1)])));
565
566            let stack = crate::stack::alloc_test_stack();
567            let stack = push(stack, list);
568            let stack = list_empty(stack);
569
570            let (_stack, result) = pop(stack);
571            assert_eq!(result, Value::Int(0));
572        }
573    }
574
575    #[test]
576    fn test_list_map_empty() {
577        unsafe {
578            let list = Value::Variant(Arc::new(VariantData::new(0, vec![])));
579
580            let stack = crate::stack::alloc_test_stack();
581            let stack = push(stack, list);
582            let fn_ptr = double_quot as usize;
583            let stack = push(
584                stack,
585                Value::Quotation {
586                    wrapper: fn_ptr,
587                    impl_: fn_ptr,
588                },
589            );
590            let stack = list_map(stack);
591
592            let (_stack, result) = pop(stack);
593            match result {
594                Value::Variant(v) => {
595                    assert_eq!(v.fields.len(), 0);
596                }
597                _ => panic!("Expected Variant"),
598            }
599        }
600    }
601
602    #[test]
603    fn test_list_map_preserves_tag() {
604        unsafe {
605            // Create list with custom tag (e.g., 42)
606            let list = Value::Variant(Arc::new(VariantData::new(
607                42,
608                vec![Value::Int(1), Value::Int(2)],
609            )));
610
611            let stack = crate::stack::alloc_test_stack();
612            let stack = push(stack, list);
613            let fn_ptr = double_quot as usize;
614            let stack = push(
615                stack,
616                Value::Quotation {
617                    wrapper: fn_ptr,
618                    impl_: fn_ptr,
619                },
620            );
621            let stack = list_map(stack);
622
623            let (_stack, result) = pop(stack);
624            match result {
625                Value::Variant(v) => {
626                    assert_eq!(v.tag, 42); // Tag preserved
627                    assert_eq!(v.fields[0], Value::Int(2));
628                    assert_eq!(v.fields[1], Value::Int(4));
629                }
630                _ => panic!("Expected Variant"),
631            }
632        }
633    }
634
635    // Helper closure function: adds captured value to element
636    // Closure receives: stack with element, env with [captured_value]
637    unsafe extern "C" fn add_captured_closure(
638        stack: Stack,
639        env: *const Value,
640        _env_len: usize,
641    ) -> Stack {
642        unsafe {
643            let (stack, val) = pop(stack);
644            let captured = &*env; // First (and only) captured value
645            match (val, captured) {
646                (Value::Int(n), Value::Int(c)) => push(stack, Value::Int(n + c)),
647                _ => panic!("Expected Int"),
648            }
649        }
650    }
651
652    #[test]
653    fn test_list_map_with_closure() {
654        unsafe {
655            // Create list [1, 2, 3]
656            let list = Value::Variant(Arc::new(VariantData::new(
657                0,
658                vec![Value::Int(1), Value::Int(2), Value::Int(3)],
659            )));
660
661            // Create closure that adds 10 to each element
662            let env: std::sync::Arc<[Value]> =
663                std::sync::Arc::from(vec![Value::Int(10)].into_boxed_slice());
664            let closure = Value::Closure {
665                fn_ptr: add_captured_closure as usize,
666                env,
667            };
668
669            let stack = crate::stack::alloc_test_stack();
670            let stack = push(stack, list);
671            let stack = push(stack, closure);
672            let stack = list_map(stack);
673
674            let (_stack, result) = pop(stack);
675            match result {
676                Value::Variant(v) => {
677                    assert_eq!(v.fields.len(), 3);
678                    assert_eq!(v.fields[0], Value::Int(11)); // 1 + 10
679                    assert_eq!(v.fields[1], Value::Int(12)); // 2 + 10
680                    assert_eq!(v.fields[2], Value::Int(13)); // 3 + 10
681                }
682                _ => panic!("Expected Variant"),
683            }
684        }
685    }
686}