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, heap_value_mut, pop, 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// Public re-exports
322pub use patch_seq_make_map as make_map;
323pub use patch_seq_map_empty as map_empty;
324pub use patch_seq_map_get as map_get;
325pub use patch_seq_map_has as map_has;
326pub use patch_seq_map_keys as map_keys;
327pub use patch_seq_map_remove as map_remove;
328pub use patch_seq_map_set as map_set;
329pub use patch_seq_map_size as map_size;
330pub use patch_seq_map_values as map_values;
331
332#[cfg(test)]
333mod tests {
334    use super::*;
335
336    #[test]
337    fn test_make_map() {
338        unsafe {
339            let stack = crate::stack::alloc_test_stack();
340            let stack = make_map(stack);
341
342            let (_stack, result) = pop(stack);
343            match result {
344                Value::Map(m) => assert!(m.is_empty()),
345                _ => panic!("Expected Map"),
346            }
347        }
348    }
349
350    #[test]
351    fn test_map_set_and_get() {
352        unsafe {
353            let stack = crate::stack::alloc_test_stack();
354            let stack = make_map(stack);
355            let stack = push(stack, Value::String("name".into()));
356            let stack = push(stack, Value::String("Alice".into()));
357            let stack = map_set(stack);
358
359            // Get the value back
360            let stack = push(stack, Value::String("name".into()));
361            let stack = map_get(stack);
362
363            // map_get returns (value Bool)
364            let (stack, flag) = pop(stack);
365            assert_eq!(flag, Value::Bool(true));
366            let (_stack, result) = pop(stack);
367            match result {
368                Value::String(s) => assert_eq!(s.as_str(), "Alice"),
369                _ => panic!("Expected String"),
370            }
371        }
372    }
373
374    #[test]
375    fn test_map_set_with_int_key() {
376        unsafe {
377            let stack = crate::stack::alloc_test_stack();
378            let stack = make_map(stack);
379            let stack = push(stack, Value::Int(42));
380            let stack = push(stack, Value::String("answer".into()));
381            let stack = map_set(stack);
382
383            let stack = push(stack, Value::Int(42));
384            let stack = map_get(stack);
385
386            // map_get returns (value Bool)
387            let (stack, flag) = pop(stack);
388            assert_eq!(flag, Value::Bool(true));
389            let (_stack, result) = pop(stack);
390            match result {
391                Value::String(s) => assert_eq!(s.as_str(), "answer"),
392                _ => panic!("Expected String"),
393            }
394        }
395    }
396
397    #[test]
398    fn test_map_has() {
399        unsafe {
400            let stack = crate::stack::alloc_test_stack();
401            let stack = make_map(stack);
402            let stack = push(stack, Value::String("key".into()));
403            let stack = push(stack, Value::Int(100));
404            let stack = map_set(stack);
405
406            // Check existing key (dup map first since map_has consumes it)
407            let stack = crate::stack::dup(stack);
408            let stack = push(stack, Value::String("key".into()));
409            let stack = map_has(stack);
410            let (stack, result) = pop(stack);
411            assert_eq!(result, Value::Bool(true));
412
413            // Check non-existing key (map is still on stack)
414            let stack = push(stack, Value::String("missing".into()));
415            let stack = map_has(stack);
416            let (_stack, result) = pop(stack);
417            assert_eq!(result, Value::Bool(false));
418        }
419    }
420
421    #[test]
422    fn test_map_remove() {
423        unsafe {
424            let stack = crate::stack::alloc_test_stack();
425            let stack = make_map(stack);
426            let stack = push(stack, Value::String("a".into()));
427            let stack = push(stack, Value::Int(1));
428            let stack = map_set(stack);
429            let stack = push(stack, Value::String("b".into()));
430            let stack = push(stack, Value::Int(2));
431            let stack = map_set(stack);
432
433            // Remove "a"
434            let stack = push(stack, Value::String("a".into()));
435            let stack = map_remove(stack);
436
437            // Check "a" is gone (dup map first since map_has consumes it)
438            let stack = crate::stack::dup(stack);
439            let stack = push(stack, Value::String("a".into()));
440            let stack = map_has(stack);
441            let (stack, result) = pop(stack);
442            assert_eq!(result, Value::Bool(false));
443
444            // Check "b" is still there (map is still on stack)
445            let stack = push(stack, Value::String("b".into()));
446            let stack = map_has(stack);
447            let (_stack, result) = pop(stack);
448            assert_eq!(result, Value::Bool(true));
449        }
450    }
451
452    #[test]
453    fn test_map_size() {
454        unsafe {
455            let stack = crate::stack::alloc_test_stack();
456            let stack = make_map(stack);
457
458            // Empty map
459            let stack = map_size(stack);
460            let (stack, result) = pop(stack);
461            assert_eq!(result, Value::Int(0));
462
463            // Add entries
464            let stack = make_map(stack);
465            let stack = push(stack, Value::String("a".into()));
466            let stack = push(stack, Value::Int(1));
467            let stack = map_set(stack);
468            let stack = push(stack, Value::String("b".into()));
469            let stack = push(stack, Value::Int(2));
470            let stack = map_set(stack);
471
472            let stack = map_size(stack);
473            let (_stack, result) = pop(stack);
474            assert_eq!(result, Value::Int(2));
475        }
476    }
477
478    #[test]
479    fn test_map_empty() {
480        unsafe {
481            let stack = crate::stack::alloc_test_stack();
482            let stack = make_map(stack);
483
484            let stack = map_empty(stack);
485            let (stack, result) = pop(stack);
486            assert_eq!(result, Value::Bool(true));
487
488            // Non-empty
489            let stack = make_map(stack);
490            let stack = push(stack, Value::String("key".into()));
491            let stack = push(stack, Value::Int(1));
492            let stack = map_set(stack);
493
494            let stack = map_empty(stack);
495            let (_stack, result) = pop(stack);
496            assert_eq!(result, Value::Bool(false));
497        }
498    }
499
500    #[test]
501    fn test_map_keys_and_values() {
502        unsafe {
503            let stack = crate::stack::alloc_test_stack();
504            let stack = make_map(stack);
505            let stack = push(stack, Value::String("x".into()));
506            let stack = push(stack, Value::Int(10));
507            let stack = map_set(stack);
508            let stack = push(stack, Value::String("y".into()));
509            let stack = push(stack, Value::Int(20));
510            let stack = map_set(stack);
511
512            // Get keys
513            let stack = crate::stack::dup(stack); // Keep map for values test
514            let stack = map_keys(stack);
515            let (stack, keys_result) = pop(stack);
516            match keys_result {
517                Value::Variant(v) => {
518                    assert_eq!(v.fields.len(), 2);
519                    // Keys are "x" and "y" but order is not guaranteed
520                }
521                _ => panic!("Expected Variant"),
522            }
523
524            // Get values
525            let stack = map_values(stack);
526            let (_stack, values_result) = pop(stack);
527            match values_result {
528                Value::Variant(v) => {
529                    assert_eq!(v.fields.len(), 2);
530                    // Values are 10 and 20 but order is not guaranteed
531                }
532                _ => panic!("Expected Variant"),
533            }
534        }
535    }
536
537    #[test]
538    fn test_map_get_found() {
539        unsafe {
540            let stack = crate::stack::alloc_test_stack();
541            let stack = make_map(stack);
542            let stack = push(stack, Value::String("key".into()));
543            let stack = push(stack, Value::Int(42));
544            let stack = map_set(stack);
545
546            let stack = push(stack, Value::String("key".into()));
547            let stack = map_get(stack);
548
549            let (stack, flag) = pop(stack);
550            let (_stack, value) = pop(stack);
551            assert_eq!(flag, Value::Bool(true));
552            assert_eq!(value, Value::Int(42));
553        }
554    }
555
556    #[test]
557    fn test_map_get_not_found() {
558        unsafe {
559            let stack = crate::stack::alloc_test_stack();
560            let stack = make_map(stack);
561
562            let stack = push(stack, Value::String("missing".into()));
563            let stack = map_get(stack);
564
565            let (stack, flag) = pop(stack);
566            let (_stack, _value) = pop(stack); // placeholder
567            assert_eq!(flag, Value::Bool(false));
568        }
569    }
570
571    #[test]
572    fn test_map_with_bool_key() {
573        unsafe {
574            let stack = crate::stack::alloc_test_stack();
575            let stack = make_map(stack);
576            let stack = push(stack, Value::Bool(true));
577            let stack = push(stack, Value::String("yes".into()));
578            let stack = map_set(stack);
579            let stack = push(stack, Value::Bool(false));
580            let stack = push(stack, Value::String("no".into()));
581            let stack = map_set(stack);
582
583            let stack = push(stack, Value::Bool(true));
584            let stack = map_get(stack);
585            // map_get returns (value Bool)
586            let (stack, flag) = pop(stack);
587            assert_eq!(flag, Value::Bool(true));
588            let (_stack, result) = pop(stack);
589            match result {
590                Value::String(s) => assert_eq!(s.as_str(), "yes"),
591                _ => panic!("Expected String"),
592            }
593        }
594    }
595
596    #[test]
597    fn test_map_key_overwrite() {
598        // Test that map-set with existing key overwrites the value
599        unsafe {
600            let stack = crate::stack::alloc_test_stack();
601            let stack = make_map(stack);
602
603            // Set initial value
604            let stack = push(stack, Value::String("key".into()));
605            let stack = push(stack, Value::Int(100));
606            let stack = map_set(stack);
607
608            // Overwrite with new value
609            let stack = push(stack, Value::String("key".into()));
610            let stack = push(stack, Value::Int(200));
611            let stack = map_set(stack);
612
613            // Verify size is still 1 (not 2)
614            let stack = crate::stack::dup(stack);
615            let stack = map_size(stack);
616            let (stack, size) = pop(stack);
617            assert_eq!(size, Value::Int(1));
618
619            // Verify value was updated
620            let stack = push(stack, Value::String("key".into()));
621            let stack = map_get(stack);
622            // map_get returns (value Bool)
623            let (stack, flag) = pop(stack);
624            assert_eq!(flag, Value::Bool(true));
625            let (_stack, result) = pop(stack);
626            assert_eq!(result, Value::Int(200));
627        }
628    }
629
630    #[test]
631    fn test_map_mixed_key_types() {
632        // Test that a single map can have different key types
633        unsafe {
634            let stack = crate::stack::alloc_test_stack();
635            let stack = make_map(stack);
636
637            // Add string key
638            let stack = push(stack, Value::String("name".into()));
639            let stack = push(stack, Value::String("Alice".into()));
640            let stack = map_set(stack);
641
642            // Add integer key
643            let stack = push(stack, Value::Int(42));
644            let stack = push(stack, Value::String("answer".into()));
645            let stack = map_set(stack);
646
647            // Add boolean key
648            let stack = push(stack, Value::Bool(true));
649            let stack = push(stack, Value::String("yes".into()));
650            let stack = map_set(stack);
651
652            // Verify size is 3
653            let stack = crate::stack::dup(stack);
654            let stack = map_size(stack);
655            let (stack, size) = pop(stack);
656            assert_eq!(size, Value::Int(3));
657
658            // Verify each key retrieves correct value
659            // map_get returns (value Bool)
660            let stack = crate::stack::dup(stack);
661            let stack = push(stack, Value::String("name".into()));
662            let stack = map_get(stack);
663            let (stack, flag) = pop(stack);
664            assert_eq!(flag, Value::Bool(true));
665            let (stack, result) = pop(stack);
666            match result {
667                Value::String(s) => assert_eq!(s.as_str(), "Alice"),
668                _ => panic!("Expected String for name key"),
669            }
670
671            let stack = crate::stack::dup(stack);
672            let stack = push(stack, Value::Int(42));
673            let stack = map_get(stack);
674            let (stack, flag) = pop(stack);
675            assert_eq!(flag, Value::Bool(true));
676            let (stack, result) = pop(stack);
677            match result {
678                Value::String(s) => assert_eq!(s.as_str(), "answer"),
679                _ => panic!("Expected String for int key"),
680            }
681
682            let stack = push(stack, Value::Bool(true));
683            let stack = map_get(stack);
684            let (stack, flag) = pop(stack);
685            assert_eq!(flag, Value::Bool(true));
686            let (_stack, result) = pop(stack);
687            match result {
688                Value::String(s) => assert_eq!(s.as_str(), "yes"),
689                _ => panic!("Expected String for bool key"),
690            }
691        }
692    }
693}