seq_runtime/variant_ops/modify.rs
1//! Mutating-by-clone variant helpers: `variant_append`, `variant_first`,
2//! `variant_last`, `variant_init`, and the field-unpacker used for match
3//! expressions.
4
5use crate::stack::{Stack, peek_heap_mut_second, pop, push};
6use crate::value::Value;
7use std::sync::Arc;
8
9/// Append a value to a variant, returning a new variant
10///
11/// Stack effect: ( Variant Value -- Variant' )
12///
13/// Creates a new variant with the same tag as the input, but with
14/// the new value appended to its fields. The original variant is
15/// not modified (functional/immutable style).
16///
17/// Example: For arrays, `[1, 2] 3 variant-append` produces `[1, 2, 3]`
18/// Example: For objects, `{} "key" variant-append "val" variant-append` builds `{"key": "val"}`
19///
20/// # Safety
21/// Stack must have a Variant and a Value on top
22#[unsafe(no_mangle)]
23pub unsafe extern "C" fn patch_seq_variant_append(stack: Stack) -> Stack {
24 use crate::value::VariantData;
25
26 unsafe {
27 // Fast path: peek at the variant at sp-2 without popping.
28 // SAFETY: variant.append requires two values on the stack (enforced by
29 // the type checker), so stack.sub(2) is valid.
30 if let Some(Value::Variant(variant_arc)) = peek_heap_mut_second(stack)
31 && let Some(data) = Arc::get_mut(variant_arc)
32 {
33 // Sole owner all the way down — mutate in place.
34 // Safety: `data` points into the Value at sp-2. `pop` only
35 // touches sp-1 (decrements sp, reads that slot), so sp-2's
36 // memory is not accessed or invalidated by the pop.
37 let (stack, value) = pop(stack);
38 data.fields.push(value);
39 return stack; // Variant is still at sp-1, mutated in place
40 }
41
42 // Slow path: pop both, clone if shared, push result
43 let (stack, value) = pop(stack);
44 let (stack, variant_val) = pop(stack);
45
46 match variant_val {
47 Value::Variant(mut arc) => {
48 if let Some(data) = Arc::get_mut(&mut arc) {
49 data.fields.push(value);
50 push(stack, Value::Variant(arc))
51 } else {
52 let mut new_fields = Vec::with_capacity(arc.fields.len() + 1);
53 new_fields.extend(arc.fields.iter().cloned());
54 new_fields.push(value);
55 let new_variant =
56 Value::Variant(Arc::new(VariantData::new(arc.tag.clone(), new_fields)));
57 push(stack, new_variant)
58 }
59 }
60 _ => panic!("variant.append: expected Variant, got {:?}", variant_val),
61 }
62 }
63}
64
65/// Get the first field from a variant
66///
67/// Stack effect: ( Variant -- Value )
68///
69/// Returns a clone of the first field. Panics if the variant has no fields.
70/// Mirrors `variant_last` so callers reaching for either head- or
71/// tail-of-fields find the same shape.
72///
73/// # Safety
74/// Stack must have a Variant on top
75#[unsafe(no_mangle)]
76pub unsafe extern "C" fn patch_seq_variant_first(stack: Stack) -> Stack {
77 unsafe {
78 let (stack, variant_val) = pop(stack);
79
80 match variant_val {
81 Value::Variant(variant_data) => {
82 if variant_data.fields.is_empty() {
83 panic!("variant.first: variant has no fields");
84 }
85
86 let first = variant_data.fields[0].clone();
87 push(stack, first)
88 }
89 _ => panic!("variant.first: expected Variant, got {:?}", variant_val),
90 }
91 }
92}
93
94/// Get the last field from a variant
95///
96/// Stack effect: ( Variant -- Value )
97///
98/// Returns a clone of the last field. Panics if the variant has no fields.
99/// This is the "peek" operation for using variants as stacks.
100///
101/// # Safety
102/// Stack must have a Variant on top
103#[unsafe(no_mangle)]
104pub unsafe extern "C" fn patch_seq_variant_last(stack: Stack) -> Stack {
105 unsafe {
106 let (stack, variant_val) = pop(stack);
107
108 match variant_val {
109 Value::Variant(variant_data) => {
110 if variant_data.fields.is_empty() {
111 panic!("variant.last: variant has no fields");
112 }
113
114 let last = variant_data.fields.last().unwrap().clone();
115 push(stack, last)
116 }
117 _ => panic!("variant.last: expected Variant, got {:?}", variant_val),
118 }
119 }
120}
121
122/// Get all but the last field from a variant
123///
124/// Stack effect: ( Variant -- Variant' )
125///
126/// Returns a new variant with the same tag but without the last field.
127/// Panics if the variant has no fields.
128/// This is the "pop" operation for using variants as stacks (without returning the popped value).
129///
130/// # Safety
131/// Stack must have a Variant on top
132#[unsafe(no_mangle)]
133pub unsafe extern "C" fn patch_seq_variant_init(stack: Stack) -> Stack {
134 use crate::value::VariantData;
135
136 unsafe {
137 let (stack, variant_val) = pop(stack);
138
139 match variant_val {
140 Value::Variant(variant_data) => {
141 if variant_data.fields.is_empty() {
142 panic!("variant.init: variant has no fields");
143 }
144
145 // Create new fields without the last element
146 let new_fields: Vec<Value> =
147 variant_data.fields[..variant_data.fields.len() - 1].to_vec();
148
149 let new_variant = Value::Variant(Arc::new(VariantData::new(
150 variant_data.tag.clone(),
151 new_fields,
152 )));
153
154 push(stack, new_variant)
155 }
156 _ => panic!("variant.init: expected Variant, got {:?}", variant_val),
157 }
158 }
159}
160
161/// Unpack a variant's fields onto the stack
162///
163/// Takes a field count as parameter and:
164/// - Pops the variant from the stack
165/// - Pushes each field (0..field_count) in order
166///
167/// Stack effect: ( Variant -- field0 field1 ... fieldN-1 )
168///
169/// Used by match expression codegen to extract variant fields.
170///
171/// # Safety
172/// Stack must have a Variant on top with at least `field_count` fields
173#[unsafe(no_mangle)]
174pub unsafe extern "C" fn patch_seq_unpack_variant(stack: Stack, field_count: i64) -> Stack {
175 unsafe {
176 let (mut stack, variant_val) = pop(stack);
177
178 match variant_val {
179 Value::Variant(variant_data) => {
180 let count = field_count as usize;
181 if count > variant_data.fields.len() {
182 panic!(
183 "unpack-variant: requested {} fields but variant only has {}",
184 count,
185 variant_data.fields.len()
186 );
187 }
188
189 // Push each field in order (field0 first, then field1, etc.)
190 for i in 0..count {
191 stack = push(stack, variant_data.fields[i].clone());
192 }
193
194 stack
195 }
196 _ => panic!("unpack-variant: expected Variant, got {:?}", variant_val),
197 }
198 }
199}