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