Skip to main content

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::error::set_runtime_error;
23use crate::stack::{Stack, drop_stack_value, get_stack_base, pop, pop_sv, push, stack_value_size};
24use crate::value::{Value, VariantData};
25use std::sync::Arc;
26
27/// Check if the stack has at least `n` values
28#[inline]
29fn stack_depth(stack: Stack) -> usize {
30    if stack.is_null() {
31        return 0;
32    }
33    let base = get_stack_base();
34    if base.is_null() {
35        return 0;
36    }
37    (stack as usize - base as usize) / stack_value_size()
38}
39
40/// Helper to drain any remaining stack values back to the base
41///
42/// This ensures no memory is leaked if a quotation misbehaves
43/// by leaving extra values on the stack.
44unsafe fn drain_stack_to_base(mut stack: Stack, base: Stack) {
45    unsafe {
46        while stack > base {
47            let (rest, sv) = pop_sv(stack);
48            drop_stack_value(sv);
49            stack = rest;
50        }
51    }
52}
53
54/// Helper to call a quotation or closure with a value on the stack
55///
56/// Pushes `value` onto a fresh stack, calls the callable, and returns (result_stack, base).
57/// The caller can compare result_stack to base to check if there are extra values.
58unsafe fn call_with_value(base: Stack, value: Value, callable: &Value) -> Stack {
59    unsafe {
60        let stack = push(base, value);
61
62        match callable {
63            Value::Quotation { wrapper, .. } => {
64                let fn_ref: unsafe extern "C" fn(Stack) -> Stack = std::mem::transmute(*wrapper);
65                fn_ref(stack)
66            }
67            Value::Closure { fn_ptr, env } => {
68                let fn_ref: unsafe extern "C" fn(Stack, *const Value, usize) -> Stack =
69                    std::mem::transmute(*fn_ptr);
70                fn_ref(stack, env.as_ptr(), env.len())
71            }
72            _ => panic!("list operation: expected Quotation or Closure"),
73        }
74    }
75}
76
77/// Map a quotation over a list, returning a new list
78///
79/// Stack effect: ( Variant Quotation -- Variant )
80///
81/// The quotation should have effect ( elem -- elem' )
82/// Each element is transformed by the quotation.
83///
84/// # Safety
85/// Stack must have a Quotation/Closure on top and a Variant below
86#[unsafe(no_mangle)]
87pub unsafe extern "C" fn patch_seq_list_map(stack: Stack) -> Stack {
88    unsafe {
89        // Pop quotation
90        let (stack, callable) = pop(stack);
91
92        // Validate callable
93        match &callable {
94            Value::Quotation { .. } | Value::Closure { .. } => {}
95            _ => panic!(
96                "list-map: expected Quotation or Closure, got {:?}",
97                callable
98            ),
99        }
100
101        // Pop variant (list)
102        let (stack, list_val) = pop(stack);
103
104        let variant_data = match list_val {
105            Value::Variant(v) => v,
106            _ => panic!("list-map: expected Variant (list), got {:?}", list_val),
107        };
108
109        // Map over each element
110        let mut results = Vec::with_capacity(variant_data.fields.len());
111
112        for field in variant_data.fields.iter() {
113            // Create a fresh temp stack for this call
114            let temp_base = crate::stack::alloc_stack();
115            let temp_stack = call_with_value(temp_base, field.clone(), &callable);
116
117            // Pop result - quotation should have effect ( elem -- elem' )
118            if temp_stack <= temp_base {
119                panic!("list-map: quotation consumed element without producing result");
120            }
121            let (remaining, result) = pop(temp_stack);
122            results.push(result);
123
124            // Stack hygiene: drain any extra values left by misbehaving quotation
125            if remaining > temp_base {
126                drain_stack_to_base(remaining, temp_base);
127            }
128        }
129
130        // Create new variant with same tag
131        let new_variant = Value::Variant(Arc::new(VariantData::new(
132            variant_data.tag.clone(),
133            results,
134        )));
135
136        push(stack, new_variant)
137    }
138}
139
140/// Filter a list, keeping elements where quotation returns true
141///
142/// Stack effect: ( Variant Quotation -- Variant )
143///
144/// The quotation should have effect ( elem -- Bool )
145/// Elements are kept if the quotation returns true.
146///
147/// # Safety
148/// Stack must have a Quotation/Closure on top and a Variant below
149#[unsafe(no_mangle)]
150pub unsafe extern "C" fn patch_seq_list_filter(stack: Stack) -> Stack {
151    unsafe {
152        // Pop quotation
153        let (stack, callable) = pop(stack);
154
155        // Validate callable
156        match &callable {
157            Value::Quotation { .. } | Value::Closure { .. } => {}
158            _ => panic!(
159                "list-filter: expected Quotation or Closure, got {:?}",
160                callable
161            ),
162        }
163
164        // Pop variant (list)
165        let (stack, list_val) = pop(stack);
166
167        let variant_data = match list_val {
168            Value::Variant(v) => v,
169            _ => panic!("list-filter: expected Variant (list), got {:?}", list_val),
170        };
171
172        // Filter elements
173        let mut results = Vec::new();
174
175        for field in variant_data.fields.iter() {
176            // Create a fresh temp stack for this call
177            let temp_base = crate::stack::alloc_stack();
178            let temp_stack = call_with_value(temp_base, field.clone(), &callable);
179
180            // Pop result - quotation should have effect ( elem -- Bool )
181            if temp_stack <= temp_base {
182                panic!("list-filter: quotation consumed element without producing result");
183            }
184            let (remaining, result) = pop(temp_stack);
185
186            let keep = match result {
187                Value::Bool(b) => b,
188                _ => panic!("list-filter: quotation must return Bool, got {:?}", result),
189            };
190
191            if keep {
192                results.push(field.clone());
193            }
194
195            // Stack hygiene: drain any extra values left by misbehaving quotation
196            if remaining > temp_base {
197                drain_stack_to_base(remaining, temp_base);
198            }
199        }
200
201        // Create new variant with same tag
202        let new_variant = Value::Variant(Arc::new(VariantData::new(
203            variant_data.tag.clone(),
204            results,
205        )));
206
207        push(stack, new_variant)
208    }
209}
210
211/// Fold a list with an accumulator and quotation
212///
213/// Stack effect: ( Variant init Quotation -- result )
214///
215/// The quotation should have effect ( acc elem -- acc' )
216/// Starts with init as accumulator, folds left through the list.
217///
218/// # Safety
219/// Stack must have Quotation on top, init below, and Variant below that
220#[unsafe(no_mangle)]
221pub unsafe extern "C" fn patch_seq_list_fold(stack: Stack) -> Stack {
222    unsafe {
223        // Pop quotation
224        let (stack, callable) = pop(stack);
225
226        // Validate callable
227        match &callable {
228            Value::Quotation { .. } | Value::Closure { .. } => {}
229            _ => panic!(
230                "list-fold: expected Quotation or Closure, got {:?}",
231                callable
232            ),
233        }
234
235        // Pop initial accumulator
236        let (stack, init) = pop(stack);
237
238        // Pop variant (list)
239        let (stack, list_val) = pop(stack);
240
241        let variant_data = match list_val {
242            Value::Variant(v) => v,
243            _ => panic!("list-fold: expected Variant (list), got {:?}", list_val),
244        };
245
246        // Fold over elements
247        let mut acc = init;
248
249        for field in variant_data.fields.iter() {
250            // Create a fresh temp stack and push acc, then element, then call quotation
251            let temp_base = crate::stack::alloc_stack();
252            let temp_stack = push(temp_base, acc);
253            let temp_stack = push(temp_stack, field.clone());
254
255            let temp_stack = match &callable {
256                Value::Quotation { wrapper, .. } => {
257                    let fn_ref: unsafe extern "C" fn(Stack) -> Stack =
258                        std::mem::transmute(*wrapper);
259                    fn_ref(temp_stack)
260                }
261                Value::Closure { fn_ptr, env } => {
262                    let fn_ref: unsafe extern "C" fn(Stack, *const Value, usize) -> Stack =
263                        std::mem::transmute(*fn_ptr);
264                    fn_ref(temp_stack, env.as_ptr(), env.len())
265                }
266                _ => unreachable!(),
267            };
268
269            // Pop new accumulator - quotation should have effect ( acc elem -- acc' )
270            if temp_stack <= temp_base {
271                panic!("list-fold: quotation consumed inputs without producing result");
272            }
273            let (remaining, new_acc) = pop(temp_stack);
274            acc = new_acc;
275
276            // Stack hygiene: drain any extra values left by misbehaving quotation
277            if remaining > temp_base {
278                drain_stack_to_base(remaining, temp_base);
279            }
280        }
281
282        push(stack, acc)
283    }
284}
285
286/// Apply a quotation to each element of a list (for side effects)
287///
288/// Stack effect: ( Variant Quotation -- )
289///
290/// The quotation should have effect ( elem -- )
291/// Each element is passed to the quotation; results are discarded.
292///
293/// # Safety
294/// Stack must have a Quotation/Closure on top and a Variant below
295#[unsafe(no_mangle)]
296pub unsafe extern "C" fn patch_seq_list_each(stack: Stack) -> Stack {
297    unsafe {
298        // Pop quotation
299        let (stack, callable) = pop(stack);
300
301        // Validate callable
302        match &callable {
303            Value::Quotation { .. } | Value::Closure { .. } => {}
304            _ => panic!(
305                "list-each: expected Quotation or Closure, got {:?}",
306                callable
307            ),
308        }
309
310        // Pop variant (list)
311        let (stack, list_val) = pop(stack);
312
313        let variant_data = match list_val {
314            Value::Variant(v) => v,
315            _ => panic!("list-each: expected Variant (list), got {:?}", list_val),
316        };
317
318        // Call quotation for each element (for side effects)
319        for field in variant_data.fields.iter() {
320            let temp_base = crate::stack::alloc_stack();
321            let temp_stack = call_with_value(temp_base, field.clone(), &callable);
322            // Stack hygiene: drain any values left by quotation (effect should be ( elem -- ))
323            if temp_stack > temp_base {
324                drain_stack_to_base(temp_stack, temp_base);
325            }
326        }
327
328        stack
329    }
330}
331
332/// Get the length of a list
333///
334/// Stack effect: ( Variant -- Int )
335///
336/// Returns the number of elements in the list.
337/// This is an alias for variant-field-count, provided for semantic clarity.
338///
339/// # Safety
340/// Stack must have a Variant on top
341#[unsafe(no_mangle)]
342pub unsafe extern "C" fn patch_seq_list_length(stack: Stack) -> Stack {
343    unsafe { crate::variant_ops::patch_seq_variant_field_count(stack) }
344}
345
346/// Check if a list is empty
347///
348/// Stack effect: ( Variant -- Bool )
349///
350/// Returns true if the list has no elements, false otherwise.
351///
352/// # Safety
353/// Stack must have a Variant on top
354#[unsafe(no_mangle)]
355pub unsafe extern "C" fn patch_seq_list_empty(stack: Stack) -> Stack {
356    unsafe {
357        let (stack, list_val) = pop(stack);
358
359        let is_empty = match list_val {
360            Value::Variant(v) => v.fields.is_empty(),
361            _ => panic!("list-empty?: expected Variant (list), got {:?}", list_val),
362        };
363
364        push(stack, Value::Bool(is_empty))
365    }
366}
367
368/// Create an empty list
369///
370/// Stack effect: ( -- Variant )
371///
372/// Returns a new empty list (Variant with tag "List" and no fields).
373///
374/// # Safety
375/// No requirements on stack
376#[unsafe(no_mangle)]
377pub unsafe extern "C" fn patch_seq_list_make(stack: Stack) -> Stack {
378    unsafe {
379        let list = Value::Variant(Arc::new(VariantData::new(
380            crate::seqstring::global_string("List".to_string()),
381            vec![],
382        )));
383        push(stack, list)
384    }
385}
386
387/// Append an element to a list (functional - returns new list)
388///
389/// Stack effect: ( Variant Value -- Variant )
390///
391/// Returns a new list with the value appended at the end.
392/// The original list is not modified.
393///
394/// # Safety
395/// Stack must have a Value on top and a Variant (list) below
396#[unsafe(no_mangle)]
397pub unsafe extern "C" fn patch_seq_list_push(stack: Stack) -> Stack {
398    unsafe {
399        let (stack, value) = pop(stack);
400        let (stack, list_val) = pop(stack);
401        let variant_arc = match list_val {
402            Value::Variant(v) => v,
403            _ => panic!("list.push: expected Variant (list), got {:?}", list_val),
404        };
405        push_to_variant(stack, variant_arc, value)
406    }
407}
408
409/// COW push: append value to variant, mutating in place when sole owner.
410unsafe fn push_to_variant(stack: Stack, mut variant_arc: Arc<VariantData>, value: Value) -> Stack {
411    unsafe {
412        if let Some(data) = Arc::get_mut(&mut variant_arc) {
413            // Sole owner — mutate in place (amortized O(1))
414            data.fields.push(value);
415            push(stack, Value::Variant(variant_arc))
416        } else {
417            // Shared — clone and append (O(n))
418            let mut new_fields = Vec::with_capacity(variant_arc.fields.len() + 1);
419            new_fields.extend(variant_arc.fields.iter().cloned());
420            new_fields.push(value);
421            let new_list = Value::Variant(Arc::new(VariantData::new(
422                variant_arc.tag.clone(),
423                new_fields,
424            )));
425            push(stack, new_list)
426        }
427    }
428}
429
430/// In-place list push: append a value to the list on the stack.
431///
432/// Stack effect: ( List T -- List )
433///
434/// Unlike `list.push`, this operates directly on the stack's tagged pointer
435/// to avoid the Arc<Value> alloc/dealloc overhead of pop/push. When the list
436/// is sole-owned (the common case in loops), this is amortized O(1).
437///
438/// Falls back to `list.push` behavior when the list is shared.
439///
440/// # Safety
441/// - Stack must have a Value on top and a Variant (list) below.
442/// - Each strand's stack is single-threaded (no concurrent access to the
443///   same StackValue slot), so the outer Arc refcount check followed by
444///   mutation is safe. The inner `Arc::get_mut` is the true atomic guard
445///   against shared VariantData — the outer check is an early exit
446///   optimization, not the sole safety mechanism.
447#[unsafe(no_mangle)]
448pub unsafe extern "C" fn patch_seq_list_push_in_place(stack: Stack) -> Stack {
449    use crate::tagged_stack::{TAG_FALSE, TAG_TRUE, is_tagged_int};
450
451    unsafe {
452        // Pop the value to append (goes through normal pop)
453        let (stack, value) = pop(stack);
454
455        // Read the list's raw StackValue from the stack WITHOUT popping.
456        // This avoids the Arc<Value> alloc/dealloc cycle.
457        let list_sv_ptr = stack.sub(1);
458        let list_sv = *list_sv_ptr;
459
460        // Must be a heap pointer (not Int or Bool)
461        if is_tagged_int(list_sv) || list_sv == TAG_FALSE || list_sv == TAG_TRUE {
462            panic!("list.push!: expected list on stack, got inline value");
463        }
464
465        // The StackValue is an Arc<Value> raw pointer.
466        // Use Arc::get_mut on the outer Arc to safely verify sole ownership
467        // (checks both strong and weak refcounts). Then Arc::get_mut on the
468        // inner Arc<VariantData> for the actual mutation guard.
469        // Strand stacks are single-threaded, so no concurrent access.
470        let mut outer_arc = Arc::from_raw(list_sv as *const Value);
471        if let Some(inner_val) = Arc::get_mut(&mut outer_arc)
472            && let Value::Variant(variant_arc) = inner_val
473            && let Some(data) = Arc::get_mut(variant_arc)
474        {
475            data.fields.push(value);
476            std::mem::forget(outer_arc); // Don't drop — Arc stays on the stack
477            return stack; // Stack unchanged — list is still at sp-1
478        }
479        // Couldn't mutate in place — put the Arc back before falling through
480        std::mem::forget(outer_arc);
481
482        // Fallback: couldn't mutate in place — pop and use shared COW helper
483        let (stack, list_val) = pop(stack);
484        let variant_arc = match list_val {
485            Value::Variant(v) => v,
486            _ => panic!("list.push!: expected list on stack"),
487        };
488        push_to_variant(stack, variant_arc, value)
489    }
490}
491
492/// Get an element from a list by index
493///
494/// Stack effect: ( Variant Int -- Value Bool )
495///
496/// Returns the value at the given index and true, or
497/// a placeholder value and false if index is out of bounds.
498///
499/// # Error Handling
500/// - Empty stack: Sets runtime error, returns 0 and false
501/// - Type mismatch: Sets runtime error, returns 0 and false
502/// - Out of bounds: Returns 0 and false (no error set, this is expected)
503///
504/// # Safety
505/// Stack must have an Int on top and a Variant (list) below
506#[unsafe(no_mangle)]
507pub unsafe extern "C" fn patch_seq_list_get(stack: Stack) -> Stack {
508    unsafe {
509        // Check stack depth before any pops to avoid partial consumption
510        if stack_depth(stack) < 2 {
511            set_runtime_error("list.get: stack underflow (need 2 values)");
512            return stack;
513        }
514        let (stack, index_val) = pop(stack);
515        let (stack, list_val) = pop(stack);
516
517        let index = match index_val {
518            Value::Int(i) => i,
519            _ => {
520                set_runtime_error(format!(
521                    "list.get: expected Int (index), got {:?}",
522                    index_val
523                ));
524                let stack = push(stack, Value::Int(0));
525                return push(stack, Value::Bool(false));
526            }
527        };
528
529        let variant_data = match list_val {
530            Value::Variant(v) => v,
531            _ => {
532                set_runtime_error(format!(
533                    "list.get: expected Variant (list), got {:?}",
534                    list_val
535                ));
536                let stack = push(stack, Value::Int(0));
537                return push(stack, Value::Bool(false));
538            }
539        };
540
541        if index < 0 || index as usize >= variant_data.fields.len() {
542            // Out of bounds - return false
543            let stack = push(stack, Value::Int(0)); // placeholder
544            push(stack, Value::Bool(false))
545        } else {
546            let value = variant_data.fields[index as usize].clone();
547            let stack = push(stack, value);
548            push(stack, Value::Bool(true))
549        }
550    }
551}
552
553/// Set an element in a list by index (functional - returns new list)
554///
555/// Stack effect: ( Variant Int Value -- Variant Bool )
556///
557/// Returns a new list with the value at the given index replaced, and true.
558/// If index is out of bounds, returns the original list and false.
559///
560/// # Error Handling
561/// - Empty stack: Sets runtime error, returns unchanged stack
562/// - Type mismatch: Sets runtime error, returns original list and false
563/// - Out of bounds: Returns original list and false (no error set, this is expected)
564///
565/// # Safety
566/// Stack must have Value on top, Int below, and Variant (list) below that
567#[unsafe(no_mangle)]
568pub unsafe extern "C" fn patch_seq_list_set(stack: Stack) -> Stack {
569    unsafe {
570        // Check stack depth before any pops to avoid partial consumption
571        if stack_depth(stack) < 3 {
572            set_runtime_error("list.set: stack underflow (need 3 values)");
573            return stack;
574        }
575        let (stack, value) = pop(stack);
576        let (stack, index_val) = pop(stack);
577        let (stack, list_val) = pop(stack);
578
579        let index = match index_val {
580            Value::Int(i) => i,
581            _ => {
582                set_runtime_error(format!(
583                    "list.set: expected Int (index), got {:?}",
584                    index_val
585                ));
586                // Return the list and false
587                let stack = push(stack, list_val);
588                return push(stack, Value::Bool(false));
589            }
590        };
591
592        let mut arc = match list_val {
593            Value::Variant(v) => v,
594            other => {
595                set_runtime_error(format!(
596                    "list.set: expected Variant (list), got {:?}",
597                    other
598                ));
599                let stack = push(stack, other);
600                return push(stack, Value::Bool(false));
601            }
602        };
603
604        if index < 0 || index as usize >= arc.fields.len() {
605            // Out of bounds - return original list and false
606            let stack = push(stack, Value::Variant(arc));
607            push(stack, Value::Bool(false))
608        } else {
609            // COW: if we're the sole owner, mutate in place
610            if let Some(data) = Arc::get_mut(&mut arc) {
611                data.fields[index as usize] = value;
612                let stack = push(stack, Value::Variant(arc));
613                push(stack, Value::Bool(true))
614            } else {
615                // Shared — clone and replace
616                let mut new_fields: Vec<Value> = arc.fields.to_vec();
617                new_fields[index as usize] = value;
618                let new_list =
619                    Value::Variant(Arc::new(VariantData::new(arc.tag.clone(), new_fields)));
620                let stack = push(stack, new_list);
621                push(stack, Value::Bool(true))
622            }
623        }
624    }
625}
626
627// Public re-exports
628pub use patch_seq_list_each as list_each;
629pub use patch_seq_list_empty as list_empty;
630pub use patch_seq_list_filter as list_filter;
631pub use patch_seq_list_fold as list_fold;
632pub use patch_seq_list_get as list_get;
633pub use patch_seq_list_length as list_length;
634pub use patch_seq_list_make as list_make;
635pub use patch_seq_list_map as list_map;
636pub use patch_seq_list_push as list_push;
637pub use patch_seq_list_set as list_set;
638
639#[cfg(test)]
640mod tests {
641    use super::*;
642    use crate::seqstring::global_string;
643
644    // Helper quotation: double an integer
645    unsafe extern "C" fn double_quot(stack: Stack) -> Stack {
646        unsafe {
647            let (stack, val) = pop(stack);
648            match val {
649                Value::Int(n) => push(stack, Value::Int(n * 2)),
650                _ => panic!("Expected Int"),
651            }
652        }
653    }
654
655    // Helper quotation: check if positive
656    unsafe extern "C" fn is_positive_quot(stack: Stack) -> Stack {
657        unsafe {
658            let (stack, val) = pop(stack);
659            match val {
660                Value::Int(n) => push(stack, Value::Bool(n > 0)),
661                _ => panic!("Expected Int"),
662            }
663        }
664    }
665
666    // Helper quotation: add two integers
667    unsafe extern "C" fn add_quot(stack: Stack) -> Stack {
668        unsafe {
669            let (stack, b) = pop(stack);
670            let (stack, a) = pop(stack);
671            match (a, b) {
672                (Value::Int(x), Value::Int(y)) => push(stack, Value::Int(x + y)),
673                _ => panic!("Expected two Ints"),
674            }
675        }
676    }
677
678    #[test]
679    fn test_list_map_double() {
680        unsafe {
681            // Create list [1, 2, 3]
682            let list = Value::Variant(Arc::new(VariantData::new(
683                global_string("List".to_string()),
684                vec![Value::Int(1), Value::Int(2), Value::Int(3)],
685            )));
686
687            let stack = crate::stack::alloc_test_stack();
688            let stack = push(stack, list);
689            let fn_ptr = double_quot as *const () as usize;
690            let stack = push(
691                stack,
692                Value::Quotation {
693                    wrapper: fn_ptr,
694                    impl_: fn_ptr,
695                },
696            );
697            let stack = list_map(stack);
698
699            let (_stack, result) = pop(stack);
700            match result {
701                Value::Variant(v) => {
702                    assert_eq!(v.fields.len(), 3);
703                    assert_eq!(v.fields[0], Value::Int(2));
704                    assert_eq!(v.fields[1], Value::Int(4));
705                    assert_eq!(v.fields[2], Value::Int(6));
706                }
707                _ => panic!("Expected Variant"),
708            }
709        }
710    }
711
712    #[test]
713    fn test_list_filter_positive() {
714        unsafe {
715            // Create list [-1, 2, -3, 4, 0]
716            let list = Value::Variant(Arc::new(VariantData::new(
717                global_string("List".to_string()),
718                vec![
719                    Value::Int(-1),
720                    Value::Int(2),
721                    Value::Int(-3),
722                    Value::Int(4),
723                    Value::Int(0),
724                ],
725            )));
726
727            let stack = crate::stack::alloc_test_stack();
728            let stack = push(stack, list);
729            let fn_ptr = is_positive_quot as *const () as usize;
730            let stack = push(
731                stack,
732                Value::Quotation {
733                    wrapper: fn_ptr,
734                    impl_: fn_ptr,
735                },
736            );
737            let stack = list_filter(stack);
738
739            let (_stack, result) = pop(stack);
740            match result {
741                Value::Variant(v) => {
742                    assert_eq!(v.fields.len(), 2);
743                    assert_eq!(v.fields[0], Value::Int(2));
744                    assert_eq!(v.fields[1], Value::Int(4));
745                }
746                _ => panic!("Expected Variant"),
747            }
748        }
749    }
750
751    #[test]
752    fn test_list_fold_sum() {
753        unsafe {
754            // Create list [1, 2, 3, 4, 5]
755            let list = Value::Variant(Arc::new(VariantData::new(
756                global_string("List".to_string()),
757                vec![
758                    Value::Int(1),
759                    Value::Int(2),
760                    Value::Int(3),
761                    Value::Int(4),
762                    Value::Int(5),
763                ],
764            )));
765
766            let stack = crate::stack::alloc_test_stack();
767            let stack = push(stack, list);
768            let stack = push(stack, Value::Int(0)); // initial accumulator
769            let fn_ptr = add_quot as *const () as usize;
770            let stack = push(
771                stack,
772                Value::Quotation {
773                    wrapper: fn_ptr,
774                    impl_: fn_ptr,
775                },
776            );
777            let stack = list_fold(stack);
778
779            let (_stack, result) = pop(stack);
780            assert_eq!(result, Value::Int(15)); // 1+2+3+4+5 = 15
781        }
782    }
783
784    #[test]
785    fn test_list_fold_empty() {
786        unsafe {
787            // Create empty list
788            let list = Value::Variant(Arc::new(VariantData::new(
789                global_string("List".to_string()),
790                vec![],
791            )));
792
793            let stack = crate::stack::alloc_test_stack();
794            let stack = push(stack, list);
795            let stack = push(stack, Value::Int(42)); // initial accumulator
796            let fn_ptr = add_quot as *const () as usize;
797            let stack = push(
798                stack,
799                Value::Quotation {
800                    wrapper: fn_ptr,
801                    impl_: fn_ptr,
802                },
803            );
804            let stack = list_fold(stack);
805
806            let (_stack, result) = pop(stack);
807            assert_eq!(result, Value::Int(42)); // Should return initial value
808        }
809    }
810
811    #[test]
812    fn test_list_length() {
813        unsafe {
814            let list = Value::Variant(Arc::new(VariantData::new(
815                global_string("List".to_string()),
816                vec![Value::Int(1), Value::Int(2), Value::Int(3)],
817            )));
818
819            let stack = crate::stack::alloc_test_stack();
820            let stack = push(stack, list);
821            let stack = list_length(stack);
822
823            let (_stack, result) = pop(stack);
824            assert_eq!(result, Value::Int(3));
825        }
826    }
827
828    #[test]
829    fn test_list_empty_true() {
830        unsafe {
831            let list = Value::Variant(Arc::new(VariantData::new(
832                global_string("List".to_string()),
833                vec![],
834            )));
835
836            let stack = crate::stack::alloc_test_stack();
837            let stack = push(stack, list);
838            let stack = list_empty(stack);
839
840            let (_stack, result) = pop(stack);
841            assert_eq!(result, Value::Bool(true));
842        }
843    }
844
845    #[test]
846    fn test_list_empty_false() {
847        unsafe {
848            let list = Value::Variant(Arc::new(VariantData::new(
849                global_string("List".to_string()),
850                vec![Value::Int(1)],
851            )));
852
853            let stack = crate::stack::alloc_test_stack();
854            let stack = push(stack, list);
855            let stack = list_empty(stack);
856
857            let (_stack, result) = pop(stack);
858            assert_eq!(result, Value::Bool(false));
859        }
860    }
861
862    #[test]
863    fn test_list_map_empty() {
864        unsafe {
865            let list = Value::Variant(Arc::new(VariantData::new(
866                global_string("List".to_string()),
867                vec![],
868            )));
869
870            let stack = crate::stack::alloc_test_stack();
871            let stack = push(stack, list);
872            let fn_ptr = double_quot as *const () as usize;
873            let stack = push(
874                stack,
875                Value::Quotation {
876                    wrapper: fn_ptr,
877                    impl_: fn_ptr,
878                },
879            );
880            let stack = list_map(stack);
881
882            let (_stack, result) = pop(stack);
883            match result {
884                Value::Variant(v) => {
885                    assert_eq!(v.fields.len(), 0);
886                }
887                _ => panic!("Expected Variant"),
888            }
889        }
890    }
891
892    #[test]
893    fn test_list_map_preserves_tag() {
894        unsafe {
895            // Create list with custom tag
896            let list = Value::Variant(Arc::new(VariantData::new(
897                global_string("CustomTag".to_string()),
898                vec![Value::Int(1), Value::Int(2)],
899            )));
900
901            let stack = crate::stack::alloc_test_stack();
902            let stack = push(stack, list);
903            let fn_ptr = double_quot as *const () as usize;
904            let stack = push(
905                stack,
906                Value::Quotation {
907                    wrapper: fn_ptr,
908                    impl_: fn_ptr,
909                },
910            );
911            let stack = list_map(stack);
912
913            let (_stack, result) = pop(stack);
914            match result {
915                Value::Variant(v) => {
916                    assert_eq!(v.tag.as_str(), "CustomTag"); // Tag preserved
917                    assert_eq!(v.fields[0], Value::Int(2));
918                    assert_eq!(v.fields[1], Value::Int(4));
919                }
920                _ => panic!("Expected Variant"),
921            }
922        }
923    }
924
925    // Helper closure function: adds captured value to element
926    // Closure receives: stack with element, env with [captured_value]
927    unsafe extern "C" fn add_captured_closure(
928        stack: Stack,
929        env: *const Value,
930        _env_len: usize,
931    ) -> Stack {
932        unsafe {
933            let (stack, val) = pop(stack);
934            let captured = &*env; // First (and only) captured value
935            match (val, captured) {
936                (Value::Int(n), Value::Int(c)) => push(stack, Value::Int(n + c)),
937                _ => panic!("Expected Int"),
938            }
939        }
940    }
941
942    #[test]
943    fn test_list_map_with_closure() {
944        unsafe {
945            // Create list [1, 2, 3]
946            let list = Value::Variant(Arc::new(VariantData::new(
947                global_string("List".to_string()),
948                vec![Value::Int(1), Value::Int(2), Value::Int(3)],
949            )));
950
951            // Create closure that adds 10 to each element
952            let env: std::sync::Arc<[Value]> =
953                std::sync::Arc::from(vec![Value::Int(10)].into_boxed_slice());
954            let closure = Value::Closure {
955                fn_ptr: add_captured_closure as *const () as usize,
956                env,
957            };
958
959            let stack = crate::stack::alloc_test_stack();
960            let stack = push(stack, list);
961            let stack = push(stack, closure);
962            let stack = list_map(stack);
963
964            let (_stack, result) = pop(stack);
965            match result {
966                Value::Variant(v) => {
967                    assert_eq!(v.fields.len(), 3);
968                    assert_eq!(v.fields[0], Value::Int(11)); // 1 + 10
969                    assert_eq!(v.fields[1], Value::Int(12)); // 2 + 10
970                    assert_eq!(v.fields[2], Value::Int(13)); // 3 + 10
971                }
972                _ => panic!("Expected Variant"),
973            }
974        }
975    }
976
977    #[test]
978    fn test_list_get_type_error_index() {
979        unsafe {
980            crate::error::clear_runtime_error();
981
982            let list = Value::Variant(Arc::new(VariantData::new(
983                global_string("List".to_string()),
984                vec![Value::Int(1), Value::Int(2)],
985            )));
986
987            let stack = crate::stack::alloc_test_stack();
988            let stack = push(stack, list);
989            let stack = push(stack, Value::Bool(true)); // Wrong type - should be Int
990            let stack = list_get(stack);
991
992            // Should have set an error
993            assert!(crate::error::has_runtime_error());
994            let error = crate::error::take_runtime_error().unwrap();
995            assert!(error.contains("expected Int"));
996
997            // Should return (0, false)
998            let (stack, success) = pop(stack);
999            assert_eq!(success, Value::Bool(false));
1000            let (_stack, value) = pop(stack);
1001            assert_eq!(value, Value::Int(0));
1002        }
1003    }
1004
1005    #[test]
1006    fn test_list_get_type_error_list() {
1007        unsafe {
1008            crate::error::clear_runtime_error();
1009
1010            let stack = crate::stack::alloc_test_stack();
1011            let stack = push(stack, Value::Int(42)); // Wrong type - should be Variant
1012            let stack = push(stack, Value::Int(0)); // index
1013            let stack = list_get(stack);
1014
1015            // Should have set an error
1016            assert!(crate::error::has_runtime_error());
1017            let error = crate::error::take_runtime_error().unwrap();
1018            assert!(error.contains("expected Variant"));
1019
1020            // Should return (0, false)
1021            let (stack, success) = pop(stack);
1022            assert_eq!(success, Value::Bool(false));
1023            let (_stack, value) = pop(stack);
1024            assert_eq!(value, Value::Int(0));
1025        }
1026    }
1027
1028    #[test]
1029    fn test_list_set_type_error_index() {
1030        unsafe {
1031            crate::error::clear_runtime_error();
1032
1033            let list = Value::Variant(Arc::new(VariantData::new(
1034                global_string("List".to_string()),
1035                vec![Value::Int(1), Value::Int(2)],
1036            )));
1037
1038            let stack = crate::stack::alloc_test_stack();
1039            let stack = push(stack, list);
1040            let stack = push(stack, Value::Bool(true)); // Wrong type - should be Int
1041            let stack = push(stack, Value::Int(99)); // new value
1042            let stack = list_set(stack);
1043
1044            // Should have set an error
1045            assert!(crate::error::has_runtime_error());
1046            let error = crate::error::take_runtime_error().unwrap();
1047            assert!(error.contains("expected Int"));
1048
1049            // Should return (list, false)
1050            let (stack, success) = pop(stack);
1051            assert_eq!(success, Value::Bool(false));
1052            let (_stack, returned_list) = pop(stack);
1053            assert!(matches!(returned_list, Value::Variant(_)));
1054        }
1055    }
1056
1057    #[test]
1058    fn test_list_set_type_error_list() {
1059        unsafe {
1060            crate::error::clear_runtime_error();
1061
1062            let stack = crate::stack::alloc_test_stack();
1063            let stack = push(stack, Value::Int(42)); // Wrong type - should be Variant
1064            let stack = push(stack, Value::Int(0)); // index
1065            let stack = push(stack, Value::Int(99)); // new value
1066            let stack = list_set(stack);
1067
1068            // Should have set an error
1069            assert!(crate::error::has_runtime_error());
1070            let error = crate::error::take_runtime_error().unwrap();
1071            assert!(error.contains("expected Variant"));
1072
1073            // Should return (original value, false)
1074            let (stack, success) = pop(stack);
1075            assert_eq!(success, Value::Bool(false));
1076            let (_stack, returned) = pop(stack);
1077            assert_eq!(returned, Value::Int(42)); // Original value returned
1078        }
1079    }
1080}