dart-petitparser icon indicating copy to clipboard operation
dart-petitparser copied to clipboard

Alternative to trim parser or how to discard

Open DanielCardonaRojas opened this issue 2 years ago • 2 comments

First of all, thanks for this awesome package.

I've been playing around with this in a hobby project for a day or two and have been wanting to have a more concise way of expressing some parsers, I've been looking through most combinators but might be missing something please point me in the right direction in case that's true.

So lets use a simple example (not my case but just to explain) lets say I want to parse a number between parens here are some way to accomplish it:

    // Version 1
    final Parser<int> parserV1 = char('(') & digit() & char(')').map((values) => int.parse(values[1]));
    // Version 2
    final Parser<int> parserV2 = digit().trim(char('('), char(')')).map((value) => int.parse(value));

With version1 you have to keep track of the index to know which value to extract from values and to correctly map the parser, so not the best solution.

version 2 works perfectly but has a default parser for left and right using whitespace()

What I would like to accomplish is something like this:

    // Desired
    final Parser<int> parserV3 = char('(').dicard() & digit().map((value) => int.parse(value)) & char(')').dicard();

Someway of telling the parser consume input throwing it away completely, so that when composed in sequence it won't accumulate in the result list.

If that's not possible then would be great to have individual TrimLeft and TrimRight parsers.

Is there an extension I'm missing ?

DanielCardonaRojas avatar Dec 28 '21 23:12 DanielCardonaRojas

Thank you for the kind words and the question with the great examples.

This is not something that is provided out of the box from this package. I don't think trim is a good stand-in here, because it consumes the opening and closing params an arbitrary amount of times. Furthermore, it also passes if the before and after characters are entirely absent.

Probably the easiest solution that comes closest to your version 2 would be to add a helper extension, like so:

extension SurroundedParserExtension<T> on Parser<T> {
  Parser<T> surroundedBy(Parser<void> left, [Parser<void>? right]) =>
      [left, this, right ?? left].toSequenceParser().pick(1).cast<T>();
}

final parserV2 = digit().map(int.parse).surroundedBy(char('('), char(')'));

You can copy that extension to your own code. We can also consider adding it to the package, if people think it is generally useful? While it obviously simplifies the creation of some quick parsers, I am afraid that in practice it might not be that useful (often exact positions of characters surrounding some other expression need to be known, i.e. for a syntax highlighter).

Something like version 3 could be implemented, but there are various complications that make it tricky: the typing in Dart doesn't work in your example, and you would still need to somehow unwrap the resulting element from a list and cast it to the right type. Also parser transformations (and optimizations) might become more complicated. Still could be worth to investigate.

renggli avatar Dec 29 '21 09:12 renggli

Hey @renggli thanks for the quick reply, playing around a bit more I found this alternative which is more declarative.

Sadly Dart doesn't seem to support generic params for operators and no way of specifying associativity and precedence for them either if I'm not mistaken.

extension SurroundParserExtension<T> on Parser<T> {
  Parser<dynamic> operator >>(Parser<dynamic> other) =>
      [this, other].toSequenceParser().pick(1);

  Parser<T> operator <<(Parser<void> other) =>
      [this, other].toSequenceParser().pick(0).cast<T>();
}

final Parser<int> num = digit().map((value) => int.parse(value));
final exampleParser = char('(') >> (num << char(')'));
final typedParser = exampleParser.cast<int>();


DanielCardonaRojas avatar Dec 29 '21 14:12 DanielCardonaRojas