parsica icon indicating copy to clipboard operation
parsica copied to clipboard

Feature idea: collect a map intead of a list

Open matthiasnoback opened this issue 3 years ago • 0 comments

Since I use collect() to capture values that are to be fed into an AST node, it would be very helpful to use a map instead of a list. Similarly to how you can capture named groups with regular expressions. Here's an example where I parse a Markdown heading:

return collect(
            keepFirst(atLeastOne(char('#')), skipSpace1()),
            atLeastOne(satisfy(fn (string $char) => ! in_array($char, ["\n"], true))),
            self::newLineOrEof()
        )->map(fn (array $output) => new Heading(strlen($output[1]), $output[2], $output[0]));

Suggested improvement (last line)

return collect(
            keepFirst(atLeastOne(char('#')), skipSpace1()),
            atLeastOne(satisfy(fn (string $char) => ! in_array($char, ["\n"], true))),
            self::newLineOrEof()
        )->map(fn (array $output) => new Heading(strlen($output['level']), $output['title']));

One way could be to use label() for each collected parser and use the label as the array key passed to map():

return collect(
            keepFirst(atLeastOne(char('#')), skipSpace1())->label('level'),
            atLeastOne(satisfy(fn (string $char) => ! in_array($char, ["\n"], true)))->label('title'),
            self::newLineOrEof()
        )->map(fn (array $output) => new Heading(strlen($output['level']), $output['title']));

The advantage being that label() is already there and used for a similar purpose. However, this might lead to values being overwritten if you collect two parsers with the same name. Another option is to provide the map upfront:

return collect([
            'level' => keepFirst(atLeastOne(char('#')), skipSpace1()),
            'title' => atLeastOne(satisfy(fn (string $char) => ! in_array($char, ["\n"], true))),
            'eol' => self::newLineOrEof()
        ])->map(fn (array $output) => new Heading(strlen($output['level']), $output['title']));

It might be useful to have separate collectList() and collectMap() functions by the way.

matthiasnoback avatar May 25 '21 08:05 matthiasnoback