bread-n-butter
bread-n-butter copied to clipboard
API Proposal: parser.flatten
It's very easy to create parsers which generates recursively nested values. When writing a lexer, we'd expect the output to be a flat (1D) array of tokens (or bnb.ParseNodes). So I find myself constantly mapping the parse result with a flatten function:
type Node = bnb.ParseNode<string, string>;
type NodeOrArrayOfNode = Node | NodeOrArrayOfNode[];
function flatten(nodes: NodeOrArrayOfNode) {
const result: Node[] = [];
function addNode(node: NodeOrArrayOfNode) {
if (Array.isArray(node)) {
for (const innerNode of node) {
addNode(innerNode);
}
} else {
result.push(node);
}
}
addNode(nodes);
return result;
}
// usage:
const parser = bnb
.text('a')
.node('a')
.and(bnb.text('b').node('b'))
.or(bnb.text('c').node('c')) // => [bnb.ParseNode<"a", "a">, bnb.ParseNode<"b", "b">] | bnb.ParseNode<"c", "c">
.map(flatten); // => bnb.ParseNode<string, string>[]
It would be great if this mechanism is provided as a method of bnb.Parser.
This also helps make sense of the inferred type information, otherwise it would quickly collapse into a pile of gibberish (for instance, in the example above, for such a simple parser, the type yielded before the map call is already not quite readable).
Could you give me a more complete example? I haven't really found myself wanting this
e.g.
// without flatten
bnb.text('1').and(bnb.text('2')).and(bnb.text('3')).parse('123')
// => [["1","2"],"3"]
// the nested array is obviously hard to deal with
// with flatten
bnb.text('1').and(bnb.text('2')).and(bnb.text('3')).flatten().parse('123')
// => ['1', '2', '3']
Yeah, so repeated and should be replaced with all instead. Maybe that could be more obvious in the docs? all links to and but and doesn't link to all