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
5mod origin;
6mod rewrite;
7mod state;
8mod tokenize;
9
10use crate::pratt::{PrattParser, default_pratt_table};
11use sim_codec::{DecodeBudget, DecodeLimits};
12use sim_kernel::{Expr, LocatedExpr, PrattTable, Result};
13
14pub(crate) use origin::{extend_tree_trivia, tree_origin, with_origin_span};
15pub use state::ParseCx;
16pub use tokenize::{SpannedToken, tokenize_algol_spanned, tokenize_algol_spanned_with_budget};
17
18pub(crate) use rewrite::{raw_number_expr, raw_number_tag};
19
20/// Decodes Algol source into a [`LocatedExpr`], attaching span and trivia
21/// origin so source layout round-trips.
22///
23/// Uses [`crate::default_pratt_table`] and a default decode budget; call
24/// [`decode_algol_located_with_budget`] to supply an explicit budget. The
25/// default [`DecodeLimits::max_input_bytes`] ceiling is applied to `source`
26/// before parsing, so this convenience entry point is bounded even when called
27/// directly. Raw number literals are carried as a tagged extension form and
28/// lowered to concrete number domains later by the runtime decoder.
29pub fn decode_algol_located(
30    codec: sim_kernel::CodecId,
31    source_id: impl Into<String>,
32    source: &str,
33) -> Result<LocatedExpr> {
34    let mut budget = DecodeBudget::new(DecodeLimits::default());
35    budget.check_input_bytes(codec, source.len())?;
36    decode_algol_located_with_budget(codec, source_id, source, &mut budget)
37}
38
39/// Decodes Algol source into a [`LocatedExpr`] under an explicit decode
40/// `budget`.
41///
42/// The budget bounds token count, nesting depth, and string/trivia sizes so a
43/// hostile input cannot exhaust resources. Otherwise behaves like
44/// [`decode_algol_located`].
45pub fn decode_algol_located_with_budget(
46    codec: sim_kernel::CodecId,
47    source_id: impl Into<String>,
48    source: &str,
49    budget: &mut DecodeBudget,
50) -> Result<LocatedExpr> {
51    let parser = PrattParser::new(default_pratt_table());
52    let source_id = sim_kernel::SourceId(source_id.into());
53    let mut tree =
54        parser.parse_text_tree_with_budget(codec, source_id.0.clone(), source, budget)?;
55    tree.origin = Some(origin::origin_from_algol_source(codec, source_id, source)?);
56    Ok(tree.located())
57}
58
59/// Parses Algol `source` into a bare [`Expr`] using a caller-supplied operator
60/// `table`, lowering raw number literals through `cx`.
61///
62/// This is the entry point the `Shape` engine uses to parse infix grammar with
63/// a custom operator table rather than [`crate::default_pratt_table`]. It
64/// applies the default [`DecodeLimits::max_input_bytes`] ceiling to `source`
65/// before parsing; call [`parse_algol_expr_with_table_and_budget`] to honor a
66/// caller-supplied budget. Number literals are lowered lossily: a literal no
67/// number domain accepts is left as the tagged raw form rather than raising an
68/// error.
69///
70/// # Examples
71///
72/// ```
73/// use sim_codec_algol::{default_pratt_table, parse_algol_expr_with_table};
74/// use sim_kernel::Expr;
75/// use sim_test_support::{core_cx, register_f64_number_domain};
76///
77/// let mut cx = core_cx();
78/// register_f64_number_domain(&mut cx);
79/// let expr = parse_algol_expr_with_table(&mut cx, default_pratt_table(), "1 + 2 * 3").unwrap();
80/// assert!(matches!(expr, Expr::Infix { .. }));
81/// ```
82pub fn parse_algol_expr_with_table(
83    cx: &mut sim_kernel::Cx,
84    table: PrattTable,
85    source: &str,
86) -> Result<Expr> {
87    let mut budget = DecodeBudget::new(DecodeLimits::default());
88    budget.check_input_bytes(sim_kernel::CodecId(0), source.len())?;
89    parse_algol_expr_with_table_and_budget(cx, table, source, &mut budget)
90}
91
92/// Parses Algol `source` into a bare [`Expr`] under an explicit decode `budget`,
93/// using a caller-supplied operator `table`.
94///
95/// Identical to [`parse_algol_expr_with_table`] but honors caller limits rather
96/// than hardcoding [`DecodeLimits::default`], so a Shape grammar driven from a
97/// limited runtime context bounds the same way the runtime decode path does.
98pub fn parse_algol_expr_with_table_and_budget(
99    cx: &mut sim_kernel::Cx,
100    table: PrattTable,
101    source: &str,
102    budget: &mut DecodeBudget,
103) -> Result<Expr> {
104    let mut tree = PrattParser::new(table).parse_text_tree_with_budget(
105        sim_kernel::CodecId(0),
106        "<shape>",
107        source,
108        budget,
109    )?;
110    rewrite::rewrite_number_domains_tree_lossy(cx, &mut tree)?;
111    Ok(tree.expr)
112}