1use std::cmp::Ordering;
19use std::collections::HashMap;
20
21use crate::value::{RuntimeError, Value, aver_repr, list_from_vec, list_slice};
22
23pub fn register(global: &mut HashMap<String, Value>) {
24 let mut members = HashMap::new();
25 for method in &[
26 "empty", "set", "get", "remove", "has", "keys", "values", "entries", "len", "fromList",
27 ] {
28 members.insert(
29 method.to_string(),
30 Value::Builtin(format!("Map.{}", method)),
31 );
32 }
33 global.insert(
34 "Map".to_string(),
35 Value::Namespace {
36 name: "Map".to_string(),
37 members,
38 },
39 );
40}
41
42pub fn effects(_name: &str) -> &'static [&'static str] {
43 &[]
44}
45
46pub fn call(name: &str, args: &[Value]) -> Option<Result<Value, RuntimeError>> {
48 match name {
49 "Map.empty" => Some(empty(args)),
50 "Map.set" => Some(set(args)),
51 "Map.get" => Some(get(args)),
52 "Map.remove" => Some(remove(args)),
53 "Map.has" => Some(has(args)),
54 "Map.keys" => Some(keys(args)),
55 "Map.values" => Some(values(args)),
56 "Map.entries" => Some(entries(args)),
57 "Map.len" => Some(len(args)),
58 "Map.fromList" => Some(from_list(args)),
59 _ => None,
60 }
61}
62
63fn empty(args: &[Value]) -> Result<Value, RuntimeError> {
64 if !args.is_empty() {
65 return Err(RuntimeError::Error(format!(
66 "Map.empty() takes 0 arguments, got {}",
67 args.len()
68 )));
69 }
70 Ok(Value::Map(HashMap::new()))
71}
72
73fn set(args: &[Value]) -> Result<Value, RuntimeError> {
74 let [map_val, key, value] = three_args("Map.set", args)?;
75 let Value::Map(map) = map_val else {
76 return Err(RuntimeError::Error(
77 "Map.set() first argument must be a Map".to_string(),
78 ));
79 };
80 ensure_hashable_key("Map.set", key)?;
81 let mut out = map.clone();
82 out.insert(key.clone(), value.clone());
83 Ok(Value::Map(out))
84}
85
86fn get(args: &[Value]) -> Result<Value, RuntimeError> {
87 let [map_val, key] = two_args("Map.get", args)?;
88 let Value::Map(map) = map_val else {
89 return Err(RuntimeError::Error(
90 "Map.get() first argument must be a Map".to_string(),
91 ));
92 };
93 ensure_hashable_key("Map.get", key)?;
94 Ok(match map.get(key) {
95 Some(v) => Value::Some(Box::new(v.clone())),
96 None => Value::None,
97 })
98}
99
100fn remove(args: &[Value]) -> Result<Value, RuntimeError> {
101 let [map_val, key] = two_args("Map.remove", args)?;
102 let Value::Map(map) = map_val else {
103 return Err(RuntimeError::Error(
104 "Map.remove() first argument must be a Map".to_string(),
105 ));
106 };
107 ensure_hashable_key("Map.remove", key)?;
108 let mut out = map.clone();
109 out.remove(key);
110 Ok(Value::Map(out))
111}
112
113fn has(args: &[Value]) -> Result<Value, RuntimeError> {
114 let [map_val, key] = two_args("Map.has", args)?;
115 let Value::Map(map) = map_val else {
116 return Err(RuntimeError::Error(
117 "Map.has() first argument must be a Map".to_string(),
118 ));
119 };
120 ensure_hashable_key("Map.has", key)?;
121 Ok(Value::Bool(map.contains_key(key)))
122}
123
124fn keys(args: &[Value]) -> Result<Value, RuntimeError> {
125 let [map_val] = one_arg("Map.keys", args)?;
126 let Value::Map(map) = map_val else {
127 return Err(RuntimeError::Error(
128 "Map.keys() argument must be a Map".to_string(),
129 ));
130 };
131 let mut out = map.keys().cloned().collect::<Vec<_>>();
132 out.sort_by(compare_scalar_keys);
133 Ok(list_from_vec(out))
134}
135
136fn values(args: &[Value]) -> Result<Value, RuntimeError> {
137 let [map_val] = one_arg("Map.values", args)?;
138 let Value::Map(map) = map_val else {
139 return Err(RuntimeError::Error(
140 "Map.values() argument must be a Map".to_string(),
141 ));
142 };
143 let mut entries = map.iter().collect::<Vec<_>>();
144 entries.sort_by(|(k1, _), (k2, _)| compare_scalar_keys(k1, k2));
145 let out = entries
146 .into_iter()
147 .map(|(_, v)| v.clone())
148 .collect::<Vec<_>>();
149 Ok(list_from_vec(out))
150}
151
152fn entries(args: &[Value]) -> Result<Value, RuntimeError> {
153 let [map_val] = one_arg("Map.entries", args)?;
154 let Value::Map(map) = map_val else {
155 return Err(RuntimeError::Error(
156 "Map.entries() argument must be a Map".to_string(),
157 ));
158 };
159 let mut entries = map.iter().collect::<Vec<_>>();
160 entries.sort_by(|(k1, _), (k2, _)| compare_scalar_keys(k1, k2));
161 let out = entries
162 .into_iter()
163 .map(|(k, v)| Value::Tuple(vec![k.clone(), v.clone()]))
164 .collect::<Vec<_>>();
165 Ok(list_from_vec(out))
166}
167
168fn len(args: &[Value]) -> Result<Value, RuntimeError> {
169 let [map_val] = one_arg("Map.len", args)?;
170 let Value::Map(map) = map_val else {
171 return Err(RuntimeError::Error(
172 "Map.len() argument must be a Map".to_string(),
173 ));
174 };
175 Ok(Value::Int(map.len() as i64))
176}
177
178fn from_list(args: &[Value]) -> Result<Value, RuntimeError> {
179 let [pairs] = one_arg("Map.fromList", args)?;
180 let items = list_slice(pairs).ok_or_else(|| {
181 RuntimeError::Error(
182 "Map.fromList() argument must be a List of (key, value) tuples".to_string(),
183 )
184 })?;
185
186 let mut out = HashMap::new();
187 for (idx, pair) in items.iter().enumerate() {
188 let Value::Tuple(parts) = pair else {
189 return Err(RuntimeError::Error(format!(
190 "Map.fromList() item {} must be (key, value)",
191 idx + 1
192 )));
193 };
194 if parts.len() != 2 {
195 return Err(RuntimeError::Error(format!(
196 "Map.fromList() item {} must have 2 elements",
197 idx + 1
198 )));
199 }
200
201 let key = &parts[0];
202 let value = &parts[1];
203 ensure_hashable_key("Map.fromList", key)?;
204 out.insert(key.clone(), value.clone());
205 }
206 Ok(Value::Map(out))
207}
208
209fn is_hashable_key(value: &Value) -> bool {
210 matches!(
211 value,
212 Value::Int(_) | Value::Float(_) | Value::Str(_) | Value::Bool(_)
213 )
214}
215
216fn ensure_hashable_key(name: &str, value: &Value) -> Result<(), RuntimeError> {
217 if is_hashable_key(value) {
218 Ok(())
219 } else {
220 Err(RuntimeError::Error(format!(
221 "{}: key must be Int, Float, String, or Bool",
222 name
223 )))
224 }
225}
226
227fn compare_scalar_keys(a: &Value, b: &Value) -> Ordering {
228 match (a, b) {
229 (Value::Int(x), Value::Int(y)) => x.cmp(y),
230 (Value::Float(x), Value::Float(y)) => x.partial_cmp(y).unwrap_or_else(|| {
231 let xb = x.to_bits();
232 let yb = y.to_bits();
233 xb.cmp(&yb)
234 }),
235 (Value::Str(x), Value::Str(y)) => x.cmp(y),
236 (Value::Bool(x), Value::Bool(y)) => x.cmp(y),
237 _ => aver_repr(a).cmp(&aver_repr(b)),
238 }
239}
240
241fn one_arg<'a>(name: &str, args: &'a [Value]) -> Result<[&'a Value; 1], RuntimeError> {
242 if args.len() != 1 {
243 return Err(RuntimeError::Error(format!(
244 "{}() takes 1 argument, got {}",
245 name,
246 args.len()
247 )));
248 }
249 Ok([&args[0]])
250}
251
252fn two_args<'a>(name: &str, args: &'a [Value]) -> Result<[&'a Value; 2], RuntimeError> {
253 if args.len() != 2 {
254 return Err(RuntimeError::Error(format!(
255 "{}() takes 2 arguments, got {}",
256 name,
257 args.len()
258 )));
259 }
260 Ok([&args[0], &args[1]])
261}
262
263fn three_args<'a>(name: &str, args: &'a [Value]) -> Result<[&'a Value; 3], RuntimeError> {
264 if args.len() != 3 {
265 return Err(RuntimeError::Error(format!(
266 "{}() takes 3 arguments, got {}",
267 name,
268 args.len()
269 )));
270 }
271 Ok([&args[0], &args[1], &args[2]])
272}