use sim_kernel::{Expr, Symbol};
use crate::kinds::{KIND_KEY, is_known_kind};
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct SceneError {
pub path: Vec<String>,
pub message: String,
}
impl SceneError {
fn at(path: &[String], message: impl Into<String>) -> Self {
Self {
path: path.to_vec(),
message: message.into(),
}
}
pub fn path_string(&self) -> String {
if self.path.is_empty() {
"<root>".to_owned()
} else {
self.path.join("")
}
}
}
impl core::fmt::Display for SceneError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(f, "{}: {}", self.path_string(), self.message)
}
}
pub use sim_value::build::map;
pub fn node(kind_name: &str, entries: Vec<(&str, Expr)>) -> Expr {
let mut pairs = Vec::with_capacity(entries.len() + 1);
pairs.push((
Expr::Symbol(Symbol::new(KIND_KEY)),
Expr::Symbol(Symbol::qualified(crate::kinds::SCENE_NAMESPACE, kind_name)),
));
for (key, value) in entries {
pairs.push((Expr::Symbol(Symbol::new(key)), value));
}
Expr::Map(pairs)
}
pub fn node_kind(expr: &Expr) -> Option<Symbol> {
sim_value::access::field_sym(expr, KIND_KEY)
}
fn kind_entry(map: &Expr) -> Option<&Expr> {
sim_value::access::field(map, KIND_KEY)
}
fn has_kind_key(map: &Expr) -> bool {
kind_entry(map).is_some()
}
pub fn validate_scene(expr: &Expr) -> Result<(), SceneError> {
let mut path = Vec::new();
validate_node(expr, &mut path)
}
fn validate_node(expr: &Expr, path: &mut Vec<String>) -> Result<(), SceneError> {
let Expr::Map(entries) = expr else {
return Err(SceneError::at(
path,
"expected a scene node map (an Expr::Map tagged with a kind)",
));
};
match kind_entry(expr) {
None => {
return Err(SceneError::at(path, "scene node is missing a 'kind' tag"));
}
Some(Expr::Symbol(kind)) => {
if !is_known_kind(kind) {
return Err(SceneError::at(
path,
format!(
"unrecognized scene kind '{kind}' -- if this is a plain data map, \
rename its 'kind' field (scene node maps reserve 'kind')"
),
));
}
}
Some(_) => {
return Err(SceneError::at(path, "scene node 'kind' must be a symbol"));
}
}
validate_children(entries, path)
}
fn validate_children(entries: &[(Expr, Expr)], path: &mut Vec<String>) -> Result<(), SceneError> {
for (key, value) in entries {
let label = match key {
Expr::Symbol(symbol) => format!(".{}", symbol.as_qualified_str()),
other => format!(".{other:?}"),
};
path.push(label);
validate_data(value, path)?;
path.pop();
}
Ok(())
}
fn validate_data(expr: &Expr, path: &mut Vec<String>) -> Result<(), SceneError> {
match expr {
Expr::Map(_) if has_kind_key(expr) => validate_node(expr, path),
Expr::Map(entries) => validate_children(entries, path),
Expr::List(items) | Expr::Vector(items) | Expr::Set(items) => {
for (index, item) in items.iter().enumerate() {
path.push(format!("[{index}]"));
validate_data(item, path)?;
path.pop();
}
Ok(())
}
_ => Ok(()),
}
}