1use sim_kernel::{Expr, Symbol};
10
11use crate::kinds::{KIND_KEY, is_known_kind};
12
13#[derive(Clone, Debug, PartialEq, Eq)]
17pub struct SceneError {
18 pub path: Vec<String>,
20 pub message: String,
22}
23
24impl SceneError {
25 fn at(path: &[String], message: impl Into<String>) -> Self {
26 Self {
27 path: path.to_vec(),
28 message: message.into(),
29 }
30 }
31
32 pub fn path_string(&self) -> String {
34 if self.path.is_empty() {
35 "<root>".to_owned()
36 } else {
37 self.path.join("")
38 }
39 }
40}
41
42impl core::fmt::Display for SceneError {
43 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
44 write!(f, "{}: {}", self.path_string(), self.message)
45 }
46}
47
48pub use sim_value::build::map;
51
52pub fn node(kind_name: &str, entries: Vec<(&str, Expr)>) -> Expr {
55 let mut pairs = Vec::with_capacity(entries.len() + 1);
56 pairs.push((
57 Expr::Symbol(Symbol::new(KIND_KEY)),
58 Expr::Symbol(Symbol::qualified(crate::kinds::SCENE_NAMESPACE, kind_name)),
59 ));
60 for (key, value) in entries {
61 pairs.push((Expr::Symbol(Symbol::new(key)), value));
62 }
63 Expr::Map(pairs)
64}
65
66pub fn node_kind(expr: &Expr) -> Option<Symbol> {
68 sim_value::access::field_sym(expr, KIND_KEY)
69}
70
71fn kind_entry(map: &Expr) -> Option<&Expr> {
72 sim_value::access::field(map, KIND_KEY)
73}
74
75fn has_kind_key(map: &Expr) -> bool {
76 kind_entry(map).is_some()
77}
78
79pub fn validate_scene(expr: &Expr) -> Result<(), SceneError> {
88 let mut path = Vec::new();
89 validate_node(expr, &mut path)
90}
91
92fn validate_node(expr: &Expr, path: &mut Vec<String>) -> Result<(), SceneError> {
93 let Expr::Map(entries) = expr else {
94 return Err(SceneError::at(
95 path,
96 "expected a scene node map (an Expr::Map tagged with a kind)",
97 ));
98 };
99 match kind_entry(expr) {
100 None => {
101 return Err(SceneError::at(path, "scene node is missing a 'kind' tag"));
102 }
103 Some(Expr::Symbol(kind)) => {
104 if !is_known_kind(kind) {
105 return Err(SceneError::at(
106 path,
107 format!(
108 "unrecognized scene kind '{kind}' -- if this is a plain data map, \
109 rename its 'kind' field (scene node maps reserve 'kind')"
110 ),
111 ));
112 }
113 }
114 Some(_) => {
115 return Err(SceneError::at(path, "scene node 'kind' must be a symbol"));
116 }
117 }
118 validate_children(entries, path)
119}
120
121fn validate_children(entries: &[(Expr, Expr)], path: &mut Vec<String>) -> Result<(), SceneError> {
122 for (key, value) in entries {
123 let label = match key {
124 Expr::Symbol(symbol) => format!(".{}", symbol.as_qualified_str()),
125 other => format!(".{other:?}"),
126 };
127 path.push(label);
128 validate_data(value, path)?;
129 path.pop();
130 }
131 Ok(())
132}
133
134fn validate_data(expr: &Expr, path: &mut Vec<String>) -> Result<(), SceneError> {
135 match expr {
136 Expr::Map(_) if has_kind_key(expr) => validate_node(expr, path),
137 Expr::Map(entries) => validate_children(entries, path),
138 Expr::List(items) | Expr::Vector(items) | Expr::Set(items) => {
139 for (index, item) in items.iter().enumerate() {
140 path.push(format!("[{index}]"));
141 validate_data(item, path)?;
142 path.pop();
143 }
144 Ok(())
145 }
146 _ => Ok(()),
147 }
148}