The combinator half of Grammar.VM's bytecode compilation, shared
between the character-level compiler (token bodies, run by the lexer)
and the token-stream-level compiler (rule bodies, run by the parser).
Seq/Choice/Star/Plus/Opt/Rep/AndPred/NotPred compile
identically either way -- only the leaf nodes differ
(Literal/CharClass/Any/RuleRef for tokens; RuleRef/Indent/
Capture for rules -- tokens can never contain a Capture), so each
caller supplies its own leaf_fun for those.
Every compile/3 clause returns {ops, next_counter}, where ops is a
flat list of instructions possibly containing {:label, n} markers
(resolved later by Grammar.VM.Linker) and counter is the next
unused label number -- threaded through so nested compilation never
reuses a label.
The instruction set is the standard PEG/LPeg one (Ierusalimschy's
"A Text Pattern-Matching Tool based on Parsing Expression Grammars"):
choice/commit for ordered choice and *, back_commit/fail_twice
for the two lookahead predicates. Grammar.Analysis already guarantees
no Star/Plus here wraps an unconditionally-empty match at the point
a grammar author wrote it directly, but test_progress below is still
a hard runtime guarantee rather than relying on that alone -- see the
comment on the Star clause.
Summary
Types
@type leaf_fun() :: (Grammar.IR.expr(), non_neg_integer() -> {ops(), non_neg_integer()})
@type ops() :: [term()]
Functions
@spec compile(Grammar.IR.expr(), non_neg_integer(), leaf_fun()) :: {ops(), non_neg_integer()}
@spec fresh(non_neg_integer()) :: {non_neg_integer(), non_neg_integer()}