Skip to main content

sim_codec_pratt/
parser.rs

1use sim_codec::{DecodeBudget, DecodeLimits};
2use sim_kernel::{
3    CodecId, Error, Expr, Fixity, LocatedExprTree, Origin, PrattOperator, PrattTable,
4    PrattToken as Token, Result, SourceId, Span, Symbol, Trivia,
5    parse_pratt_symbol as parse_symbol,
6};
7
8use crate::{PrattTokenSource, SpannedPrattToken};
9
10/// Returns the raw-number tag consumed by Algol number-domain lowering.
11pub fn raw_number_tag() -> Symbol {
12    Symbol::qualified("codec", "algol-number-literal")
13}
14
15/// Builds the raw-number expression emitted for Pratt numeric literal tokens.
16pub fn raw_number_expr(raw: String) -> Expr {
17    Expr::Extension {
18        tag: raw_number_tag(),
19        payload: Box::new(Expr::String(raw)),
20    }
21}
22
23/// Language-neutral Pratt driver: an operator table plus any token source.
24pub struct PrattCodecParser<S> {
25    operators: PrattTable,
26    token_source: S,
27    surface_name: &'static str,
28}
29
30impl<S: PrattTokenSource> PrattCodecParser<S> {
31    /// Creates a parser driven by `operators` and `token_source`.
32    pub fn new(operators: PrattTable, token_source: S) -> Self {
33        Self {
34            operators,
35            token_source,
36            surface_name: "pratt",
37        }
38    }
39
40    /// Sets the surface name used in parse error messages.
41    pub fn with_surface_name(mut self, surface_name: &'static str) -> Self {
42        self.surface_name = surface_name;
43        self
44    }
45
46    /// Returns the parser's operator table.
47    pub fn operators(&self) -> &PrattTable {
48        &self.operators
49    }
50
51    /// Returns the parser's token source.
52    pub fn token_source(&self) -> &S {
53        &self.token_source
54    }
55
56    /// Parses `source` into a located expression tree under a default budget.
57    pub fn parse_tree(&self, codec: CodecId, source: &str) -> Result<LocatedExprTree> {
58        let mut budget = DecodeBudget::new(DecodeLimits::default());
59        self.parse_tree_with_budget(codec, source, &mut budget)
60    }
61
62    /// Parses `source` into a located expression tree under an explicit budget.
63    pub fn parse_tree_with_budget(
64        &self,
65        codec: CodecId,
66        source: &str,
67        budget: &mut DecodeBudget,
68    ) -> Result<LocatedExprTree> {
69        self.parse_tree_with_source_and_budget(
70            codec,
71            SourceId(format!("<{}>", self.surface_name)),
72            source,
73            budget,
74        )
75    }
76
77    /// Parses `source` into a located tree with the caller's source id.
78    pub fn parse_tree_with_source(
79        &self,
80        codec: CodecId,
81        source_id: impl Into<String>,
82        source: &str,
83    ) -> Result<LocatedExprTree> {
84        let mut budget = DecodeBudget::new(DecodeLimits::default());
85        self.parse_tree_with_source_and_budget(
86            codec,
87            SourceId(source_id.into()),
88            source,
89            &mut budget,
90        )
91    }
92
93    /// Parses `source` into a located tree with the caller's source id and budget.
94    pub fn parse_tree_with_source_and_budget(
95        &self,
96        codec: CodecId,
97        source_id: SourceId,
98        source: &str,
99        budget: &mut DecodeBudget,
100    ) -> Result<LocatedExprTree> {
101        let tokens = self.token_source.tokenize_pratt(codec, source, budget)?;
102        budget.check_tokens(codec, tokens.len())?;
103        let mut cx = ParseCx::new(tokens, self.surface_name);
104        let expr = self.parse_expr_tree(&mut cx, codec, &source_id, source, 0, budget, 0)?;
105        if !cx.is_empty() {
106            return Err(Error::Eval(format!(
107                "trailing {} tokens",
108                self.surface_name
109            )));
110        }
111        Ok(expr)
112    }
113
114    #[allow(clippy::too_many_arguments)]
115    pub(super) fn parse_expr_tree(
116        &self,
117        cx: &mut ParseCx,
118        codec: CodecId,
119        source_id: &SourceId,
120        source: &str,
121        min_bp: u16,
122        budget: &mut DecodeBudget,
123        depth: usize,
124    ) -> Result<LocatedExprTree> {
125        budget.enter_node(codec, depth)?;
126        let mut left = self.parse_nud_tree(cx, codec, source_id, source, budget, depth)?;
127
128        loop {
129            if matches!(
130                cx.peek(),
131                Some(SpannedPrattToken {
132                    token: Token::OpenParen,
133                    ..
134                })
135            ) {
136                if 110 < min_bp {
137                    break;
138                }
139                let open = cx.next_required()?;
140                left = self.parse_call_tree(
141                    cx, codec, source_id, source, left, open.start, budget, depth,
142                )?;
143                continue;
144            }
145
146            let Some(token) = cx.peek().cloned() else {
147                break;
148            };
149
150            let Some(op) = self.operators.lookup_led(&token.token) else {
151                break;
152            };
153
154            if op.fixity == Fixity::Postfix {
155                if op.left_bp < min_bp {
156                    break;
157                }
158                cx.advance();
159                left = self.parse_postfix_tree(
160                    codec, source_id, source, left, op, token.end, budget, depth,
161                )?;
162                continue;
163            }
164
165            if op.left_bp < min_bp {
166                break;
167            }
168
169            cx.advance();
170            left = self.parse_led_tree(cx, codec, source_id, source, left, op, budget, depth)?;
171        }
172
173        Ok(left)
174    }
175
176    fn parse_nud_tree(
177        &self,
178        cx: &mut ParseCx,
179        codec: CodecId,
180        source_id: &SourceId,
181        source: &str,
182        budget: &mut DecodeBudget,
183        depth: usize,
184    ) -> Result<LocatedExprTree> {
185        let token = cx.next_required()?;
186
187        if let Some(op) = self.operators.lookup_nud(&token.token) {
188            let right =
189                self.parse_expr_tree(cx, codec, source_id, source, op.right_bp, budget, depth + 1)?;
190            return self.build_prefix_tree(
191                codec,
192                source_id,
193                source,
194                op,
195                token.start,
196                right,
197                budget,
198                depth,
199            );
200        }
201
202        match token.token {
203            Token::Ident(name) => {
204                budget.enter_node(codec, depth)?;
205                let expr = match name.as_str() {
206                    "nil" => Expr::Nil,
207                    "true" => Expr::Bool(true),
208                    "false" => Expr::Bool(false),
209                    _ => Expr::Symbol(parse_symbol(&name)),
210                };
211                Ok(LocatedExprTree::without_children(
212                    expr,
213                    Some(tree_origin(
214                        codec,
215                        source_id.clone(),
216                        source,
217                        token.start,
218                        token.end,
219                        token.leading_trivia,
220                    )),
221                ))
222            }
223            Token::Number(number) => {
224                budget.enter_node(codec, depth)?;
225                Ok(LocatedExprTree::without_children(
226                    raw_number_expr(number),
227                    Some(tree_origin(
228                        codec,
229                        source_id.clone(),
230                        source,
231                        token.start,
232                        token.end,
233                        token.leading_trivia,
234                    )),
235                ))
236            }
237            Token::String(value) => {
238                budget.enter_node(codec, depth)?;
239                budget.check_string_bytes(codec, value.len())?;
240                Ok(LocatedExprTree::without_children(
241                    Expr::String(value),
242                    Some(tree_origin(
243                        codec,
244                        source_id.clone(),
245                        source,
246                        token.start,
247                        token.end,
248                        token.leading_trivia,
249                    )),
250                ))
251            }
252            Token::OpenParen => {
253                let mut expr =
254                    self.parse_expr_tree(cx, codec, source_id, source, 0, budget, depth + 1)?;
255                let close = cx.next_required()?;
256                if close.token != Token::CloseParen {
257                    return Err(Error::Eval(format!(
258                        "expected ')' in {} input, found {:?}",
259                        self.surface_name, close
260                    )));
261                }
262                extend_tree_trivia(&mut expr, close.leading_trivia.clone());
263                Ok(with_origin_span(
264                    expr,
265                    tree_origin(codec, source_id.clone(), source, token.start, close.end, {
266                        let mut trivia = token.leading_trivia;
267                        trivia.extend(close.leading_trivia);
268                        trivia
269                    }),
270                ))
271            }
272            other => Err(Error::Eval(format!(
273                "unexpected {} token in nud {:?}",
274                self.surface_name, other
275            ))),
276        }
277    }
278
279    #[allow(clippy::too_many_arguments)]
280    fn parse_led_tree(
281        &self,
282        cx: &mut ParseCx,
283        codec: CodecId,
284        source_id: &SourceId,
285        source: &str,
286        left: LocatedExprTree,
287        op: PrattOperator,
288        budget: &mut DecodeBudget,
289        depth: usize,
290    ) -> Result<LocatedExprTree> {
291        match op.fixity {
292            Fixity::InfixLeft | Fixity::InfixRight => {
293                let right = self.parse_expr_tree(
294                    cx,
295                    codec,
296                    source_id,
297                    source,
298                    op.right_bp,
299                    budget,
300                    depth + 1,
301                )?;
302                self.build_infix_tree(codec, source_id, source, op, left, right, budget, depth)
303            }
304            _ => Err(Error::Eval("operator cannot be used here".to_owned())),
305        }
306    }
307}
308
309pub(super) struct ParseCx {
310    tokens: Vec<SpannedPrattToken>,
311    index: usize,
312    surface_name: &'static str,
313}
314
315impl ParseCx {
316    pub(super) fn new(tokens: Vec<SpannedPrattToken>, surface_name: &'static str) -> Self {
317        Self {
318            tokens,
319            index: 0,
320            surface_name,
321        }
322    }
323
324    pub(super) fn peek(&self) -> Option<&SpannedPrattToken> {
325        self.tokens.get(self.index)
326    }
327
328    pub(super) fn advance(&mut self) -> Option<SpannedPrattToken> {
329        let token = self.tokens.get(self.index).cloned()?;
330        self.index += 1;
331        Some(token)
332    }
333
334    pub(super) fn next_required(&mut self) -> Result<SpannedPrattToken> {
335        self.advance()
336            .ok_or_else(|| Error::Eval(format!("unexpected end of {} input", self.surface_name)))
337    }
338
339    pub(super) fn is_empty(&self) -> bool {
340        self.index >= self.tokens.len()
341    }
342}
343
344pub(super) fn with_origin_span(mut tree: LocatedExprTree, origin: Origin) -> LocatedExprTree {
345    tree.origin = Some(origin);
346    tree
347}
348
349pub(super) fn extend_tree_trivia(tree: &mut LocatedExprTree, trivia: Vec<Trivia>) {
350    if trivia.is_empty() {
351        return;
352    }
353    if let Some(origin) = &mut tree.origin {
354        origin.trivia.extend(trivia);
355    }
356}
357
358pub(super) fn tree_origin(
359    codec: CodecId,
360    source: SourceId,
361    _raw: &str,
362    start: usize,
363    end: usize,
364    trivia: Vec<Trivia>,
365) -> Origin {
366    Origin {
367        codec,
368        source,
369        span: Span { start, end },
370        trivia,
371    }
372}