Parser
The parser is scannerless and top-down: it matches characters and builds the AST in the same pass. There is no token stream in between, and no token list to collect — a charseq action is the nearest thing to a token, and it is already a node in the tree.
The parser { } block is itself syntac: every run of it produces a root
node, and those roots are the whole tree. result.nodes is the list of them,
in the order they were parsed.
The parser block names the start grammar — the single grammar the parser begins from. Parsing runs it from the front of the input; when it returns a match and text remains, the parser runs it again from where it left off, and so on until the input is consumed or a run matches nothing. Each run is one top-level block. There is exactly one start grammar.
parser {
main_grammar; # the single start grammar (a syntac/charseq action, an option, a series, or any unit)
};
The start grammar can be a named action:
parser {
my_action;
};
an option:
parser {
"A" | "B" | "C";
};
any single unit:
parser {
"A";
};
or a series:
parser {
"A" "B" "C" "D";
};
Repetition is the loop; separators are .
Because the parser re-runs the start grammar over what remains, a grammar of adjacent items needs no counter — the loop walks them:
parser {
item; # the loop repeats it: item item item ...
};
The loop does not skip anything between runs. Inbetween is the . building block — it runs the configured skip only where . appears in the grammar. So when items are separated (e.g. by spaces), put . where the separator goes:
parser {
item (. item)*; # items separated by the inbetween
};
. { spc };