Skip to main content

sim_shape/primitives/
object.rs

1//! Object-grammar shapes: `ObjectExpr`, `FieldSpec`, and `FieldShape` for
2//! matching structured object expressions field by field.
3
4use std::sync::Arc;
5
6use crate::base::{MatchScore, Shape, ShapeDoc, ShapeMatch};
7use sim_kernel::{Cx, Expr, Result, Symbol, Value};
8
9/// The decoded form of an object expression: a class symbol and its fields.
10///
11/// A view over the `expr/object` extension form, letting object-grammar shapes
12/// read a structured object's class and named field expressions without
13/// re-walking the raw `Expr`.
14#[derive(Clone, Debug, PartialEq, Eq)]
15pub struct ObjectExpr {
16    /// The class this object form is tagged with.
17    pub class: Symbol,
18    /// The object's named fields, in source order.
19    pub fields: Vec<(Symbol, Expr)>,
20}
21
22impl ObjectExpr {
23    /// Encode this object back into its `expr/object` extension expression.
24    pub fn to_expr(self) -> Expr {
25        Expr::Extension {
26            tag: Symbol::qualified("expr", "object"),
27            payload: Box::new(Expr::Map(vec![
28                (Expr::Symbol(Symbol::new("class")), Expr::Symbol(self.class)),
29                (
30                    Expr::Symbol(Symbol::new("fields")),
31                    Expr::Map(
32                        self.fields
33                            .into_iter()
34                            .map(|(key, value)| (Expr::Symbol(key), value))
35                            .collect(),
36                    ),
37                ),
38            ])),
39        }
40    }
41
42    /// Decode an `expr/object` extension expression, or `None` if it is not one.
43    pub fn parse(expr: &Expr) -> Option<Self> {
44        let Expr::Extension { tag, payload } = expr else {
45            return None;
46        };
47        if *tag != Symbol::qualified("expr", "object") {
48            return None;
49        }
50        let Expr::Map(entries) = payload.as_ref() else {
51            return None;
52        };
53        let mut class = None;
54        let mut fields = None;
55        for (key, value) in entries {
56            let Expr::Symbol(key) = key else {
57                continue;
58            };
59            if *key == Symbol::new("class") {
60                if let Expr::Symbol(symbol) = value {
61                    class = Some(symbol.clone());
62                }
63            } else if *key == Symbol::new("fields")
64                && let Expr::Map(entries) = value
65            {
66                let parsed = entries
67                    .iter()
68                    .map(|(field, value)| match field {
69                        Expr::Symbol(symbol) => Some((symbol.clone(), value.clone())),
70                        _ => None,
71                    })
72                    .collect::<Option<Vec<_>>>();
73                fields = parsed;
74            }
75        }
76        Some(Self {
77            class: class?,
78            fields: fields?,
79        })
80    }
81
82    /// The expression bound to the named field, if present.
83    pub fn field(&self, name: &Symbol) -> Option<&Expr> {
84        self.fields
85            .iter()
86            .find_map(|(field, value)| (field == name).then_some(value))
87    }
88}
89
90/// A single field requirement within a [`FieldShape`]: a name, a shape, and
91/// whether the field must be present.
92pub struct FieldSpec {
93    pub(crate) name: Symbol,
94    pub(crate) shape: Arc<dyn Shape>,
95    pub(crate) required: bool,
96}
97
98impl FieldSpec {
99    /// Build a required field spec binding `name` to `shape`.
100    pub fn required(name: Symbol, shape: Arc<dyn Shape>) -> Self {
101        Self {
102            name,
103            shape,
104            required: true,
105        }
106    }
107
108    /// The field name.
109    pub fn name(&self) -> &Symbol {
110        &self.name
111    }
112
113    /// The shape the field's value must match.
114    pub fn shape(&self) -> &Arc<dyn Shape> {
115        &self.shape
116    }
117}
118
119/// A shape that matches an object form field by field.
120///
121/// Each [`FieldSpec`] checks the matching field's value; required fields must
122/// be present. When bound to a class the object's class must match; when
123/// anonymous, plain `Map` expressions match as well.
124pub struct FieldShape {
125    class: Option<Symbol>,
126    fields: Vec<FieldSpec>,
127}
128
129impl FieldShape {
130    /// Build a field shape that requires the object's class to be `class`.
131    pub fn new(class: Symbol, fields: Vec<FieldSpec>) -> Self {
132        Self {
133            class: Some(class),
134            fields,
135        }
136    }
137
138    /// Build a class-free field shape that also accepts plain map expressions.
139    pub fn anonymous(fields: Vec<FieldSpec>) -> Self {
140        Self {
141            class: None,
142            fields,
143        }
144    }
145
146    /// The required class symbol, or `None` for an anonymous field shape.
147    pub fn class_symbol(&self) -> Option<&Symbol> {
148        self.class.as_ref()
149    }
150
151    /// The field specs this shape checks.
152    pub fn fields(&self) -> &[FieldSpec] {
153        &self.fields
154    }
155
156    fn match_entries(
157        &self,
158        cx: &mut Cx,
159        class: Option<&Symbol>,
160        entries: &[(Symbol, Expr)],
161    ) -> Result<ShapeMatch> {
162        if let Some(expected) = &self.class
163            && class != Some(expected)
164        {
165            return Ok(ShapeMatch::reject(format!("expected class {}", expected)));
166        }
167
168        let mut matched = ShapeMatch::accept(MatchScore::exact(20));
169        for spec in &self.fields {
170            let Some(value) = entries
171                .iter()
172                .find_map(|(name, value)| (name == &spec.name).then_some(value))
173            else {
174                if spec.required {
175                    return Ok(ShapeMatch::reject(format!("missing field {}", spec.name)));
176                }
177                continue;
178            };
179            let field_match = spec.shape.check_expr(cx, value)?;
180            if !field_match.accepted {
181                return Ok(field_match);
182            }
183            matched.captures.extend(field_match.captures);
184            matched.score += field_match.score;
185        }
186        Ok(matched)
187    }
188}
189
190impl Shape for FieldShape {
191    fn check_value(&self, cx: &mut Cx, value: Value) -> Result<ShapeMatch> {
192        let expr = value.object().as_expr(cx)?;
193        self.check_expr(cx, &expr)
194    }
195
196    fn check_expr(&self, cx: &mut Cx, expr: &Expr) -> Result<ShapeMatch> {
197        if let Some(object) = ObjectExpr::parse(expr) {
198            return self.match_entries(cx, Some(&object.class), &object.fields);
199        }
200        if self.class.is_none()
201            && let Expr::Map(entries) = expr
202        {
203            let entries = entries
204                .iter()
205                .map(|(key, value)| match key {
206                    Expr::Symbol(symbol) => Some((symbol.clone(), value.clone())),
207                    _ => None,
208                })
209                .collect::<Option<Vec<_>>>();
210            if let Some(entries) = entries {
211                return self.match_entries(cx, None, &entries);
212            }
213        }
214        Ok(ShapeMatch::reject("expected object fields"))
215    }
216
217    fn describe(&self, cx: &mut Cx) -> Result<ShapeDoc> {
218        let mut doc = match &self.class {
219            Some(class) => ShapeDoc::new(format!("fields {}", class)),
220            None => ShapeDoc::new("fields"),
221        };
222        for spec in &self.fields {
223            let detail = spec.shape.describe(cx)?;
224            doc = doc.with_detail(format!("{}: {}", spec.name, detail.name));
225        }
226        Ok(doc)
227    }
228}