seq_runtime/list_ops/basic.rs
1//! Basic list operations: `list_length`, `list_empty`, `list_make`,
2//! `list_push`, `list_reverse`.
3
4use crate::stack::{Stack, peek_heap_mut_second, pop, push};
5use crate::value::{Value, VariantData};
6use std::sync::Arc;
7
8/// # Safety
9/// Stack must have the expected values on top for this operation.
10#[unsafe(no_mangle)]
11pub unsafe extern "C" fn patch_seq_list_length(stack: Stack) -> Stack {
12 unsafe { crate::variant_ops::patch_seq_variant_field_count(stack) }
13}
14
15/// Check if a list is empty
16///
17/// Stack effect: ( Variant -- Bool )
18///
19/// Returns true if the list has no elements, false otherwise.
20///
21/// # Safety
22/// Stack must have a Variant on top
23#[unsafe(no_mangle)]
24pub unsafe extern "C" fn patch_seq_list_empty(stack: Stack) -> Stack {
25 unsafe {
26 let (stack, list_val) = pop(stack);
27
28 let is_empty = match list_val {
29 Value::Variant(v) => v.fields.is_empty(),
30 _ => panic!("list-empty?: expected Variant (list), got {:?}", list_val),
31 };
32
33 push(stack, Value::Bool(is_empty))
34 }
35}
36
37/// Create an empty list
38///
39/// Stack effect: ( -- Variant )
40///
41/// Returns a new empty list (Variant with tag "List" and no fields).
42///
43/// # Safety
44/// No requirements on stack
45#[unsafe(no_mangle)]
46pub unsafe extern "C" fn patch_seq_list_make(stack: Stack) -> Stack {
47 unsafe {
48 let list = Value::Variant(Arc::new(VariantData::new(
49 crate::seqstring::global_string("List".to_string()),
50 vec![],
51 )));
52 push(stack, list)
53 }
54}
55
56/// Append an element to a list with COW optimization.
57///
58/// Stack effect: ( Variant Value -- Variant )
59///
60/// Fast path: if the list (at sp-2) is a sole-owned heap value, mutates
61/// in place via `peek_heap_mut_second` — no Arc alloc/dealloc cycle.
62/// Slow path: pops, clones, and pushes a new list.
63///
64/// # Safety
65/// Stack must have a Value on top and a Variant (list) below
66#[unsafe(no_mangle)]
67pub unsafe extern "C" fn patch_seq_list_push(stack: Stack) -> Stack {
68 unsafe {
69 // Try the fast path: peek at the list without popping.
70 // SAFETY: list.push requires two values on the stack (enforced by
71 // the type checker), so stack.sub(2) is valid.
72 if let Some(Value::Variant(variant_arc)) = peek_heap_mut_second(stack)
73 && let Some(data) = Arc::get_mut(variant_arc)
74 {
75 // Sole owner all the way down — mutate in place.
76 // Safety: `data` points into the Value at sp-2. `pop` only
77 // touches sp-1 (decrements sp, reads that slot), so sp-2's
78 // memory is not accessed or invalidated by the pop.
79 let (stack, value) = pop(stack);
80 data.fields.push(value);
81 return stack; // List is still at sp-1, untouched
82 }
83
84 // Slow path: pop both, clone if shared, push result
85 let (stack, value) = pop(stack);
86 let (stack, list_val) = pop(stack);
87 let variant_arc = match list_val {
88 Value::Variant(v) => v,
89 _ => panic!("list.push: expected Variant (list), got {:?}", list_val),
90 };
91 push_to_variant(stack, variant_arc, value)
92 }
93}
94
95/// COW push helper: append value to variant, mutating in place when sole owner.
96unsafe fn push_to_variant(stack: Stack, mut variant_arc: Arc<VariantData>, value: Value) -> Stack {
97 unsafe {
98 if let Some(data) = Arc::get_mut(&mut variant_arc) {
99 // Sole owner — mutate in place (amortized O(1))
100 data.fields.push(value);
101 push(stack, Value::Variant(variant_arc))
102 } else {
103 // Shared — clone and append (O(n))
104 let mut new_fields = Vec::with_capacity(variant_arc.fields.len() + 1);
105 new_fields.extend(variant_arc.fields.iter().cloned());
106 new_fields.push(value);
107 let new_list = Value::Variant(Arc::new(VariantData::new(
108 variant_arc.tag.clone(),
109 new_fields,
110 )));
111 push(stack, new_list)
112 }
113 }
114}
115
116/// Get an element from a list by index
117///
118/// Stack effect: ( Variant Int -- Value Bool )
119///
120/// Returns the value at the given index and true, or
121/// a placeholder value and false if index is out of bounds.
122///
123/// # Error Handling
124/// - Empty stack: Sets runtime error, returns 0 and false
125/// - Type mismatch: Sets runtime error, returns 0 and false
126/// - Out of bounds: Returns 0 and false (no error set, this is expected)
127///
128/// # Safety
129/// Stack must have an Int on top and a Variant (list) below
130#[unsafe(no_mangle)]
131pub unsafe extern "C" fn patch_seq_list_reverse(stack: Stack) -> Stack {
132 unsafe {
133 let (stack, list_val) = pop(stack);
134
135 let variant_data = match list_val {
136 Value::Variant(v) => v,
137 _ => panic!("list.reverse: expected Variant (list), got {:?}", list_val),
138 };
139
140 let mut reversed: Vec<Value> = variant_data.fields.to_vec();
141 reversed.reverse();
142
143 let new_variant = Value::Variant(Arc::new(VariantData::new(
144 variant_data.tag.clone(),
145 reversed,
146 )));
147
148 push(stack, new_variant)
149 }
150}