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