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::algebra::{
10    AndShape, NotShape, OrShape, RepeatShape, TableExtraPolicy, TableFieldSpec, TableShape,
11};
12use crate::base::{Shape, ShapeMatch};
13use crate::primitives::{
14    AnyShape, CaptureShape, ClassShape, ExprKindShape, FieldShape, FieldSpec, ListShape,
15    NumberValueShape,
16};
17
18/// Build a [`Shape`] from a shape-grammar expression.
19///
20/// Bare symbols name the built-in atomic shapes (`Any`, `Number`, `String`,
21/// `Bool`, `Symbol`, `Map`, `List`, `Nil`, and the `core` `Number` value
22/// shape); any other symbol becomes a [`ClassShape`] for that class. Lists
23/// drive the combinator grammar: `(capture name Shape)` wraps a shape in a
24/// [`CaptureShape`], `(fields ...)` builds an anonymous [`FieldShape`],
25/// `(and ...)`/`(or ...)`/`(not ...)` build boolean algebra, `(list-rest ...)`
26/// and `(repeat...)` build collection algebra, `(table-open ...)` and
27/// `(table-closed ...)` build table algebra, and any other list becomes a
28/// [`ListShape`] over its parsed items.
29///
30/// This is parser behavior layered on the kernel `Shape` protocol; the kernel
31/// owns the protocol, this function owns the concrete grammar.
32///
33/// # Examples
34///
35/// ```rust
36/// use sim_kernel::{Expr, Symbol};
37/// use sim_shape::{Shape, parse_shape_expr};
38///
39/// let shape = parse_shape_expr(&Expr::Symbol(Symbol::new("Number"))).unwrap();
40/// assert!(!shape.is_total());
41/// ```
42pub fn parse_shape_expr(expr: &Expr) -> Result<Arc<dyn Shape>> {
43    // Bound the recursive grammar descent so a pathologically deep expression
44    // tree fails closed instead of overflowing the stack.
45    let Some(_guard) = crate::recursion::DepthGuard::enter() else {
46        return Err(Error::Eval(
47            "shape grammar nesting exceeds the recursion budget".to_owned(),
48        ));
49    };
50    match expr {
51        Expr::Symbol(symbol) => Ok(match symbol.name.as_ref() {
52            "Any" if symbol.namespace.is_none() => Arc::new(AnyShape),
53            "Number" if symbol.namespace.is_none() => {
54                Arc::new(ExprKindShape::new(ExprKind::Number))
55            }
56            "Number" if symbol.namespace.as_deref() == Some("core") => Arc::new(NumberValueShape),
57            "String" if symbol.namespace.is_none() => {
58                Arc::new(ExprKindShape::new(ExprKind::String))
59            }
60            "Bool" if symbol.namespace.is_none() => Arc::new(ExprKindShape::new(ExprKind::Bool)),
61            "Symbol" if symbol.namespace.is_none() => {
62                Arc::new(ExprKindShape::new(ExprKind::Symbol))
63            }
64            "Map" if symbol.namespace.is_none() => Arc::new(ExprKindShape::new(ExprKind::Map)),
65            "List" if symbol.namespace.is_none() => Arc::new(ExprKindShape::new(ExprKind::List)),
66            "Nil" if symbol.namespace.is_none() => Arc::new(ExprKindShape::new(ExprKind::Nil)),
67            _ => Arc::new(ClassShape::new(symbol.clone())),
68        }),
69        Expr::List(items) => parse_shape_list(items),
70        other => Err(Error::Eval(format!(
71            "cannot build shape from expression kind {:?}",
72            other
73        ))),
74    }
75}
76
77fn parse_shape_list(items: &[Expr]) -> Result<Arc<dyn Shape>> {
78    let Some(Expr::Symbol(head)) = items.first() else {
79        return parse_tuple_shape(items);
80    };
81
82    match shape_form_name(head) {
83        Some("and" | "all") => Ok(Arc::new(AndShape::new(parse_shape_items(
84            shape_sequence_args(head, &items[1..]),
85        )?))),
86        Some("or" | "any") => Ok(Arc::new(OrShape::new(parse_shape_items(
87            shape_sequence_args(head, &items[1..]),
88        )?))),
89        Some("not" | "none") => {
90            expect_arity(head, items, 2)?;
91            Ok(Arc::new(NotShape::new(parse_shape_expr(&items[1])?)))
92        }
93        Some("capture") => parse_capture_shape(head, items),
94        Some("fields") => parse_fields_shape(&items[1..]),
95        Some("list") => Ok(Arc::new(ListShape::new(parse_shape_items(
96            shape_sequence_args(head, &items[1..]),
97        )?))),
98        Some("list-rest") => parse_list_rest_shape(head, items),
99        Some("repeat") => {
100            expect_arity(head, items, 2)?;
101            Ok(Arc::new(RepeatShape::new(parse_shape_expr(&items[1])?)))
102        }
103        Some("repeat-bounds") => parse_repeat_bounds_shape(head, items),
104        Some("table") => parse_single_table_shape(head, items),
105        Some("table-required" | "table-open") => {
106            parse_table_shape(&items[1..], TableExtraPolicy::Allow)
107        }
108        Some("table-closed") => parse_table_shape(&items[1..], TableExtraPolicy::Reject),
109        Some("without" | "difference") => parse_without_shape(head, items),
110        _ => parse_tuple_shape(items),
111    }
112}
113
114fn parse_tuple_shape(items: &[Expr]) -> Result<Arc<dyn Shape>> {
115    let items = items
116        .iter()
117        .map(parse_shape_expr)
118        .collect::<Result<Vec<_>>>()?;
119    Ok(Arc::new(ListShape::new(items)))
120}
121
122fn parse_shape_items(items: &[Expr]) -> Result<Vec<Arc<dyn Shape>>> {
123    items.iter().map(parse_shape_expr).collect()
124}
125
126fn shape_sequence_args<'a>(head: &Symbol, items: &'a [Expr]) -> &'a [Expr] {
127    if head.namespace.as_deref() == Some("shape")
128        && let [Expr::List(shapes)] = items
129    {
130        return shapes;
131    }
132    items
133}
134
135fn parse_capture_shape(head: &Symbol, items: &[Expr]) -> Result<Arc<dyn Shape>> {
136    expect_arity(head, items, 3)?;
137    let Expr::Symbol(name) = &items[1] else {
138        return Err(Error::Eval("capture name must be a symbol".to_owned()));
139    };
140    Ok(Arc::new(CaptureShape::new(
141        name.clone(),
142        parse_shape_expr(&items[2])?,
143    )))
144}
145
146fn parse_fields_shape(items: &[Expr]) -> Result<Arc<dyn Shape>> {
147    let specs = items
148        .iter()
149        .map(parse_field_spec_expr)
150        .collect::<Result<Vec<_>>>()?;
151    Ok(Arc::new(FieldShape::anonymous(specs)))
152}
153
154fn parse_list_rest_shape(head: &Symbol, items: &[Expr]) -> Result<Arc<dyn Shape>> {
155    expect_arity(head, items, 3)?;
156    let Expr::List(prefix) = &items[1] else {
157        return Err(Error::Eval(
158            "list-rest prefix must be a list of shapes".to_owned(),
159        ));
160    };
161    Ok(Arc::new(ListShape::with_rest(
162        parse_shape_items(prefix)?,
163        parse_shape_expr(&items[2])?,
164    )))
165}
166
167fn parse_repeat_bounds_shape(head: &Symbol, items: &[Expr]) -> Result<Arc<dyn Shape>> {
168    expect_arity(head, items, 4)?;
169    let min = parse_usize_expr(&items[2], "repeat-bounds min")?;
170    let max = parse_optional_usize_expr(&items[3], "repeat-bounds max")?;
171    if matches!(max, Some(max) if max < min) {
172        return Err(Error::Eval(
173            "repeat-bounds max must be greater than or equal to min".to_owned(),
174        ));
175    }
176    Ok(Arc::new(RepeatShape::with_bounds(
177        parse_shape_expr(&items[1])?,
178        min,
179        max,
180    )))
181}
182
183fn parse_table_shape(items: &[Expr], extra: TableExtraPolicy) -> Result<Arc<dyn Shape>> {
184    let fields = table_field_exprs(items)
185        .iter()
186        .map(parse_table_field_spec_expr)
187        .collect::<Result<Vec<_>>>()?;
188    Ok(Arc::new(TableShape::new(fields, extra)))
189}
190
191fn parse_single_table_shape(head: &Symbol, items: &[Expr]) -> Result<Arc<dyn Shape>> {
192    expect_arity(head, items, 3)?;
193    let Expr::Symbol(name) = &items[1] else {
194        return Err(Error::Eval("table key must be a symbol".to_owned()));
195    };
196    Ok(Arc::new(TableShape::single(
197        normalize_field_symbol(name),
198        parse_shape_expr(&items[2])?,
199    )))
200}
201
202fn table_field_exprs(items: &[Expr]) -> &[Expr] {
203    if let [Expr::List(fields)] = items
204        && (fields.is_empty() || fields.iter().all(is_table_field_expr))
205    {
206        return fields;
207    }
208    items
209}
210
211fn is_table_field_expr(expr: &Expr) -> bool {
212    matches!(expr, Expr::List(items) if matches!(items.as_slice(), [Expr::Symbol(_), _]))
213}
214
215fn parse_table_field_spec_expr(expr: &Expr) -> Result<TableFieldSpec> {
216    let Expr::List(items) = expr else {
217        return Err(Error::Eval("table field shape must be a list".to_owned()));
218    };
219    let [Expr::Symbol(name), shape] = items.as_slice() else {
220        return Err(Error::Eval(
221            "table field shape must be of the form (:field Shape)".to_owned(),
222        ));
223    };
224    Ok(TableFieldSpec {
225        key: normalize_field_symbol(name),
226        shape: parse_shape_expr(shape)?,
227        required: true,
228    })
229}
230
231fn parse_without_shape(head: &Symbol, items: &[Expr]) -> Result<Arc<dyn Shape>> {
232    expect_arity(head, items, 3)?;
233    let left = parse_shape_expr(&items[1])?;
234    let right = parse_shape_expr(&items[2])?;
235    let negated_right: Arc<dyn Shape> = Arc::new(NotShape::new(right));
236    Ok(Arc::new(AndShape::new(vec![left, negated_right])))
237}
238
239fn parse_optional_usize_expr(expr: &Expr, context: &str) -> Result<Option<usize>> {
240    if matches!(expr, Expr::Nil) {
241        Ok(None)
242    } else {
243        parse_usize_expr(expr, context).map(Some)
244    }
245}
246
247fn parse_usize_expr(expr: &Expr, context: &str) -> Result<usize> {
248    let Expr::Number(number) = expr else {
249        return Err(Error::Eval(format!("{context} expects a number")));
250    };
251    number
252        .canonical
253        .parse::<usize>()
254        .map_err(|_| Error::Eval(format!("{context} expects a non-negative integer")))
255}
256
257fn shape_form_name(symbol: &Symbol) -> Option<&str> {
258    if symbol.namespace.is_none() || symbol.namespace.as_deref() == Some("shape") {
259        Some(symbol.name.as_ref())
260    } else {
261        None
262    }
263}
264
265fn expect_arity(head: &Symbol, items: &[Expr], expected: usize) -> Result<()> {
266    if items.len() == expected {
267        Ok(())
268    } else {
269        Err(Error::Eval(format!(
270            "{head} expects {} argument(s), got {}",
271            expected - 1,
272            items.len().saturating_sub(1)
273        )))
274    }
275}
276
277fn parse_field_spec_expr(expr: &Expr) -> Result<FieldSpec> {
278    let Expr::List(items) = expr else {
279        return Err(Error::Eval("field shape must be a list".to_owned()));
280    };
281    let [Expr::Symbol(name), shape] = items.as_slice() else {
282        return Err(Error::Eval(
283            "field shape must be of the form (:field Shape)".to_owned(),
284        ));
285    };
286    Ok(FieldSpec::required(
287        normalize_field_symbol(name),
288        parse_shape_expr(shape)?,
289    ))
290}
291
292fn normalize_field_symbol(symbol: &Symbol) -> Symbol {
293    if symbol.namespace.is_none()
294        && let Some(stripped) = symbol.name.strip_prefix(':')
295    {
296        return Symbol::new(stripped.to_owned());
297    }
298    symbol.clone()
299}
300
301/// Check an expression against a shape, returning the resulting match.
302///
303/// A thin entry point that defers to the shape's own `check_expr`; it exists so
304/// callers can drive a shape without depending on the `Shape` trait directly.
305pub fn check_shape_on_expr(shape: &dyn Shape, cx: &mut Cx, expr: &Expr) -> Result<ShapeMatch> {
306    shape.check_expr(cx, expr)
307}
308
309/// Check a runtime value against a shape, returning the resulting match.
310///
311/// A thin entry point that defers to the shape's own `check_value`; it exists so
312/// callers can drive a shape without depending on the `Shape` trait directly.
313pub fn check_shape_on_value(shape: &dyn Shape, cx: &mut Cx, value: Value) -> Result<ShapeMatch> {
314    shape.check_value(cx, value)
315}
316
317/// Build the `WrongShape` error an expression produces against an expected shape.
318///
319/// Re-runs the check and packages the rejection diagnostics into an
320/// `Error::WrongShape`. Returns a `HostError` instead if the expression is in
321/// fact accepted, since there is no error to report in that case.
322pub fn shape_error(expected: &dyn Shape, cx: &mut Cx, expr: &Expr) -> Result<Error> {
323    let matched = expected.check_expr(cx, expr)?;
324    if matched.accepted {
325        Err(Error::HostError(
326            "shape_error called for an accepted shape".to_owned(),
327        ))
328    } else {
329        Ok(Error::WrongShape {
330            expected: expected.id().unwrap_or(ShapeId(0)),
331            diagnostics: matched.diagnostics,
332        })
333    }
334}