1use sim_kernel::{Error, Expr, Result, Symbol};
10
11use crate::build::sym;
12
13fn key_is(key: &Expr, name: &str) -> bool {
14 matches!(key, Expr::Symbol(symbol) if &*symbol.name == name && symbol.namespace.is_none())
15}
16
17fn key_is_any(key: &Expr, name: &str) -> bool {
19 key_is(key, name) || matches!(key, Expr::String(text) if text == name)
20}
21
22fn key_name(key: &Expr) -> Option<&str> {
26 match key {
27 Expr::Symbol(symbol) if symbol.namespace.is_none() => Some(&symbol.name),
28 Expr::String(text) => Some(text),
29 _ => None,
30 }
31}
32
33pub fn entry_field<'a>(entries: &'a [(Expr, Expr)], name: &str) -> Option<&'a Expr> {
37 entries
38 .iter()
39 .find_map(|(key, value)| key_is(key, name).then_some(value))
40}
41
42pub fn entry_field_any<'a>(entries: &'a [(Expr, Expr)], name: &str) -> Option<&'a Expr> {
45 entries
46 .iter()
47 .find_map(|(key, value)| key_is_any(key, name).then_some(value))
48}
49
50pub fn field<'a>(map: &'a Expr, name: &str) -> Option<&'a Expr> {
52 match map {
53 Expr::Map(entries) => entry_field(entries, name),
54 _ => None,
55 }
56}
57
58pub fn field_q<'a>(map: &'a Expr, ns: &str, name: &str) -> Option<&'a Expr> {
60 let Expr::Map(entries) = map else {
61 return None;
62 };
63 entries.iter().find_map(|(key, value)| {
64 matches!(key, Expr::Symbol(symbol) if symbol.namespace.as_deref() == Some(ns) && &*symbol.name == name)
65 .then_some(value)
66 })
67}
68
69pub fn field_any<'a>(map: &'a Expr, name: &str) -> Option<&'a Expr> {
74 match map {
75 Expr::Map(entries) => entry_field_any(entries, name),
76 _ => None,
77 }
78}
79
80pub fn required<'a>(map: &'a Expr, name: &str, context: &str) -> Result<&'a Expr> {
83 field_any(map, name).ok_or_else(|| Error::Eval(format!("{context} is missing field {name}")))
84}
85
86pub fn entry_required<'a>(
90 entries: &'a [(Expr, Expr)],
91 name: &str,
92 context: &str,
93) -> Result<&'a Expr> {
94 entry_field_any(entries, name)
95 .ok_or_else(|| Error::Eval(format!("{context} is missing field {name}")))
96}
97
98pub fn required_str<'a>(map: &'a Expr, name: &str, context: &str) -> Result<&'a str> {
103 as_str(required(map, name, context)?)
104 .ok_or_else(|| Error::Eval(format!("{context} field {name} is not a string")))
105}
106
107pub fn required_sym(map: &Expr, name: &str, context: &str) -> Result<Symbol> {
109 match required(map, name, context)? {
110 Expr::Symbol(symbol) => Ok(symbol.clone()),
111 _ => Err(Error::Eval(format!(
112 "{context} field {name} is not a symbol"
113 ))),
114 }
115}
116
117pub fn required_bool(map: &Expr, name: &str, context: &str) -> Result<bool> {
119 match required(map, name, context)? {
120 Expr::Bool(value) => Ok(*value),
121 _ => Err(Error::Eval(format!("{context} field {name} is not a bool"))),
122 }
123}
124
125pub fn required_map<'a>(map: &'a Expr, name: &str, context: &str) -> Result<&'a [(Expr, Expr)]> {
128 match required(map, name, context)? {
129 Expr::Map(entries) => Ok(entries),
130 _ => Err(Error::Eval(format!("{context} field {name} is not a map"))),
131 }
132}
133
134pub fn map_entries<'a>(map: &'a Expr, expected: &'static str) -> Result<&'a [(Expr, Expr)]> {
138 match map {
139 Expr::Map(entries) => Ok(entries),
140 _ => Err(Error::TypeMismatch {
141 expected,
142 found: "non-map",
143 }),
144 }
145}
146
147pub fn extra_fields<'a>(map: &'a Expr, known: &[&str]) -> Vec<&'a str> {
151 let Expr::Map(entries) = map else {
152 return Vec::new();
153 };
154 entries
155 .iter()
156 .filter_map(|(key, _)| key_name(key))
157 .filter(|name| !known.contains(name))
158 .collect()
159}
160
161pub fn field_sym(map: &Expr, name: &str) -> Option<Symbol> {
163 match field(map, name) {
164 Some(Expr::Symbol(symbol)) => Some(symbol.clone()),
165 _ => None,
166 }
167}
168
169pub fn field_str<'a>(map: &'a Expr, name: &str) -> Option<&'a str> {
171 field(map, name).and_then(as_str)
172}
173
174pub fn field_i64(map: &Expr, name: &str) -> Option<i64> {
176 field(map, name).and_then(as_i64)
177}
178
179pub fn field_f64(map: &Expr, name: &str) -> Option<f64> {
181 field(map, name).and_then(as_f64)
182}
183
184pub fn field_bool(map: &Expr, name: &str) -> Option<bool> {
187 match field_any(map, name) {
188 Some(Expr::Bool(value)) => Some(*value),
189 _ => None,
190 }
191}
192
193pub fn as_i64(value: &Expr) -> Option<i64> {
195 match value {
196 Expr::Number(number) => number.canonical.parse::<i64>().ok(),
197 _ => None,
198 }
199}
200
201pub fn as_f64(value: &Expr) -> Option<f64> {
203 match value {
204 Expr::Number(number) => number.canonical.parse::<f64>().ok(),
205 _ => None,
206 }
207}
208
209pub fn as_str(value: &Expr) -> Option<&str> {
211 match value {
212 Expr::String(text) => Some(text),
213 _ => None,
214 }
215}
216
217pub fn set(map: &Expr, name: &str, value: Expr) -> Expr {
220 let mut entries = match map {
221 Expr::Map(entries) => entries.clone(),
222 _ => Vec::new(),
223 };
224 if let Some(slot) = entries.iter_mut().find(|(key, _)| key_is(key, name)) {
225 slot.1 = value;
226 } else {
227 entries.push((sym(name), value));
228 }
229 Expr::Map(entries)
230}
231
232pub fn remove(map: &Expr, name: &str) -> Expr {
234 let entries = match map {
235 Expr::Map(entries) => entries.clone(),
236 _ => Vec::new(),
237 };
238 Expr::Map(
239 entries
240 .into_iter()
241 .filter(|(key, _)| !key_is(key, name))
242 .collect(),
243 )
244}