seq_runtime/
variant_ops.rs

1//! Variant field access operations for Seq
2//!
3//! Provides runtime functions for accessing variant fields, tags, and metadata.
4//! These are used to work with composite data created by operations like string-split.
5
6use crate::stack::{Stack, pop, push};
7use crate::value::Value;
8
9/// Get the number of fields in a variant
10///
11/// Stack effect: ( Variant -- Int )
12///
13/// # Safety
14/// Stack must have a Variant on top
15#[unsafe(no_mangle)]
16pub unsafe extern "C" fn patch_seq_variant_field_count(stack: Stack) -> Stack {
17    unsafe {
18        let (stack, value) = pop(stack);
19
20        match value {
21            Value::Variant(variant_data) => {
22                let count = variant_data.fields.len() as i64;
23                push(stack, Value::Int(count))
24            }
25            _ => panic!("variant-field-count: expected Variant, got {:?}", value),
26        }
27    }
28}
29
30/// Get the tag of a variant
31///
32/// Stack effect: ( Variant -- Int )
33///
34/// # Safety
35/// Stack must have a Variant on top
36#[unsafe(no_mangle)]
37pub unsafe extern "C" fn patch_seq_variant_tag(stack: Stack) -> Stack {
38    unsafe {
39        let (stack, value) = pop(stack);
40
41        match value {
42            Value::Variant(variant_data) => {
43                let tag = variant_data.tag as i64;
44                push(stack, Value::Int(tag))
45            }
46            _ => panic!("variant-tag: expected Variant, got {:?}", value),
47        }
48    }
49}
50
51/// Get a field from a variant at the given index
52///
53/// Stack effect: ( Variant Int -- Value )
54///
55/// Returns a clone of the field value at the specified index.
56/// Panics if index is out of bounds.
57///
58/// # Safety
59/// Stack must have a Variant and Int on top
60#[unsafe(no_mangle)]
61pub unsafe extern "C" fn patch_seq_variant_field_at(stack: Stack) -> Stack {
62    unsafe {
63        let (stack, index_val) = pop(stack);
64        let index = match index_val {
65            Value::Int(i) => i,
66            _ => panic!(
67                "variant-field-at: expected Int (index), got {:?}",
68                index_val
69            ),
70        };
71
72        if index < 0 {
73            panic!("variant-field-at: index cannot be negative: {}", index);
74        }
75
76        let (stack, variant_val) = pop(stack);
77
78        match variant_val {
79            Value::Variant(variant_data) => {
80                let idx = index as usize;
81                if idx >= variant_data.fields.len() {
82                    panic!(
83                        "variant-field-at: index {} out of bounds (variant has {} fields)",
84                        index,
85                        variant_data.fields.len()
86                    );
87                }
88
89                // Clone the field value and push it
90                let field = variant_data.fields[idx].clone();
91                push(stack, field)
92            }
93            _ => panic!("variant-field-at: expected Variant, got {:?}", variant_val),
94        }
95    }
96}
97
98// ============================================================================
99// Type-safe variant constructors with fixed arity
100// ============================================================================
101
102/// Create a variant with 0 fields (just a tag)
103///
104/// Stack effect: ( tag -- Variant )
105///
106/// # Safety
107/// Stack must have at least one Int (the tag) on top
108#[unsafe(no_mangle)]
109pub unsafe extern "C" fn patch_seq_make_variant_0(stack: Stack) -> Stack {
110    use crate::value::VariantData;
111
112    unsafe {
113        let (stack, tag_val) = pop(stack);
114        let tag = match tag_val {
115            Value::Int(t) => {
116                if t < 0 {
117                    panic!("make-variant-0: tag cannot be negative: {}", t);
118                }
119                t as u32
120            }
121            _ => panic!("make-variant-0: expected Int (tag), got {:?}", tag_val),
122        };
123
124        let variant = Value::Variant(Box::new(VariantData::new(tag, vec![])));
125        push(stack, variant)
126    }
127}
128
129/// Create a variant with 1 field
130///
131/// Stack effect: ( field1 tag -- Variant )
132///
133/// # Safety
134/// Stack must have field1 and tag on top
135#[unsafe(no_mangle)]
136pub unsafe extern "C" fn patch_seq_make_variant_1(stack: Stack) -> Stack {
137    use crate::value::VariantData;
138
139    unsafe {
140        let (stack, tag_val) = pop(stack);
141        let tag = match tag_val {
142            Value::Int(t) => {
143                if t < 0 {
144                    panic!("make-variant-1: tag cannot be negative: {}", t);
145                }
146                t as u32
147            }
148            _ => panic!("make-variant-1: expected Int (tag), got {:?}", tag_val),
149        };
150
151        let (stack, field1) = pop(stack);
152        let variant = Value::Variant(Box::new(VariantData::new(tag, vec![field1])));
153        push(stack, variant)
154    }
155}
156
157/// Create a variant with 2 fields
158///
159/// Stack effect: ( field1 field2 tag -- Variant )
160///
161/// # Safety
162/// Stack must have field1, field2, and tag on top
163#[unsafe(no_mangle)]
164pub unsafe extern "C" fn patch_seq_make_variant_2(stack: Stack) -> Stack {
165    use crate::value::VariantData;
166
167    unsafe {
168        let (stack, tag_val) = pop(stack);
169        let tag = match tag_val {
170            Value::Int(t) => {
171                if t < 0 {
172                    panic!("make-variant-2: tag cannot be negative: {}", t);
173                }
174                t as u32
175            }
176            _ => panic!("make-variant-2: expected Int (tag), got {:?}", tag_val),
177        };
178
179        let (stack, field2) = pop(stack);
180        let (stack, field1) = pop(stack);
181        let variant = Value::Variant(Box::new(VariantData::new(tag, vec![field1, field2])));
182        push(stack, variant)
183    }
184}
185
186/// Create a variant with 3 fields
187///
188/// Stack effect: ( field1 field2 field3 tag -- Variant )
189///
190/// # Safety
191/// Stack must have field1, field2, field3, and tag on top
192#[unsafe(no_mangle)]
193pub unsafe extern "C" fn patch_seq_make_variant_3(stack: Stack) -> Stack {
194    use crate::value::VariantData;
195
196    unsafe {
197        let (stack, tag_val) = pop(stack);
198        let tag = match tag_val {
199            Value::Int(t) => {
200                if t < 0 {
201                    panic!("make-variant-3: tag cannot be negative: {}", t);
202                }
203                t as u32
204            }
205            _ => panic!("make-variant-3: expected Int (tag), got {:?}", tag_val),
206        };
207
208        let (stack, field3) = pop(stack);
209        let (stack, field2) = pop(stack);
210        let (stack, field1) = pop(stack);
211        let variant = Value::Variant(Box::new(VariantData::new(
212            tag,
213            vec![field1, field2, field3],
214        )));
215        push(stack, variant)
216    }
217}
218
219/// Create a variant with 4 fields
220///
221/// Stack effect: ( field1 field2 field3 field4 tag -- Variant )
222///
223/// # Safety
224/// Stack must have field1, field2, field3, field4, and tag on top
225#[unsafe(no_mangle)]
226pub unsafe extern "C" fn patch_seq_make_variant_4(stack: Stack) -> Stack {
227    use crate::value::VariantData;
228
229    unsafe {
230        let (stack, tag_val) = pop(stack);
231        let tag = match tag_val {
232            Value::Int(t) => {
233                if t < 0 {
234                    panic!("make-variant-4: tag cannot be negative: {}", t);
235                }
236                t as u32
237            }
238            _ => panic!("make-variant-4: expected Int (tag), got {:?}", tag_val),
239        };
240
241        let (stack, field4) = pop(stack);
242        let (stack, field3) = pop(stack);
243        let (stack, field2) = pop(stack);
244        let (stack, field1) = pop(stack);
245        let variant = Value::Variant(Box::new(VariantData::new(
246            tag,
247            vec![field1, field2, field3, field4],
248        )));
249        push(stack, variant)
250    }
251}
252
253// Re-exports for internal use
254pub use patch_seq_make_variant_0 as make_variant_0;
255pub use patch_seq_make_variant_1 as make_variant_1;
256pub use patch_seq_make_variant_2 as make_variant_2;
257pub use patch_seq_make_variant_3 as make_variant_3;
258pub use patch_seq_make_variant_4 as make_variant_4;
259
260/// Append a value to a variant, returning a new variant
261///
262/// Stack effect: ( Variant Value -- Variant' )
263///
264/// Creates a new variant with the same tag as the input, but with
265/// the new value appended to its fields. The original variant is
266/// not modified (functional/immutable style).
267///
268/// Example: For arrays, `[1, 2] 3 variant-append` produces `[1, 2, 3]`
269/// Example: For objects, `{} "key" variant-append "val" variant-append` builds `{"key": "val"}`
270///
271/// # Safety
272/// Stack must have a Variant and a Value on top
273#[unsafe(no_mangle)]
274pub unsafe extern "C" fn patch_seq_variant_append(stack: Stack) -> Stack {
275    use crate::value::VariantData;
276
277    unsafe {
278        // Pop the value to append
279        let (stack, value) = pop(stack);
280
281        // Pop the variant
282        let (stack, variant_val) = pop(stack);
283
284        match variant_val {
285            Value::Variant(variant_data) => {
286                // Create new fields vector with the appended value
287                let mut new_fields = variant_data.fields.to_vec();
288                new_fields.push(value);
289
290                // Create new variant with same tag
291                let new_variant =
292                    Value::Variant(Box::new(VariantData::new(variant_data.tag, new_fields)));
293
294                push(stack, new_variant)
295            }
296            _ => panic!("variant-append: expected Variant, got {:?}", variant_val),
297        }
298    }
299}
300
301/// Get the last field from a variant
302///
303/// Stack effect: ( Variant -- Value )
304///
305/// Returns a clone of the last field. Panics if the variant has no fields.
306/// This is the "peek" operation for using variants as stacks.
307///
308/// # Safety
309/// Stack must have a Variant on top
310#[unsafe(no_mangle)]
311pub unsafe extern "C" fn patch_seq_variant_last(stack: Stack) -> Stack {
312    unsafe {
313        let (stack, variant_val) = pop(stack);
314
315        match variant_val {
316            Value::Variant(variant_data) => {
317                if variant_data.fields.is_empty() {
318                    panic!("variant-last: variant has no fields");
319                }
320
321                let last = variant_data.fields.last().unwrap().clone();
322                push(stack, last)
323            }
324            _ => panic!("variant-last: expected Variant, got {:?}", variant_val),
325        }
326    }
327}
328
329/// Get all but the last field from a variant
330///
331/// Stack effect: ( Variant -- Variant' )
332///
333/// Returns a new variant with the same tag but without the last field.
334/// Panics if the variant has no fields.
335/// This is the "pop" operation for using variants as stacks (without returning the popped value).
336///
337/// # Safety
338/// Stack must have a Variant on top
339#[unsafe(no_mangle)]
340pub unsafe extern "C" fn patch_seq_variant_init(stack: Stack) -> Stack {
341    use crate::value::VariantData;
342
343    unsafe {
344        let (stack, variant_val) = pop(stack);
345
346        match variant_val {
347            Value::Variant(variant_data) => {
348                if variant_data.fields.is_empty() {
349                    panic!("variant-init: variant has no fields");
350                }
351
352                // Create new fields without the last element
353                let new_fields: Vec<Value> =
354                    variant_data.fields[..variant_data.fields.len() - 1].to_vec();
355
356                let new_variant =
357                    Value::Variant(Box::new(VariantData::new(variant_data.tag, new_fields)));
358
359                push(stack, new_variant)
360            }
361            _ => panic!("variant-init: expected Variant, got {:?}", variant_val),
362        }
363    }
364}
365
366/// Unpack a variant's fields onto the stack
367///
368/// Takes a field count as parameter and:
369/// - Pops the variant from the stack
370/// - Pushes each field (0..field_count) in order
371///
372/// Stack effect: ( Variant -- field0 field1 ... fieldN-1 )
373///
374/// Used by match expression codegen to extract variant fields.
375///
376/// # Safety
377/// Stack must have a Variant on top with at least `field_count` fields
378#[unsafe(no_mangle)]
379pub unsafe extern "C" fn patch_seq_unpack_variant(stack: Stack, field_count: i64) -> Stack {
380    unsafe {
381        let (mut stack, variant_val) = pop(stack);
382
383        match variant_val {
384            Value::Variant(variant_data) => {
385                let count = field_count as usize;
386                if count > variant_data.fields.len() {
387                    panic!(
388                        "unpack-variant: requested {} fields but variant only has {}",
389                        count,
390                        variant_data.fields.len()
391                    );
392                }
393
394                // Push each field in order (field0 first, then field1, etc.)
395                for i in 0..count {
396                    stack = push(stack, variant_data.fields[i].clone());
397                }
398
399                stack
400            }
401            _ => panic!("unpack-variant: expected Variant, got {:?}", variant_val),
402        }
403    }
404}
405
406// Public re-exports with short names for internal use
407pub use patch_seq_unpack_variant as unpack_variant;
408pub use patch_seq_variant_append as variant_append;
409pub use patch_seq_variant_field_at as variant_field_at;
410pub use patch_seq_variant_field_count as variant_field_count;
411pub use patch_seq_variant_init as variant_init;
412pub use patch_seq_variant_last as variant_last;
413pub use patch_seq_variant_tag as variant_tag;
414
415#[cfg(test)]
416mod tests {
417    use super::*;
418    use crate::seqstring::global_string;
419    use crate::value::VariantData;
420
421    #[test]
422    fn test_variant_field_count() {
423        unsafe {
424            // Create a variant with 3 fields
425            let variant = Value::Variant(Box::new(VariantData::new(
426                0,
427                vec![Value::Int(10), Value::Int(20), Value::Int(30)],
428            )));
429
430            let stack = std::ptr::null_mut();
431            let stack = push(stack, variant);
432            let stack = variant_field_count(stack);
433
434            let (stack, result) = pop(stack);
435            assert_eq!(result, Value::Int(3));
436            assert!(stack.is_null());
437        }
438    }
439
440    #[test]
441    fn test_variant_tag() {
442        unsafe {
443            // Create a variant with tag 42
444            let variant = Value::Variant(Box::new(VariantData::new(42, vec![Value::Int(10)])));
445
446            let stack = std::ptr::null_mut();
447            let stack = push(stack, variant);
448            let stack = variant_tag(stack);
449
450            let (stack, result) = pop(stack);
451            assert_eq!(result, Value::Int(42));
452            assert!(stack.is_null());
453        }
454    }
455
456    #[test]
457    fn test_variant_field_at() {
458        unsafe {
459            let str1 = global_string("hello".to_string());
460            let str2 = global_string("world".to_string());
461
462            // Create a variant with mixed fields
463            let variant = Value::Variant(Box::new(VariantData::new(
464                0,
465                vec![
466                    Value::String(str1.clone()),
467                    Value::Int(42),
468                    Value::String(str2.clone()),
469                ],
470            )));
471
472            // Test accessing field 0
473            let stack = std::ptr::null_mut();
474            let stack = push(stack, variant.clone());
475            let stack = push(stack, Value::Int(0));
476            let stack = variant_field_at(stack);
477
478            let (stack, result) = pop(stack);
479            assert_eq!(result, Value::String(str1.clone()));
480            assert!(stack.is_null());
481
482            // Test accessing field 1
483            let stack = push(stack, variant.clone());
484            let stack = push(stack, Value::Int(1));
485            let stack = variant_field_at(stack);
486
487            let (stack, result) = pop(stack);
488            assert_eq!(result, Value::Int(42));
489            assert!(stack.is_null());
490
491            // Test accessing field 2
492            let stack = push(stack, variant.clone());
493            let stack = push(stack, Value::Int(2));
494            let stack = variant_field_at(stack);
495
496            let (stack, result) = pop(stack);
497            assert_eq!(result, Value::String(str2));
498            assert!(stack.is_null());
499        }
500    }
501
502    #[test]
503    fn test_variant_field_count_empty() {
504        unsafe {
505            // Create a variant with no fields
506            let variant = Value::Variant(Box::new(VariantData::new(0, vec![])));
507
508            let stack = std::ptr::null_mut();
509            let stack = push(stack, variant);
510            let stack = variant_field_count(stack);
511
512            let (stack, result) = pop(stack);
513            assert_eq!(result, Value::Int(0));
514            assert!(stack.is_null());
515        }
516    }
517
518    #[test]
519    fn test_make_variant_with_fields() {
520        unsafe {
521            // Create: 10 20 30 42 make-variant-3
522            // Should produce variant with tag 42 and fields [10, 20, 30]
523            let stack = std::ptr::null_mut();
524            let stack = push(stack, Value::Int(10)); // field 0
525            let stack = push(stack, Value::Int(20)); // field 1
526            let stack = push(stack, Value::Int(30)); // field 2
527            let stack = push(stack, Value::Int(42)); // tag
528
529            let stack = make_variant_3(stack);
530
531            let (stack, result) = pop(stack);
532
533            match result {
534                Value::Variant(v) => {
535                    assert_eq!(v.tag, 42);
536                    assert_eq!(v.fields.len(), 3);
537                    assert_eq!(v.fields[0], Value::Int(10));
538                    assert_eq!(v.fields[1], Value::Int(20));
539                    assert_eq!(v.fields[2], Value::Int(30));
540                }
541                _ => panic!("Expected Variant"),
542            }
543            assert!(stack.is_null());
544        }
545    }
546
547    #[test]
548    fn test_make_variant_empty() {
549        unsafe {
550            // Create: 0 make-variant-0
551            // Should produce variant with tag 0 and no fields
552            let stack = std::ptr::null_mut();
553            let stack = push(stack, Value::Int(0)); // tag
554
555            let stack = make_variant_0(stack);
556
557            let (stack, result) = pop(stack);
558
559            match result {
560                Value::Variant(v) => {
561                    assert_eq!(v.tag, 0);
562                    assert_eq!(v.fields.len(), 0);
563                }
564                _ => panic!("Expected Variant"),
565            }
566            assert!(stack.is_null());
567        }
568    }
569
570    #[test]
571    fn test_make_variant_with_mixed_types() {
572        unsafe {
573            let s = global_string("hello".to_string());
574
575            // Create variant with mixed field types using make-variant-3
576            let stack = std::ptr::null_mut();
577            let stack = push(stack, Value::Int(42));
578            let stack = push(stack, Value::String(s.clone()));
579            let stack = push(stack, Value::Float(3.5));
580            let stack = push(stack, Value::Int(1)); // tag
581
582            let stack = make_variant_3(stack);
583
584            let (stack, result) = pop(stack);
585
586            match result {
587                Value::Variant(v) => {
588                    assert_eq!(v.tag, 1);
589                    assert_eq!(v.fields.len(), 3);
590                    assert_eq!(v.fields[0], Value::Int(42));
591                    assert_eq!(v.fields[1], Value::String(s));
592                    assert_eq!(v.fields[2], Value::Float(3.5));
593                }
594                _ => panic!("Expected Variant"),
595            }
596            assert!(stack.is_null());
597        }
598    }
599
600    #[test]
601    fn test_variant_append() {
602        unsafe {
603            // Create an empty variant (tag 4 for array)
604            let stack = std::ptr::null_mut();
605            let stack = push(stack, Value::Int(4)); // tag (array)
606            let stack = make_variant_0(stack);
607
608            // Append a value
609            let stack = push(stack, Value::Int(42));
610            let stack = variant_append(stack);
611
612            // Check result
613            let (stack, result) = pop(stack);
614            match result {
615                Value::Variant(v) => {
616                    assert_eq!(v.tag, 4);
617                    assert_eq!(v.fields.len(), 1);
618                    assert_eq!(v.fields[0], Value::Int(42));
619                }
620                _ => panic!("Expected Variant"),
621            }
622            assert!(stack.is_null());
623        }
624    }
625
626    #[test]
627    fn test_variant_append_multiple() {
628        unsafe {
629            // Create an empty variant (tag 5 for object)
630            let stack = std::ptr::null_mut();
631            let stack = push(stack, Value::Int(5)); // tag (object)
632            let stack = make_variant_0(stack);
633
634            // Append key
635            let key = global_string("name".to_string());
636            let stack = push(stack, Value::String(key.clone()));
637            let stack = variant_append(stack);
638
639            // Append value
640            let val = global_string("John".to_string());
641            let stack = push(stack, Value::String(val.clone()));
642            let stack = variant_append(stack);
643
644            // Check result - should have 2 fields
645            let (stack, result) = pop(stack);
646            match result {
647                Value::Variant(v) => {
648                    assert_eq!(v.tag, 5);
649                    assert_eq!(v.fields.len(), 2);
650                    assert_eq!(v.fields[0], Value::String(key));
651                    assert_eq!(v.fields[1], Value::String(val));
652                }
653                _ => panic!("Expected Variant"),
654            }
655            assert!(stack.is_null());
656        }
657    }
658
659    #[test]
660    fn test_variant_last() {
661        unsafe {
662            // Create a variant with 3 fields
663            let variant = Value::Variant(Box::new(VariantData::new(
664                0,
665                vec![Value::Int(10), Value::Int(20), Value::Int(30)],
666            )));
667
668            let stack = std::ptr::null_mut();
669            let stack = push(stack, variant);
670            let stack = variant_last(stack);
671
672            let (stack, result) = pop(stack);
673            assert_eq!(result, Value::Int(30));
674            assert!(stack.is_null());
675        }
676    }
677
678    #[test]
679    fn test_variant_init() {
680        unsafe {
681            // Create a variant with 3 fields
682            let variant = Value::Variant(Box::new(VariantData::new(
683                42,
684                vec![Value::Int(10), Value::Int(20), Value::Int(30)],
685            )));
686
687            let stack = std::ptr::null_mut();
688            let stack = push(stack, variant);
689            let stack = variant_init(stack);
690
691            let (stack, result) = pop(stack);
692            match result {
693                Value::Variant(v) => {
694                    assert_eq!(v.tag, 42); // tag preserved
695                    assert_eq!(v.fields.len(), 2);
696                    assert_eq!(v.fields[0], Value::Int(10));
697                    assert_eq!(v.fields[1], Value::Int(20));
698                }
699                _ => panic!("Expected Variant"),
700            }
701            assert!(stack.is_null());
702        }
703    }
704
705    #[test]
706    fn test_variant_stack_operations() {
707        // Test using variant as a stack: append, append, last, init, last
708        unsafe {
709            // Create empty "stack" variant (tag 99)
710            let stack = std::ptr::null_mut();
711            let stack = push(stack, Value::Int(99)); // tag
712            let stack = make_variant_0(stack);
713
714            // Append 10
715            let stack = push(stack, Value::Int(10));
716            let stack = variant_append(stack);
717
718            // Append 20
719            let stack = push(stack, Value::Int(20));
720            let stack = variant_append(stack);
721
722            // Now have variant with [10, 20] on stack
723            // Dup and get last (should be 20)
724            let (stack, variant) = pop(stack);
725            let stack = push(stack, variant.clone());
726            let stack = push(stack, variant);
727            let stack = variant_last(stack);
728            let (stack, top) = pop(stack);
729            assert_eq!(top, Value::Int(20));
730
731            // Now use init to remove last element
732            let stack = variant_init(stack);
733
734            // Dup and get last (should now be 10)
735            let (stack, variant) = pop(stack);
736            let stack = push(stack, variant.clone());
737            let stack = push(stack, variant);
738            let stack = variant_last(stack);
739            let (stack, top) = pop(stack);
740            assert_eq!(top, Value::Int(10));
741
742            // Verify remaining variant has 1 field
743            let (stack, result) = pop(stack);
744            match result {
745                Value::Variant(v) => {
746                    assert_eq!(v.fields.len(), 1);
747                    assert_eq!(v.fields[0], Value::Int(10));
748                }
749                _ => panic!("Expected Variant"),
750            }
751            assert!(stack.is_null());
752        }
753    }
754}