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}