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(
118            variant_data.tag.clone(),
119            results,
120        )));
121
122        push(stack, new_variant)
123    }
124}
125
126/// Filter a list, keeping elements where quotation returns true
127///
128/// Stack effect: ( Variant Quotation -- Variant )
129///
130/// The quotation should have effect ( elem -- Bool )
131/// Elements are kept if the quotation returns true.
132///
133/// # Safety
134/// Stack must have a Quotation/Closure on top and a Variant below
135#[unsafe(no_mangle)]
136pub unsafe extern "C" fn patch_seq_list_filter(stack: Stack) -> Stack {
137    unsafe {
138        // Pop quotation
139        let (stack, callable) = pop(stack);
140
141        // Validate callable
142        match &callable {
143            Value::Quotation { .. } | Value::Closure { .. } => {}
144            _ => panic!(
145                "list-filter: expected Quotation or Closure, got {:?}",
146                callable
147            ),
148        }
149
150        // Pop variant (list)
151        let (stack, list_val) = pop(stack);
152
153        let variant_data = match list_val {
154            Value::Variant(v) => v,
155            _ => panic!("list-filter: expected Variant (list), got {:?}", list_val),
156        };
157
158        // Filter elements
159        let mut results = Vec::new();
160
161        for field in variant_data.fields.iter() {
162            // Create a fresh temp stack for this call
163            let temp_base = crate::stack::alloc_stack();
164            let temp_stack = call_with_value(temp_base, field.clone(), &callable);
165
166            // Pop result - quotation should have effect ( elem -- Bool )
167            if temp_stack <= temp_base {
168                panic!("list-filter: quotation consumed element without producing result");
169            }
170            let (remaining, result) = pop(temp_stack);
171
172            let keep = match result {
173                Value::Bool(b) => b,
174                _ => panic!("list-filter: quotation must return Bool, got {:?}", result),
175            };
176
177            if keep {
178                results.push(field.clone());
179            }
180
181            // Stack hygiene: drain any extra values left by misbehaving quotation
182            if remaining > temp_base {
183                drain_stack_to_base(remaining, temp_base);
184            }
185        }
186
187        // Create new variant with same tag
188        let new_variant = Value::Variant(Arc::new(VariantData::new(
189            variant_data.tag.clone(),
190            results,
191        )));
192
193        push(stack, new_variant)
194    }
195}
196
197/// Fold a list with an accumulator and quotation
198///
199/// Stack effect: ( Variant init Quotation -- result )
200///
201/// The quotation should have effect ( acc elem -- acc' )
202/// Starts with init as accumulator, folds left through the list.
203///
204/// # Safety
205/// Stack must have Quotation on top, init below, and Variant below that
206#[unsafe(no_mangle)]
207pub unsafe extern "C" fn patch_seq_list_fold(stack: Stack) -> Stack {
208    unsafe {
209        // Pop quotation
210        let (stack, callable) = pop(stack);
211
212        // Validate callable
213        match &callable {
214            Value::Quotation { .. } | Value::Closure { .. } => {}
215            _ => panic!(
216                "list-fold: expected Quotation or Closure, got {:?}",
217                callable
218            ),
219        }
220
221        // Pop initial accumulator
222        let (stack, init) = pop(stack);
223
224        // Pop variant (list)
225        let (stack, list_val) = pop(stack);
226
227        let variant_data = match list_val {
228            Value::Variant(v) => v,
229            _ => panic!("list-fold: expected Variant (list), got {:?}", list_val),
230        };
231
232        // Fold over elements
233        let mut acc = init;
234
235        for field in variant_data.fields.iter() {
236            // Create a fresh temp stack and push acc, then element, then call quotation
237            let temp_base = crate::stack::alloc_stack();
238            let temp_stack = push(temp_base, acc);
239            let temp_stack = push(temp_stack, field.clone());
240
241            let temp_stack = match &callable {
242                Value::Quotation { wrapper, .. } => {
243                    let fn_ref: unsafe extern "C" fn(Stack) -> Stack =
244                        std::mem::transmute(*wrapper);
245                    fn_ref(temp_stack)
246                }
247                Value::Closure { fn_ptr, env } => {
248                    let fn_ref: unsafe extern "C" fn(Stack, *const Value, usize) -> Stack =
249                        std::mem::transmute(*fn_ptr);
250                    fn_ref(temp_stack, env.as_ptr(), env.len())
251                }
252                _ => unreachable!(),
253            };
254
255            // Pop new accumulator - quotation should have effect ( acc elem -- acc' )
256            if temp_stack <= temp_base {
257                panic!("list-fold: quotation consumed inputs without producing result");
258            }
259            let (remaining, new_acc) = pop(temp_stack);
260            acc = new_acc;
261
262            // Stack hygiene: drain any extra values left by misbehaving quotation
263            if remaining > temp_base {
264                drain_stack_to_base(remaining, temp_base);
265            }
266        }
267
268        push(stack, acc)
269    }
270}
271
272/// Apply a quotation to each element of a list (for side effects)
273///
274/// Stack effect: ( Variant Quotation -- )
275///
276/// The quotation should have effect ( elem -- )
277/// Each element is passed to the quotation; results are discarded.
278///
279/// # Safety
280/// Stack must have a Quotation/Closure on top and a Variant below
281#[unsafe(no_mangle)]
282pub unsafe extern "C" fn patch_seq_list_each(stack: Stack) -> Stack {
283    unsafe {
284        // Pop quotation
285        let (stack, callable) = pop(stack);
286
287        // Validate callable
288        match &callable {
289            Value::Quotation { .. } | Value::Closure { .. } => {}
290            _ => panic!(
291                "list-each: expected Quotation or Closure, got {:?}",
292                callable
293            ),
294        }
295
296        // Pop variant (list)
297        let (stack, list_val) = pop(stack);
298
299        let variant_data = match list_val {
300            Value::Variant(v) => v,
301            _ => panic!("list-each: expected Variant (list), got {:?}", list_val),
302        };
303
304        // Call quotation for each element (for side effects)
305        for field in variant_data.fields.iter() {
306            let temp_base = crate::stack::alloc_stack();
307            let temp_stack = call_with_value(temp_base, field.clone(), &callable);
308            // Stack hygiene: drain any values left by quotation (effect should be ( elem -- ))
309            if temp_stack > temp_base {
310                drain_stack_to_base(temp_stack, temp_base);
311            }
312        }
313
314        stack
315    }
316}
317
318/// Get the length of a list
319///
320/// Stack effect: ( Variant -- Int )
321///
322/// Returns the number of elements in the list.
323/// This is an alias for variant-field-count, provided for semantic clarity.
324///
325/// # Safety
326/// Stack must have a Variant on top
327#[unsafe(no_mangle)]
328pub unsafe extern "C" fn patch_seq_list_length(stack: Stack) -> Stack {
329    unsafe { crate::variant_ops::patch_seq_variant_field_count(stack) }
330}
331
332/// Check if a list is empty
333///
334/// Stack effect: ( Variant -- Bool )
335///
336/// Returns true if the list has no elements, false otherwise.
337///
338/// # Safety
339/// Stack must have a Variant on top
340#[unsafe(no_mangle)]
341pub unsafe extern "C" fn patch_seq_list_empty(stack: Stack) -> Stack {
342    unsafe {
343        let (stack, list_val) = pop(stack);
344
345        let is_empty = match list_val {
346            Value::Variant(v) => v.fields.is_empty(),
347            _ => panic!("list-empty?: expected Variant (list), got {:?}", list_val),
348        };
349
350        push(stack, Value::Bool(is_empty))
351    }
352}
353
354/// Create an empty list
355///
356/// Stack effect: ( -- Variant )
357///
358/// Returns a new empty list (Variant with tag "List" and no fields).
359///
360/// # Safety
361/// No requirements on stack
362#[unsafe(no_mangle)]
363pub unsafe extern "C" fn patch_seq_list_make(stack: Stack) -> Stack {
364    unsafe {
365        let list = Value::Variant(Arc::new(VariantData::new(
366            crate::seqstring::global_string("List".to_string()),
367            vec![],
368        )));
369        push(stack, list)
370    }
371}
372
373/// Append an element to a list (functional - returns new list)
374///
375/// Stack effect: ( Variant Value -- Variant )
376///
377/// Returns a new list with the value appended at the end.
378/// The original list is not modified.
379///
380/// # Safety
381/// Stack must have a Value on top and a Variant (list) below
382#[unsafe(no_mangle)]
383pub unsafe extern "C" fn patch_seq_list_push(stack: Stack) -> Stack {
384    unsafe {
385        let (stack, value) = pop(stack);
386        let (stack, list_val) = pop(stack);
387
388        let variant_data = match list_val {
389            Value::Variant(v) => v,
390            _ => panic!("list.push: expected Variant (list), got {:?}", list_val),
391        };
392
393        // Create new list with element appended
394        let mut new_fields = Vec::with_capacity(variant_data.fields.len() + 1);
395        new_fields.extend(variant_data.fields.iter().cloned());
396        new_fields.push(value);
397
398        let new_list = Value::Variant(Arc::new(VariantData::new(
399            variant_data.tag.clone(),
400            new_fields,
401        )));
402
403        push(stack, new_list)
404    }
405}
406
407/// Get an element from a list by index
408///
409/// Stack effect: ( Variant Int -- Value Bool )
410///
411/// Returns the value at the given index and true, or
412/// a placeholder value and false if index is out of bounds.
413///
414/// # Safety
415/// Stack must have an Int on top and a Variant (list) below
416#[unsafe(no_mangle)]
417pub unsafe extern "C" fn patch_seq_list_get(stack: Stack) -> Stack {
418    unsafe {
419        let (stack, index_val) = pop(stack);
420        let (stack, list_val) = pop(stack);
421
422        let index = match index_val {
423            Value::Int(i) => i,
424            _ => panic!("list.get: expected Int (index), got {:?}", index_val),
425        };
426
427        let variant_data = match list_val {
428            Value::Variant(v) => v,
429            _ => panic!("list.get: expected Variant (list), got {:?}", list_val),
430        };
431
432        if index < 0 || index as usize >= variant_data.fields.len() {
433            // Out of bounds - return false
434            let stack = push(stack, Value::Int(0)); // placeholder
435            push(stack, Value::Bool(false))
436        } else {
437            let value = variant_data.fields[index as usize].clone();
438            let stack = push(stack, value);
439            push(stack, Value::Bool(true))
440        }
441    }
442}
443
444/// Set an element in a list by index (functional - returns new list)
445///
446/// Stack effect: ( Variant Int Value -- Variant Bool )
447///
448/// Returns a new list with the value at the given index replaced, and true.
449/// If index is out of bounds, returns the original list and false.
450///
451/// # Safety
452/// Stack must have Value on top, Int below, and Variant (list) below that
453#[unsafe(no_mangle)]
454pub unsafe extern "C" fn patch_seq_list_set(stack: Stack) -> Stack {
455    unsafe {
456        let (stack, value) = pop(stack);
457        let (stack, index_val) = pop(stack);
458        let (stack, list_val) = pop(stack);
459
460        let index = match index_val {
461            Value::Int(i) => i,
462            _ => panic!("list.set: expected Int (index), got {:?}", index_val),
463        };
464
465        let variant_data = match &list_val {
466            Value::Variant(v) => v,
467            _ => panic!("list.set: expected Variant (list), got {:?}", list_val),
468        };
469
470        if index < 0 || index as usize >= variant_data.fields.len() {
471            // Out of bounds - return original list and false
472            let stack = push(stack, list_val);
473            push(stack, Value::Bool(false))
474        } else {
475            // Create new list with element replaced
476            let mut new_fields: Vec<Value> = variant_data.fields.to_vec();
477            new_fields[index as usize] = value;
478
479            let new_list = Value::Variant(Arc::new(VariantData::new(
480                variant_data.tag.clone(),
481                new_fields,
482            )));
483
484            let stack = push(stack, new_list);
485            push(stack, Value::Bool(true))
486        }
487    }
488}
489
490// Public re-exports
491pub use patch_seq_list_each as list_each;
492pub use patch_seq_list_empty as list_empty;
493pub use patch_seq_list_filter as list_filter;
494pub use patch_seq_list_fold as list_fold;
495pub use patch_seq_list_get as list_get;
496pub use patch_seq_list_length as list_length;
497pub use patch_seq_list_make as list_make;
498pub use patch_seq_list_map as list_map;
499pub use patch_seq_list_push as list_push;
500pub use patch_seq_list_set as list_set;
501
502#[cfg(test)]
503mod tests {
504    use super::*;
505    use crate::seqstring::global_string;
506
507    // Helper quotation: double an integer
508    unsafe extern "C" fn double_quot(stack: Stack) -> Stack {
509        unsafe {
510            let (stack, val) = pop(stack);
511            match val {
512                Value::Int(n) => push(stack, Value::Int(n * 2)),
513                _ => panic!("Expected Int"),
514            }
515        }
516    }
517
518    // Helper quotation: check if positive
519    unsafe extern "C" fn is_positive_quot(stack: Stack) -> Stack {
520        unsafe {
521            let (stack, val) = pop(stack);
522            match val {
523                Value::Int(n) => push(stack, Value::Bool(n > 0)),
524                _ => panic!("Expected Int"),
525            }
526        }
527    }
528
529    // Helper quotation: add two integers
530    unsafe extern "C" fn add_quot(stack: Stack) -> Stack {
531        unsafe {
532            let (stack, b) = pop(stack);
533            let (stack, a) = pop(stack);
534            match (a, b) {
535                (Value::Int(x), Value::Int(y)) => push(stack, Value::Int(x + y)),
536                _ => panic!("Expected two Ints"),
537            }
538        }
539    }
540
541    #[test]
542    fn test_list_map_double() {
543        unsafe {
544            // Create list [1, 2, 3]
545            let list = Value::Variant(Arc::new(VariantData::new(
546                global_string("List".to_string()),
547                vec![Value::Int(1), Value::Int(2), Value::Int(3)],
548            )));
549
550            let stack = crate::stack::alloc_test_stack();
551            let stack = push(stack, list);
552            let fn_ptr = double_quot as usize;
553            let stack = push(
554                stack,
555                Value::Quotation {
556                    wrapper: fn_ptr,
557                    impl_: fn_ptr,
558                },
559            );
560            let stack = list_map(stack);
561
562            let (_stack, result) = pop(stack);
563            match result {
564                Value::Variant(v) => {
565                    assert_eq!(v.fields.len(), 3);
566                    assert_eq!(v.fields[0], Value::Int(2));
567                    assert_eq!(v.fields[1], Value::Int(4));
568                    assert_eq!(v.fields[2], Value::Int(6));
569                }
570                _ => panic!("Expected Variant"),
571            }
572        }
573    }
574
575    #[test]
576    fn test_list_filter_positive() {
577        unsafe {
578            // Create list [-1, 2, -3, 4, 0]
579            let list = Value::Variant(Arc::new(VariantData::new(
580                global_string("List".to_string()),
581                vec![
582                    Value::Int(-1),
583                    Value::Int(2),
584                    Value::Int(-3),
585                    Value::Int(4),
586                    Value::Int(0),
587                ],
588            )));
589
590            let stack = crate::stack::alloc_test_stack();
591            let stack = push(stack, list);
592            let fn_ptr = is_positive_quot as usize;
593            let stack = push(
594                stack,
595                Value::Quotation {
596                    wrapper: fn_ptr,
597                    impl_: fn_ptr,
598                },
599            );
600            let stack = list_filter(stack);
601
602            let (_stack, result) = pop(stack);
603            match result {
604                Value::Variant(v) => {
605                    assert_eq!(v.fields.len(), 2);
606                    assert_eq!(v.fields[0], Value::Int(2));
607                    assert_eq!(v.fields[1], Value::Int(4));
608                }
609                _ => panic!("Expected Variant"),
610            }
611        }
612    }
613
614    #[test]
615    fn test_list_fold_sum() {
616        unsafe {
617            // Create list [1, 2, 3, 4, 5]
618            let list = Value::Variant(Arc::new(VariantData::new(
619                global_string("List".to_string()),
620                vec![
621                    Value::Int(1),
622                    Value::Int(2),
623                    Value::Int(3),
624                    Value::Int(4),
625                    Value::Int(5),
626                ],
627            )));
628
629            let stack = crate::stack::alloc_test_stack();
630            let stack = push(stack, list);
631            let stack = push(stack, Value::Int(0)); // initial accumulator
632            let fn_ptr = add_quot as usize;
633            let stack = push(
634                stack,
635                Value::Quotation {
636                    wrapper: fn_ptr,
637                    impl_: fn_ptr,
638                },
639            );
640            let stack = list_fold(stack);
641
642            let (_stack, result) = pop(stack);
643            assert_eq!(result, Value::Int(15)); // 1+2+3+4+5 = 15
644        }
645    }
646
647    #[test]
648    fn test_list_fold_empty() {
649        unsafe {
650            // Create empty list
651            let list = Value::Variant(Arc::new(VariantData::new(
652                global_string("List".to_string()),
653                vec![],
654            )));
655
656            let stack = crate::stack::alloc_test_stack();
657            let stack = push(stack, list);
658            let stack = push(stack, Value::Int(42)); // initial accumulator
659            let fn_ptr = add_quot as usize;
660            let stack = push(
661                stack,
662                Value::Quotation {
663                    wrapper: fn_ptr,
664                    impl_: fn_ptr,
665                },
666            );
667            let stack = list_fold(stack);
668
669            let (_stack, result) = pop(stack);
670            assert_eq!(result, Value::Int(42)); // Should return initial value
671        }
672    }
673
674    #[test]
675    fn test_list_length() {
676        unsafe {
677            let list = Value::Variant(Arc::new(VariantData::new(
678                global_string("List".to_string()),
679                vec![Value::Int(1), Value::Int(2), Value::Int(3)],
680            )));
681
682            let stack = crate::stack::alloc_test_stack();
683            let stack = push(stack, list);
684            let stack = list_length(stack);
685
686            let (_stack, result) = pop(stack);
687            assert_eq!(result, Value::Int(3));
688        }
689    }
690
691    #[test]
692    fn test_list_empty_true() {
693        unsafe {
694            let list = Value::Variant(Arc::new(VariantData::new(
695                global_string("List".to_string()),
696                vec![],
697            )));
698
699            let stack = crate::stack::alloc_test_stack();
700            let stack = push(stack, list);
701            let stack = list_empty(stack);
702
703            let (_stack, result) = pop(stack);
704            assert_eq!(result, Value::Bool(true));
705        }
706    }
707
708    #[test]
709    fn test_list_empty_false() {
710        unsafe {
711            let list = Value::Variant(Arc::new(VariantData::new(
712                global_string("List".to_string()),
713                vec![Value::Int(1)],
714            )));
715
716            let stack = crate::stack::alloc_test_stack();
717            let stack = push(stack, list);
718            let stack = list_empty(stack);
719
720            let (_stack, result) = pop(stack);
721            assert_eq!(result, Value::Bool(false));
722        }
723    }
724
725    #[test]
726    fn test_list_map_empty() {
727        unsafe {
728            let list = Value::Variant(Arc::new(VariantData::new(
729                global_string("List".to_string()),
730                vec![],
731            )));
732
733            let stack = crate::stack::alloc_test_stack();
734            let stack = push(stack, list);
735            let fn_ptr = double_quot as usize;
736            let stack = push(
737                stack,
738                Value::Quotation {
739                    wrapper: fn_ptr,
740                    impl_: fn_ptr,
741                },
742            );
743            let stack = list_map(stack);
744
745            let (_stack, result) = pop(stack);
746            match result {
747                Value::Variant(v) => {
748                    assert_eq!(v.fields.len(), 0);
749                }
750                _ => panic!("Expected Variant"),
751            }
752        }
753    }
754
755    #[test]
756    fn test_list_map_preserves_tag() {
757        unsafe {
758            // Create list with custom tag
759            let list = Value::Variant(Arc::new(VariantData::new(
760                global_string("CustomTag".to_string()),
761                vec![Value::Int(1), Value::Int(2)],
762            )));
763
764            let stack = crate::stack::alloc_test_stack();
765            let stack = push(stack, list);
766            let fn_ptr = double_quot as usize;
767            let stack = push(
768                stack,
769                Value::Quotation {
770                    wrapper: fn_ptr,
771                    impl_: fn_ptr,
772                },
773            );
774            let stack = list_map(stack);
775
776            let (_stack, result) = pop(stack);
777            match result {
778                Value::Variant(v) => {
779                    assert_eq!(v.tag.as_str(), "CustomTag"); // Tag preserved
780                    assert_eq!(v.fields[0], Value::Int(2));
781                    assert_eq!(v.fields[1], Value::Int(4));
782                }
783                _ => panic!("Expected Variant"),
784            }
785        }
786    }
787
788    // Helper closure function: adds captured value to element
789    // Closure receives: stack with element, env with [captured_value]
790    unsafe extern "C" fn add_captured_closure(
791        stack: Stack,
792        env: *const Value,
793        _env_len: usize,
794    ) -> Stack {
795        unsafe {
796            let (stack, val) = pop(stack);
797            let captured = &*env; // First (and only) captured value
798            match (val, captured) {
799                (Value::Int(n), Value::Int(c)) => push(stack, Value::Int(n + c)),
800                _ => panic!("Expected Int"),
801            }
802        }
803    }
804
805    #[test]
806    fn test_list_map_with_closure() {
807        unsafe {
808            // Create list [1, 2, 3]
809            let list = Value::Variant(Arc::new(VariantData::new(
810                global_string("List".to_string()),
811                vec![Value::Int(1), Value::Int(2), Value::Int(3)],
812            )));
813
814            // Create closure that adds 10 to each element
815            let env: std::sync::Arc<[Value]> =
816                std::sync::Arc::from(vec![Value::Int(10)].into_boxed_slice());
817            let closure = Value::Closure {
818                fn_ptr: add_captured_closure as usize,
819                env,
820            };
821
822            let stack = crate::stack::alloc_test_stack();
823            let stack = push(stack, list);
824            let stack = push(stack, closure);
825            let stack = list_map(stack);
826
827            let (_stack, result) = pop(stack);
828            match result {
829                Value::Variant(v) => {
830                    assert_eq!(v.fields.len(), 3);
831                    assert_eq!(v.fields[0], Value::Int(11)); // 1 + 10
832                    assert_eq!(v.fields[1], Value::Int(12)); // 2 + 10
833                    assert_eq!(v.fields[2], Value::Int(13)); // 3 + 10
834                }
835                _ => panic!("Expected Variant"),
836            }
837        }
838    }
839}