treeOf<Tok, Syn> function

Parser<ParseError, GreenNode<Tok, Syn>> treeOf<Tok, Syn>(
  1. Syn kind,
  2. List<Parser<ParseError, GreenNode<Tok, Syn>>> parts
)

Compose child green-producing parsers into a GreenTree of kind kind.

The child parsers run in sequence; all must succeed (or recover to a Result.Partial) for the composed parser to produce a tree. Their green results become the tree's children, in the order given.

This is the combinator form of Rowan's GreenNodeBuilder.start_node / finish_node pair — a grammar author writes

final array = treeOf(JsonSyn.array, [
  lbracket,                  // Parser<ParseError, JsonGreen>
  element.sepBy(comma),      // ... flattened into the children
  rbracket,
]);

rather than threading ~/.map tuple plumbing. Each part yields exactly one green; to splice a variable number of children (e.g. a separated list) into the same parent, have that part yield a wrapper green, or compose the list at the green level before calling treeOf.

Implementation: a left fold over parts via flatMap, accumulating each child green into a fresh list per run (the defer gives every parse its own accumulator), terminated by a map that wraps the collected children. No new Parser ADT case — composes the existing combinators. The fold chain length is the static child count at one tree level, not input depth, so the existing trampoline handles runtime nesting.

Implementation

Parser<ParseError, GreenNode<Tok, Syn>> treeOf<Tok, Syn>(
  Syn kind,
  List<Parser<ParseError, GreenNode<Tok, Syn>>> parts,
) => defer(() {
  final collected = <GreenNode<Tok, Syn>>[];
  Parser<ParseError, void> chain = succeed<ParseError, void>(null);
  for (final part in parts) {
    // Each part runs for its side effect of appending to `collected`;
    // its value is discarded by the void cast and thenSkip.
    chain = chain.thenSkip(part.map<void>(collected.add));
  }
  return chain.map(
    (_) => GreenTree<Tok, Syn>(kind, List<GreenNode<Tok, Syn>>.of(collected)),
  );
});