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, 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 (functional style)
95///
96/// Stack effect: ( Map key value -- Map )
97///
98/// Returns a new map with the key-value pair added/updated.
99/// Panics if the key type is not hashable.
100///
101/// # Safety
102/// Stack must have value on top, key below, and Map at third position
103#[unsafe(no_mangle)]
104pub unsafe extern "C" fn patch_seq_map_set(stack: Stack) -> Stack {
105    unsafe {
106        // Pop value
107        let (stack, value) = pop(stack);
108
109        // Pop key
110        let (stack, key_val) = pop(stack);
111        let key = MapKey::from_value(&key_val).unwrap_or_else(|| {
112            panic!(
113                "map-set: key must be Int, String, or Bool, got {:?}",
114                key_val
115            )
116        });
117
118        // Pop map
119        let (stack, map_val) = pop(stack);
120        let mut map = match map_val {
121            Value::Map(m) => *m,
122            _ => panic!("map-set: expected Map, got {:?}", map_val),
123        };
124
125        // Insert key-value pair
126        map.insert(key, value);
127
128        push(stack, Value::Map(Box::new(map)))
129    }
130}
131
132/// Check if a key exists in the map
133///
134/// Stack effect: ( Map key -- Int )
135///
136/// Returns 1 if the key exists, 0 otherwise.
137/// Panics if the key type is not hashable.
138///
139/// # Safety
140/// Stack must have a hashable key on top and a Map below
141#[unsafe(no_mangle)]
142pub unsafe extern "C" fn patch_seq_map_has(stack: Stack) -> Stack {
143    unsafe {
144        // Pop key
145        let (stack, key_val) = pop(stack);
146        let key = MapKey::from_value(&key_val).unwrap_or_else(|| {
147            panic!(
148                "map-has?: key must be Int, String, or Bool, got {:?}",
149                key_val
150            )
151        });
152
153        // Pop map
154        let (stack, map_val) = pop(stack);
155        let map = match map_val {
156            Value::Map(m) => m,
157            _ => panic!("map-has?: expected Map, got {:?}", map_val),
158        };
159
160        let has_key = map.contains_key(&key);
161        push(stack, Value::Bool(has_key))
162    }
163}
164
165/// Remove a key from the map (functional style)
166///
167/// Stack effect: ( Map key -- Map )
168///
169/// Returns a new map without the specified key.
170/// If the key doesn't exist, returns the map unchanged.
171/// Panics if the key type is not hashable.
172///
173/// # Safety
174/// Stack must have a hashable key on top and a Map below
175#[unsafe(no_mangle)]
176pub unsafe extern "C" fn patch_seq_map_remove(stack: Stack) -> Stack {
177    unsafe {
178        // Pop key
179        let (stack, key_val) = pop(stack);
180        let key = MapKey::from_value(&key_val).unwrap_or_else(|| {
181            panic!(
182                "map-remove: key must be Int, String, or Bool, got {:?}",
183                key_val
184            )
185        });
186
187        // Pop map
188        let (stack, map_val) = pop(stack);
189        let mut map = match map_val {
190            Value::Map(m) => *m,
191            _ => panic!("map-remove: expected Map, got {:?}", map_val),
192        };
193
194        // Remove key (if present)
195        map.remove(&key);
196
197        push(stack, Value::Map(Box::new(map)))
198    }
199}
200
201/// Get all keys from the map as a list
202///
203/// Stack effect: ( Map -- Variant )
204///
205/// Returns a Variant containing all keys in the map.
206/// Note: Order is not guaranteed (HashMap iteration order).
207///
208/// # Safety
209/// Stack must have a Map on top
210#[unsafe(no_mangle)]
211pub unsafe extern "C" fn patch_seq_map_keys(stack: Stack) -> Stack {
212    unsafe {
213        let (stack, map_val) = pop(stack);
214        let map = match map_val {
215            Value::Map(m) => m,
216            _ => panic!("map-keys: expected Map, got {:?}", map_val),
217        };
218
219        let keys: Vec<Value> = map.keys().map(|k| k.to_value()).collect();
220        let variant = Value::Variant(Arc::new(VariantData::new(
221            global_string("List".to_string()),
222            keys,
223        )));
224        push(stack, variant)
225    }
226}
227
228/// Get all values from the map as a list
229///
230/// Stack effect: ( Map -- Variant )
231///
232/// Returns a Variant containing all values in the map.
233/// Note: Order is not guaranteed (HashMap iteration order).
234///
235/// # Safety
236/// Stack must have a Map on top
237#[unsafe(no_mangle)]
238pub unsafe extern "C" fn patch_seq_map_values(stack: Stack) -> Stack {
239    unsafe {
240        let (stack, map_val) = pop(stack);
241        let map = match map_val {
242            Value::Map(m) => m,
243            _ => panic!("map-values: expected Map, got {:?}", map_val),
244        };
245
246        let values: Vec<Value> = map.values().cloned().collect();
247        let variant = Value::Variant(Arc::new(VariantData::new(
248            global_string("List".to_string()),
249            values,
250        )));
251        push(stack, variant)
252    }
253}
254
255/// Get the number of entries in the map
256///
257/// Stack effect: ( Map -- Int )
258///
259/// # Safety
260/// Stack must have a Map on top
261#[unsafe(no_mangle)]
262pub unsafe extern "C" fn patch_seq_map_size(stack: Stack) -> Stack {
263    unsafe {
264        let (stack, map_val) = pop(stack);
265        let map = match map_val {
266            Value::Map(m) => m,
267            _ => panic!("map-size: expected Map, got {:?}", map_val),
268        };
269
270        push(stack, Value::Int(map.len() as i64))
271    }
272}
273
274/// Check if the map is empty
275///
276/// Stack effect: ( Map -- Int )
277///
278/// Returns 1 if the map has no entries, 0 otherwise.
279///
280/// # Safety
281/// Stack must have a Map on top
282#[unsafe(no_mangle)]
283pub unsafe extern "C" fn patch_seq_map_empty(stack: Stack) -> Stack {
284    unsafe {
285        let (stack, map_val) = pop(stack);
286        let map = match map_val {
287            Value::Map(m) => m,
288            _ => panic!("map-empty?: expected Map, got {:?}", map_val),
289        };
290
291        let is_empty = map.is_empty();
292        push(stack, Value::Bool(is_empty))
293    }
294}
295
296// Public re-exports
297pub use patch_seq_make_map as make_map;
298pub use patch_seq_map_empty as map_empty;
299pub use patch_seq_map_get as map_get;
300pub use patch_seq_map_has as map_has;
301pub use patch_seq_map_keys as map_keys;
302pub use patch_seq_map_remove as map_remove;
303pub use patch_seq_map_set as map_set;
304pub use patch_seq_map_size as map_size;
305pub use patch_seq_map_values as map_values;
306
307#[cfg(test)]
308mod tests {
309    use super::*;
310
311    #[test]
312    fn test_make_map() {
313        unsafe {
314            let stack = crate::stack::alloc_test_stack();
315            let stack = make_map(stack);
316
317            let (_stack, result) = pop(stack);
318            match result {
319                Value::Map(m) => assert!(m.is_empty()),
320                _ => panic!("Expected Map"),
321            }
322        }
323    }
324
325    #[test]
326    fn test_map_set_and_get() {
327        unsafe {
328            let stack = crate::stack::alloc_test_stack();
329            let stack = make_map(stack);
330            let stack = push(stack, Value::String("name".into()));
331            let stack = push(stack, Value::String("Alice".into()));
332            let stack = map_set(stack);
333
334            // Get the value back
335            let stack = push(stack, Value::String("name".into()));
336            let stack = map_get(stack);
337
338            // map_get returns (value Bool)
339            let (stack, flag) = pop(stack);
340            assert_eq!(flag, Value::Bool(true));
341            let (_stack, result) = pop(stack);
342            match result {
343                Value::String(s) => assert_eq!(s.as_str(), "Alice"),
344                _ => panic!("Expected String"),
345            }
346        }
347    }
348
349    #[test]
350    fn test_map_set_with_int_key() {
351        unsafe {
352            let stack = crate::stack::alloc_test_stack();
353            let stack = make_map(stack);
354            let stack = push(stack, Value::Int(42));
355            let stack = push(stack, Value::String("answer".into()));
356            let stack = map_set(stack);
357
358            let stack = push(stack, Value::Int(42));
359            let stack = map_get(stack);
360
361            // map_get returns (value Bool)
362            let (stack, flag) = pop(stack);
363            assert_eq!(flag, Value::Bool(true));
364            let (_stack, result) = pop(stack);
365            match result {
366                Value::String(s) => assert_eq!(s.as_str(), "answer"),
367                _ => panic!("Expected String"),
368            }
369        }
370    }
371
372    #[test]
373    fn test_map_has() {
374        unsafe {
375            let stack = crate::stack::alloc_test_stack();
376            let stack = make_map(stack);
377            let stack = push(stack, Value::String("key".into()));
378            let stack = push(stack, Value::Int(100));
379            let stack = map_set(stack);
380
381            // Check existing key (dup map first since map_has consumes it)
382            let stack = crate::stack::dup(stack);
383            let stack = push(stack, Value::String("key".into()));
384            let stack = map_has(stack);
385            let (stack, result) = pop(stack);
386            assert_eq!(result, Value::Bool(true));
387
388            // Check non-existing key (map is still on stack)
389            let stack = push(stack, Value::String("missing".into()));
390            let stack = map_has(stack);
391            let (_stack, result) = pop(stack);
392            assert_eq!(result, Value::Bool(false));
393        }
394    }
395
396    #[test]
397    fn test_map_remove() {
398        unsafe {
399            let stack = crate::stack::alloc_test_stack();
400            let stack = make_map(stack);
401            let stack = push(stack, Value::String("a".into()));
402            let stack = push(stack, Value::Int(1));
403            let stack = map_set(stack);
404            let stack = push(stack, Value::String("b".into()));
405            let stack = push(stack, Value::Int(2));
406            let stack = map_set(stack);
407
408            // Remove "a"
409            let stack = push(stack, Value::String("a".into()));
410            let stack = map_remove(stack);
411
412            // Check "a" is gone (dup map first since map_has consumes it)
413            let stack = crate::stack::dup(stack);
414            let stack = push(stack, Value::String("a".into()));
415            let stack = map_has(stack);
416            let (stack, result) = pop(stack);
417            assert_eq!(result, Value::Bool(false));
418
419            // Check "b" is still there (map is still on stack)
420            let stack = push(stack, Value::String("b".into()));
421            let stack = map_has(stack);
422            let (_stack, result) = pop(stack);
423            assert_eq!(result, Value::Bool(true));
424        }
425    }
426
427    #[test]
428    fn test_map_size() {
429        unsafe {
430            let stack = crate::stack::alloc_test_stack();
431            let stack = make_map(stack);
432
433            // Empty map
434            let stack = map_size(stack);
435            let (stack, result) = pop(stack);
436            assert_eq!(result, Value::Int(0));
437
438            // Add entries
439            let stack = make_map(stack);
440            let stack = push(stack, Value::String("a".into()));
441            let stack = push(stack, Value::Int(1));
442            let stack = map_set(stack);
443            let stack = push(stack, Value::String("b".into()));
444            let stack = push(stack, Value::Int(2));
445            let stack = map_set(stack);
446
447            let stack = map_size(stack);
448            let (_stack, result) = pop(stack);
449            assert_eq!(result, Value::Int(2));
450        }
451    }
452
453    #[test]
454    fn test_map_empty() {
455        unsafe {
456            let stack = crate::stack::alloc_test_stack();
457            let stack = make_map(stack);
458
459            let stack = map_empty(stack);
460            let (stack, result) = pop(stack);
461            assert_eq!(result, Value::Bool(true));
462
463            // Non-empty
464            let stack = make_map(stack);
465            let stack = push(stack, Value::String("key".into()));
466            let stack = push(stack, Value::Int(1));
467            let stack = map_set(stack);
468
469            let stack = map_empty(stack);
470            let (_stack, result) = pop(stack);
471            assert_eq!(result, Value::Bool(false));
472        }
473    }
474
475    #[test]
476    fn test_map_keys_and_values() {
477        unsafe {
478            let stack = crate::stack::alloc_test_stack();
479            let stack = make_map(stack);
480            let stack = push(stack, Value::String("x".into()));
481            let stack = push(stack, Value::Int(10));
482            let stack = map_set(stack);
483            let stack = push(stack, Value::String("y".into()));
484            let stack = push(stack, Value::Int(20));
485            let stack = map_set(stack);
486
487            // Get keys
488            let stack = crate::stack::dup(stack); // Keep map for values test
489            let stack = map_keys(stack);
490            let (stack, keys_result) = pop(stack);
491            match keys_result {
492                Value::Variant(v) => {
493                    assert_eq!(v.fields.len(), 2);
494                    // Keys are "x" and "y" but order is not guaranteed
495                }
496                _ => panic!("Expected Variant"),
497            }
498
499            // Get values
500            let stack = map_values(stack);
501            let (_stack, values_result) = pop(stack);
502            match values_result {
503                Value::Variant(v) => {
504                    assert_eq!(v.fields.len(), 2);
505                    // Values are 10 and 20 but order is not guaranteed
506                }
507                _ => panic!("Expected Variant"),
508            }
509        }
510    }
511
512    #[test]
513    fn test_map_get_found() {
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(42));
519            let stack = map_set(stack);
520
521            let stack = push(stack, Value::String("key".into()));
522            let stack = map_get(stack);
523
524            let (stack, flag) = pop(stack);
525            let (_stack, value) = pop(stack);
526            assert_eq!(flag, Value::Bool(true));
527            assert_eq!(value, Value::Int(42));
528        }
529    }
530
531    #[test]
532    fn test_map_get_not_found() {
533        unsafe {
534            let stack = crate::stack::alloc_test_stack();
535            let stack = make_map(stack);
536
537            let stack = push(stack, Value::String("missing".into()));
538            let stack = map_get(stack);
539
540            let (stack, flag) = pop(stack);
541            let (_stack, _value) = pop(stack); // placeholder
542            assert_eq!(flag, Value::Bool(false));
543        }
544    }
545
546    #[test]
547    fn test_map_with_bool_key() {
548        unsafe {
549            let stack = crate::stack::alloc_test_stack();
550            let stack = make_map(stack);
551            let stack = push(stack, Value::Bool(true));
552            let stack = push(stack, Value::String("yes".into()));
553            let stack = map_set(stack);
554            let stack = push(stack, Value::Bool(false));
555            let stack = push(stack, Value::String("no".into()));
556            let stack = map_set(stack);
557
558            let stack = push(stack, Value::Bool(true));
559            let stack = map_get(stack);
560            // map_get returns (value Bool)
561            let (stack, flag) = pop(stack);
562            assert_eq!(flag, Value::Bool(true));
563            let (_stack, result) = pop(stack);
564            match result {
565                Value::String(s) => assert_eq!(s.as_str(), "yes"),
566                _ => panic!("Expected String"),
567            }
568        }
569    }
570
571    #[test]
572    fn test_map_key_overwrite() {
573        // Test that map-set with existing key overwrites the value
574        unsafe {
575            let stack = crate::stack::alloc_test_stack();
576            let stack = make_map(stack);
577
578            // Set initial value
579            let stack = push(stack, Value::String("key".into()));
580            let stack = push(stack, Value::Int(100));
581            let stack = map_set(stack);
582
583            // Overwrite with new value
584            let stack = push(stack, Value::String("key".into()));
585            let stack = push(stack, Value::Int(200));
586            let stack = map_set(stack);
587
588            // Verify size is still 1 (not 2)
589            let stack = crate::stack::dup(stack);
590            let stack = map_size(stack);
591            let (stack, size) = pop(stack);
592            assert_eq!(size, Value::Int(1));
593
594            // Verify value was updated
595            let stack = push(stack, Value::String("key".into()));
596            let stack = map_get(stack);
597            // map_get returns (value Bool)
598            let (stack, flag) = pop(stack);
599            assert_eq!(flag, Value::Bool(true));
600            let (_stack, result) = pop(stack);
601            assert_eq!(result, Value::Int(200));
602        }
603    }
604
605    #[test]
606    fn test_map_mixed_key_types() {
607        // Test that a single map can have different key types
608        unsafe {
609            let stack = crate::stack::alloc_test_stack();
610            let stack = make_map(stack);
611
612            // Add string key
613            let stack = push(stack, Value::String("name".into()));
614            let stack = push(stack, Value::String("Alice".into()));
615            let stack = map_set(stack);
616
617            // Add integer key
618            let stack = push(stack, Value::Int(42));
619            let stack = push(stack, Value::String("answer".into()));
620            let stack = map_set(stack);
621
622            // Add boolean key
623            let stack = push(stack, Value::Bool(true));
624            let stack = push(stack, Value::String("yes".into()));
625            let stack = map_set(stack);
626
627            // Verify size is 3
628            let stack = crate::stack::dup(stack);
629            let stack = map_size(stack);
630            let (stack, size) = pop(stack);
631            assert_eq!(size, Value::Int(3));
632
633            // Verify each key retrieves correct value
634            // map_get returns (value Bool)
635            let stack = crate::stack::dup(stack);
636            let stack = push(stack, Value::String("name".into()));
637            let stack = map_get(stack);
638            let (stack, flag) = pop(stack);
639            assert_eq!(flag, Value::Bool(true));
640            let (stack, result) = pop(stack);
641            match result {
642                Value::String(s) => assert_eq!(s.as_str(), "Alice"),
643                _ => panic!("Expected String for name key"),
644            }
645
646            let stack = crate::stack::dup(stack);
647            let stack = push(stack, Value::Int(42));
648            let stack = map_get(stack);
649            let (stack, flag) = pop(stack);
650            assert_eq!(flag, Value::Bool(true));
651            let (stack, result) = pop(stack);
652            match result {
653                Value::String(s) => assert_eq!(s.as_str(), "answer"),
654                _ => panic!("Expected String for int key"),
655            }
656
657            let stack = push(stack, Value::Bool(true));
658            let stack = map_get(stack);
659            let (stack, flag) = pop(stack);
660            assert_eq!(flag, Value::Bool(true));
661            let (_stack, result) = pop(stack);
662            match result {
663                Value::String(s) => assert_eq!(s.as_str(), "yes"),
664                _ => panic!("Expected String for bool key"),
665            }
666        }
667    }
668}