1use 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
50pub 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
264pub 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 !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
324pub 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}