Skip to main content

aver/types/
map.rs

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