petitparser / dart-petitparser

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

How do I define optional named parameter grammar with any order? #114

Closed WenhaoWu closed 3 years ago

WenhaoWu commented 3 years ago

Given the below testing grammar trying to accept the optional named parameter with any oder :

 Parser test() =>
      ref1(token, 'test') &
      ref1(token, '(') &
      (ref0(ab) | ref0(ba)) &
      ref1(token, ')');

  Parser ab() => ref0(a).optional() & ref0(b).optional();

  Parser ba() => ref0(b).optional() & ref0(a).optional();

  Parser a() =>
      ref1(token, 'a1') &
      ref1(token, ':') &
      ref1(token, 'a2') &
      ref1(token, ',').optional();

  Parser b() =>
      ref1(token, 'b1') &
      ref1(token, ':') &
      ref1(token, 'b2') &
      ref1(token, ',').optional();

However, the test case fails when providing both optional named parameter in reverse order:

    test('test', () {
      final stmGrammar = grammar.build(start: testGrammar.test);
      expect('test(a1:a2, b1:b2)', accept(stmGrammar)); // pass
      expect('test(b1:b2, a1:a2)', accept(stmGrammar)); // fail
      expect('test(a1:a2)', accept(stmGrammar)); // pass
      expect('test(b1:b2)', accept(stmGrammar)); // pass
      expect('test()', accept(stmGrammar)); // pass
    });

It fails at expect('test(b1:b2, a1:a2)', accept(stmGrammar));

All other tests seem to be ok. Shouldn't I put optional within choice parsers?

P.s. Big thanks to this awesome library and the great effort that has been put into it :)

WenhaoWu commented 3 years ago

To my understanding, it never actually goes to the second parser (ba) in the choice list, when the first parser (ab) sees the input (b1:b2, a1:a2) is only partially valid, it terminates the whole parsing process and returns failure, without trying the second parser.

renggli commented 3 years ago

Your conclusion in https://github.com/petitparser/dart-petitparser/issues/114#issuecomment-879791030 is correct. PEGs like PetitParser use ordered-choices, once a choice is picked it never backtracks and tries any of the other choices.

I would write it as follows:

Parser arguments() => 
    (ref0(a) & ref0(b)) |
    (ref0(a)) |
    (ref0(b) & ref0(a)) |
    (ref0(b)) |
    epsilon();

Important is that the longer variations are before the shorter ones (prefixes of other parsers need to come last).

Probably simpler would be to write it more forgiving and then validate the names either in a second step or using the new predicate operator (not on pub.dev yet).

Parser arguments() => ref0(argument).separatedBy(ref1(token, ','));

Parser argument() => ref0(id) & ref1(token, ':') & ref0(id);
WenhaoWu commented 3 years ago

Ok thanks to reply. In reality we have unknown number of predefined optional named parameters, so listing all possible combination might not be ideal. I think we can proceed now with more forgiving grammar and then later validate in parsers case by case. Thanks again 👍