Skip to main content

sim_shape/primitives/
atomic.rs

1//! Atomic shapes: the leaf matchers `AnyShape`, `ExprKindShape`,
2//! `NumberValueShape`, `ClassShape`, and `ExactExprShape`.
3
4use crate::base::{ExprKind, MatchScore, Shape, ShapeDoc, ShapeMatch};
5use crate::diagnostics::{expected_shape_diagnostic, expr_actual_label};
6use crate::functions::shape_value;
7use crate::primitives::object::ObjectExpr;
8use crate::recursion::{DepthGuard, class_is_subclass_of_guarded, is_cyclic_parent_edge};
9use sim_kernel::{Cx, Expr, Result, ShapeRef, Symbol, Value};
10
11/// The total shape that accepts every value and expression.
12///
13/// This is the `core/Any` atomic shape: it matches anything with an exact
14/// score and reports `is_total() == true`.
15///
16/// # Examples
17///
18/// ```rust
19/// # use std::sync::Arc;
20/// use sim_kernel::{Cx, DefaultFactory, Expr, NoopEvalPolicy};
21/// use sim_shape::{AnyShape, Shape};
22///
23/// let mut cx = Cx::new(Arc::new(NoopEvalPolicy), Arc::new(DefaultFactory));
24/// let matched = AnyShape.check_expr(&mut cx, &Expr::Bool(true)).unwrap();
25/// assert!(matched.accepted);
26/// assert!(AnyShape.is_total());
27/// ```
28pub struct AnyShape;
29
30impl Shape for AnyShape {
31    fn symbol(&self) -> Option<Symbol> {
32        Some(Symbol::qualified("core", "Any"))
33    }
34
35    fn is_total(&self) -> bool {
36        true
37    }
38
39    fn check_value(&self, _cx: &mut Cx, _value: Value) -> Result<ShapeMatch> {
40        Ok(ShapeMatch::accept(MatchScore::exact(0)))
41    }
42
43    fn check_expr(&self, _cx: &mut Cx, _expr: &Expr) -> Result<ShapeMatch> {
44        Ok(ShapeMatch::accept(MatchScore::exact(0)))
45    }
46
47    fn describe(&self, _cx: &mut Cx) -> Result<ShapeDoc> {
48        Ok(ShapeDoc::new("Any"))
49    }
50}
51
52/// A shape that matches expressions of a single [`ExprKind`].
53///
54/// Accepts any expression whose syntactic kind equals the configured kind (for
55/// example, every `String` expression); a subshape of the `core/Expr` shape.
56pub struct ExprKindShape {
57    kind: ExprKind,
58}
59
60impl ExprKindShape {
61    /// Build a shape that matches expressions of the given kind.
62    pub fn new(kind: ExprKind) -> Self {
63        Self { kind }
64    }
65
66    /// The expression kind this shape matches.
67    pub fn kind(&self) -> &ExprKind {
68        &self.kind
69    }
70}
71
72impl Shape for ExprKindShape {
73    fn parents(&self, cx: &mut Cx) -> Result<Vec<ShapeRef>> {
74        Ok(cx
75            .registry()
76            .shape_by_symbol(&Symbol::qualified("core", "Expr"))
77            .cloned()
78            .into_iter()
79            .collect())
80    }
81
82    fn is_subshape_of(&self, _cx: &mut Cx, parent: &dyn Shape) -> Result<Option<bool>> {
83        if let Some(parent) = parent.as_any().downcast_ref::<Self>() {
84            return Ok(Some(self.kind == parent.kind));
85        }
86        if parent.as_any().is::<ExactExprShape>()
87            || matches!(
88                parent.symbol(),
89                Some(symbol) if symbol == Symbol::qualified("core", "ExactExprShape")
90            )
91        {
92            return Ok(Some(false));
93        }
94        Ok(matches!(
95            parent.symbol(),
96            Some(symbol) if symbol == Symbol::qualified("core", "Expr")
97        )
98        .then_some(true))
99    }
100
101    fn check_value(&self, cx: &mut Cx, value: Value) -> Result<ShapeMatch> {
102        let expr = value.object().as_expr(cx)?;
103        self.check_expr(cx, &expr)
104    }
105
106    fn check_expr(&self, _cx: &mut Cx, expr: &Expr) -> Result<ShapeMatch> {
107        if self.kind.matches(expr) {
108            Ok(ShapeMatch::accept(MatchScore::exact(10)))
109        } else {
110            Ok(ShapeMatch::reject_with_diagnostic(
111                expected_shape_diagnostic(
112                    format!("{} expression", self.kind.name()),
113                    expr_actual_label(expr),
114                ),
115            ))
116        }
117    }
118
119    fn describe(&self, _cx: &mut Cx) -> Result<ShapeDoc> {
120        Ok(ShapeDoc::new(format!("expr-kind {}", self.kind.name())))
121    }
122}
123
124/// A shape that matches number-domain values and number expressions.
125///
126/// On values it accepts anything the active number backend recognizes as a
127/// number; on expressions it accepts literal `Number` forms. Named
128/// `core/Number`.
129pub struct NumberValueShape;
130
131impl Shape for NumberValueShape {
132    fn symbol(&self) -> Option<Symbol> {
133        Some(Symbol::qualified("core", "Number"))
134    }
135
136    fn check_value(&self, cx: &mut Cx, value: Value) -> Result<ShapeMatch> {
137        if cx.number_value_ref(value)?.is_some() {
138            Ok(ShapeMatch::accept(MatchScore::exact(20)))
139        } else {
140            Ok(ShapeMatch::reject_with_diagnostic(
141                expected_shape_diagnostic("number value", "non-number value"),
142            ))
143        }
144    }
145
146    fn check_expr(&self, _cx: &mut Cx, expr: &Expr) -> Result<ShapeMatch> {
147        if matches!(expr, Expr::Number(_)) {
148            Ok(ShapeMatch::accept(MatchScore::exact(10)))
149        } else {
150            Ok(ShapeMatch::reject_with_diagnostic(
151                expected_shape_diagnostic("number expression", expr_actual_label(expr)),
152            ))
153        }
154    }
155
156    fn describe(&self, _cx: &mut Cx) -> Result<ShapeDoc> {
157        Ok(ShapeDoc::new("number value"))
158    }
159}
160
161/// A shape that matches values and expressions of a named class.
162///
163/// Accepts a value whose class is, or is a subclass of, the named class, and
164/// accepts class-symbol or object expressions for that class. Subshape
165/// relationships follow the class hierarchy in the registry.
166pub struct ClassShape {
167    symbol: Symbol,
168}
169
170impl ClassShape {
171    /// Build a shape that matches the class named by `symbol`.
172    pub fn new(symbol: Symbol) -> Self {
173        Self { symbol }
174    }
175
176    /// The class symbol this shape matches against.
177    pub fn symbol(&self) -> &Symbol {
178        &self.symbol
179    }
180}
181
182impl Shape for ClassShape {
183    fn parents(&self, cx: &mut Cx) -> Result<Vec<ShapeRef>> {
184        let Some(class_value) = cx.registry().class_by_symbol(&self.symbol).cloned() else {
185            return Ok(Vec::new());
186        };
187        let Some(class) = class_value.object().as_class() else {
188            return Ok(Vec::new());
189        };
190        let child_id = class.id();
191        let parent_classes = class.parents(cx)?;
192        let mut out = Vec::new();
193        for parent in parent_classes {
194            let Some(parent_class) = parent.object().as_class() else {
195                continue;
196            };
197            // Prune cyclic back-edges so the kernel subshape walk over the
198            // reported parents terminates on an adversarial hierarchy.
199            if is_cyclic_parent_edge(cx, child_id, parent_class)? {
200                continue;
201            }
202            out.push(shape_value(
203                Symbol::qualified("shape-class-parent", parent_class.symbol().to_string()),
204                std::sync::Arc::new(ClassShape::new(parent_class.symbol())),
205            ));
206        }
207        Ok(out)
208    }
209
210    fn is_subshape_of(&self, cx: &mut Cx, parent: &dyn Shape) -> Result<Option<bool>> {
211        let Some(parent) = parent.as_any().downcast_ref::<Self>() else {
212            return Ok(None);
213        };
214        if self.symbol == parent.symbol {
215            return Ok(Some(true));
216        }
217        let Some(child_class) = cx.registry().class_by_symbol(&self.symbol).cloned() else {
218            return Ok(Some(false));
219        };
220        let Some(child_class) = child_class.object().as_class() else {
221            return Ok(Some(false));
222        };
223        let Some(parent_class) = cx.registry().class_by_symbol(&parent.symbol).cloned() else {
224            return Ok(Some(false));
225        };
226        class_is_subclass_of_guarded(cx, child_class, parent_class).map(Some)
227    }
228
229    fn check_value(&self, cx: &mut Cx, value: Value) -> Result<ShapeMatch> {
230        if let Some(class) = value.object().as_class()
231            && class_matches(cx, class, &self.symbol)?
232        {
233            return Ok(ShapeMatch::accept(MatchScore::exact(30)));
234        }
235
236        let class = value.object().class(cx)?;
237        let Some(class) = class.object().as_class() else {
238            return Ok(ShapeMatch::reject_with_diagnostic(
239                expected_shape_diagnostic("class-backed value", "value without class metadata"),
240            ));
241        };
242        if class_matches(cx, class, &self.symbol)? {
243            Ok(ShapeMatch::accept(MatchScore::exact(30)))
244        } else {
245            Ok(ShapeMatch::reject_with_diagnostic(
246                expected_shape_diagnostic(
247                    format!("class {}", self.symbol),
248                    format!("class {}", class.symbol()),
249                ),
250            ))
251        }
252    }
253
254    fn check_expr(&self, cx: &mut Cx, expr: &Expr) -> Result<ShapeMatch> {
255        match expr {
256            Expr::Symbol(symbol) if class_symbol_matches(cx, symbol, &self.symbol)? => {
257                Ok(ShapeMatch::accept(MatchScore::exact(20)))
258            }
259            _ => {
260                let object = match ObjectExpr::parse(expr) {
261                    Some(object) => object,
262                    None => {
263                        return Ok(ShapeMatch::reject_with_diagnostic(
264                            expected_shape_diagnostic(
265                                format!("class {}", self.symbol),
266                                expr_actual_label(expr),
267                            ),
268                        ));
269                    }
270                };
271                if !class_symbol_matches(cx, &object.class, &self.symbol)? {
272                    return Ok(ShapeMatch::reject_with_diagnostic(
273                        expected_shape_diagnostic(
274                            format!("class {}", self.symbol),
275                            format!("class {}", object.class),
276                        ),
277                    ));
278                }
279                if let Some(class_value) = cx.registry().class_by_symbol(&self.symbol).cloned()
280                    && let Some(class) = class_value.object().as_class()
281                {
282                    let shape = class.instance_shape(cx)?;
283                    if let Some(shape) = shape.object().as_shape() {
284                        // The instance shape can resolve back to this class
285                        // (self-referential metadata); bound the re-entry so a
286                        // cycle rejects rather than overflowing the stack.
287                        let Some(_guard) = DepthGuard::enter() else {
288                            return Ok(ShapeMatch::reject_with_diagnostic(
289                                expected_shape_diagnostic(
290                                    format!("class {} within shape recursion budget", self.symbol),
291                                    "shape recursion budget exceeded",
292                                ),
293                            ));
294                        };
295                        let matched = shape.check_expr(cx, expr)?;
296                        if matched.accepted {
297                            return Ok(ShapeMatch::accept(MatchScore::exact(30)));
298                        }
299                        return Ok(matched);
300                    }
301                }
302                Ok(ShapeMatch::accept(MatchScore::exact(25)))
303            }
304        }
305    }
306
307    fn describe(&self, _cx: &mut Cx) -> Result<ShapeDoc> {
308        Ok(ShapeDoc::new(format!("class {}", self.symbol)))
309    }
310}
311
312/// A shape that matches one exact expression form.
313///
314/// Accepts only expressions canonically equal to the stored form; a subshape of
315/// the [`ExprKindShape`] for that form's kind.
316///
317/// # Examples
318///
319/// ```rust
320/// # use std::sync::Arc;
321/// use sim_kernel::{Cx, DefaultFactory, Expr, NoopEvalPolicy};
322/// use sim_shape::{ExactExprShape, Shape};
323///
324/// let mut cx = Cx::new(Arc::new(NoopEvalPolicy), Arc::new(DefaultFactory));
325/// let shape = ExactExprShape::new(Expr::Bool(true));
326/// assert!(shape.check_expr(&mut cx, &Expr::Bool(true)).unwrap().accepted);
327/// assert!(!shape.check_expr(&mut cx, &Expr::Bool(false)).unwrap().accepted);
328/// ```
329pub struct ExactExprShape {
330    expected: Expr,
331}
332
333impl ExactExprShape {
334    /// Build a shape that matches only the given exact expression.
335    pub fn new(expected: Expr) -> Self {
336        Self { expected }
337    }
338
339    /// The exact expression this shape matches.
340    pub fn expected(&self) -> &Expr {
341        &self.expected
342    }
343}
344
345impl Shape for ExactExprShape {
346    fn parents(&self, _cx: &mut Cx) -> Result<Vec<ShapeRef>> {
347        Ok(vec![shape_value(
348            Symbol::qualified("shape-exact-parent", expr_kind_of(&self.expected).name()),
349            std::sync::Arc::new(ExprKindShape::new(expr_kind_of(&self.expected))),
350        )])
351    }
352
353    fn is_subshape_of(&self, _cx: &mut Cx, parent: &dyn Shape) -> Result<Option<bool>> {
354        if let Some(parent) = parent.as_any().downcast_ref::<Self>() {
355            return Ok(Some(self.expected.canonical_eq(parent.expected())));
356        }
357        if let Some(parent) = parent.as_any().downcast_ref::<ExprKindShape>() {
358            return Ok(Some(parent.kind().matches(&self.expected)));
359        }
360        Ok(None)
361    }
362
363    fn check_value(&self, cx: &mut Cx, value: Value) -> Result<ShapeMatch> {
364        let expr = value.object().as_expr(cx)?;
365        self.check_expr(cx, &expr)
366    }
367
368    fn check_expr(&self, _cx: &mut Cx, expr: &Expr) -> Result<ShapeMatch> {
369        if self.expected.canonical_eq(expr) {
370            Ok(ShapeMatch::accept(MatchScore::exact(20)))
371        } else {
372            Ok(ShapeMatch::reject_with_diagnostic(
373                expected_shape_diagnostic("exact expression form", expr_actual_label(expr)),
374            ))
375        }
376    }
377
378    fn describe(&self, _cx: &mut Cx) -> Result<ShapeDoc> {
379        Ok(ShapeDoc::new("exact expr").with_detail(format!("{:?}", self.expected)))
380    }
381}
382
383fn class_matches(cx: &mut Cx, class: &dyn sim_kernel::Class, expected: &Symbol) -> Result<bool> {
384    if class.symbol() == *expected {
385        return Ok(true);
386    }
387    let Some(expected) = cx.registry().class_by_symbol(expected).cloned() else {
388        return Ok(false);
389    };
390    class_is_subclass_of_guarded(cx, class, expected)
391}
392
393fn class_symbol_matches(cx: &mut Cx, actual: &Symbol, expected: &Symbol) -> Result<bool> {
394    if actual == expected {
395        return Ok(true);
396    }
397    let Some(actual) = cx.registry().class_by_symbol(actual).cloned() else {
398        return Ok(false);
399    };
400    let Some(actual) = actual.object().as_class() else {
401        return Ok(false);
402    };
403    class_matches(cx, actual, expected)
404}
405
406fn expr_kind_of(expr: &Expr) -> ExprKind {
407    match expr {
408        Expr::Nil => ExprKind::Nil,
409        Expr::Bool(_) => ExprKind::Bool,
410        Expr::Number(_) => ExprKind::Number,
411        Expr::Symbol(_) => ExprKind::Symbol,
412        Expr::Local(_) => ExprKind::Symbol,
413        Expr::String(_) => ExprKind::String,
414        Expr::Bytes(_) => ExprKind::Bytes,
415        Expr::List(_) => ExprKind::List,
416        Expr::Vector(_) => ExprKind::Vector,
417        Expr::Map(_) => ExprKind::Map,
418        Expr::Set(_) => ExprKind::Set,
419        Expr::Call { .. } => ExprKind::Call,
420        Expr::Infix { .. } => ExprKind::Infix,
421        Expr::Prefix { .. } => ExprKind::Prefix,
422        Expr::Postfix { .. } => ExprKind::Postfix,
423        Expr::Block(_) => ExprKind::Block,
424        Expr::Quote { .. } => ExprKind::Quote,
425        Expr::Annotated { .. } => ExprKind::Annotated,
426        Expr::Extension { .. } => ExprKind::Extension,
427    }
428}