1use 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
48pub 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!(
213 value,
214 Value::Int(_) | Value::Float(_) | Value::Str(_) | Value::Bool(_)
215 )
216}
217
218fn ensure_hashable_key(name: &str, value: &Value) -> Result<(), RuntimeError> {
219 if is_hashable_key(value) {
220 Ok(())
221 } else {
222 Err(RuntimeError::Error(format!(
223 "{}: key must be Int, Float, String, or Bool",
224 name
225 )))
226 }
227}
228
229fn compare_scalar_keys(a: &Value, b: &Value) -> Ordering {
230 match (a, b) {
231 (Value::Int(x), Value::Int(y)) => x.cmp(y),
232 (Value::Float(x), Value::Float(y)) => x.partial_cmp(y).unwrap_or_else(|| {
233 let xb = x.to_bits();
234 let yb = y.to_bits();
235 xb.cmp(&yb)
236 }),
237 (Value::Str(x), Value::Str(y)) => x.cmp(y),
238 (Value::Bool(x), Value::Bool(y)) => x.cmp(y),
239 _ => aver_repr(a).cmp(&aver_repr(b)),
240 }
241}
242
243fn one_arg<'a>(name: &str, args: &'a [Value]) -> Result<[&'a Value; 1], RuntimeError> {
244 if args.len() != 1 {
245 return Err(RuntimeError::Error(format!(
246 "{}() takes 1 argument, got {}",
247 name,
248 args.len()
249 )));
250 }
251 Ok([&args[0]])
252}
253
254fn two_args<'a>(name: &str, args: &'a [Value]) -> Result<[&'a Value; 2], RuntimeError> {
255 if args.len() != 2 {
256 return Err(RuntimeError::Error(format!(
257 "{}() takes 2 arguments, got {}",
258 name,
259 args.len()
260 )));
261 }
262 Ok([&args[0], &args[1]])
263}
264
265fn three_args<'a>(name: &str, args: &'a [Value]) -> Result<[&'a Value; 3], RuntimeError> {
266 if args.len() != 3 {
267 return Err(RuntimeError::Error(format!(
268 "{}() takes 3 arguments, got {}",
269 name,
270 args.len()
271 )));
272 }
273 Ok([&args[0], &args[1], &args[2]])
274}
275
276pub fn register_nv(global: &mut HashMap<String, NanValue>, arena: &mut Arena) {
279 let methods = &[
280 "empty", "set", "get", "remove", "has", "keys", "values", "entries", "len", "fromList",
281 ];
282 let mut members: Vec<(Rc<str>, NanValue)> = Vec::with_capacity(methods.len());
283 for method in methods {
284 let idx = arena.push_builtin(&format!("Map.{}", method));
285 members.push((Rc::from(*method), NanValue::new_builtin(idx)));
286 }
287 let ns_idx = arena.push(crate::nan_value::ArenaEntry::Namespace {
288 name: Rc::from("Map"),
289 members,
290 });
291 global.insert("Map".to_string(), NanValue::new_namespace(ns_idx));
292}
293
294pub fn call_nv(
295 name: &str,
296 args: &[NanValue],
297 arena: &mut Arena,
298) -> Option<Result<NanValue, RuntimeError>> {
299 match name {
300 "Map.empty" => Some(empty_nv(args, arena)),
301 "Map.set" => Some(set_nv(args, arena)),
302 "Map.get" => Some(get_nv(args, arena)),
303 "Map.remove" => Some(remove_nv(args, arena)),
304 "Map.has" => Some(has_nv(args, arena)),
305 "Map.keys" => Some(keys_nv(args, arena)),
306 "Map.values" => Some(values_nv(args, arena)),
307 "Map.entries" => Some(entries_nv(args, arena)),
308 "Map.len" => Some(len_nv(args, arena)),
309 "Map.fromList" => Some(from_list_nv(args, arena)),
310 _ => None,
311 }
312}
313
314fn is_hashable_nv(v: NanValue) -> bool {
315 v.is_int() || v.is_float() || v.is_string() || v.is_bool()
316}
317
318fn ensure_hashable_nv(name: &str, v: NanValue) -> Result<(), RuntimeError> {
319 if is_hashable_nv(v) {
320 Ok(())
321 } else {
322 Err(RuntimeError::Error(format!(
323 "{}: key must be Int, Float, String, or Bool",
324 name
325 )))
326 }
327}
328
329fn nv_key_bits(v: NanValue, arena: &Arena) -> u64 {
330 v.map_key_hash(arena)
331}
332
333fn empty_nv(args: &[NanValue], arena: &mut Arena) -> Result<NanValue, RuntimeError> {
334 if !args.is_empty() {
335 return Err(RuntimeError::Error(format!(
336 "Map.empty() takes 0 arguments, got {}",
337 args.len()
338 )));
339 }
340 let _ = arena;
341 Ok(NanValue::EMPTY_MAP)
342}
343
344pub fn set_nv_owned(args: &[NanValue], arena: &mut Arena) -> Result<NanValue, RuntimeError> {
346 if args.len() != 3 {
347 return Err(RuntimeError::Error(format!(
348 "Map.set() takes 3 arguments, got {}",
349 args.len()
350 )));
351 }
352 if !args[0].is_map() {
353 return Err(RuntimeError::Error(
354 "Map.set() first argument must be a Map".to_string(),
355 ));
356 }
357 ensure_hashable_nv("Map.set", args[1])?;
358 let source = args[0];
359 let old_map = arena.take_map_value(source);
360 let key_hash = nv_key_bits(args[1], arena);
361 let new_map = old_map.insert_owned(key_hash, (args[1], args[2]));
362 let map_idx = arena.push_inheriting_source_space(aver_memory::ArenaEntry::Map(new_map), source);
363 Ok(NanValue::new_map(map_idx))
364}
365
366fn set_nv(args: &[NanValue], arena: &mut Arena) -> Result<NanValue, RuntimeError> {
367 if args.len() != 3 {
368 return Err(RuntimeError::Error(format!(
369 "Map.set() takes 3 arguments, got {}",
370 args.len()
371 )));
372 }
373 if !args[0].is_map() {
374 return Err(RuntimeError::Error(
375 "Map.set() first argument must be a Map".to_string(),
376 ));
377 }
378 ensure_hashable_nv("Map.set", args[1])?;
379 let old_map = arena.clone_map_value(args[0]);
380 let key_hash = nv_key_bits(args[1], arena);
381 let new_map = old_map.insert(key_hash, (args[1], args[2]));
382 let map_idx = arena.push_map(new_map);
383 Ok(NanValue::new_map(map_idx))
384}
385
386fn get_nv(args: &[NanValue], arena: &mut Arena) -> Result<NanValue, RuntimeError> {
387 if args.len() != 2 {
388 return Err(RuntimeError::Error(format!(
389 "Map.get() takes 2 arguments, got {}",
390 args.len()
391 )));
392 }
393 if !args[0].is_map() {
394 return Err(RuntimeError::Error(
395 "Map.get() first argument must be a Map".to_string(),
396 ));
397 }
398 ensure_hashable_nv("Map.get", args[1])?;
399 let key_hash = nv_key_bits(args[1], arena);
400 let map = arena.map_ref_value(args[0]);
401 match map.get(&key_hash) {
402 Some((_, v)) => Ok(NanValue::new_some_value(*v, arena)),
403 None => Ok(NanValue::NONE),
404 }
405}
406
407fn remove_nv(args: &[NanValue], arena: &mut Arena) -> Result<NanValue, RuntimeError> {
408 if args.len() != 2 {
409 return Err(RuntimeError::Error(format!(
410 "Map.remove() takes 2 arguments, got {}",
411 args.len()
412 )));
413 }
414 if !args[0].is_map() {
415 return Err(RuntimeError::Error(
416 "Map.remove() first argument must be a Map".to_string(),
417 ));
418 }
419 ensure_hashable_nv("Map.remove", args[1])?;
420 let old_map = arena.clone_map_value(args[0]);
421 let key_hash = nv_key_bits(args[1], arena);
422 let new_map = old_map.remove(&key_hash);
423 if new_map.is_empty() {
424 Ok(NanValue::EMPTY_MAP)
425 } else {
426 let map_idx = arena.push_map(new_map);
427 Ok(NanValue::new_map(map_idx))
428 }
429}
430
431fn has_nv(args: &[NanValue], arena: &mut Arena) -> Result<NanValue, RuntimeError> {
432 if args.len() != 2 {
433 return Err(RuntimeError::Error(format!(
434 "Map.has() takes 2 arguments, got {}",
435 args.len()
436 )));
437 }
438 if !args[0].is_map() {
439 return Err(RuntimeError::Error(
440 "Map.has() first argument must be a Map".to_string(),
441 ));
442 }
443 ensure_hashable_nv("Map.has", args[1])?;
444 let key_hash = nv_key_bits(args[1], arena);
445 let map = arena.map_ref_value(args[0]);
446 Ok(NanValue::new_bool(map.contains_key(&key_hash)))
447}
448
449fn keys_nv(args: &[NanValue], arena: &mut Arena) -> Result<NanValue, RuntimeError> {
450 if args.len() != 1 {
451 return Err(RuntimeError::Error(format!(
452 "Map.keys() takes 1 argument, got {}",
453 args.len()
454 )));
455 }
456 if !args[0].is_map() {
457 return Err(RuntimeError::Error(
458 "Map.keys() argument must be a Map".to_string(),
459 ));
460 }
461 let map = arena.clone_map_value(args[0]);
462 let mut keys: Vec<NanValue> = map.values().map(|(k, _)| *k).collect();
463 keys.sort_by_key(|a| a.repr(arena));
464 if keys.is_empty() {
465 return Ok(NanValue::EMPTY_LIST);
466 }
467 let list_idx = arena.push_list(keys);
468 Ok(NanValue::new_list(list_idx))
469}
470
471fn values_nv(args: &[NanValue], arena: &mut Arena) -> Result<NanValue, RuntimeError> {
472 if args.len() != 1 {
473 return Err(RuntimeError::Error(format!(
474 "Map.values() takes 1 argument, got {}",
475 args.len()
476 )));
477 }
478 if !args[0].is_map() {
479 return Err(RuntimeError::Error(
480 "Map.values() argument must be a Map".to_string(),
481 ));
482 }
483 let map = arena.clone_map_value(args[0]);
484 let mut entries: Vec<(NanValue, NanValue)> = map.values().cloned().collect();
485 entries.sort_by_key(|(a, _)| a.repr(arena));
486 let vals: Vec<NanValue> = entries.into_iter().map(|(_, v)| v).collect();
487 if vals.is_empty() {
488 return Ok(NanValue::EMPTY_LIST);
489 }
490 let list_idx = arena.push_list(vals);
491 Ok(NanValue::new_list(list_idx))
492}
493
494fn entries_nv(args: &[NanValue], arena: &mut Arena) -> Result<NanValue, RuntimeError> {
495 if args.len() != 1 {
496 return Err(RuntimeError::Error(format!(
497 "Map.entries() takes 1 argument, got {}",
498 args.len()
499 )));
500 }
501 if !args[0].is_map() {
502 return Err(RuntimeError::Error(
503 "Map.entries() argument must be a Map".to_string(),
504 ));
505 }
506 let map = arena.clone_map_value(args[0]);
507 let mut entries: Vec<(NanValue, NanValue)> = map.values().cloned().collect();
508 entries.sort_by_key(|(a, _)| a.repr(arena));
509 let pairs: Vec<NanValue> = entries
510 .into_iter()
511 .map(|(k, v)| {
512 let tuple_idx = arena.push_tuple(vec![k, v]);
513 NanValue::new_tuple(tuple_idx)
514 })
515 .collect();
516 if pairs.is_empty() {
517 return Ok(NanValue::EMPTY_LIST);
518 }
519 let list_idx = arena.push_list(pairs);
520 Ok(NanValue::new_list(list_idx))
521}
522
523fn len_nv(args: &[NanValue], arena: &mut Arena) -> Result<NanValue, RuntimeError> {
524 if args.len() != 1 {
525 return Err(RuntimeError::Error(format!(
526 "Map.len() takes 1 argument, got {}",
527 args.len()
528 )));
529 }
530 if !args[0].is_map() {
531 return Err(RuntimeError::Error(
532 "Map.len() argument must be a Map".to_string(),
533 ));
534 }
535 let map = arena.map_ref_value(args[0]);
536 Ok(NanValue::new_int(map.len() as i64, arena))
537}
538
539fn from_list_nv(args: &[NanValue], arena: &mut Arena) -> Result<NanValue, RuntimeError> {
540 if args.len() != 1 {
541 return Err(RuntimeError::Error(format!(
542 "Map.fromList() takes 1 argument, got {}",
543 args.len()
544 )));
545 }
546 if !args[0].is_list() {
547 return Err(RuntimeError::Error(
548 "Map.fromList() argument must be a List of (key, value) tuples".to_string(),
549 ));
550 }
551 let items = arena.list_to_vec_value(args[0]);
552 let mut out = crate::nan_value::PersistentMap::new();
553 for (idx, pair) in items.iter().enumerate() {
554 if !pair.is_tuple() {
555 return Err(RuntimeError::Error(format!(
556 "Map.fromList() item {} must be (key, value)",
557 idx + 1
558 )));
559 }
560 let parts = arena.get_tuple(pair.arena_index());
561 if parts.len() != 2 {
562 return Err(RuntimeError::Error(format!(
563 "Map.fromList() item {} must have 2 elements",
564 idx + 1
565 )));
566 }
567 let key = parts[0];
568 let value = parts[1];
569 ensure_hashable_nv("Map.fromList", key)?;
570 let key_hash = nv_key_bits(key, arena);
571 out = out.insert(key_hash, (key, value));
572 }
573 if out.is_empty() {
574 Ok(NanValue::EMPTY_MAP)
575 } else {
576 let map_idx = arena.push_map(out);
577 Ok(NanValue::new_map(map_idx))
578 }
579}