Skip to main content

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}