Skip to main content

online_dsl_forge/parser/
parse.rs

1use super::ast::{AstExpression, BinaryOp, ExprKind, UnaryOp};
2use super::diagnostics::{Diagnostic, DiagnosticReport};
3use super::lexer::{Token, TokenKind, tokenize};
4use super::span::SourceSpan;
5
6const MAX_PARSE_RECURSION_DEPTH: usize = 256;
7// `serde_json` rejects the 128th nested container by default. Keep every AST
8// produced by the parser below that boundary so its public JSON form can be
9// deserialized without caller-specific configuration.
10const MAX_AST_SERIALIZED_DEPTH: usize = 127;
11const PARSE_RECURSION_DEPTH_EXCEEDED: &str = "parse recursion depth limit exceeded";
12const AST_DEPTH_EXCEEDED: &str = "AST depth limit exceeded";
13
14pub fn parse_expression(input: &str) -> Result<AstExpression, DiagnosticReport> {
15  let tokens = tokenize(input).map_err(DiagnosticReport::new)?;
16  Parser::new(tokens).parse()
17}
18
19struct ParsedExpression {
20  ast: AstExpression,
21  serialized_depth: usize,
22}
23
24impl ParsedExpression {
25  fn leaf(kind: ExprKind, span: SourceSpan) -> Self {
26    Self {
27      ast: AstExpression::new(kind, span),
28      serialized_depth: 2,
29    }
30  }
31
32  fn checked(
33    kind: ExprKind,
34    span: SourceSpan,
35    serialized_depth: usize,
36  ) -> Result<Self, DiagnosticReport> {
37    if serialized_depth > MAX_AST_SERIALIZED_DEPTH {
38      Err(DiagnosticReport::single(AST_DEPTH_EXCEEDED, span))
39    } else {
40      Ok(Self {
41        ast: AstExpression::new(kind, span),
42        serialized_depth,
43      })
44    }
45  }
46
47  fn span(&self) -> SourceSpan {
48    self.ast.span
49  }
50}
51
52#[derive(Default)]
53struct ParsedSequence {
54  expressions: Vec<AstExpression>,
55  max_serialized_depth: usize,
56}
57
58impl ParsedSequence {
59  fn push(&mut self, expression: ParsedExpression) {
60    self.max_serialized_depth = self.max_serialized_depth.max(expression.serialized_depth);
61    self.expressions.push(expression.ast);
62  }
63}
64
65struct Parser {
66  tokens: Vec<Token>,
67  position: usize,
68  recursion_depth: usize,
69}
70
71impl Parser {
72  fn new(tokens: Vec<Token>) -> Self {
73    Self {
74      tokens,
75      position: 0,
76      recursion_depth: 0,
77    }
78  }
79
80  fn parse(mut self) -> Result<AstExpression, DiagnosticReport> {
81    let expression = self.parse_or()?;
82    if !matches!(self.peek().kind, TokenKind::Eof) {
83      return Err(self.error_here("unexpected token after expression"));
84    }
85    Ok(expression.ast)
86  }
87
88  fn parse_or(&mut self) -> Result<ParsedExpression, DiagnosticReport> {
89    let mut expression = self.parse_and()?;
90    while self
91      .consume_kind(|kind| matches!(kind, TokenKind::OrOr))
92      .is_some()
93    {
94      let right = self.parse_and()?;
95      expression = binary(expression, BinaryOp::Or, right)?;
96    }
97    Ok(expression)
98  }
99
100  fn parse_and(&mut self) -> Result<ParsedExpression, DiagnosticReport> {
101    let mut expression = self.parse_equality()?;
102    while self
103      .consume_kind(|kind| matches!(kind, TokenKind::AndAnd))
104      .is_some()
105    {
106      let right = self.parse_equality()?;
107      expression = binary(expression, BinaryOp::And, right)?;
108    }
109    Ok(expression)
110  }
111
112  fn parse_equality(&mut self) -> Result<ParsedExpression, DiagnosticReport> {
113    let mut expression = self.parse_comparison()?;
114    loop {
115      let op = if self
116        .consume_kind(|kind| matches!(kind, TokenKind::EqEq))
117        .is_some()
118      {
119        Some(BinaryOp::Eq)
120      } else if self
121        .consume_kind(|kind| matches!(kind, TokenKind::Ne))
122        .is_some()
123      {
124        Some(BinaryOp::Ne)
125      } else {
126        None
127      };
128      let Some(op) = op else {
129        break;
130      };
131      let right = self.parse_comparison()?;
132      expression = binary(expression, op, right)?;
133    }
134    Ok(expression)
135  }
136
137  fn parse_comparison(&mut self) -> Result<ParsedExpression, DiagnosticReport> {
138    let mut expression = self.parse_additive()?;
139    loop {
140      let op = if self
141        .consume_kind(|kind| matches!(kind, TokenKind::Lt))
142        .is_some()
143      {
144        Some(BinaryOp::Lt)
145      } else if self
146        .consume_kind(|kind| matches!(kind, TokenKind::Le))
147        .is_some()
148      {
149        Some(BinaryOp::Le)
150      } else if self
151        .consume_kind(|kind| matches!(kind, TokenKind::Gt))
152        .is_some()
153      {
154        Some(BinaryOp::Gt)
155      } else if self
156        .consume_kind(|kind| matches!(kind, TokenKind::Ge))
157        .is_some()
158      {
159        Some(BinaryOp::Ge)
160      } else {
161        None
162      };
163      let Some(op) = op else {
164        break;
165      };
166      let right = self.parse_additive()?;
167      expression = binary(expression, op, right)?;
168    }
169    Ok(expression)
170  }
171
172  fn parse_additive(&mut self) -> Result<ParsedExpression, DiagnosticReport> {
173    let mut expression = self.parse_multiplicative()?;
174    loop {
175      let op = if self
176        .consume_kind(|kind| matches!(kind, TokenKind::Plus))
177        .is_some()
178      {
179        Some(BinaryOp::Add)
180      } else if self
181        .consume_kind(|kind| matches!(kind, TokenKind::Minus))
182        .is_some()
183      {
184        Some(BinaryOp::Sub)
185      } else {
186        None
187      };
188      let Some(op) = op else {
189        break;
190      };
191      let right = self.parse_multiplicative()?;
192      expression = binary(expression, op, right)?;
193    }
194    Ok(expression)
195  }
196
197  fn parse_multiplicative(&mut self) -> Result<ParsedExpression, DiagnosticReport> {
198    let mut expression = self.parse_unary()?;
199    loop {
200      let op = if self
201        .consume_kind(|kind| matches!(kind, TokenKind::Star))
202        .is_some()
203      {
204        Some(BinaryOp::Mul)
205      } else if self
206        .consume_kind(|kind| matches!(kind, TokenKind::Slash))
207        .is_some()
208      {
209        Some(BinaryOp::Div)
210      } else if self
211        .consume_kind(|kind| matches!(kind, TokenKind::Percent))
212        .is_some()
213      {
214        Some(BinaryOp::Rem)
215      } else {
216        None
217      };
218      let Some(op) = op else {
219        break;
220      };
221      let right = self.parse_unary()?;
222      expression = binary(expression, op, right)?;
223    }
224    Ok(expression)
225  }
226
227  fn parse_unary(&mut self) -> Result<ParsedExpression, DiagnosticReport> {
228    if let Some(token) = self.consume_kind(|kind| matches!(kind, TokenKind::Bang)) {
229      let expr = self.parse_nested(token.span, |parser| parser.parse_unary())?;
230      let span = token.span.join(expr.span());
231      let serialized_depth = 2 + expr.serialized_depth;
232      return ParsedExpression::checked(
233        ExprKind::Unary {
234          op: UnaryOp::Not,
235          expr: Box::new(expr.ast),
236        },
237        span,
238        serialized_depth,
239      );
240    }
241
242    if let Some(token) = self.consume_kind(|kind| matches!(kind, TokenKind::Minus)) {
243      let expr = self.parse_nested(token.span, |parser| parser.parse_unary())?;
244      let span = token.span.join(expr.span());
245      let serialized_depth = 2 + expr.serialized_depth;
246      return ParsedExpression::checked(
247        ExprKind::Unary {
248          op: UnaryOp::Neg,
249          expr: Box::new(expr.ast),
250        },
251        span,
252        serialized_depth,
253      );
254    }
255
256    self.parse_postfix()
257  }
258
259  fn parse_postfix(&mut self) -> Result<ParsedExpression, DiagnosticReport> {
260    let mut expression = self.parse_primary()?;
261    while self
262      .consume_kind(|kind| matches!(kind, TokenKind::Dot))
263      .is_some()
264    {
265      let name = self.expect_identifier()?;
266      if self
267        .consume_kind(|kind| matches!(kind, TokenKind::LParen))
268        .is_some()
269      {
270        let (args, end_span) = self.parse_call_args()?;
271        let span = expression.span().join(end_span);
272        let serialized_depth = (2 + expression.serialized_depth).max(3 + args.max_serialized_depth);
273        expression = ParsedExpression::checked(
274          ExprKind::MethodCall {
275            receiver: Box::new(expression.ast),
276            name,
277            args: args.expressions,
278          },
279          span,
280          serialized_depth,
281        )?;
282      } else {
283        let span = expression.span().join(self.previous_span());
284        let serialized_depth = 2 + expression.serialized_depth;
285        expression = ParsedExpression::checked(
286          ExprKind::Member {
287            receiver: Box::new(expression.ast),
288            name,
289          },
290          span,
291          serialized_depth,
292        )?;
293      }
294    }
295    Ok(expression)
296  }
297
298  fn parse_primary(&mut self) -> Result<ParsedExpression, DiagnosticReport> {
299    let token = self.advance().clone();
300    match token.kind {
301      TokenKind::True => Ok(ParsedExpression::leaf(
302        ExprKind::Bool { value: true },
303        token.span,
304      )),
305      TokenKind::False => Ok(ParsedExpression::leaf(
306        ExprKind::Bool { value: false },
307        token.span,
308      )),
309      TokenKind::Null => Ok(ParsedExpression::leaf(ExprKind::Null, token.span)),
310      TokenKind::Int(value) => Ok(ParsedExpression::leaf(ExprKind::Int { value }, token.span)),
311      TokenKind::Float(value) => Ok(ParsedExpression::leaf(
312        ExprKind::Float { value },
313        token.span,
314      )),
315      TokenKind::String(value) => Ok(ParsedExpression::leaf(
316        ExprKind::String { value },
317        token.span,
318      )),
319      TokenKind::Identifier(name) => {
320        validate_identifier(&name, token.span)?;
321        if self
322          .consume_kind(|kind| matches!(kind, TokenKind::LParen))
323          .is_some()
324        {
325          let (args, end_span) = self.parse_call_args()?;
326          let serialized_depth = 3 + args.max_serialized_depth;
327          ParsedExpression::checked(
328            ExprKind::FunctionCall {
329              name,
330              args: args.expressions,
331            },
332            token.span.join(end_span),
333            serialized_depth,
334          )
335        } else {
336          Ok(ParsedExpression::leaf(
337            ExprKind::Identifier { name },
338            token.span,
339          ))
340        }
341      }
342      TokenKind::LParen => {
343        let expression = self.parse_nested(token.span, |parser| parser.parse_or())?;
344        self.expect_kind("expected closing parenthesis", |kind| {
345          matches!(kind, TokenKind::RParen)
346        })?;
347        Ok(expression)
348      }
349      TokenKind::LBracket => self.parse_array(token.span),
350      _ => Err(DiagnosticReport::single("expected expression", token.span)),
351    }
352  }
353
354  fn parse_array(&mut self, start_span: SourceSpan) -> Result<ParsedExpression, DiagnosticReport> {
355    let mut items = ParsedSequence::default();
356    if let Some(end) = self.consume_kind(|kind| matches!(kind, TokenKind::RBracket)) {
357      return ParsedExpression::checked(
358        ExprKind::Array {
359          items: items.expressions,
360        },
361        start_span.join(end.span),
362        3,
363      );
364    }
365
366    loop {
367      items.push(self.parse_nested(start_span, |parser| parser.parse_or())?);
368      if let Some(end) = self.consume_kind(|kind| matches!(kind, TokenKind::RBracket)) {
369        let serialized_depth = 3 + items.max_serialized_depth;
370        return ParsedExpression::checked(
371          ExprKind::Array {
372            items: items.expressions,
373          },
374          start_span.join(end.span),
375          serialized_depth,
376        );
377      }
378      self.expect_kind("expected comma in array literal", |kind| {
379        matches!(kind, TokenKind::Comma)
380      })?;
381    }
382  }
383
384  fn parse_call_args(&mut self) -> Result<(ParsedSequence, SourceSpan), DiagnosticReport> {
385    let mut args = ParsedSequence::default();
386    if let Some(end) = self.consume_kind(|kind| matches!(kind, TokenKind::RParen)) {
387      return Ok((args, end.span));
388    }
389
390    loop {
391      let span = self.peek().span;
392      args.push(self.parse_nested(span, |parser| parser.parse_or())?);
393      if let Some(end) = self.consume_kind(|kind| matches!(kind, TokenKind::RParen)) {
394        return Ok((args, end.span));
395      }
396      self.expect_kind("expected comma in argument list", |kind| {
397        matches!(kind, TokenKind::Comma)
398      })?;
399    }
400  }
401
402  fn expect_identifier(&mut self) -> Result<String, DiagnosticReport> {
403    let token = self.advance().clone();
404    match token.kind {
405      TokenKind::Identifier(name) => {
406        validate_identifier(&name, token.span)?;
407        Ok(name)
408      }
409      _ => Err(DiagnosticReport::single("expected identifier", token.span)),
410    }
411  }
412
413  fn expect_kind(
414    &mut self,
415    message: &'static str,
416    predicate: impl FnOnce(&TokenKind) -> bool,
417  ) -> Result<Token, DiagnosticReport> {
418    let token = self.advance().clone();
419    if predicate(&token.kind) {
420      Ok(token)
421    } else {
422      Err(DiagnosticReport::single(message, token.span))
423    }
424  }
425
426  fn consume_kind(&mut self, predicate: impl FnOnce(&TokenKind) -> bool) -> Option<Token> {
427    if predicate(&self.peek().kind) {
428      let token = self.peek().clone();
429      self.position += 1;
430      Some(token)
431    } else {
432      None
433    }
434  }
435
436  fn advance(&mut self) -> &Token {
437    let index = self.position.min(self.tokens.len().saturating_sub(1));
438    if !matches!(self.tokens[index].kind, TokenKind::Eof) {
439      self.position += 1;
440    }
441    &self.tokens[index]
442  }
443
444  fn peek(&self) -> &Token {
445    self.tokens.get(self.position).unwrap_or_else(|| {
446      self
447        .tokens
448        .last()
449        .expect("parser requires lexer to append an EOF token")
450    })
451  }
452
453  fn previous_span(&self) -> SourceSpan {
454    self
455      .tokens
456      .get(self.position.saturating_sub(1))
457      .map(|token| token.span)
458      .unwrap_or_default()
459  }
460
461  fn error_here(&self, message: &'static str) -> DiagnosticReport {
462    DiagnosticReport::single(message, self.peek().span)
463  }
464
465  fn parse_nested<T>(
466    &mut self,
467    span: SourceSpan,
468    parse: impl FnOnce(&mut Self) -> Result<T, DiagnosticReport>,
469  ) -> Result<T, DiagnosticReport> {
470    if self.recursion_depth >= MAX_PARSE_RECURSION_DEPTH {
471      return Err(DiagnosticReport::single(
472        PARSE_RECURSION_DEPTH_EXCEEDED,
473        span,
474      ));
475    }
476    self.recursion_depth += 1;
477    let result = parse(self);
478    self.recursion_depth -= 1;
479    result
480  }
481}
482
483fn binary(
484  left: ParsedExpression,
485  op: BinaryOp,
486  right: ParsedExpression,
487) -> Result<ParsedExpression, DiagnosticReport> {
488  let span = left.span().join(right.span());
489  let serialized_depth = 2 + left.serialized_depth.max(right.serialized_depth);
490  ParsedExpression::checked(
491    ExprKind::Binary {
492      left: Box::new(left.ast),
493      op,
494      right: Box::new(right.ast),
495    },
496    span,
497    serialized_depth,
498  )
499}
500
501fn validate_identifier(identifier: &str, span: SourceSpan) -> Result<(), DiagnosticReport> {
502  if is_reserved_identifier(identifier) {
503    Err(DiagnosticReport::new(vec![Diagnostic::new(
504      format!("reserved identifier {identifier}"),
505      span,
506    )]))
507  } else {
508    Ok(())
509  }
510}
511
512fn is_reserved_identifier(identifier: &str) -> bool {
513  matches!(
514    identifier,
515    "if"
516      | "else"
517      | "for"
518      | "while"
519      | "do"
520      | "switch"
521      | "let"
522      | "const"
523      | "function"
524      | "import"
525      | "export"
526      | "new"
527      | "try"
528      | "catch"
529      | "throw"
530      | "await"
531      | "return"
532      | "true"
533      | "false"
534      | "null"
535  )
536}
537
538#[cfg(test)]
539mod tests {
540  use crate::format_expression;
541
542  use super::parse_expression;
543
544  #[test]
545  fn parses_precedence() {
546    let ast = parse_expression("1 + 2 * 3 == 7 || false").expect("expression should parse");
547    assert_eq!(format_expression(&ast), "1 + 2 * 3 == 7 || false");
548  }
549
550  #[test]
551  fn parses_calls_members_and_arrays() {
552    let ast = parse_expression("user.name.starts_with('pi') && len([1, 2]) == 2")
553      .expect("expression should parse");
554    assert_eq!(
555      format_expression(&ast),
556      "user.name.starts_with(\"pi\") && len([1, 2]) == 2"
557    );
558  }
559}