petitparser / dart-petitparser

Dynamic parser combinators in Dart.
https://pub.dartlang.org/packages/petitparser
MIT License
457 stars 48 forks source link

endless recursion when adding binary operation expressions to grammar #171

Closed leeflix closed 4 months ago

leeflix commented 5 months ago

i am implementing a parser for my own programming language. i am currently definining expressions as:

exp := lit | variable

this works but when i add binary expressions:

exp := lit | variable | binaryOperationExpression

i get a stack overflow for whatever i am trying to parse even when it does not contain an expression. here some more rules that could be relevant but it is kinda weird since i implemented the same grammar in scala and it worked:

statement := classDeclaration | ifStatement | whileStatement | methodCall | functionCall | expression lit := floatLit | intLit | boolLit | stringLit | tupleLit | listLit2 | listLit1 binaryOperationExpression := expression & operator & expression

renggli commented 5 months ago

Please provide a minimal reproducible example (MRE).

An MRE is intended to reproduce an error using the smallest amount of code. It saves package developers time trying to reproduce the problem; and wading through code that is not relevant to the bug. Ultimately it helps you to get your problem solved quicker.

The MRE should include:

  1. Packages to be imported.
  2. The shortest amount of code needed to reproduce the problem.
  3. A main function that calls your code and demonstrates the problem, i.e. with a failing assertion.
leeflix commented 5 months ago
import 'package:petitparser/petitparser.dart';

Parser a() => char("a");

Parser expression() => (ref0(binaryOperationExpression) | ref0(a)).cast();

Parser binaryOperationExpression() => (ref0(expression) & char("+") & ref0(expression));

Parser statement() => (ref0(expression)).cast();

main() {
  print(resolve(statement().end()).parse("a+a"));
}
renggli commented 5 months ago

Your grammar has left-recursive productions expression -> binaryOperationExpression, which leads to infinite recursion at runtime. Have a look look at the tutorial Writing a More Complicated Grammar and Using the Expression Builder, that demonstrate how to build expression grammars.

The simplest way to avoid the recursion is to rewrite the grammar as:

Parser a() => char("a");
Parser statement() => ref0(a).starSeparated(char('+'));