Skip to main content

seq_runtime/list_ops/
access.rs

1//! Indexed list access: `list_get` (returns success flag) and `list_set`
2//! (functional update returning a new list + success flag).
3
4use super::combinators::stack_depth;
5use crate::error::set_runtime_error;
6use crate::stack::{Stack, heap_value_mut, pop, push};
7use crate::value::{Value, VariantData};
8use std::sync::Arc;
9
10/// # Safety
11/// Stack must have the expected values on top for this operation.
12#[unsafe(no_mangle)]
13pub unsafe extern "C" fn patch_seq_list_get(stack: Stack) -> Stack {
14    unsafe {
15        // Check stack depth before any pops to avoid partial consumption
16        if stack_depth(stack) < 2 {
17            set_runtime_error("list.get: stack underflow (need 2 values)");
18            return stack;
19        }
20        let (stack, index_val) = pop(stack);
21        let (stack, list_val) = pop(stack);
22
23        let index = match index_val {
24            Value::Int(i) => i,
25            _ => {
26                set_runtime_error(format!(
27                    "list.get: expected Int (index), got {:?}",
28                    index_val
29                ));
30                let stack = push(stack, Value::Int(0));
31                return push(stack, Value::Bool(false));
32            }
33        };
34
35        let variant_data = match list_val {
36            Value::Variant(v) => v,
37            _ => {
38                set_runtime_error(format!(
39                    "list.get: expected Variant (list), got {:?}",
40                    list_val
41                ));
42                let stack = push(stack, Value::Int(0));
43                return push(stack, Value::Bool(false));
44            }
45        };
46
47        if index < 0 || index as usize >= variant_data.fields.len() {
48            // Out of bounds - return false
49            let stack = push(stack, Value::Int(0)); // placeholder
50            push(stack, Value::Bool(false))
51        } else {
52            let value = variant_data.fields[index as usize].clone();
53            let stack = push(stack, value);
54            push(stack, Value::Bool(true))
55        }
56    }
57}
58
59/// Set an element in a list by index with COW optimization.
60///
61/// Stack effect: ( Variant Int Value -- Variant Bool )
62///
63/// Fast path: if the list (at sp-3) is sole-owned and the index (at sp-2)
64/// is a valid tagged int, peeks at both without popping, then pops value
65/// and index and mutates the list in place.
66/// Slow path: pops all three, clones if shared, pushes new list.
67///
68/// Returns the list with the value at the given index replaced, and true.
69/// If index is out of bounds, returns the original list and false.
70///
71/// # Error Handling
72/// - Empty stack: Sets runtime error, returns unchanged stack
73/// - Type mismatch: Sets runtime error, returns original list and false
74/// - Out of bounds: Returns original list and false (no error set, this is expected)
75///
76/// # Safety
77/// Stack must have Value on top, Int below, and Variant (list) below that
78#[unsafe(no_mangle)]
79pub unsafe extern "C" fn patch_seq_list_set(stack: Stack) -> Stack {
80    unsafe {
81        // Check stack depth before any pops to avoid partial consumption
82        if stack_depth(stack) < 3 {
83            set_runtime_error("list.set: stack underflow (need 3 values)");
84            return stack;
85        }
86
87        // Fast path: peek at the list at sp-3 without popping.
88        // SAFETY: stack depth >= 3 verified above, so stack.sub(3) is valid.
89        // The index at sp-2 must be an Int for the fast path; read it inline
90        // to avoid popping/pushing back on type mismatch.
91        if let Some(Value::Variant(variant_arc)) = heap_value_mut(stack.sub(3))
92            && let Some(data) = Arc::get_mut(variant_arc)
93        {
94            // Peek at the index at sp-2 without popping — it's an Int (inline),
95            // so we can read it directly from the tagged value.
96            let index_sv = *stack.sub(2);
97            if crate::tagged_stack::is_tagged_int(index_sv) {
98                let index = crate::tagged_stack::untag_int(index_sv);
99                if index >= 0 && (index as usize) < data.fields.len() {
100                    // Sole owner, valid index — pop value and index, mutate in place.
101                    // Safety: two pops move sp by 2; the list at the
102                    // original sp-3 (now sp-1) is not invalidated.
103                    let (stack, value) = pop(stack);
104                    let (stack, _index) = pop(stack);
105                    data.fields[index as usize] = value;
106                    return push(stack, Value::Bool(true));
107                }
108                // Out of bounds — pop value and index, leave list at sp-1
109                let (stack, _value) = pop(stack);
110                let (stack, _index) = pop(stack);
111                return push(stack, Value::Bool(false));
112            }
113        }
114
115        // Slow path: pop all three, clone if shared, push result
116        let (stack, value) = pop(stack);
117        let (stack, index_val) = pop(stack);
118        let (stack, list_val) = pop(stack);
119
120        let index = match index_val {
121            Value::Int(i) => i,
122            _ => {
123                set_runtime_error(format!(
124                    "list.set: expected Int (index), got {:?}",
125                    index_val
126                ));
127                let stack = push(stack, list_val);
128                return push(stack, Value::Bool(false));
129            }
130        };
131
132        let mut arc = match list_val {
133            Value::Variant(v) => v,
134            other => {
135                set_runtime_error(format!(
136                    "list.set: expected Variant (list), got {:?}",
137                    other
138                ));
139                let stack = push(stack, other);
140                return push(stack, Value::Bool(false));
141            }
142        };
143
144        if index < 0 || index as usize >= arc.fields.len() {
145            let stack = push(stack, Value::Variant(arc));
146            push(stack, Value::Bool(false))
147        } else if let Some(data) = Arc::get_mut(&mut arc) {
148            data.fields[index as usize] = value;
149            let stack = push(stack, Value::Variant(arc));
150            push(stack, Value::Bool(true))
151        } else {
152            let mut new_fields: Vec<Value> = arc.fields.to_vec();
153            new_fields[index as usize] = value;
154            let new_list = Value::Variant(Arc::new(VariantData::new(arc.tag.clone(), new_fields)));
155            let stack = push(stack, new_list);
156            push(stack, Value::Bool(true))
157        }
158    }
159}