Skip to main content

seq_runtime/list_ops/
access.rs

1//! Indexed list access: `list_get` (returns success flag), `list_set`
2//! (functional update returning a new list + success flag), and the
3//! `list_first` / `list_last` convenience accessors.
4
5use super::combinators::stack_depth;
6use crate::error::set_runtime_error;
7use crate::stack::{Stack, heap_value_mut, pop, push};
8use crate::value::{Value, VariantData};
9use std::sync::Arc;
10
11/// # Safety
12/// Stack must have the expected values on top for this operation.
13#[unsafe(no_mangle)]
14pub unsafe extern "C" fn patch_seq_list_get(stack: Stack) -> Stack {
15    unsafe {
16        // Check stack depth before any pops to avoid partial consumption
17        if stack_depth(stack) < 2 {
18            set_runtime_error("list.get: stack underflow (need 2 values)");
19            return stack;
20        }
21        let (stack, index_val) = pop(stack);
22        let (stack, list_val) = pop(stack);
23
24        let index = match index_val {
25            Value::Int(i) => i,
26            _ => {
27                set_runtime_error(format!(
28                    "list.get: expected Int (index), got {:?}",
29                    index_val
30                ));
31                let stack = push(stack, Value::Int(0));
32                return push(stack, Value::Bool(false));
33            }
34        };
35
36        let variant_data = match list_val {
37            Value::Variant(v) => v,
38            _ => {
39                set_runtime_error(format!(
40                    "list.get: expected Variant (list), got {:?}",
41                    list_val
42                ));
43                let stack = push(stack, Value::Int(0));
44                return push(stack, Value::Bool(false));
45            }
46        };
47
48        if index < 0 || index as usize >= variant_data.fields.len() {
49            // Out of bounds - return false
50            let stack = push(stack, Value::Int(0)); // placeholder
51            push(stack, Value::Bool(false))
52        } else {
53            let value = variant_data.fields[index as usize].clone();
54            let stack = push(stack, value);
55            push(stack, Value::Bool(true))
56        }
57    }
58}
59
60/// Set an element in a list by index with COW optimization.
61///
62/// Stack effect: ( Variant Int Value -- Variant Bool )
63///
64/// Fast path: if the list (at sp-3) is sole-owned and the index (at sp-2)
65/// is a valid tagged int, peeks at both without popping, then pops value
66/// and index and mutates the list in place.
67/// Slow path: pops all three, clones if shared, pushes new list.
68///
69/// Returns the list with the value at the given index replaced, and true.
70/// If index is out of bounds, returns the original list and false.
71///
72/// # Error Handling
73/// - Empty stack: Sets runtime error, returns unchanged stack
74/// - Type mismatch: Sets runtime error, returns original list and false
75/// - Out of bounds: Returns original list and false (no error set, this is expected)
76///
77/// # Safety
78/// Stack must have Value on top, Int below, and Variant (list) below that
79#[unsafe(no_mangle)]
80pub unsafe extern "C" fn patch_seq_list_set(stack: Stack) -> Stack {
81    unsafe {
82        // Check stack depth before any pops to avoid partial consumption
83        if stack_depth(stack) < 3 {
84            set_runtime_error("list.set: stack underflow (need 3 values)");
85            return stack;
86        }
87
88        // Fast path: peek at the list at sp-3 without popping.
89        // SAFETY: stack depth >= 3 verified above, so stack.sub(3) is valid.
90        // The index at sp-2 must be an Int for the fast path; read it inline
91        // to avoid popping/pushing back on type mismatch.
92        if let Some(Value::Variant(variant_arc)) = heap_value_mut(stack.sub(3))
93            && let Some(data) = Arc::get_mut(variant_arc)
94        {
95            // Peek at the index at sp-2 without popping — it's an Int (inline),
96            // so we can read it directly from the tagged value.
97            let index_sv = *stack.sub(2);
98            if crate::tagged_stack::is_tagged_int(index_sv) {
99                let index = crate::tagged_stack::untag_int(index_sv);
100                if index >= 0 && (index as usize) < data.fields.len() {
101                    // Sole owner, valid index — pop value and index, mutate in place.
102                    // Safety: two pops move sp by 2; the list at the
103                    // original sp-3 (now sp-1) is not invalidated.
104                    let (stack, value) = pop(stack);
105                    let (stack, _index) = pop(stack);
106                    data.fields[index as usize] = value;
107                    return push(stack, Value::Bool(true));
108                }
109                // Out of bounds — pop value and index, leave list at sp-1
110                let (stack, _value) = pop(stack);
111                let (stack, _index) = pop(stack);
112                return push(stack, Value::Bool(false));
113            }
114        }
115
116        // Slow path: pop all three, clone if shared, push result
117        let (stack, value) = pop(stack);
118        let (stack, index_val) = pop(stack);
119        let (stack, list_val) = pop(stack);
120
121        let index = match index_val {
122            Value::Int(i) => i,
123            _ => {
124                set_runtime_error(format!(
125                    "list.set: expected Int (index), got {:?}",
126                    index_val
127                ));
128                let stack = push(stack, list_val);
129                return push(stack, Value::Bool(false));
130            }
131        };
132
133        let mut arc = match list_val {
134            Value::Variant(v) => v,
135            other => {
136                set_runtime_error(format!(
137                    "list.set: expected Variant (list), got {:?}",
138                    other
139                ));
140                let stack = push(stack, other);
141                return push(stack, Value::Bool(false));
142            }
143        };
144
145        if index < 0 || index as usize >= arc.fields.len() {
146            let stack = push(stack, Value::Variant(arc));
147            push(stack, Value::Bool(false))
148        } else if let Some(data) = Arc::get_mut(&mut arc) {
149            data.fields[index as usize] = value;
150            let stack = push(stack, Value::Variant(arc));
151            push(stack, Value::Bool(true))
152        } else {
153            let mut new_fields: Vec<Value> = arc.fields.to_vec();
154            new_fields[index as usize] = value;
155            let new_list = Value::Variant(Arc::new(VariantData::new(arc.tag.clone(), new_fields)));
156            let stack = push(stack, new_list);
157            push(stack, Value::Bool(true))
158        }
159    }
160}
161
162/// Shared body for `list.first` and `list.last` — both pop a list, push
163/// `(value true)` on a non-empty list or `(Int 0, false)` on an empty
164/// or wrong-typed list. `pick` selects which element the non-empty path
165/// returns; the only difference between `list.first` and `list.last` is
166/// `[0]` vs `last()`. Stays a free function (not a closure-taking
167/// generic) so the FFI extern wrappers remain trivial inline calls.
168unsafe fn list_endpoint(stack: Stack, op_name: &'static str, pick: fn(&[Value]) -> Value) -> Stack {
169    unsafe {
170        if stack_depth(stack) < 1 {
171            set_runtime_error(format!("{}: stack underflow (need 1 value)", op_name));
172            return stack;
173        }
174        let (stack, list_val) = pop(stack);
175
176        let variant_data = match list_val {
177            Value::Variant(v) => v,
178            _ => {
179                set_runtime_error(format!(
180                    "{}: expected Variant (list), got {:?}",
181                    op_name, list_val
182                ));
183                let stack = push(stack, Value::Int(0));
184                return push(stack, Value::Bool(false));
185            }
186        };
187
188        if variant_data.fields.is_empty() {
189            let stack = push(stack, Value::Int(0));
190            push(stack, Value::Bool(false))
191        } else {
192            let value = pick(&variant_data.fields);
193            let stack = push(stack, value);
194            push(stack, Value::Bool(true))
195        }
196    }
197}
198
199/// First element of a list.
200///
201/// Stack effect: ( V -- T Bool )
202///
203/// Convenience for the common `0 list.get` idiom. Returns the element at
204/// index 0 plus `true` on a non-empty list, or a placeholder `Int 0` plus
205/// `false` on an empty list (matching `list.get`'s out-of-bounds shape).
206///
207/// # Safety
208/// Stack must have a Variant (list) value on top.
209#[unsafe(no_mangle)]
210pub unsafe extern "C" fn patch_seq_list_first(stack: Stack) -> Stack {
211    unsafe { list_endpoint(stack, "list.first", |fields| fields[0].clone()) }
212}
213
214/// Last element of a list.
215///
216/// Stack effect: ( V -- T Bool )
217///
218/// Convenience for the `dup list.length 1 i.- list.get` idiom. Returns the
219/// element at the highest index plus `true` on a non-empty list, or a
220/// placeholder `Int 0` plus `false` on an empty list.
221///
222/// # Safety
223/// Stack must have a Variant (list) value on top.
224#[unsafe(no_mangle)]
225pub unsafe extern "C" fn patch_seq_list_last(stack: Stack) -> Stack {
226    unsafe { list_endpoint(stack, "list.last", |fields| fields.last().unwrap().clone()) }
227}