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