1use std::sync::Arc;
5
6use crate::base::{MatchScore, Shape, ShapeDoc, ShapeMatch};
7use crate::duplicate_keys::{reject_duplicate_expr_symbol_keys, reject_duplicate_symbol_keys};
8use sim_kernel::{Cx, Expr, Result, Symbol, Value};
9
10#[derive(Clone, Debug, PartialEq, Eq)]
16pub struct ObjectExpr {
17 pub class: Symbol,
19 pub fields: Vec<(Symbol, Expr)>,
21}
22
23impl ObjectExpr {
24 pub fn to_expr(self) -> Expr {
26 Expr::Extension {
27 tag: Symbol::qualified("expr", "object"),
28 payload: Box::new(Expr::Map(vec![
29 (Expr::Symbol(Symbol::new("class")), Expr::Symbol(self.class)),
30 (
31 Expr::Symbol(Symbol::new("fields")),
32 Expr::Map(
33 self.fields
34 .into_iter()
35 .map(|(key, value)| (Expr::Symbol(key), value))
36 .collect(),
37 ),
38 ),
39 ])),
40 }
41 }
42
43 pub fn parse(expr: &Expr) -> Option<Self> {
46 Self::parse_checked(expr).ok().flatten()
47 }
48
49 pub(crate) fn parse_checked(expr: &Expr) -> Result<Option<Self>> {
50 let Expr::Extension { tag, payload } = expr else {
51 return Ok(None);
52 };
53 if *tag != Symbol::qualified("expr", "object") {
54 return Ok(None);
55 }
56 let Expr::Map(entries) = payload.as_ref() else {
57 return Ok(None);
58 };
59 reject_duplicate_expr_symbol_keys(entries, "shape-object")?;
60 let mut class = None;
61 let mut fields = None;
62 for (key, value) in entries {
63 let Expr::Symbol(key) = key else {
64 continue;
65 };
66 if *key == Symbol::new("class") {
67 if let Expr::Symbol(symbol) = value {
68 class = Some(symbol.clone());
69 }
70 } else if *key == Symbol::new("fields")
71 && let Expr::Map(entries) = value
72 {
73 reject_duplicate_expr_symbol_keys(entries, "shape-object fields")?;
74 let parsed = entries
75 .iter()
76 .map(|(field, value)| match field {
77 Expr::Symbol(symbol) => Some((symbol.clone(), value.clone())),
78 _ => None,
79 })
80 .collect::<Option<Vec<_>>>();
81 fields = parsed;
82 }
83 }
84 let Some(class) = class else {
85 return Ok(None);
86 };
87 let Some(fields) = fields else {
88 return Ok(None);
89 };
90 Ok(Some(Self { class, fields }))
91 }
92
93 pub fn field(&self, name: &Symbol) -> Option<&Expr> {
95 self.fields
96 .iter()
97 .find_map(|(field, value)| (field == name).then_some(value))
98 }
99}
100
101pub struct FieldSpec {
104 pub(crate) name: Symbol,
105 pub(crate) shape: Arc<dyn Shape>,
106 pub(crate) required: bool,
107}
108
109impl FieldSpec {
110 pub fn required(name: Symbol, shape: Arc<dyn Shape>) -> Self {
112 Self {
113 name,
114 shape,
115 required: true,
116 }
117 }
118
119 pub fn name(&self) -> &Symbol {
121 &self.name
122 }
123
124 pub fn shape(&self) -> &Arc<dyn Shape> {
126 &self.shape
127 }
128}
129
130pub struct FieldShape {
136 class: Option<Symbol>,
137 fields: Vec<FieldSpec>,
138}
139
140impl FieldShape {
141 pub fn new(class: Symbol, fields: Vec<FieldSpec>) -> Self {
143 Self {
144 class: Some(class),
145 fields,
146 }
147 }
148
149 pub fn anonymous(fields: Vec<FieldSpec>) -> Self {
151 Self {
152 class: None,
153 fields,
154 }
155 }
156
157 pub fn class_symbol(&self) -> Option<&Symbol> {
159 self.class.as_ref()
160 }
161
162 pub fn fields(&self) -> &[FieldSpec] {
164 &self.fields
165 }
166
167 fn match_entries(
168 &self,
169 cx: &mut Cx,
170 class: Option<&Symbol>,
171 entries: &[(Symbol, Expr)],
172 context: &str,
173 ) -> Result<ShapeMatch> {
174 match reject_duplicate_symbol_keys(entries, context) {
175 Ok(()) => {}
176 Err(sim_kernel::Error::Eval(message)) => return Ok(ShapeMatch::reject(message)),
177 Err(err) => return Err(err),
178 }
179
180 if let Some(expected) = &self.class
181 && class != Some(expected)
182 {
183 return Ok(ShapeMatch::reject(format!("expected class {}", expected)));
184 }
185
186 let mut matched = ShapeMatch::accept(MatchScore::exact(20));
187 for spec in &self.fields {
188 let Some(value) = entries
189 .iter()
190 .find_map(|(name, value)| (name == &spec.name).then_some(value))
191 else {
192 if spec.required {
193 return Ok(ShapeMatch::reject(format!("missing field {}", spec.name)));
194 }
195 continue;
196 };
197 let field_match = spec.shape.check_expr(cx, value)?;
198 if !field_match.accepted {
199 return Ok(field_match);
200 }
201 matched.captures.extend(field_match.captures);
202 matched.score += field_match.score;
203 }
204 Ok(matched)
205 }
206}
207
208impl Shape for FieldShape {
209 fn is_effectful(&self) -> bool {
210 self.fields.iter().any(|field| field.shape.is_effectful())
211 }
212
213 fn check_value(&self, cx: &mut Cx, value: Value) -> Result<ShapeMatch> {
214 let expr = value.object().as_expr(cx)?;
215 self.check_expr(cx, &expr)
216 }
217
218 fn check_expr(&self, cx: &mut Cx, expr: &Expr) -> Result<ShapeMatch> {
219 match ObjectExpr::parse_checked(expr) {
220 Ok(Some(object)) => {
221 return self.match_entries(
222 cx,
223 Some(&object.class),
224 &object.fields,
225 "shape-object fields",
226 );
227 }
228 Ok(None) => {}
229 Err(sim_kernel::Error::Eval(message)) => return Ok(ShapeMatch::reject(message)),
230 Err(err) => return Err(err),
231 }
232 if self.class.is_none()
233 && let Expr::Map(entries) = expr
234 {
235 let entries = entries
236 .iter()
237 .map(|(key, value)| match key {
238 Expr::Symbol(symbol) => Some((symbol.clone(), value.clone())),
239 _ => None,
240 })
241 .collect::<Option<Vec<_>>>();
242 if let Some(entries) = entries {
243 return self.match_entries(cx, None, &entries, "shape-fields");
244 }
245 }
246 Ok(ShapeMatch::reject("expected object fields"))
247 }
248
249 fn describe(&self, cx: &mut Cx) -> Result<ShapeDoc> {
250 let mut doc = match &self.class {
251 Some(class) => ShapeDoc::new(format!("fields {}", class)),
252 None => ShapeDoc::new("fields"),
253 };
254 for spec in &self.fields {
255 let detail = spec.shape.describe(cx)?;
256 doc = doc.with_detail(format!("{}: {}", spec.name, detail.name));
257 }
258 Ok(doc)
259 }
260}