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