Skip to main content

aver/types/
map.rs

1/// Map namespace — immutable key/value map helpers.
2///
3/// Methods:
4///   Map.empty()                 → Map<K, V>
5///   Map.set(map, key, value)    → Map<K, V>
6///   Map.get(map, key)           → Option<V>
7///   Map.remove(map, key)        → Map<K, V>
8///   Map.has(map, key)           → Bool
9///   Map.keys(map)               → List<K>
10///   Map.values(map)             → List<V>
11///   Map.entries(map)            → List<(K, V)>
12///   Map.len(map)                → Int
13///   Map.fromList(pairs)         → Map<K, V> where each pair is (key, value)
14///
15/// Key constraint: only scalar keys are allowed (Int, Float, String, Bool).
16///
17/// No effects required.
18use std::cmp::Ordering;
19use std::collections::HashMap;
20use std::sync::Arc as Rc;
21
22use crate::nan_value::{Arena, NanValue};
23use crate::value::{RuntimeError, Value, aver_repr, list_from_vec, list_view};
24
25pub fn register(global: &mut HashMap<String, Value>) {
26    let mut members = HashMap::new();
27    for method in &[
28        "empty", "set", "get", "remove", "has", "keys", "values", "entries", "len", "fromList",
29    ] {
30        members.insert(
31            method.to_string(),
32            Value::Builtin(format!("Map.{}", method)),
33        );
34    }
35    global.insert(
36        "Map".to_string(),
37        Value::Namespace {
38            name: "Map".to_string(),
39            members,
40        },
41    );
42}
43
44pub fn effects(_name: &str) -> &'static [&'static str] {
45    &[]
46}
47
48/// Returns `Some(result)` when `name` is owned by this namespace, `None` otherwise.
49pub fn call(name: &str, args: &[Value]) -> Option<Result<Value, RuntimeError>> {
50    match name {
51        "Map.empty" => Some(empty(args)),
52        "Map.set" => Some(set(args)),
53        "Map.get" => Some(get(args)),
54        "Map.remove" => Some(remove(args)),
55        "Map.has" => Some(has(args)),
56        "Map.keys" => Some(keys(args)),
57        "Map.values" => Some(values(args)),
58        "Map.entries" => Some(entries(args)),
59        "Map.len" => Some(len(args)),
60        "Map.fromList" => Some(from_list(args)),
61        _ => None,
62    }
63}
64
65fn empty(args: &[Value]) -> Result<Value, RuntimeError> {
66    if !args.is_empty() {
67        return Err(RuntimeError::Error(format!(
68            "Map.empty() takes 0 arguments, got {}",
69            args.len()
70        )));
71    }
72    Ok(Value::Map(HashMap::new()))
73}
74
75fn set(args: &[Value]) -> Result<Value, RuntimeError> {
76    let [map_val, key, value] = three_args("Map.set", args)?;
77    let Value::Map(map) = map_val else {
78        return Err(RuntimeError::Error(
79            "Map.set() first argument must be a Map".to_string(),
80        ));
81    };
82    ensure_hashable_key("Map.set", key)?;
83    let mut out = map.clone();
84    out.insert(key.clone(), value.clone());
85    Ok(Value::Map(out))
86}
87
88fn get(args: &[Value]) -> Result<Value, RuntimeError> {
89    let [map_val, key] = two_args("Map.get", args)?;
90    let Value::Map(map) = map_val else {
91        return Err(RuntimeError::Error(
92            "Map.get() first argument must be a Map".to_string(),
93        ));
94    };
95    ensure_hashable_key("Map.get", key)?;
96    Ok(match map.get(key) {
97        Some(v) => Value::Some(Box::new(v.clone())),
98        None => Value::None,
99    })
100}
101
102fn remove(args: &[Value]) -> Result<Value, RuntimeError> {
103    let [map_val, key] = two_args("Map.remove", args)?;
104    let Value::Map(map) = map_val else {
105        return Err(RuntimeError::Error(
106            "Map.remove() first argument must be a Map".to_string(),
107        ));
108    };
109    ensure_hashable_key("Map.remove", key)?;
110    let mut out = map.clone();
111    out.remove(key);
112    Ok(Value::Map(out))
113}
114
115fn has(args: &[Value]) -> Result<Value, RuntimeError> {
116    let [map_val, key] = two_args("Map.has", args)?;
117    let Value::Map(map) = map_val else {
118        return Err(RuntimeError::Error(
119            "Map.has() first argument must be a Map".to_string(),
120        ));
121    };
122    ensure_hashable_key("Map.has", key)?;
123    Ok(Value::Bool(map.contains_key(key)))
124}
125
126fn keys(args: &[Value]) -> Result<Value, RuntimeError> {
127    let [map_val] = one_arg("Map.keys", args)?;
128    let Value::Map(map) = map_val else {
129        return Err(RuntimeError::Error(
130            "Map.keys() argument must be a Map".to_string(),
131        ));
132    };
133    let mut out = map.keys().cloned().collect::<Vec<_>>();
134    out.sort_by(compare_scalar_keys);
135    Ok(list_from_vec(out))
136}
137
138fn values(args: &[Value]) -> Result<Value, RuntimeError> {
139    let [map_val] = one_arg("Map.values", args)?;
140    let Value::Map(map) = map_val else {
141        return Err(RuntimeError::Error(
142            "Map.values() argument must be a Map".to_string(),
143        ));
144    };
145    let mut entries = map.iter().collect::<Vec<_>>();
146    entries.sort_by(|(k1, _), (k2, _)| compare_scalar_keys(k1, k2));
147    let out = entries
148        .into_iter()
149        .map(|(_, v)| v.clone())
150        .collect::<Vec<_>>();
151    Ok(list_from_vec(out))
152}
153
154fn entries(args: &[Value]) -> Result<Value, RuntimeError> {
155    let [map_val] = one_arg("Map.entries", args)?;
156    let Value::Map(map) = map_val else {
157        return Err(RuntimeError::Error(
158            "Map.entries() argument must be a Map".to_string(),
159        ));
160    };
161    let mut entries = map.iter().collect::<Vec<_>>();
162    entries.sort_by(|(k1, _), (k2, _)| compare_scalar_keys(k1, k2));
163    let out = entries
164        .into_iter()
165        .map(|(k, v)| Value::Tuple(vec![k.clone(), v.clone()]))
166        .collect::<Vec<_>>();
167    Ok(list_from_vec(out))
168}
169
170fn len(args: &[Value]) -> Result<Value, RuntimeError> {
171    let [map_val] = one_arg("Map.len", args)?;
172    let Value::Map(map) = map_val else {
173        return Err(RuntimeError::Error(
174            "Map.len() argument must be a Map".to_string(),
175        ));
176    };
177    Ok(Value::Int(map.len() as i64))
178}
179
180fn from_list(args: &[Value]) -> Result<Value, RuntimeError> {
181    let [pairs] = one_arg("Map.fromList", args)?;
182    let items = list_view(pairs).ok_or_else(|| {
183        RuntimeError::Error(
184            "Map.fromList() argument must be a List of (key, value) tuples".to_string(),
185        )
186    })?;
187
188    let mut out = HashMap::new();
189    for (idx, pair) in items.iter().enumerate() {
190        let Value::Tuple(parts) = pair else {
191            return Err(RuntimeError::Error(format!(
192                "Map.fromList() item {} must be (key, value)",
193                idx + 1
194            )));
195        };
196        if parts.len() != 2 {
197            return Err(RuntimeError::Error(format!(
198                "Map.fromList() item {} must have 2 elements",
199                idx + 1
200            )));
201        }
202
203        let key = &parts[0];
204        let value = &parts[1];
205        ensure_hashable_key("Map.fromList", key)?;
206        out.insert(key.clone(), value.clone());
207    }
208    Ok(Value::Map(out))
209}
210
211fn is_hashable_key(value: &Value) -> bool {
212    !matches!(value, Value::Fn(_))
213}
214
215fn ensure_hashable_key(name: &str, value: &Value) -> Result<(), RuntimeError> {
216    if is_hashable_key(value) {
217        Ok(())
218    } else {
219        Err(RuntimeError::Error(format!(
220            "{}: key must be hashable (functions are not)",
221            name
222        )))
223    }
224}
225
226fn compare_scalar_keys(a: &Value, b: &Value) -> Ordering {
227    match (a, b) {
228        (Value::Int(x), Value::Int(y)) => x.cmp(y),
229        (Value::Float(x), Value::Float(y)) => x.partial_cmp(y).unwrap_or_else(|| {
230            let xb = x.to_bits();
231            let yb = y.to_bits();
232            xb.cmp(&yb)
233        }),
234        (Value::Str(x), Value::Str(y)) => x.cmp(y),
235        (Value::Bool(x), Value::Bool(y)) => x.cmp(y),
236        _ => aver_repr(a).cmp(&aver_repr(b)),
237    }
238}
239
240fn one_arg<'a>(name: &str, args: &'a [Value]) -> Result<[&'a Value; 1], RuntimeError> {
241    if args.len() != 1 {
242        return Err(RuntimeError::Error(format!(
243            "{}() takes 1 argument, got {}",
244            name,
245            args.len()
246        )));
247    }
248    Ok([&args[0]])
249}
250
251fn two_args<'a>(name: &str, args: &'a [Value]) -> Result<[&'a Value; 2], RuntimeError> {
252    if args.len() != 2 {
253        return Err(RuntimeError::Error(format!(
254            "{}() takes 2 arguments, got {}",
255            name,
256            args.len()
257        )));
258    }
259    Ok([&args[0], &args[1]])
260}
261
262fn three_args<'a>(name: &str, args: &'a [Value]) -> Result<[&'a Value; 3], RuntimeError> {
263    if args.len() != 3 {
264        return Err(RuntimeError::Error(format!(
265            "{}() takes 3 arguments, got {}",
266            name,
267            args.len()
268        )));
269    }
270    Ok([&args[0], &args[1], &args[2]])
271}
272
273// ─── NanValue-native API ─────────────────────────────────────────────────────
274
275pub fn register_nv(global: &mut HashMap<String, NanValue>, arena: &mut Arena) {
276    let methods = &[
277        "empty", "set", "get", "remove", "has", "keys", "values", "entries", "len", "fromList",
278    ];
279    let mut members: Vec<(Rc<str>, NanValue)> = Vec::with_capacity(methods.len());
280    for method in methods {
281        let idx = arena.push_builtin(&format!("Map.{}", method));
282        members.push((Rc::from(*method), NanValue::new_builtin(idx)));
283    }
284    let ns_idx = arena.push(crate::nan_value::ArenaEntry::Namespace {
285        name: Rc::from("Map"),
286        members,
287    });
288    global.insert("Map".to_string(), NanValue::new_namespace(ns_idx));
289}
290
291pub fn call_nv(
292    name: &str,
293    args: &[NanValue],
294    arena: &mut Arena,
295) -> Option<Result<NanValue, RuntimeError>> {
296    match name {
297        "Map.empty" => Some(empty_nv(args, arena)),
298        "Map.set" => Some(set_nv(args, arena)),
299        "Map.get" => Some(get_nv(args, arena)),
300        "Map.remove" => Some(remove_nv(args, arena)),
301        "Map.has" => Some(has_nv(args, arena)),
302        "Map.keys" => Some(keys_nv(args, arena)),
303        "Map.values" => Some(values_nv(args, arena)),
304        "Map.entries" => Some(entries_nv(args, arena)),
305        "Map.len" => Some(len_nv(args, arena)),
306        "Map.fromList" => Some(from_list_nv(args, arena)),
307        _ => None,
308    }
309}
310
311fn is_hashable_nv(v: NanValue) -> bool {
312    // Functions are the only Aver value not hashable — see the `Value`
313    // path above. Everything else (variants, tuples, records, lists,
314    // vectors, wrappers, scalars) participates in `hash_in` and
315    // `eq_in`.
316    !v.is_fn()
317}
318
319fn ensure_hashable_nv(name: &str, v: NanValue) -> Result<(), RuntimeError> {
320    if is_hashable_nv(v) {
321        Ok(())
322    } else {
323        Err(RuntimeError::Error(format!(
324            "{}: key must be hashable (functions are not)",
325            name
326        )))
327    }
328}
329
330fn nv_key_bits(v: NanValue, arena: &Arena) -> u64 {
331    v.map_key_hash_deep(arena)
332}
333
334fn empty_nv(args: &[NanValue], arena: &mut Arena) -> Result<NanValue, RuntimeError> {
335    if !args.is_empty() {
336        return Err(RuntimeError::Error(format!(
337            "Map.empty() takes 0 arguments, got {}",
338            args.len()
339        )));
340    }
341    let _ = arena;
342    Ok(NanValue::EMPTY_MAP)
343}
344
345/// Map.set with sole-owned first argument — takes instead of cloning.
346pub fn set_nv_owned(args: &[NanValue], arena: &mut Arena) -> Result<NanValue, RuntimeError> {
347    if args.len() != 3 {
348        return Err(RuntimeError::Error(format!(
349            "Map.set() takes 3 arguments, got {}",
350            args.len()
351        )));
352    }
353    if !args[0].is_map() {
354        return Err(RuntimeError::Error(
355            "Map.set() first argument must be a Map".to_string(),
356        ));
357    }
358    ensure_hashable_nv("Map.set", args[1])?;
359    let source = args[0];
360    let old_map = arena.take_map_value(source);
361    let key_hash = nv_key_bits(args[1], arena);
362    let new_map = old_map.insert_owned(key_hash, (args[1], args[2]));
363    let map_idx = arena.push_inheriting_source_space(aver_memory::ArenaEntry::Map(new_map), source);
364    Ok(NanValue::new_map(map_idx))
365}
366
367fn set_nv(args: &[NanValue], arena: &mut Arena) -> Result<NanValue, RuntimeError> {
368    if args.len() != 3 {
369        return Err(RuntimeError::Error(format!(
370            "Map.set() takes 3 arguments, got {}",
371            args.len()
372        )));
373    }
374    if !args[0].is_map() {
375        return Err(RuntimeError::Error(
376            "Map.set() first argument must be a Map".to_string(),
377        ));
378    }
379    ensure_hashable_nv("Map.set", args[1])?;
380    let old_map = arena.clone_map_value(args[0]);
381    let key_hash = nv_key_bits(args[1], arena);
382    let new_map = old_map.insert(key_hash, (args[1], args[2]));
383    let map_idx = arena.push_map(new_map);
384    Ok(NanValue::new_map(map_idx))
385}
386
387fn get_nv(args: &[NanValue], arena: &mut Arena) -> Result<NanValue, RuntimeError> {
388    if args.len() != 2 {
389        return Err(RuntimeError::Error(format!(
390            "Map.get() takes 2 arguments, got {}",
391            args.len()
392        )));
393    }
394    if !args[0].is_map() {
395        return Err(RuntimeError::Error(
396            "Map.get() first argument must be a Map".to_string(),
397        ));
398    }
399    ensure_hashable_nv("Map.get", args[1])?;
400    let key_hash = nv_key_bits(args[1], arena);
401    let map = arena.map_ref_value(args[0]);
402    match map.get(&key_hash) {
403        Some((_, v)) => Ok(NanValue::new_some_value(*v, arena)),
404        None => Ok(NanValue::NONE),
405    }
406}
407
408fn remove_nv(args: &[NanValue], arena: &mut Arena) -> Result<NanValue, RuntimeError> {
409    if args.len() != 2 {
410        return Err(RuntimeError::Error(format!(
411            "Map.remove() takes 2 arguments, got {}",
412            args.len()
413        )));
414    }
415    if !args[0].is_map() {
416        return Err(RuntimeError::Error(
417            "Map.remove() first argument must be a Map".to_string(),
418        ));
419    }
420    ensure_hashable_nv("Map.remove", args[1])?;
421    let old_map = arena.clone_map_value(args[0]);
422    let key_hash = nv_key_bits(args[1], arena);
423    let new_map = old_map.remove(&key_hash);
424    if new_map.is_empty() {
425        Ok(NanValue::EMPTY_MAP)
426    } else {
427        let map_idx = arena.push_map(new_map);
428        Ok(NanValue::new_map(map_idx))
429    }
430}
431
432fn has_nv(args: &[NanValue], arena: &mut Arena) -> Result<NanValue, RuntimeError> {
433    if args.len() != 2 {
434        return Err(RuntimeError::Error(format!(
435            "Map.has() takes 2 arguments, got {}",
436            args.len()
437        )));
438    }
439    if !args[0].is_map() {
440        return Err(RuntimeError::Error(
441            "Map.has() first argument must be a Map".to_string(),
442        ));
443    }
444    ensure_hashable_nv("Map.has", args[1])?;
445    let key_hash = nv_key_bits(args[1], arena);
446    let map = arena.map_ref_value(args[0]);
447    Ok(NanValue::new_bool(map.contains_key(&key_hash)))
448}
449
450fn keys_nv(args: &[NanValue], arena: &mut Arena) -> Result<NanValue, RuntimeError> {
451    if args.len() != 1 {
452        return Err(RuntimeError::Error(format!(
453            "Map.keys() takes 1 argument, got {}",
454            args.len()
455        )));
456    }
457    if !args[0].is_map() {
458        return Err(RuntimeError::Error(
459            "Map.keys() argument must be a Map".to_string(),
460        ));
461    }
462    let map = arena.clone_map_value(args[0]);
463    let mut keys: Vec<NanValue> = map.values().map(|(k, _)| *k).collect();
464    keys.sort_by_key(|a| a.repr(arena));
465    if keys.is_empty() {
466        return Ok(NanValue::EMPTY_LIST);
467    }
468    let list_idx = arena.push_list(keys);
469    Ok(NanValue::new_list(list_idx))
470}
471
472fn values_nv(args: &[NanValue], arena: &mut Arena) -> Result<NanValue, RuntimeError> {
473    if args.len() != 1 {
474        return Err(RuntimeError::Error(format!(
475            "Map.values() takes 1 argument, got {}",
476            args.len()
477        )));
478    }
479    if !args[0].is_map() {
480        return Err(RuntimeError::Error(
481            "Map.values() argument must be a Map".to_string(),
482        ));
483    }
484    let map = arena.clone_map_value(args[0]);
485    let mut entries: Vec<(NanValue, NanValue)> = map.values().cloned().collect();
486    entries.sort_by_key(|(a, _)| a.repr(arena));
487    let vals: Vec<NanValue> = entries.into_iter().map(|(_, v)| v).collect();
488    if vals.is_empty() {
489        return Ok(NanValue::EMPTY_LIST);
490    }
491    let list_idx = arena.push_list(vals);
492    Ok(NanValue::new_list(list_idx))
493}
494
495fn entries_nv(args: &[NanValue], arena: &mut Arena) -> Result<NanValue, RuntimeError> {
496    if args.len() != 1 {
497        return Err(RuntimeError::Error(format!(
498            "Map.entries() takes 1 argument, got {}",
499            args.len()
500        )));
501    }
502    if !args[0].is_map() {
503        return Err(RuntimeError::Error(
504            "Map.entries() argument must be a Map".to_string(),
505        ));
506    }
507    let map = arena.clone_map_value(args[0]);
508    let mut entries: Vec<(NanValue, NanValue)> = map.values().cloned().collect();
509    entries.sort_by_key(|(a, _)| a.repr(arena));
510    let pairs: Vec<NanValue> = entries
511        .into_iter()
512        .map(|(k, v)| {
513            let tuple_idx = arena.push_tuple(vec![k, v]);
514            NanValue::new_tuple(tuple_idx)
515        })
516        .collect();
517    if pairs.is_empty() {
518        return Ok(NanValue::EMPTY_LIST);
519    }
520    let list_idx = arena.push_list(pairs);
521    Ok(NanValue::new_list(list_idx))
522}
523
524fn len_nv(args: &[NanValue], arena: &mut Arena) -> Result<NanValue, RuntimeError> {
525    if args.len() != 1 {
526        return Err(RuntimeError::Error(format!(
527            "Map.len() takes 1 argument, got {}",
528            args.len()
529        )));
530    }
531    if !args[0].is_map() {
532        return Err(RuntimeError::Error(
533            "Map.len() argument must be a Map".to_string(),
534        ));
535    }
536    let map = arena.map_ref_value(args[0]);
537    Ok(NanValue::new_int(map.len() as i64, arena))
538}
539
540fn from_list_nv(args: &[NanValue], arena: &mut Arena) -> Result<NanValue, RuntimeError> {
541    if args.len() != 1 {
542        return Err(RuntimeError::Error(format!(
543            "Map.fromList() takes 1 argument, got {}",
544            args.len()
545        )));
546    }
547    if !args[0].is_list() {
548        return Err(RuntimeError::Error(
549            "Map.fromList() argument must be a List of (key, value) tuples".to_string(),
550        ));
551    }
552    let items = arena.list_to_vec_value(args[0]);
553    let mut out = crate::nan_value::PersistentMap::new();
554    for (idx, pair) in items.iter().enumerate() {
555        if !pair.is_tuple() {
556            return Err(RuntimeError::Error(format!(
557                "Map.fromList() item {} must be (key, value)",
558                idx + 1
559            )));
560        }
561        let parts = arena.get_tuple(pair.arena_index());
562        if parts.len() != 2 {
563            return Err(RuntimeError::Error(format!(
564                "Map.fromList() item {} must have 2 elements",
565                idx + 1
566            )));
567        }
568        let key = parts[0];
569        let value = parts[1];
570        ensure_hashable_nv("Map.fromList", key)?;
571        let key_hash = nv_key_bits(key, arena);
572        out = out.insert(key_hash, (key, value));
573    }
574    if out.is_empty() {
575        Ok(NanValue::EMPTY_MAP)
576    } else {
577        let map_idx = arena.push_map(out);
578        Ok(NanValue::new_map(map_idx))
579    }
580}