Skip to main content

sim_codec_algol/
parse.rs

1//! Parsing root for the Algol codec, aggregating its `tokenize`, `state`,
2//! `origin`, and `rewrite` submodules and exposing the `decode_algol_located`
3//! decode entry points built on top of the Pratt parser.
4
5use std::sync::Arc;
6
7mod origin;
8mod rewrite;
9mod state;
10mod tokenize;
11
12use crate::pratt::{PrattParser, default_pratt_table};
13use sim_codec::{DecodeBudget, DecodeLimits};
14use sim_kernel::{Expr, LocatedExpr, PrattTable, Result};
15use sim_shape::{PrattShape, Shape, ShapeExprParser};
16
17pub use state::ParseCx;
18pub use tokenize::{SpannedToken, tokenize_algol_spanned, tokenize_algol_spanned_with_budget};
19
20pub(crate) use sim_codec_pratt::raw_number_tag;
21
22/// Decodes Algol source into a [`LocatedExpr`], attaching span and trivia
23/// origin so source layout round-trips.
24///
25/// Uses [`crate::default_pratt_table`] and a default decode budget; call
26/// [`decode_algol_located_with_budget`] to supply an explicit budget. The
27/// default [`DecodeLimits::max_input_bytes`] ceiling is applied to `source`
28/// before parsing, so this convenience entry point is bounded even when called
29/// directly. Raw number literals are carried as a tagged extension form and
30/// lowered to concrete number domains later by the runtime decoder.
31pub fn decode_algol_located(
32    codec: sim_kernel::CodecId,
33    source_id: impl Into<String>,
34    source: &str,
35) -> Result<LocatedExpr> {
36    let mut budget = DecodeBudget::new(DecodeLimits::default());
37    budget.check_input_bytes(codec, source.len())?;
38    decode_algol_located_with_budget(codec, source_id, source, &mut budget)
39}
40
41/// Decodes Algol source into a [`LocatedExpr`] under an explicit decode
42/// `budget`.
43///
44/// The budget bounds token count, nesting depth, and string/trivia sizes so a
45/// hostile input cannot exhaust resources. Otherwise behaves like
46/// [`decode_algol_located`].
47pub fn decode_algol_located_with_budget(
48    codec: sim_kernel::CodecId,
49    source_id: impl Into<String>,
50    source: &str,
51    budget: &mut DecodeBudget,
52) -> Result<LocatedExpr> {
53    let parser = PrattParser::new(default_pratt_table());
54    let source_id = sim_kernel::SourceId(source_id.into());
55    let mut tree =
56        parser.parse_text_tree_with_budget(codec, source_id.0.clone(), source, budget)?;
57    tree.origin = Some(origin::origin_from_algol_source(codec, source_id, source)?);
58    Ok(tree.located())
59}
60
61/// Parses Algol `source` into a bare [`Expr`] using a caller-supplied operator
62/// `table`, lowering raw number literals through `cx`.
63///
64/// This is the entry point the `Shape` engine uses to parse infix grammar with
65/// a custom operator table rather than [`crate::default_pratt_table`]. It
66/// applies the default [`DecodeLimits::max_input_bytes`] ceiling to `source`
67/// before parsing; call [`parse_algol_expr_with_table_and_budget`] to honor a
68/// caller-supplied budget. Number literals are lowered lossily: a literal no
69/// number domain accepts is left as the tagged raw form rather than raising an
70/// error.
71///
72/// # Examples
73///
74/// ```
75/// use sim_codec_algol::{default_pratt_table, parse_algol_expr_with_table};
76/// use sim_kernel::Expr;
77/// use sim_test_support::{core_cx, register_f64_number_domain};
78///
79/// let mut cx = core_cx();
80/// register_f64_number_domain(&mut cx);
81/// let expr = parse_algol_expr_with_table(&mut cx, default_pratt_table(), "1 + 2 * 3").unwrap();
82/// assert!(matches!(expr, Expr::Infix { .. }));
83/// ```
84pub fn parse_algol_expr_with_table(
85    cx: &mut sim_kernel::Cx,
86    table: PrattTable,
87    source: &str,
88) -> Result<Expr> {
89    let mut budget = DecodeBudget::new(DecodeLimits::default());
90    budget.check_input_bytes(sim_kernel::CodecId(0), source.len())?;
91    parse_algol_expr_with_table_and_budget(cx, table, source, &mut budget)
92}
93
94/// Parses Algol `source` into a bare [`Expr`] under an explicit decode `budget`,
95/// using a caller-supplied operator `table`.
96///
97/// Identical to [`parse_algol_expr_with_table`] but honors caller limits rather
98/// than hardcoding [`DecodeLimits::default`], so a Shape grammar driven from a
99/// limited runtime context bounds the same way the runtime decode path does.
100pub fn parse_algol_expr_with_table_and_budget(
101    cx: &mut sim_kernel::Cx,
102    table: PrattTable,
103    source: &str,
104    budget: &mut DecodeBudget,
105) -> Result<Expr> {
106    let mut tree = PrattParser::new(table).parse_text_tree_with_budget(
107        sim_kernel::CodecId(0),
108        "<shape>",
109        source,
110        budget,
111    )?;
112    rewrite::rewrite_number_domains_tree_lossy(cx, &mut tree)?;
113    Ok(tree.expr)
114}
115
116/// [`ShapeExprParser`] adapter that parses string input with an Algol
117/// [`PrattTable`].
118///
119/// This lets callers build a [`PrattShape`] over the real Algol parser instead
120/// of carrying a local test-only adapter. The parser returns the raw Pratt
121/// expression tree; number-domain lowering belongs to codec decode paths that
122/// have a runtime context.
123pub struct AlgolShapeParser {
124    table: PrattTable,
125}
126
127impl AlgolShapeParser {
128    /// Build an adapter backed by `table`.
129    pub fn new(table: PrattTable) -> Self {
130        Self { table }
131    }
132
133    /// Return the Pratt table used by this adapter.
134    pub fn table(&self) -> &PrattTable {
135        &self.table
136    }
137}
138
139impl ShapeExprParser for AlgolShapeParser {
140    fn label(&self) -> &str {
141        "algol-pratt"
142    }
143
144    fn parse_expr(&self, source: &str) -> Result<Expr> {
145        Ok(PrattParser::new(self.table.clone())
146            .parse_text_tree(sim_kernel::CodecId(0), "<shape>", source)?
147            .expr)
148    }
149}
150
151/// Build a [`PrattShape`] that parses string expressions through the Algol
152/// Pratt parser before matching `inner`.
153pub fn algol_pratt_shape(table: PrattTable, inner: Arc<dyn Shape>) -> PrattShape {
154    PrattShape::new(Arc::new(AlgolShapeParser::new(table)), inner)
155}