Skip to main content

sim_shape/
parse.rs

1//! Shape grammar parser: turns an `Expr` into a `Shape` and runs shape checks
2//! against expressions and values.
3
4use std::sync::Arc;
5
6use sim_kernel::{Cx, Error, Expr, Result, ShapeId, Symbol, Value};
7
8use crate::ExprKind;
9use crate::base::{Shape, ShapeMatch};
10use crate::primitives::{
11    AnyShape, CaptureShape, ClassShape, ExprKindShape, FieldShape, FieldSpec, ListShape,
12    NumberValueShape,
13};
14
15/// Build a [`Shape`] from a shape-grammar expression.
16///
17/// Bare symbols name the built-in atomic shapes (`Any`, `Number`, `String`,
18/// `Bool`, `Symbol`, `Map`, `List`, `Nil`, and the `core` `Number` value
19/// shape); any other symbol becomes a [`ClassShape`] for that class. Lists
20/// drive the combinator grammar: `(capture name Shape)` wraps a shape in a
21/// [`CaptureShape`], `(fields ...)` builds an anonymous [`FieldShape`], and any
22/// other list becomes a [`ListShape`] over its parsed items.
23///
24/// This is parser behavior layered on the kernel `Shape` protocol; the kernel
25/// owns the protocol, this function owns the concrete grammar.
26///
27/// # Examples
28///
29/// ```rust
30/// use sim_kernel::{Expr, Symbol};
31/// use sim_shape::{Shape, parse_shape_expr};
32///
33/// let shape = parse_shape_expr(&Expr::Symbol(Symbol::new("Number"))).unwrap();
34/// assert!(!shape.is_total());
35/// ```
36pub fn parse_shape_expr(expr: &Expr) -> Result<Arc<dyn Shape>> {
37    // Bound the recursive grammar descent so a pathologically deep expression
38    // tree fails closed instead of overflowing the stack.
39    let Some(_guard) = crate::recursion::DepthGuard::enter() else {
40        return Err(Error::Eval(
41            "shape grammar nesting exceeds the recursion budget".to_owned(),
42        ));
43    };
44    match expr {
45        Expr::Symbol(symbol) => Ok(match symbol.name.as_ref() {
46            "Any" if symbol.namespace.is_none() => Arc::new(AnyShape),
47            "Number" if symbol.namespace.is_none() => {
48                Arc::new(ExprKindShape::new(ExprKind::Number))
49            }
50            "Number" if symbol.namespace.as_deref() == Some("core") => Arc::new(NumberValueShape),
51            "String" if symbol.namespace.is_none() => {
52                Arc::new(ExprKindShape::new(ExprKind::String))
53            }
54            "Bool" if symbol.namespace.is_none() => Arc::new(ExprKindShape::new(ExprKind::Bool)),
55            "Symbol" if symbol.namespace.is_none() => {
56                Arc::new(ExprKindShape::new(ExprKind::Symbol))
57            }
58            "Map" if symbol.namespace.is_none() => Arc::new(ExprKindShape::new(ExprKind::Map)),
59            "List" if symbol.namespace.is_none() => Arc::new(ExprKindShape::new(ExprKind::List)),
60            "Nil" if symbol.namespace.is_none() => Arc::new(ExprKindShape::new(ExprKind::Nil)),
61            _ => Arc::new(ClassShape::new(symbol.clone())),
62        }),
63        Expr::List(items) => parse_shape_list(items),
64        other => Err(Error::Eval(format!(
65            "cannot build shape from expression kind {:?}",
66            other
67        ))),
68    }
69}
70
71fn parse_shape_list(items: &[Expr]) -> Result<Arc<dyn Shape>> {
72    let Some(Expr::Symbol(head)) = items.first() else {
73        let items = items
74            .iter()
75            .map(parse_shape_expr)
76            .collect::<Result<Vec<_>>>()?;
77        return Ok(Arc::new(ListShape::new(items)));
78    };
79
80    if head.namespace.is_none() && head.name.as_ref() == "capture" && items.len() == 3 {
81        let Expr::Symbol(name) = &items[1] else {
82            return Err(Error::Eval("capture name must be a symbol".to_owned()));
83        };
84        return Ok(Arc::new(CaptureShape::new(
85            name.clone(),
86            parse_shape_expr(&items[2])?,
87        )));
88    }
89
90    if head.namespace.is_none() && head.name.as_ref() == "fields" {
91        let specs = items
92            .iter()
93            .skip(1)
94            .map(parse_field_spec_expr)
95            .collect::<Result<Vec<_>>>()?;
96        return Ok(Arc::new(FieldShape::anonymous(specs)));
97    }
98
99    let items = items
100        .iter()
101        .map(parse_shape_expr)
102        .collect::<Result<Vec<_>>>()?;
103    Ok(Arc::new(ListShape::new(items)))
104}
105
106fn parse_field_spec_expr(expr: &Expr) -> Result<FieldSpec> {
107    let Expr::List(items) = expr else {
108        return Err(Error::Eval("field shape must be a list".to_owned()));
109    };
110    let [Expr::Symbol(name), shape] = items.as_slice() else {
111        return Err(Error::Eval(
112            "field shape must be of the form (:field Shape)".to_owned(),
113        ));
114    };
115    Ok(FieldSpec::required(
116        normalize_field_symbol(name),
117        parse_shape_expr(shape)?,
118    ))
119}
120
121fn normalize_field_symbol(symbol: &Symbol) -> Symbol {
122    if symbol.namespace.is_none()
123        && let Some(stripped) = symbol.name.strip_prefix(':')
124    {
125        return Symbol::new(stripped.to_owned());
126    }
127    symbol.clone()
128}
129
130/// Check an expression against a shape, returning the resulting match.
131///
132/// A thin entry point that defers to the shape's own `check_expr`; it exists so
133/// callers can drive a shape without depending on the `Shape` trait directly.
134pub fn check_shape_on_expr(shape: &dyn Shape, cx: &mut Cx, expr: &Expr) -> Result<ShapeMatch> {
135    shape.check_expr(cx, expr)
136}
137
138/// Check a runtime value against a shape, returning the resulting match.
139///
140/// A thin entry point that defers to the shape's own `check_value`; it exists so
141/// callers can drive a shape without depending on the `Shape` trait directly.
142pub fn check_shape_on_value(shape: &dyn Shape, cx: &mut Cx, value: Value) -> Result<ShapeMatch> {
143    shape.check_value(cx, value)
144}
145
146/// Build the `WrongShape` error an expression produces against an expected shape.
147///
148/// Re-runs the check and packages the rejection diagnostics into an
149/// `Error::WrongShape`. Returns a `HostError` instead if the expression is in
150/// fact accepted, since there is no error to report in that case.
151pub fn shape_error(expected: &dyn Shape, cx: &mut Cx, expr: &Expr) -> Result<Error> {
152    let matched = expected.check_expr(cx, expr)?;
153    if matched.accepted {
154        Err(Error::HostError(
155            "shape_error called for an accepted shape".to_owned(),
156        ))
157    } else {
158        Ok(Error::WrongShape {
159            expected: expected.id().unwrap_or(ShapeId(0)),
160            diagnostics: matched.diagnostics,
161        })
162    }
163}