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
402        let variant_data = match list_val {
403            Value::Variant(v) => v,
404            _ => panic!("list.push: expected Variant (list), got {:?}", list_val),
405        };
406
407        // Create new list with element appended
408        let mut new_fields = Vec::with_capacity(variant_data.fields.len() + 1);
409        new_fields.extend(variant_data.fields.iter().cloned());
410        new_fields.push(value);
411
412        let new_list = Value::Variant(Arc::new(VariantData::new(
413            variant_data.tag.clone(),
414            new_fields,
415        )));
416
417        push(stack, new_list)
418    }
419}
420
421/// Get an element from a list by index
422///
423/// Stack effect: ( Variant Int -- Value Bool )
424///
425/// Returns the value at the given index and true, or
426/// a placeholder value and false if index is out of bounds.
427///
428/// # Error Handling
429/// - Empty stack: Sets runtime error, returns 0 and false
430/// - Type mismatch: Sets runtime error, returns 0 and false
431/// - Out of bounds: Returns 0 and false (no error set, this is expected)
432///
433/// # Safety
434/// Stack must have an Int on top and a Variant (list) below
435#[unsafe(no_mangle)]
436pub unsafe extern "C" fn patch_seq_list_get(stack: Stack) -> Stack {
437    unsafe {
438        // Check stack depth before any pops to avoid partial consumption
439        if stack_depth(stack) < 2 {
440            set_runtime_error("list.get: stack underflow (need 2 values)");
441            return stack;
442        }
443        let (stack, index_val) = pop(stack);
444        let (stack, list_val) = pop(stack);
445
446        let index = match index_val {
447            Value::Int(i) => i,
448            _ => {
449                set_runtime_error(format!(
450                    "list.get: expected Int (index), got {:?}",
451                    index_val
452                ));
453                let stack = push(stack, Value::Int(0));
454                return push(stack, Value::Bool(false));
455            }
456        };
457
458        let variant_data = match list_val {
459            Value::Variant(v) => v,
460            _ => {
461                set_runtime_error(format!(
462                    "list.get: expected Variant (list), got {:?}",
463                    list_val
464                ));
465                let stack = push(stack, Value::Int(0));
466                return push(stack, Value::Bool(false));
467            }
468        };
469
470        if index < 0 || index as usize >= variant_data.fields.len() {
471            // Out of bounds - return false
472            let stack = push(stack, Value::Int(0)); // placeholder
473            push(stack, Value::Bool(false))
474        } else {
475            let value = variant_data.fields[index as usize].clone();
476            let stack = push(stack, value);
477            push(stack, Value::Bool(true))
478        }
479    }
480}
481
482/// Set an element in a list by index (functional - returns new list)
483///
484/// Stack effect: ( Variant Int Value -- Variant Bool )
485///
486/// Returns a new list with the value at the given index replaced, and true.
487/// If index is out of bounds, returns the original list and false.
488///
489/// # Error Handling
490/// - Empty stack: Sets runtime error, returns unchanged stack
491/// - Type mismatch: Sets runtime error, returns original list and false
492/// - Out of bounds: Returns original list and false (no error set, this is expected)
493///
494/// # Safety
495/// Stack must have Value on top, Int below, and Variant (list) below that
496#[unsafe(no_mangle)]
497pub unsafe extern "C" fn patch_seq_list_set(stack: Stack) -> Stack {
498    unsafe {
499        // Check stack depth before any pops to avoid partial consumption
500        if stack_depth(stack) < 3 {
501            set_runtime_error("list.set: stack underflow (need 3 values)");
502            return stack;
503        }
504        let (stack, value) = pop(stack);
505        let (stack, index_val) = pop(stack);
506        let (stack, list_val) = pop(stack);
507
508        let index = match index_val {
509            Value::Int(i) => i,
510            _ => {
511                set_runtime_error(format!(
512                    "list.set: expected Int (index), got {:?}",
513                    index_val
514                ));
515                // Return the list and false
516                let stack = push(stack, list_val);
517                return push(stack, Value::Bool(false));
518            }
519        };
520
521        let variant_data = match &list_val {
522            Value::Variant(v) => v,
523            _ => {
524                set_runtime_error(format!(
525                    "list.set: expected Variant (list), got {:?}",
526                    list_val
527                ));
528                let stack = push(stack, list_val);
529                return push(stack, Value::Bool(false));
530            }
531        };
532
533        if index < 0 || index as usize >= variant_data.fields.len() {
534            // Out of bounds - return original list and false
535            let stack = push(stack, list_val);
536            push(stack, Value::Bool(false))
537        } else {
538            // Create new list with element replaced
539            let mut new_fields: Vec<Value> = variant_data.fields.to_vec();
540            new_fields[index as usize] = value;
541
542            let new_list = Value::Variant(Arc::new(VariantData::new(
543                variant_data.tag.clone(),
544                new_fields,
545            )));
546
547            let stack = push(stack, new_list);
548            push(stack, Value::Bool(true))
549        }
550    }
551}
552
553// Public re-exports
554pub use patch_seq_list_each as list_each;
555pub use patch_seq_list_empty as list_empty;
556pub use patch_seq_list_filter as list_filter;
557pub use patch_seq_list_fold as list_fold;
558pub use patch_seq_list_get as list_get;
559pub use patch_seq_list_length as list_length;
560pub use patch_seq_list_make as list_make;
561pub use patch_seq_list_map as list_map;
562pub use patch_seq_list_push as list_push;
563pub use patch_seq_list_set as list_set;
564
565#[cfg(test)]
566mod tests {
567    use super::*;
568    use crate::seqstring::global_string;
569
570    // Helper quotation: double an integer
571    unsafe extern "C" fn double_quot(stack: Stack) -> Stack {
572        unsafe {
573            let (stack, val) = pop(stack);
574            match val {
575                Value::Int(n) => push(stack, Value::Int(n * 2)),
576                _ => panic!("Expected Int"),
577            }
578        }
579    }
580
581    // Helper quotation: check if positive
582    unsafe extern "C" fn is_positive_quot(stack: Stack) -> Stack {
583        unsafe {
584            let (stack, val) = pop(stack);
585            match val {
586                Value::Int(n) => push(stack, Value::Bool(n > 0)),
587                _ => panic!("Expected Int"),
588            }
589        }
590    }
591
592    // Helper quotation: add two integers
593    unsafe extern "C" fn add_quot(stack: Stack) -> Stack {
594        unsafe {
595            let (stack, b) = pop(stack);
596            let (stack, a) = pop(stack);
597            match (a, b) {
598                (Value::Int(x), Value::Int(y)) => push(stack, Value::Int(x + y)),
599                _ => panic!("Expected two Ints"),
600            }
601        }
602    }
603
604    #[test]
605    fn test_list_map_double() {
606        unsafe {
607            // Create list [1, 2, 3]
608            let list = Value::Variant(Arc::new(VariantData::new(
609                global_string("List".to_string()),
610                vec![Value::Int(1), Value::Int(2), Value::Int(3)],
611            )));
612
613            let stack = crate::stack::alloc_test_stack();
614            let stack = push(stack, list);
615            let fn_ptr = double_quot as usize;
616            let stack = push(
617                stack,
618                Value::Quotation {
619                    wrapper: fn_ptr,
620                    impl_: fn_ptr,
621                },
622            );
623            let stack = list_map(stack);
624
625            let (_stack, result) = pop(stack);
626            match result {
627                Value::Variant(v) => {
628                    assert_eq!(v.fields.len(), 3);
629                    assert_eq!(v.fields[0], Value::Int(2));
630                    assert_eq!(v.fields[1], Value::Int(4));
631                    assert_eq!(v.fields[2], Value::Int(6));
632                }
633                _ => panic!("Expected Variant"),
634            }
635        }
636    }
637
638    #[test]
639    fn test_list_filter_positive() {
640        unsafe {
641            // Create list [-1, 2, -3, 4, 0]
642            let list = Value::Variant(Arc::new(VariantData::new(
643                global_string("List".to_string()),
644                vec![
645                    Value::Int(-1),
646                    Value::Int(2),
647                    Value::Int(-3),
648                    Value::Int(4),
649                    Value::Int(0),
650                ],
651            )));
652
653            let stack = crate::stack::alloc_test_stack();
654            let stack = push(stack, list);
655            let fn_ptr = is_positive_quot as usize;
656            let stack = push(
657                stack,
658                Value::Quotation {
659                    wrapper: fn_ptr,
660                    impl_: fn_ptr,
661                },
662            );
663            let stack = list_filter(stack);
664
665            let (_stack, result) = pop(stack);
666            match result {
667                Value::Variant(v) => {
668                    assert_eq!(v.fields.len(), 2);
669                    assert_eq!(v.fields[0], Value::Int(2));
670                    assert_eq!(v.fields[1], Value::Int(4));
671                }
672                _ => panic!("Expected Variant"),
673            }
674        }
675    }
676
677    #[test]
678    fn test_list_fold_sum() {
679        unsafe {
680            // Create list [1, 2, 3, 4, 5]
681            let list = Value::Variant(Arc::new(VariantData::new(
682                global_string("List".to_string()),
683                vec![
684                    Value::Int(1),
685                    Value::Int(2),
686                    Value::Int(3),
687                    Value::Int(4),
688                    Value::Int(5),
689                ],
690            )));
691
692            let stack = crate::stack::alloc_test_stack();
693            let stack = push(stack, list);
694            let stack = push(stack, Value::Int(0)); // initial accumulator
695            let fn_ptr = add_quot as usize;
696            let stack = push(
697                stack,
698                Value::Quotation {
699                    wrapper: fn_ptr,
700                    impl_: fn_ptr,
701                },
702            );
703            let stack = list_fold(stack);
704
705            let (_stack, result) = pop(stack);
706            assert_eq!(result, Value::Int(15)); // 1+2+3+4+5 = 15
707        }
708    }
709
710    #[test]
711    fn test_list_fold_empty() {
712        unsafe {
713            // Create empty list
714            let list = Value::Variant(Arc::new(VariantData::new(
715                global_string("List".to_string()),
716                vec![],
717            )));
718
719            let stack = crate::stack::alloc_test_stack();
720            let stack = push(stack, list);
721            let stack = push(stack, Value::Int(42)); // initial accumulator
722            let fn_ptr = add_quot as usize;
723            let stack = push(
724                stack,
725                Value::Quotation {
726                    wrapper: fn_ptr,
727                    impl_: fn_ptr,
728                },
729            );
730            let stack = list_fold(stack);
731
732            let (_stack, result) = pop(stack);
733            assert_eq!(result, Value::Int(42)); // Should return initial value
734        }
735    }
736
737    #[test]
738    fn test_list_length() {
739        unsafe {
740            let list = Value::Variant(Arc::new(VariantData::new(
741                global_string("List".to_string()),
742                vec![Value::Int(1), Value::Int(2), Value::Int(3)],
743            )));
744
745            let stack = crate::stack::alloc_test_stack();
746            let stack = push(stack, list);
747            let stack = list_length(stack);
748
749            let (_stack, result) = pop(stack);
750            assert_eq!(result, Value::Int(3));
751        }
752    }
753
754    #[test]
755    fn test_list_empty_true() {
756        unsafe {
757            let list = Value::Variant(Arc::new(VariantData::new(
758                global_string("List".to_string()),
759                vec![],
760            )));
761
762            let stack = crate::stack::alloc_test_stack();
763            let stack = push(stack, list);
764            let stack = list_empty(stack);
765
766            let (_stack, result) = pop(stack);
767            assert_eq!(result, Value::Bool(true));
768        }
769    }
770
771    #[test]
772    fn test_list_empty_false() {
773        unsafe {
774            let list = Value::Variant(Arc::new(VariantData::new(
775                global_string("List".to_string()),
776                vec![Value::Int(1)],
777            )));
778
779            let stack = crate::stack::alloc_test_stack();
780            let stack = push(stack, list);
781            let stack = list_empty(stack);
782
783            let (_stack, result) = pop(stack);
784            assert_eq!(result, Value::Bool(false));
785        }
786    }
787
788    #[test]
789    fn test_list_map_empty() {
790        unsafe {
791            let list = Value::Variant(Arc::new(VariantData::new(
792                global_string("List".to_string()),
793                vec![],
794            )));
795
796            let stack = crate::stack::alloc_test_stack();
797            let stack = push(stack, list);
798            let fn_ptr = double_quot as usize;
799            let stack = push(
800                stack,
801                Value::Quotation {
802                    wrapper: fn_ptr,
803                    impl_: fn_ptr,
804                },
805            );
806            let stack = list_map(stack);
807
808            let (_stack, result) = pop(stack);
809            match result {
810                Value::Variant(v) => {
811                    assert_eq!(v.fields.len(), 0);
812                }
813                _ => panic!("Expected Variant"),
814            }
815        }
816    }
817
818    #[test]
819    fn test_list_map_preserves_tag() {
820        unsafe {
821            // Create list with custom tag
822            let list = Value::Variant(Arc::new(VariantData::new(
823                global_string("CustomTag".to_string()),
824                vec![Value::Int(1), Value::Int(2)],
825            )));
826
827            let stack = crate::stack::alloc_test_stack();
828            let stack = push(stack, list);
829            let fn_ptr = double_quot as usize;
830            let stack = push(
831                stack,
832                Value::Quotation {
833                    wrapper: fn_ptr,
834                    impl_: fn_ptr,
835                },
836            );
837            let stack = list_map(stack);
838
839            let (_stack, result) = pop(stack);
840            match result {
841                Value::Variant(v) => {
842                    assert_eq!(v.tag.as_str(), "CustomTag"); // Tag preserved
843                    assert_eq!(v.fields[0], Value::Int(2));
844                    assert_eq!(v.fields[1], Value::Int(4));
845                }
846                _ => panic!("Expected Variant"),
847            }
848        }
849    }
850
851    // Helper closure function: adds captured value to element
852    // Closure receives: stack with element, env with [captured_value]
853    unsafe extern "C" fn add_captured_closure(
854        stack: Stack,
855        env: *const Value,
856        _env_len: usize,
857    ) -> Stack {
858        unsafe {
859            let (stack, val) = pop(stack);
860            let captured = &*env; // First (and only) captured value
861            match (val, captured) {
862                (Value::Int(n), Value::Int(c)) => push(stack, Value::Int(n + c)),
863                _ => panic!("Expected Int"),
864            }
865        }
866    }
867
868    #[test]
869    fn test_list_map_with_closure() {
870        unsafe {
871            // Create list [1, 2, 3]
872            let list = Value::Variant(Arc::new(VariantData::new(
873                global_string("List".to_string()),
874                vec![Value::Int(1), Value::Int(2), Value::Int(3)],
875            )));
876
877            // Create closure that adds 10 to each element
878            let env: std::sync::Arc<[Value]> =
879                std::sync::Arc::from(vec![Value::Int(10)].into_boxed_slice());
880            let closure = Value::Closure {
881                fn_ptr: add_captured_closure as usize,
882                env,
883            };
884
885            let stack = crate::stack::alloc_test_stack();
886            let stack = push(stack, list);
887            let stack = push(stack, closure);
888            let stack = list_map(stack);
889
890            let (_stack, result) = pop(stack);
891            match result {
892                Value::Variant(v) => {
893                    assert_eq!(v.fields.len(), 3);
894                    assert_eq!(v.fields[0], Value::Int(11)); // 1 + 10
895                    assert_eq!(v.fields[1], Value::Int(12)); // 2 + 10
896                    assert_eq!(v.fields[2], Value::Int(13)); // 3 + 10
897                }
898                _ => panic!("Expected Variant"),
899            }
900        }
901    }
902
903    #[test]
904    fn test_list_get_type_error_index() {
905        unsafe {
906            crate::error::clear_runtime_error();
907
908            let list = Value::Variant(Arc::new(VariantData::new(
909                global_string("List".to_string()),
910                vec![Value::Int(1), Value::Int(2)],
911            )));
912
913            let stack = crate::stack::alloc_test_stack();
914            let stack = push(stack, list);
915            let stack = push(stack, Value::Bool(true)); // Wrong type - should be Int
916            let stack = list_get(stack);
917
918            // Should have set an error
919            assert!(crate::error::has_runtime_error());
920            let error = crate::error::take_runtime_error().unwrap();
921            assert!(error.contains("expected Int"));
922
923            // Should return (0, false)
924            let (stack, success) = pop(stack);
925            assert_eq!(success, Value::Bool(false));
926            let (_stack, value) = pop(stack);
927            assert_eq!(value, Value::Int(0));
928        }
929    }
930
931    #[test]
932    fn test_list_get_type_error_list() {
933        unsafe {
934            crate::error::clear_runtime_error();
935
936            let stack = crate::stack::alloc_test_stack();
937            let stack = push(stack, Value::Int(42)); // Wrong type - should be Variant
938            let stack = push(stack, Value::Int(0)); // index
939            let stack = list_get(stack);
940
941            // Should have set an error
942            assert!(crate::error::has_runtime_error());
943            let error = crate::error::take_runtime_error().unwrap();
944            assert!(error.contains("expected Variant"));
945
946            // Should return (0, false)
947            let (stack, success) = pop(stack);
948            assert_eq!(success, Value::Bool(false));
949            let (_stack, value) = pop(stack);
950            assert_eq!(value, Value::Int(0));
951        }
952    }
953
954    #[test]
955    fn test_list_set_type_error_index() {
956        unsafe {
957            crate::error::clear_runtime_error();
958
959            let list = Value::Variant(Arc::new(VariantData::new(
960                global_string("List".to_string()),
961                vec![Value::Int(1), Value::Int(2)],
962            )));
963
964            let stack = crate::stack::alloc_test_stack();
965            let stack = push(stack, list);
966            let stack = push(stack, Value::Bool(true)); // Wrong type - should be Int
967            let stack = push(stack, Value::Int(99)); // new value
968            let stack = list_set(stack);
969
970            // Should have set an error
971            assert!(crate::error::has_runtime_error());
972            let error = crate::error::take_runtime_error().unwrap();
973            assert!(error.contains("expected Int"));
974
975            // Should return (list, false)
976            let (stack, success) = pop(stack);
977            assert_eq!(success, Value::Bool(false));
978            let (_stack, returned_list) = pop(stack);
979            assert!(matches!(returned_list, Value::Variant(_)));
980        }
981    }
982
983    #[test]
984    fn test_list_set_type_error_list() {
985        unsafe {
986            crate::error::clear_runtime_error();
987
988            let stack = crate::stack::alloc_test_stack();
989            let stack = push(stack, Value::Int(42)); // Wrong type - should be Variant
990            let stack = push(stack, Value::Int(0)); // index
991            let stack = push(stack, Value::Int(99)); // new value
992            let stack = list_set(stack);
993
994            // Should have set an error
995            assert!(crate::error::has_runtime_error());
996            let error = crate::error::take_runtime_error().unwrap();
997            assert!(error.contains("expected Variant"));
998
999            // Should return (original value, false)
1000            let (stack, success) = pop(stack);
1001            assert_eq!(success, Value::Bool(false));
1002            let (_stack, returned) = pop(stack);
1003            assert_eq!(returned, Value::Int(42)); // Original value returned
1004        }
1005    }
1006}