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