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. Raw
25/// number literals are carried as a tagged extension form and lowered to
26/// concrete number domains later by the runtime decoder.
27pub fn decode_algol_located(
28 codec: sim_kernel::CodecId,
29 source_id: impl Into<String>,
30 source: &str,
31) -> Result<LocatedExpr> {
32 let mut budget = DecodeBudget::new(DecodeLimits::default());
33 decode_algol_located_with_budget(codec, source_id, source, &mut budget)
34}
35
36/// Decodes Algol source into a [`LocatedExpr`] under an explicit decode
37/// `budget`.
38///
39/// The budget bounds token count, nesting depth, and string/trivia sizes so a
40/// hostile input cannot exhaust resources. Otherwise behaves like
41/// [`decode_algol_located`].
42pub fn decode_algol_located_with_budget(
43 codec: sim_kernel::CodecId,
44 source_id: impl Into<String>,
45 source: &str,
46 budget: &mut DecodeBudget,
47) -> Result<LocatedExpr> {
48 let parser = PrattParser::new(default_pratt_table());
49 let source_id = sim_kernel::SourceId(source_id.into());
50 let mut tree =
51 parser.parse_text_tree_with_budget(codec, source_id.0.clone(), source, budget)?;
52 tree.origin = Some(origin::origin_from_algol_source(codec, source_id, source)?);
53 Ok(tree.located())
54}
55
56/// Parses Algol `source` into a bare [`Expr`] using a caller-supplied operator
57/// `table`, lowering raw number literals through `cx`.
58///
59/// This is the entry point the `Shape` engine uses to parse infix grammar with
60/// a custom operator table rather than [`crate::default_pratt_table`]. Number
61/// literals are lowered lossily: a literal no number domain accepts is left as
62/// the tagged raw form rather than raising an error.
63///
64/// # Examples
65///
66/// ```
67/// use sim_codec_algol::{default_pratt_table, parse_algol_expr_with_table};
68/// use sim_kernel::Expr;
69/// use sim_test_support::{core_cx, register_f64_number_domain};
70///
71/// let mut cx = core_cx();
72/// register_f64_number_domain(&mut cx);
73/// let expr = parse_algol_expr_with_table(&mut cx, default_pratt_table(), "1 + 2 * 3").unwrap();
74/// assert!(matches!(expr, Expr::Infix { .. }));
75/// ```
76pub fn parse_algol_expr_with_table(
77 cx: &mut sim_kernel::Cx,
78 table: PrattTable,
79 source: &str,
80) -> Result<Expr> {
81 let mut budget = DecodeBudget::new(DecodeLimits::default());
82 parse_algol_expr_with_table_and_budget(cx, table, source, &mut budget)
83}
84
85/// Parses Algol `source` into a bare [`Expr`] under an explicit decode `budget`,
86/// using a caller-supplied operator `table`.
87///
88/// Identical to [`parse_algol_expr_with_table`] but honors caller limits rather
89/// than hardcoding [`DecodeLimits::default`], so a Shape grammar driven from a
90/// limited runtime context bounds the same way the runtime decode path does.
91pub fn parse_algol_expr_with_table_and_budget(
92 cx: &mut sim_kernel::Cx,
93 table: PrattTable,
94 source: &str,
95 budget: &mut DecodeBudget,
96) -> Result<Expr> {
97 let mut tree = PrattParser::new(table).parse_text_tree_with_budget(
98 sim_kernel::CodecId(0),
99 "<shape>",
100 source,
101 budget,
102 )?;
103 rewrite::rewrite_number_domains_tree_lossy(cx, &mut tree)?;
104 Ok(tree.expr)
105}