1use sim_kernel::{Error, Expr, Result, Symbol};
17
18use crate::build::sym;
19
20fn key_is(key: &Expr, name: &str) -> bool {
21 matches!(key, Expr::Symbol(symbol) if &*symbol.name == name && symbol.namespace.is_none())
22}
23
24fn key_is_any(key: &Expr, name: &str) -> bool {
26 key_is(key, name) || matches!(key, Expr::String(text) if text == name)
27}
28
29fn key_name(key: &Expr) -> Option<&str> {
33 match key {
34 Expr::Symbol(symbol) if symbol.namespace.is_none() => Some(&symbol.name),
35 Expr::String(text) => Some(text),
36 _ => None,
37 }
38}
39
40pub fn entry_field<'a>(entries: &'a [(Expr, Expr)], name: &str) -> Option<&'a Expr> {
44 entries
45 .iter()
46 .find_map(|(key, value)| key_is(key, name).then_some(value))
47}
48
49pub fn entry_field_any<'a>(entries: &'a [(Expr, Expr)], name: &str) -> Option<&'a Expr> {
52 entries
53 .iter()
54 .find_map(|(key, value)| key_is_any(key, name).then_some(value))
55}
56
57pub fn field<'a>(map: &'a Expr, name: &str) -> Option<&'a Expr> {
59 match map {
60 Expr::Map(entries) => entry_field(entries, name),
61 _ => None,
62 }
63}
64
65pub fn field_q<'a>(map: &'a Expr, ns: &str, name: &str) -> Option<&'a Expr> {
67 let Expr::Map(entries) = map else {
68 return None;
69 };
70 entries.iter().find_map(|(key, value)| {
71 matches!(key, Expr::Symbol(symbol) if symbol.namespace.as_deref() == Some(ns) && &*symbol.name == name)
72 .then_some(value)
73 })
74}
75
76pub fn field_any<'a>(map: &'a Expr, name: &str) -> Option<&'a Expr> {
81 match map {
82 Expr::Map(entries) => entry_field_any(entries, name),
83 _ => None,
84 }
85}
86
87pub fn required<'a>(map: &'a Expr, name: &str, context: &str) -> Result<&'a Expr> {
90 field_any(map, name).ok_or_else(|| Error::Eval(format!("{context} is missing field {name}")))
91}
92
93pub fn entry_required<'a>(
97 entries: &'a [(Expr, Expr)],
98 name: &str,
99 context: &str,
100) -> Result<&'a Expr> {
101 entry_field_any(entries, name)
102 .ok_or_else(|| Error::Eval(format!("{context} is missing field {name}")))
103}
104
105fn type_mismatch(expected: &'static str, found: &Expr) -> Error {
109 Error::TypeMismatch {
110 expected,
111 found: crate::kind::expr_kind(found),
112 }
113}
114
115fn entry_required_bare<'a>(
122 entries: &'a [(Expr, Expr)],
123 name: &str,
124 context: &str,
125) -> Result<&'a Expr> {
126 entry_field(entries, name)
127 .ok_or_else(|| Error::Eval(format!("{context} is missing field {name}")))
128}
129
130pub fn entry_required_str<'a>(
136 entries: &'a [(Expr, Expr)],
137 name: &str,
138 expected: &'static str,
139) -> Result<&'a str> {
140 match entry_required_bare(entries, name, expected)? {
141 Expr::String(value) => Ok(value),
142 other => Err(type_mismatch(expected, other)),
143 }
144}
145
146pub fn entry_required_sym<'a>(
149 entries: &'a [(Expr, Expr)],
150 name: &str,
151 expected: &'static str,
152) -> Result<&'a Symbol> {
153 match entry_required_bare(entries, name, expected)? {
154 Expr::Symbol(value) => Ok(value),
155 other => Err(type_mismatch(expected, other)),
156 }
157}
158
159pub fn entry_required_bool(
162 entries: &[(Expr, Expr)],
163 name: &str,
164 expected: &'static str,
165) -> Result<bool> {
166 match entry_required_bare(entries, name, expected)? {
167 Expr::Bool(value) => Ok(*value),
168 other => Err(type_mismatch(expected, other)),
169 }
170}
171
172pub fn entry_required_list<'a>(
175 entries: &'a [(Expr, Expr)],
176 name: &str,
177 expected: &'static str,
178) -> Result<&'a [Expr]> {
179 match entry_required_bare(entries, name, expected)? {
180 Expr::List(items) => Ok(items),
181 other => Err(type_mismatch(expected, other)),
182 }
183}
184
185pub fn entry_required_str_any<'a>(
189 entries: &'a [(Expr, Expr)],
190 name: &str,
191 expected: &'static str,
192) -> Result<&'a str> {
193 match entry_required(entries, name, expected)? {
194 Expr::String(value) => Ok(value),
195 other => Err(type_mismatch(expected, other)),
196 }
197}
198
199pub fn entry_required_sym_any<'a>(
202 entries: &'a [(Expr, Expr)],
203 name: &str,
204 expected: &'static str,
205) -> Result<&'a Symbol> {
206 match entry_required(entries, name, expected)? {
207 Expr::Symbol(value) => Ok(value),
208 other => Err(type_mismatch(expected, other)),
209 }
210}
211
212pub fn entry_required_bool_any(
215 entries: &[(Expr, Expr)],
216 name: &str,
217 expected: &'static str,
218) -> Result<bool> {
219 match entry_required(entries, name, expected)? {
220 Expr::Bool(value) => Ok(*value),
221 other => Err(type_mismatch(expected, other)),
222 }
223}
224
225pub fn entry_required_list_any<'a>(
228 entries: &'a [(Expr, Expr)],
229 name: &str,
230 expected: &'static str,
231) -> Result<&'a [Expr]> {
232 match entry_required(entries, name, expected)? {
233 Expr::List(items) => Ok(items),
234 other => Err(type_mismatch(expected, other)),
235 }
236}
237
238pub fn required_str<'a>(map: &'a Expr, name: &str, context: &str) -> Result<&'a str> {
243 as_str(required(map, name, context)?)
244 .ok_or_else(|| Error::Eval(format!("{context} field {name} is not a string")))
245}
246
247pub fn required_sym(map: &Expr, name: &str, context: &str) -> Result<Symbol> {
249 match required(map, name, context)? {
250 Expr::Symbol(symbol) => Ok(symbol.clone()),
251 _ => Err(Error::Eval(format!(
252 "{context} field {name} is not a symbol"
253 ))),
254 }
255}
256
257pub fn required_bool(map: &Expr, name: &str, context: &str) -> Result<bool> {
259 match required(map, name, context)? {
260 Expr::Bool(value) => Ok(*value),
261 _ => Err(Error::Eval(format!("{context} field {name} is not a bool"))),
262 }
263}
264
265pub fn required_map<'a>(map: &'a Expr, name: &str, context: &str) -> Result<&'a [(Expr, Expr)]> {
268 match required(map, name, context)? {
269 Expr::Map(entries) => Ok(entries),
270 _ => Err(Error::Eval(format!("{context} field {name} is not a map"))),
271 }
272}
273
274pub fn map_entries<'a>(map: &'a Expr, expected: &'static str) -> Result<&'a [(Expr, Expr)]> {
278 match map {
279 Expr::Map(entries) => Ok(entries),
280 _ => Err(Error::TypeMismatch {
281 expected,
282 found: "non-map",
283 }),
284 }
285}
286
287pub fn extra_fields<'a>(map: &'a Expr, known: &[&str]) -> Vec<&'a str> {
291 let Expr::Map(entries) = map else {
292 return Vec::new();
293 };
294 entries
295 .iter()
296 .filter_map(|(key, _)| key_name(key))
297 .filter(|name| !known.contains(name))
298 .collect()
299}
300
301pub fn field_sym(map: &Expr, name: &str) -> Option<Symbol> {
303 match field(map, name) {
304 Some(Expr::Symbol(symbol)) => Some(symbol.clone()),
305 _ => None,
306 }
307}
308
309pub fn field_str<'a>(map: &'a Expr, name: &str) -> Option<&'a str> {
311 field(map, name).and_then(as_str)
312}
313
314pub fn field_i64(map: &Expr, name: &str) -> Option<i64> {
316 field(map, name).and_then(as_i64)
317}
318
319pub fn field_f64(map: &Expr, name: &str) -> Option<f64> {
321 field(map, name).and_then(as_f64)
322}
323
324pub fn field_bool(map: &Expr, name: &str) -> Option<bool> {
327 match field_any(map, name) {
328 Some(Expr::Bool(value)) => Some(*value),
329 _ => None,
330 }
331}
332
333pub fn as_i64(value: &Expr) -> Option<i64> {
335 match value {
336 Expr::Number(number) if number.domain.name.as_ref() == "i64" => {
337 number.canonical.parse::<i64>().ok()
338 }
339 _ => None,
340 }
341}
342
343pub fn as_u64(value: &Expr) -> Option<u64> {
345 match value {
346 Expr::Number(number) if number.domain.name.as_ref() == "u64" => {
347 number.canonical.parse::<u64>().ok()
348 }
349 _ => None,
350 }
351}
352
353pub fn as_f64(value: &Expr) -> Option<f64> {
355 match value {
356 Expr::Number(number) => number.canonical.parse::<f64>().ok(),
357 _ => None,
358 }
359}
360
361pub fn as_str(value: &Expr) -> Option<&str> {
363 match value {
364 Expr::String(text) => Some(text),
365 _ => None,
366 }
367}
368
369fn set_matching<F>(map: &Expr, name: &str, value: Expr, matches: F) -> Expr
370where
371 F: Fn(&Expr, &str) -> bool,
372{
373 let entries: &[(Expr, Expr)] = match map {
374 Expr::Map(entries) => entries,
375 _ => &[],
376 };
377 let mut updated = Vec::with_capacity(entries.len().saturating_add(1));
378 let mut replacement = Some(value);
379 let mut matched = false;
380
381 for (key, existing) in entries {
382 if matches(key, name) {
383 if !matched {
384 updated.push((key.clone(), replacement.take().unwrap()));
385 matched = true;
386 }
387 } else {
388 updated.push((key.clone(), existing.clone()));
389 }
390 }
391
392 if !matched {
393 updated.push((sym(name), replacement.take().unwrap()));
394 }
395
396 Expr::Map(updated)
397}
398
399pub fn set(map: &Expr, name: &str, value: Expr) -> Expr {
405 set_matching(map, name, value, key_is_any)
406}
407
408pub fn set_strict(map: &Expr, name: &str, value: Expr) -> Expr {
414 if matches!(map, Expr::Map(entries) if entries.iter().any(|(key, _)| key_is_any(key, name)) && !entries.iter().any(|(key, _)| key_is(key, name)))
415 {
416 return map.clone();
417 }
418 set_matching(map, name, value, key_is)
419}
420
421fn remove_matching<F>(map: &Expr, name: &str, matches: F) -> Expr
422where
423 F: Fn(&Expr, &str) -> bool,
424{
425 let entries: &[(Expr, Expr)] = match map {
426 Expr::Map(entries) => entries,
427 _ => &[],
428 };
429 Expr::Map(
430 entries
431 .iter()
432 .filter(|(key, _)| !matches(key, name))
433 .cloned()
434 .collect(),
435 )
436}
437
438pub fn remove(map: &Expr, name: &str) -> Expr {
441 remove_matching(map, name, key_is_any)
442}
443
444pub fn remove_strict(map: &Expr, name: &str) -> Expr {
446 remove_matching(map, name, key_is)
447}