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