The characters a token's match can begin with, worked out from its IR when a parser is generated.
A generated tokenizer does maximal munch: at every position it tries
every token and keeps the longest match. Most of those tries fail on
the very first character, so the generated lex_candidates/2 asks
each token's start set first (Grammar.Native.TokenizerCompiler) and
tries only the tokens that can begin with the next character.
Too much, never too little. Where a start set cannot be told
exactly -- any character (.), a @native(...) token, a token that
reaches itself again before consuming anything -- the answer is
:any, and the token is always tried. A start set that holds a
character the token cannot really begin with only costs a try; one
that missed a character would change which token wins. Only the
characters a non-empty match can begin with count: a zero-width
match never wins maximal munch.
Summary
Functions
The start set of token name, whose body and every token it refers to
are in tokens.
Types
@type t() :: {:ranges, [{non_neg_integer(), non_neg_integer()}]} | :any
Sorted, merged {lo, hi} codepoint ranges, or :any.
Functions
@spec of(%{required(atom()) => Grammar.IR.expr()}, atom()) :: t()
The start set of token name, whose body and every token it refers to
are in tokens.
iex> Grammar.FirstChars.of(%{KW: Grammar.IR.literal("let")}, :KW)
{:ranges, [{?l, ?l}]}