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