parsica
parsica copied to clipboard
Feature idea: collect a map intead of a list
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.