use sim_kernel::{Error, Expr, Result, Symbol};
use crate::build::sym;
fn key_is(key: &Expr, name: &str) -> bool {
matches!(key, Expr::Symbol(symbol) if &*symbol.name == name && symbol.namespace.is_none())
}
fn key_is_any(key: &Expr, name: &str) -> bool {
key_is(key, name) || matches!(key, Expr::String(text) if text == name)
}
fn key_name(key: &Expr) -> Option<&str> {
match key {
Expr::Symbol(symbol) if symbol.namespace.is_none() => Some(&symbol.name),
Expr::String(text) => Some(text),
_ => None,
}
}
pub fn entry_field<'a>(entries: &'a [(Expr, Expr)], name: &str) -> Option<&'a Expr> {
entries
.iter()
.find_map(|(key, value)| key_is(key, name).then_some(value))
}
pub fn entry_field_any<'a>(entries: &'a [(Expr, Expr)], name: &str) -> Option<&'a Expr> {
entries
.iter()
.find_map(|(key, value)| key_is_any(key, name).then_some(value))
}
pub fn field<'a>(map: &'a Expr, name: &str) -> Option<&'a Expr> {
match map {
Expr::Map(entries) => entry_field(entries, name),
_ => None,
}
}
pub fn field_q<'a>(map: &'a Expr, ns: &str, name: &str) -> Option<&'a Expr> {
let Expr::Map(entries) = map else {
return None;
};
entries.iter().find_map(|(key, value)| {
matches!(key, Expr::Symbol(symbol) if symbol.namespace.as_deref() == Some(ns) && &*symbol.name == name)
.then_some(value)
})
}
pub fn field_any<'a>(map: &'a Expr, name: &str) -> Option<&'a Expr> {
match map {
Expr::Map(entries) => entry_field_any(entries, name),
_ => None,
}
}
pub fn required<'a>(map: &'a Expr, name: &str, context: &str) -> Result<&'a Expr> {
field_any(map, name).ok_or_else(|| Error::Eval(format!("{context} is missing field {name}")))
}
pub fn entry_required<'a>(
entries: &'a [(Expr, Expr)],
name: &str,
context: &str,
) -> Result<&'a Expr> {
entry_field_any(entries, name)
.ok_or_else(|| Error::Eval(format!("{context} is missing field {name}")))
}
pub fn required_str<'a>(map: &'a Expr, name: &str, context: &str) -> Result<&'a str> {
as_str(required(map, name, context)?)
.ok_or_else(|| Error::Eval(format!("{context} field {name} is not a string")))
}
pub fn required_sym(map: &Expr, name: &str, context: &str) -> Result<Symbol> {
match required(map, name, context)? {
Expr::Symbol(symbol) => Ok(symbol.clone()),
_ => Err(Error::Eval(format!(
"{context} field {name} is not a symbol"
))),
}
}
pub fn required_bool(map: &Expr, name: &str, context: &str) -> Result<bool> {
match required(map, name, context)? {
Expr::Bool(value) => Ok(*value),
_ => Err(Error::Eval(format!("{context} field {name} is not a bool"))),
}
}
pub fn required_map<'a>(map: &'a Expr, name: &str, context: &str) -> Result<&'a [(Expr, Expr)]> {
match required(map, name, context)? {
Expr::Map(entries) => Ok(entries),
_ => Err(Error::Eval(format!("{context} field {name} is not a map"))),
}
}
pub fn map_entries<'a>(map: &'a Expr, expected: &'static str) -> Result<&'a [(Expr, Expr)]> {
match map {
Expr::Map(entries) => Ok(entries),
_ => Err(Error::TypeMismatch {
expected,
found: "non-map",
}),
}
}
pub fn extra_fields<'a>(map: &'a Expr, known: &[&str]) -> Vec<&'a str> {
let Expr::Map(entries) = map else {
return Vec::new();
};
entries
.iter()
.filter_map(|(key, _)| key_name(key))
.filter(|name| !known.contains(name))
.collect()
}
pub fn field_sym(map: &Expr, name: &str) -> Option<Symbol> {
match field(map, name) {
Some(Expr::Symbol(symbol)) => Some(symbol.clone()),
_ => None,
}
}
pub fn field_str<'a>(map: &'a Expr, name: &str) -> Option<&'a str> {
field(map, name).and_then(as_str)
}
pub fn field_i64(map: &Expr, name: &str) -> Option<i64> {
field(map, name).and_then(as_i64)
}
pub fn field_f64(map: &Expr, name: &str) -> Option<f64> {
field(map, name).and_then(as_f64)
}
pub fn field_bool(map: &Expr, name: &str) -> Option<bool> {
match field_any(map, name) {
Some(Expr::Bool(value)) => Some(*value),
_ => None,
}
}
pub fn as_i64(value: &Expr) -> Option<i64> {
match value {
Expr::Number(number) => number.canonical.parse::<i64>().ok(),
_ => None,
}
}
pub fn as_f64(value: &Expr) -> Option<f64> {
match value {
Expr::Number(number) => number.canonical.parse::<f64>().ok(),
_ => None,
}
}
pub fn as_str(value: &Expr) -> Option<&str> {
match value {
Expr::String(text) => Some(text),
_ => None,
}
}
pub fn set(map: &Expr, name: &str, value: Expr) -> Expr {
let mut entries = match map {
Expr::Map(entries) => entries.clone(),
_ => Vec::new(),
};
if let Some(slot) = entries.iter_mut().find(|(key, _)| key_is(key, name)) {
slot.1 = value;
} else {
entries.push((sym(name), value));
}
Expr::Map(entries)
}
pub fn remove(map: &Expr, name: &str) -> Expr {
let entries = match map {
Expr::Map(entries) => entries.clone(),
_ => Vec::new(),
};
Expr::Map(
entries
.into_iter()
.filter(|(key, _)| !key_is(key, name))
.collect(),
)
}