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 = crate::stack::alloc_test_stack();
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        }
354    }
355
356    #[test]
357    fn test_map_set_and_get() {
358        unsafe {
359            let stack = crate::stack::alloc_test_stack();
360            let stack = make_map(stack);
361            let stack = push(stack, Value::String("name".into()));
362            let stack = push(stack, Value::String("Alice".into()));
363            let stack = map_set(stack);
364
365            // Get the value back
366            let stack = push(stack, Value::String("name".into()));
367            let stack = map_get(stack);
368
369            let (_stack, result) = pop(stack);
370            match result {
371                Value::String(s) => assert_eq!(s.as_str(), "Alice"),
372                _ => panic!("Expected String"),
373            }
374        }
375    }
376
377    #[test]
378    fn test_map_set_with_int_key() {
379        unsafe {
380            let stack = crate::stack::alloc_test_stack();
381            let stack = make_map(stack);
382            let stack = push(stack, Value::Int(42));
383            let stack = push(stack, Value::String("answer".into()));
384            let stack = map_set(stack);
385
386            let stack = push(stack, Value::Int(42));
387            let stack = map_get(stack);
388
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::Int(1));
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::Int(0));
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::Int(0));
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::Int(1));
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::Int(1));
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::Int(0));
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_safe_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_safe(stack);
548
549            let (stack, flag) = pop(stack);
550            let (_stack, value) = pop(stack);
551            assert_eq!(flag, Value::Int(1));
552            assert_eq!(value, Value::Int(42));
553        }
554    }
555
556    #[test]
557    fn test_map_get_safe_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_safe(stack);
564
565            let (stack, flag) = pop(stack);
566            let (_stack, _value) = pop(stack); // placeholder
567            assert_eq!(flag, Value::Int(0));
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            let (_stack, result) = pop(stack);
586            match result {
587                Value::String(s) => assert_eq!(s.as_str(), "yes"),
588                _ => panic!("Expected String"),
589            }
590        }
591    }
592
593    #[test]
594    fn test_map_key_overwrite() {
595        // Test that map-set with existing key overwrites the value
596        unsafe {
597            let stack = crate::stack::alloc_test_stack();
598            let stack = make_map(stack);
599
600            // Set initial value
601            let stack = push(stack, Value::String("key".into()));
602            let stack = push(stack, Value::Int(100));
603            let stack = map_set(stack);
604
605            // Overwrite with new value
606            let stack = push(stack, Value::String("key".into()));
607            let stack = push(stack, Value::Int(200));
608            let stack = map_set(stack);
609
610            // Verify size is still 1 (not 2)
611            let stack = crate::stack::dup(stack);
612            let stack = map_size(stack);
613            let (stack, size) = pop(stack);
614            assert_eq!(size, Value::Int(1));
615
616            // Verify value was updated
617            let stack = push(stack, Value::String("key".into()));
618            let stack = map_get(stack);
619            let (_stack, result) = pop(stack);
620            assert_eq!(result, Value::Int(200));
621        }
622    }
623
624    #[test]
625    fn test_map_mixed_key_types() {
626        // Test that a single map can have different key types
627        unsafe {
628            let stack = crate::stack::alloc_test_stack();
629            let stack = make_map(stack);
630
631            // Add string key
632            let stack = push(stack, Value::String("name".into()));
633            let stack = push(stack, Value::String("Alice".into()));
634            let stack = map_set(stack);
635
636            // Add integer key
637            let stack = push(stack, Value::Int(42));
638            let stack = push(stack, Value::String("answer".into()));
639            let stack = map_set(stack);
640
641            // Add boolean key
642            let stack = push(stack, Value::Bool(true));
643            let stack = push(stack, Value::String("yes".into()));
644            let stack = map_set(stack);
645
646            // Verify size is 3
647            let stack = crate::stack::dup(stack);
648            let stack = map_size(stack);
649            let (stack, size) = pop(stack);
650            assert_eq!(size, Value::Int(3));
651
652            // Verify each key retrieves correct value
653            let stack = crate::stack::dup(stack);
654            let stack = push(stack, Value::String("name".into()));
655            let stack = map_get(stack);
656            let (stack, result) = pop(stack);
657            match result {
658                Value::String(s) => assert_eq!(s.as_str(), "Alice"),
659                _ => panic!("Expected String for name key"),
660            }
661
662            let stack = crate::stack::dup(stack);
663            let stack = push(stack, Value::Int(42));
664            let stack = map_get(stack);
665            let (stack, result) = pop(stack);
666            match result {
667                Value::String(s) => assert_eq!(s.as_str(), "answer"),
668                _ => panic!("Expected String for int key"),
669            }
670
671            let stack = push(stack, Value::Bool(true));
672            let stack = map_get(stack);
673            let (_stack, result) = pop(stack);
674            match result {
675                Value::String(s) => assert_eq!(s.as_str(), "yes"),
676                _ => panic!("Expected String for bool key"),
677            }
678        }
679    }
680}