Skip to main content

seq_runtime/
map_ops.rs

1//! Map operations for Seq
2//!
3//! Dictionary/hash map operations with O(1) lookup.
4//! Maps use hashable keys (Int, String, Bool) and can store any Value.
5//!
6//! # Examples
7//!
8//! ```seq
9//! # Create empty map and add entries
10//! make-map "name" "Alice" map-set "age" 30 map-set
11//!
12//! # Get value by key
13//! my-map "name" map-get  # -> "Alice"
14//!
15//! # Check if key exists
16//! my-map "email" map-has?  # -> 0 (false)
17//!
18//! # Get keys/values as lists
19//! my-map map-keys    # -> ["name", "age"]
20//! my-map map-values  # -> ["Alice", 30]
21//! ```
22//!
23//! # Error Handling
24//!
25//! - `map-get` returns (value Bool) - false if key not found (errors are values, not crashes)
26//! - Type errors (invalid key types, non-Map values) still panic (internal bugs)
27//!
28//! # Performance Notes
29//!
30//! - Operations use functional style: `map-set` and `map-remove` return new maps
31//! - Each mutation clones the underlying HashMap (O(n) for n entries)
32//! - For small maps (<100 entries), this is typically fast enough
33//! - Key/value iteration order is not guaranteed (HashMap iteration order)
34
35use crate::seqstring::global_string;
36use crate::stack::{Stack, drop_stack_value, heap_value_mut, pop, pop_sv, push};
37use crate::value::{MapKey, Value, VariantData};
38use std::sync::Arc;
39
40/// Create an empty map
41///
42/// Stack effect: ( -- Map )
43///
44/// # Safety
45/// Stack can be any valid stack pointer (including null for empty stack)
46#[unsafe(no_mangle)]
47pub unsafe extern "C" fn patch_seq_make_map(stack: Stack) -> Stack {
48    unsafe { push(stack, Value::Map(Box::default())) }
49}
50
51/// Get a value from the map by key
52///
53/// Stack effect: ( Map key -- value Bool )
54///
55/// Returns (value true) if found, or (0 false) if not found.
56/// Errors are values, not crashes.
57/// Panics only for internal bugs (invalid key type, non-Map value).
58///
59/// # Safety
60/// Stack must have a hashable key on top and a Map below
61#[unsafe(no_mangle)]
62pub unsafe extern "C" fn patch_seq_map_get(stack: Stack) -> Stack {
63    unsafe {
64        // Pop key
65        let (stack, key_val) = pop(stack);
66        let key = MapKey::from_value(&key_val).unwrap_or_else(|| {
67            panic!(
68                "map-get: key must be Int, String, or Bool, got {:?}",
69                key_val
70            )
71        });
72
73        // Pop map
74        let (stack, map_val) = pop(stack);
75        let map = match map_val {
76            Value::Map(m) => m,
77            _ => panic!("map-get: expected Map, got {:?}", map_val),
78        };
79
80        // Look up value - return success flag instead of panicking
81        match map.get(&key) {
82            Some(value) => {
83                let stack = push(stack, value.clone());
84                push(stack, Value::Bool(true))
85            }
86            None => {
87                let stack = push(stack, Value::Int(0)); // placeholder value
88                push(stack, Value::Bool(false)) // not found
89            }
90        }
91    }
92}
93
94/// Set a key-value pair in the map with COW optimization.
95///
96/// Stack effect: ( Map key value -- Map )
97///
98/// Fast path: if the map (at sp-3) is sole-owned, pops key and value,
99/// inserts directly into the map in place — no Box alloc/dealloc cycle.
100/// Slow path: pops all three, clones the map, inserts, pushes new map.
101///
102/// # Safety
103/// Stack must have value on top, key below, and Map at third position
104#[unsafe(no_mangle)]
105pub unsafe extern "C" fn patch_seq_map_set(stack: Stack) -> Stack {
106    unsafe {
107        // Fast path: peek at the map at sp-3 without popping.
108        // SAFETY: map.set requires three values on the stack (enforced by
109        // the type checker), so stack.sub(3) is valid.
110        if let Some(Value::Map(map)) = heap_value_mut(stack.sub(3)) {
111            // Sole owner — pop key and value, mutate map in place.
112            let (stack, value) = pop(stack);
113            let (stack, key_val) = pop(stack);
114            let key = MapKey::from_value(&key_val).unwrap_or_else(|| {
115                panic!(
116                    "map-set: key must be Int, String, or Bool, got {:?}",
117                    key_val
118                )
119            });
120            // Safety: `pop` only touches sp-1 per call; the map at
121            // the original sp-3 (now sp-1) is not invalidated.
122            map.insert(key, value);
123            return stack; // Map is still at sp-1, mutated in place
124        }
125
126        // Slow path: pop all three, clone map, insert, push
127        let (stack, value) = pop(stack);
128        let (stack, key_val) = pop(stack);
129        let key = MapKey::from_value(&key_val).unwrap_or_else(|| {
130            panic!(
131                "map-set: key must be Int, String, or Bool, got {:?}",
132                key_val
133            )
134        });
135        let (stack, map_val) = pop(stack);
136        let mut map = match map_val {
137            Value::Map(m) => *m,
138            _ => panic!("map-set: expected Map, got {:?}", map_val),
139        };
140        map.insert(key, value);
141        push(stack, Value::Map(Box::new(map)))
142    }
143}
144
145/// Check if a key exists in the map
146///
147/// Stack effect: ( Map key -- Int )
148///
149/// Returns 1 if the key exists, 0 otherwise.
150/// Panics if the key type is not hashable.
151///
152/// # Safety
153/// Stack must have a hashable key on top and a Map below
154#[unsafe(no_mangle)]
155pub unsafe extern "C" fn patch_seq_map_has(stack: Stack) -> Stack {
156    unsafe {
157        // Pop key
158        let (stack, key_val) = pop(stack);
159        let key = MapKey::from_value(&key_val).unwrap_or_else(|| {
160            panic!(
161                "map-has?: key must be Int, String, or Bool, got {:?}",
162                key_val
163            )
164        });
165
166        // Pop map
167        let (stack, map_val) = pop(stack);
168        let map = match map_val {
169            Value::Map(m) => m,
170            _ => panic!("map-has?: expected Map, got {:?}", map_val),
171        };
172
173        let has_key = map.contains_key(&key);
174        push(stack, Value::Bool(has_key))
175    }
176}
177
178/// Remove a key from the map with COW optimization.
179///
180/// Stack effect: ( Map key -- Map )
181///
182/// Fast path: if the map (at sp-2) is sole-owned, pops key and
183/// removes directly from the map in place.
184/// Slow path: pops both, clones, removes, pushes new map.
185///
186/// # Safety
187/// Stack must have a hashable key on top and a Map below
188#[unsafe(no_mangle)]
189pub unsafe extern "C" fn patch_seq_map_remove(stack: Stack) -> Stack {
190    unsafe {
191        // Fast path: peek at the map at sp-2 without popping.
192        // SAFETY: map.remove requires two values on the stack (enforced by
193        // the type checker), so stack.sub(2) is valid.
194        if let Some(Value::Map(map)) = heap_value_mut(stack.sub(2)) {
195            let (stack, key_val) = pop(stack);
196            let key = MapKey::from_value(&key_val).unwrap_or_else(|| {
197                panic!(
198                    "map-remove: key must be Int, String, or Bool, got {:?}",
199                    key_val
200                )
201            });
202            // Safety: pop only touches sp-1; the map at the original
203            // sp-2 (now sp-1) is not invalidated.
204            map.remove(&key);
205            return stack; // Map is still at sp-1, mutated in place
206        }
207
208        // Slow path: pop both, clone map, remove, push
209        let (stack, key_val) = pop(stack);
210        let key = MapKey::from_value(&key_val).unwrap_or_else(|| {
211            panic!(
212                "map-remove: key must be Int, String, or Bool, got {:?}",
213                key_val
214            )
215        });
216        let (stack, map_val) = pop(stack);
217        let mut map = match map_val {
218            Value::Map(m) => *m,
219            _ => panic!("map-remove: expected Map, got {:?}", map_val),
220        };
221        map.remove(&key);
222        push(stack, Value::Map(Box::new(map)))
223    }
224}
225
226/// Get all keys from the map as a list
227///
228/// Stack effect: ( Map -- Variant )
229///
230/// Returns a Variant containing all keys in the map.
231/// Note: Order is not guaranteed (HashMap iteration order).
232///
233/// # Safety
234/// Stack must have a Map on top
235#[unsafe(no_mangle)]
236pub unsafe extern "C" fn patch_seq_map_keys(stack: Stack) -> Stack {
237    unsafe {
238        let (stack, map_val) = pop(stack);
239        let map = match map_val {
240            Value::Map(m) => m,
241            _ => panic!("map-keys: expected Map, got {:?}", map_val),
242        };
243
244        let keys: Vec<Value> = map.keys().map(|k| k.to_value()).collect();
245        let variant = Value::Variant(Arc::new(VariantData::new(
246            global_string("List".to_string()),
247            keys,
248        )));
249        push(stack, variant)
250    }
251}
252
253/// Get all values from the map as a list
254///
255/// Stack effect: ( Map -- Variant )
256///
257/// Returns a Variant containing all values in the map.
258/// Note: Order is not guaranteed (HashMap iteration order).
259///
260/// # Safety
261/// Stack must have a Map on top
262#[unsafe(no_mangle)]
263pub unsafe extern "C" fn patch_seq_map_values(stack: Stack) -> Stack {
264    unsafe {
265        let (stack, map_val) = pop(stack);
266        let map = match map_val {
267            Value::Map(m) => m,
268            _ => panic!("map-values: expected Map, got {:?}", map_val),
269        };
270
271        let values: Vec<Value> = map.values().cloned().collect();
272        let variant = Value::Variant(Arc::new(VariantData::new(
273            global_string("List".to_string()),
274            values,
275        )));
276        push(stack, variant)
277    }
278}
279
280/// Get the number of entries in the map
281///
282/// Stack effect: ( Map -- Int )
283///
284/// # Safety
285/// Stack must have a Map on top
286#[unsafe(no_mangle)]
287pub unsafe extern "C" fn patch_seq_map_size(stack: Stack) -> Stack {
288    unsafe {
289        let (stack, map_val) = pop(stack);
290        let map = match map_val {
291            Value::Map(m) => m,
292            _ => panic!("map-size: expected Map, got {:?}", map_val),
293        };
294
295        push(stack, Value::Int(map.len() as i64))
296    }
297}
298
299/// Check if the map is empty
300///
301/// Stack effect: ( Map -- Int )
302///
303/// Returns 1 if the map has no entries, 0 otherwise.
304///
305/// # Safety
306/// Stack must have a Map on top
307#[unsafe(no_mangle)]
308pub unsafe extern "C" fn patch_seq_map_empty(stack: Stack) -> Stack {
309    unsafe {
310        let (stack, map_val) = pop(stack);
311        let map = match map_val {
312            Value::Map(m) => m,
313            _ => panic!("map-empty?: expected Map, got {:?}", map_val),
314        };
315
316        let is_empty = map.is_empty();
317        push(stack, Value::Bool(is_empty))
318    }
319}
320
321/// Iterate over all key-value pairs in a map, calling a quotation for each.
322///
323/// Stack effect: ( Map Quotation -- )
324///   where Quotation : ( key value -- )
325///
326/// The quotation receives each key and value on a fresh stack.
327/// Iteration order is not guaranteed.
328///
329/// # Safety
330/// Stack must have a Quotation/Closure on top and a Map below
331#[unsafe(no_mangle)]
332pub unsafe extern "C" fn patch_seq_map_each(stack: Stack) -> Stack {
333    unsafe {
334        // Pop quotation
335        let (stack, callable) = pop(stack);
336        match &callable {
337            Value::Quotation { .. } | Value::Closure { .. } => {}
338            _ => panic!(
339                "map.each: expected Quotation or Closure, got {:?}",
340                callable
341            ),
342        }
343
344        // Pop map
345        let (stack, map_val) = pop(stack);
346        let map = match &map_val {
347            Value::Map(m) => m,
348            _ => panic!("map.each: expected Map, got {:?}", map_val),
349        };
350
351        // Call quotation for each key-value pair
352        for (key, value) in map.iter() {
353            let temp_base = crate::stack::alloc_stack();
354            let temp_stack = push(temp_base, key.to_value());
355            let temp_stack = push(temp_stack, value.clone());
356            let temp_stack = invoke_callable(temp_stack, &callable);
357            // Drain any leftover values
358            drain_to_base(temp_stack, temp_base);
359        }
360
361        stack
362    }
363}
364
365/// Fold over all key-value pairs in a map with an accumulator.
366///
367/// Stack effect: ( Map init Quotation -- result )
368///   where Quotation : ( acc key value -- acc' )
369///
370/// The quotation receives the accumulator, key, and value, and must
371/// return the new accumulator. Iteration order is not guaranteed.
372///
373/// # Safety
374/// Stack must have Quotation on top, init below, and Map below that
375#[unsafe(no_mangle)]
376pub unsafe extern "C" fn patch_seq_map_fold(stack: Stack) -> Stack {
377    unsafe {
378        // Pop quotation
379        let (stack, callable) = pop(stack);
380        match &callable {
381            Value::Quotation { .. } | Value::Closure { .. } => {}
382            _ => panic!(
383                "map.fold: expected Quotation or Closure, got {:?}",
384                callable
385            ),
386        }
387
388        // Pop initial accumulator
389        let (stack, mut acc) = pop(stack);
390
391        // Pop map
392        let (stack, map_val) = pop(stack);
393        let map = match &map_val {
394            Value::Map(m) => m,
395            _ => panic!("map.fold: expected Map, got {:?}", map_val),
396        };
397
398        // Fold over each key-value pair
399        for (key, value) in map.iter() {
400            let temp_base = crate::stack::alloc_stack();
401            let temp_stack = push(temp_base, acc);
402            let temp_stack = push(temp_stack, key.to_value());
403            let temp_stack = push(temp_stack, value.clone());
404            let temp_stack = invoke_callable(temp_stack, &callable);
405            // Pop new accumulator
406            if temp_stack <= temp_base {
407                panic!("map.fold: quotation consumed accumulator without producing result");
408            }
409            let (remaining, new_acc) = pop(temp_stack);
410            acc = new_acc;
411            // Drain any extra values left by the quotation
412            if remaining > temp_base {
413                drain_to_base(remaining, temp_base);
414            }
415        }
416
417        push(stack, acc)
418    }
419}
420
421use crate::quotations::invoke_callable;
422
423/// Drain stack values back to base, properly freeing heap-allocated values.
424unsafe fn drain_to_base(mut stack: Stack, base: Stack) {
425    unsafe {
426        while stack > base {
427            let (rest, sv) = pop_sv(stack);
428            drop_stack_value(sv);
429            stack = rest;
430        }
431    }
432}
433
434// Public re-exports
435pub use patch_seq_make_map as make_map;
436pub use patch_seq_map_each as map_each;
437pub use patch_seq_map_empty as map_empty;
438pub use patch_seq_map_fold as map_fold;
439pub use patch_seq_map_get as map_get;
440pub use patch_seq_map_has as map_has;
441pub use patch_seq_map_keys as map_keys;
442pub use patch_seq_map_remove as map_remove;
443pub use patch_seq_map_set as map_set;
444pub use patch_seq_map_size as map_size;
445pub use patch_seq_map_values as map_values;
446
447#[cfg(test)]
448mod tests {
449    use super::*;
450
451    #[test]
452    fn test_make_map() {
453        unsafe {
454            let stack = crate::stack::alloc_test_stack();
455            let stack = make_map(stack);
456
457            let (_stack, result) = pop(stack);
458            match result {
459                Value::Map(m) => assert!(m.is_empty()),
460                _ => panic!("Expected Map"),
461            }
462        }
463    }
464
465    #[test]
466    fn test_map_set_and_get() {
467        unsafe {
468            let stack = crate::stack::alloc_test_stack();
469            let stack = make_map(stack);
470            let stack = push(stack, Value::String("name".into()));
471            let stack = push(stack, Value::String("Alice".into()));
472            let stack = map_set(stack);
473
474            // Get the value back
475            let stack = push(stack, Value::String("name".into()));
476            let stack = map_get(stack);
477
478            // map_get returns (value Bool)
479            let (stack, flag) = pop(stack);
480            assert_eq!(flag, Value::Bool(true));
481            let (_stack, result) = pop(stack);
482            match result {
483                Value::String(s) => assert_eq!(s.as_str(), "Alice"),
484                _ => panic!("Expected String"),
485            }
486        }
487    }
488
489    #[test]
490    fn test_map_set_with_int_key() {
491        unsafe {
492            let stack = crate::stack::alloc_test_stack();
493            let stack = make_map(stack);
494            let stack = push(stack, Value::Int(42));
495            let stack = push(stack, Value::String("answer".into()));
496            let stack = map_set(stack);
497
498            let stack = push(stack, Value::Int(42));
499            let stack = map_get(stack);
500
501            // map_get returns (value Bool)
502            let (stack, flag) = pop(stack);
503            assert_eq!(flag, Value::Bool(true));
504            let (_stack, result) = pop(stack);
505            match result {
506                Value::String(s) => assert_eq!(s.as_str(), "answer"),
507                _ => panic!("Expected String"),
508            }
509        }
510    }
511
512    #[test]
513    fn test_map_has() {
514        unsafe {
515            let stack = crate::stack::alloc_test_stack();
516            let stack = make_map(stack);
517            let stack = push(stack, Value::String("key".into()));
518            let stack = push(stack, Value::Int(100));
519            let stack = map_set(stack);
520
521            // Check existing key (dup map first since map_has consumes it)
522            let stack = crate::stack::dup(stack);
523            let stack = push(stack, Value::String("key".into()));
524            let stack = map_has(stack);
525            let (stack, result) = pop(stack);
526            assert_eq!(result, Value::Bool(true));
527
528            // Check non-existing key (map is still on stack)
529            let stack = push(stack, Value::String("missing".into()));
530            let stack = map_has(stack);
531            let (_stack, result) = pop(stack);
532            assert_eq!(result, Value::Bool(false));
533        }
534    }
535
536    #[test]
537    fn test_map_remove() {
538        unsafe {
539            let stack = crate::stack::alloc_test_stack();
540            let stack = make_map(stack);
541            let stack = push(stack, Value::String("a".into()));
542            let stack = push(stack, Value::Int(1));
543            let stack = map_set(stack);
544            let stack = push(stack, Value::String("b".into()));
545            let stack = push(stack, Value::Int(2));
546            let stack = map_set(stack);
547
548            // Remove "a"
549            let stack = push(stack, Value::String("a".into()));
550            let stack = map_remove(stack);
551
552            // Check "a" is gone (dup map first since map_has consumes it)
553            let stack = crate::stack::dup(stack);
554            let stack = push(stack, Value::String("a".into()));
555            let stack = map_has(stack);
556            let (stack, result) = pop(stack);
557            assert_eq!(result, Value::Bool(false));
558
559            // Check "b" is still there (map is still on stack)
560            let stack = push(stack, Value::String("b".into()));
561            let stack = map_has(stack);
562            let (_stack, result) = pop(stack);
563            assert_eq!(result, Value::Bool(true));
564        }
565    }
566
567    #[test]
568    fn test_map_size() {
569        unsafe {
570            let stack = crate::stack::alloc_test_stack();
571            let stack = make_map(stack);
572
573            // Empty map
574            let stack = map_size(stack);
575            let (stack, result) = pop(stack);
576            assert_eq!(result, Value::Int(0));
577
578            // Add entries
579            let stack = make_map(stack);
580            let stack = push(stack, Value::String("a".into()));
581            let stack = push(stack, Value::Int(1));
582            let stack = map_set(stack);
583            let stack = push(stack, Value::String("b".into()));
584            let stack = push(stack, Value::Int(2));
585            let stack = map_set(stack);
586
587            let stack = map_size(stack);
588            let (_stack, result) = pop(stack);
589            assert_eq!(result, Value::Int(2));
590        }
591    }
592
593    #[test]
594    fn test_map_empty() {
595        unsafe {
596            let stack = crate::stack::alloc_test_stack();
597            let stack = make_map(stack);
598
599            let stack = map_empty(stack);
600            let (stack, result) = pop(stack);
601            assert_eq!(result, Value::Bool(true));
602
603            // Non-empty
604            let stack = make_map(stack);
605            let stack = push(stack, Value::String("key".into()));
606            let stack = push(stack, Value::Int(1));
607            let stack = map_set(stack);
608
609            let stack = map_empty(stack);
610            let (_stack, result) = pop(stack);
611            assert_eq!(result, Value::Bool(false));
612        }
613    }
614
615    #[test]
616    fn test_map_keys_and_values() {
617        unsafe {
618            let stack = crate::stack::alloc_test_stack();
619            let stack = make_map(stack);
620            let stack = push(stack, Value::String("x".into()));
621            let stack = push(stack, Value::Int(10));
622            let stack = map_set(stack);
623            let stack = push(stack, Value::String("y".into()));
624            let stack = push(stack, Value::Int(20));
625            let stack = map_set(stack);
626
627            // Get keys
628            let stack = crate::stack::dup(stack); // Keep map for values test
629            let stack = map_keys(stack);
630            let (stack, keys_result) = pop(stack);
631            match keys_result {
632                Value::Variant(v) => {
633                    assert_eq!(v.fields.len(), 2);
634                    // Keys are "x" and "y" but order is not guaranteed
635                }
636                _ => panic!("Expected Variant"),
637            }
638
639            // Get values
640            let stack = map_values(stack);
641            let (_stack, values_result) = pop(stack);
642            match values_result {
643                Value::Variant(v) => {
644                    assert_eq!(v.fields.len(), 2);
645                    // Values are 10 and 20 but order is not guaranteed
646                }
647                _ => panic!("Expected Variant"),
648            }
649        }
650    }
651
652    #[test]
653    fn test_map_get_found() {
654        unsafe {
655            let stack = crate::stack::alloc_test_stack();
656            let stack = make_map(stack);
657            let stack = push(stack, Value::String("key".into()));
658            let stack = push(stack, Value::Int(42));
659            let stack = map_set(stack);
660
661            let stack = push(stack, Value::String("key".into()));
662            let stack = map_get(stack);
663
664            let (stack, flag) = pop(stack);
665            let (_stack, value) = pop(stack);
666            assert_eq!(flag, Value::Bool(true));
667            assert_eq!(value, Value::Int(42));
668        }
669    }
670
671    #[test]
672    fn test_map_get_not_found() {
673        unsafe {
674            let stack = crate::stack::alloc_test_stack();
675            let stack = make_map(stack);
676
677            let stack = push(stack, Value::String("missing".into()));
678            let stack = map_get(stack);
679
680            let (stack, flag) = pop(stack);
681            let (_stack, _value) = pop(stack); // placeholder
682            assert_eq!(flag, Value::Bool(false));
683        }
684    }
685
686    #[test]
687    fn test_map_with_bool_key() {
688        unsafe {
689            let stack = crate::stack::alloc_test_stack();
690            let stack = make_map(stack);
691            let stack = push(stack, Value::Bool(true));
692            let stack = push(stack, Value::String("yes".into()));
693            let stack = map_set(stack);
694            let stack = push(stack, Value::Bool(false));
695            let stack = push(stack, Value::String("no".into()));
696            let stack = map_set(stack);
697
698            let stack = push(stack, Value::Bool(true));
699            let stack = map_get(stack);
700            // map_get returns (value Bool)
701            let (stack, flag) = pop(stack);
702            assert_eq!(flag, Value::Bool(true));
703            let (_stack, result) = pop(stack);
704            match result {
705                Value::String(s) => assert_eq!(s.as_str(), "yes"),
706                _ => panic!("Expected String"),
707            }
708        }
709    }
710
711    #[test]
712    fn test_map_key_overwrite() {
713        // Test that map-set with existing key overwrites the value
714        unsafe {
715            let stack = crate::stack::alloc_test_stack();
716            let stack = make_map(stack);
717
718            // Set initial value
719            let stack = push(stack, Value::String("key".into()));
720            let stack = push(stack, Value::Int(100));
721            let stack = map_set(stack);
722
723            // Overwrite with new value
724            let stack = push(stack, Value::String("key".into()));
725            let stack = push(stack, Value::Int(200));
726            let stack = map_set(stack);
727
728            // Verify size is still 1 (not 2)
729            let stack = crate::stack::dup(stack);
730            let stack = map_size(stack);
731            let (stack, size) = pop(stack);
732            assert_eq!(size, Value::Int(1));
733
734            // Verify value was updated
735            let stack = push(stack, Value::String("key".into()));
736            let stack = map_get(stack);
737            // map_get returns (value Bool)
738            let (stack, flag) = pop(stack);
739            assert_eq!(flag, Value::Bool(true));
740            let (_stack, result) = pop(stack);
741            assert_eq!(result, Value::Int(200));
742        }
743    }
744
745    #[test]
746    fn test_map_mixed_key_types() {
747        // Test that a single map can have different key types
748        unsafe {
749            let stack = crate::stack::alloc_test_stack();
750            let stack = make_map(stack);
751
752            // Add string key
753            let stack = push(stack, Value::String("name".into()));
754            let stack = push(stack, Value::String("Alice".into()));
755            let stack = map_set(stack);
756
757            // Add integer key
758            let stack = push(stack, Value::Int(42));
759            let stack = push(stack, Value::String("answer".into()));
760            let stack = map_set(stack);
761
762            // Add boolean key
763            let stack = push(stack, Value::Bool(true));
764            let stack = push(stack, Value::String("yes".into()));
765            let stack = map_set(stack);
766
767            // Verify size is 3
768            let stack = crate::stack::dup(stack);
769            let stack = map_size(stack);
770            let (stack, size) = pop(stack);
771            assert_eq!(size, Value::Int(3));
772
773            // Verify each key retrieves correct value
774            // map_get returns (value Bool)
775            let stack = crate::stack::dup(stack);
776            let stack = push(stack, Value::String("name".into()));
777            let stack = map_get(stack);
778            let (stack, flag) = pop(stack);
779            assert_eq!(flag, Value::Bool(true));
780            let (stack, result) = pop(stack);
781            match result {
782                Value::String(s) => assert_eq!(s.as_str(), "Alice"),
783                _ => panic!("Expected String for name key"),
784            }
785
786            let stack = crate::stack::dup(stack);
787            let stack = push(stack, Value::Int(42));
788            let stack = map_get(stack);
789            let (stack, flag) = pop(stack);
790            assert_eq!(flag, Value::Bool(true));
791            let (stack, result) = pop(stack);
792            match result {
793                Value::String(s) => assert_eq!(s.as_str(), "answer"),
794                _ => panic!("Expected String for int key"),
795            }
796
797            let stack = push(stack, Value::Bool(true));
798            let stack = map_get(stack);
799            let (stack, flag) = pop(stack);
800            assert_eq!(flag, Value::Bool(true));
801            let (_stack, result) = pop(stack);
802            match result {
803                Value::String(s) => assert_eq!(s.as_str(), "yes"),
804                _ => panic!("Expected String for bool key"),
805            }
806        }
807    }
808
809    // =========================================================================
810    // map.fold tests
811    // =========================================================================
812
813    #[test]
814    fn test_map_fold_empty() {
815        // Folding an empty map should return the initial accumulator
816        unsafe {
817            use crate::quotations::push_quotation;
818
819            let stack = crate::stack::alloc_test_stack();
820
821            // Push empty map
822            let stack = make_map(stack);
823
824            // Push initial accumulator
825            let stack = push(stack, Value::Int(99));
826
827            // Push a dummy quotation (won't be called for empty map)
828            unsafe extern "C" fn noop(stack: Stack) -> Stack {
829                stack
830            }
831            let fn_ptr = noop as *const () as usize;
832            let stack = push_quotation(stack, fn_ptr, fn_ptr);
833
834            let stack = patch_seq_map_fold(stack);
835
836            let (_stack, result) = pop(stack);
837            assert_eq!(result, Value::Int(99));
838        }
839    }
840}