Turns a parsed W3C-style EBNF grammar_file (the notation the XML 1.0
spec's own section 6 uses, also shared by XQuery/XPath's grammars)
into a real %{ident_atom => Grammar.IR.expr()} map -- one
Grammar.IR tree per rule, the same target category every other
importer this project builds uses.
A [...] character class is matched as one token (CHAR_CLASS) and
its interior parsed as plain text here (handle_token(:CHAR_CLASS, ...)), splitting on - for ranges and decoding #xNN escapes along
the way -- exactly the kind of lexing a real character-class parser
does, rather than asking Aether's own per-character tokenizer to
agree with a rule-reference token (IDENT) about where one class
member ends and the next begins (they don't, in general: adjacent
range boundaries with no separator, like [a-zA-Z0-9]'s zA, are
genuinely ambiguous between "two single characters" and "one
multi-character identifier").
The exception operator (sequence := term (DASH term)?) gets the
same best-effort treatment as Ichor.EBNF.ISO.Actions's own: Seq([ NotPred(exception), factor]), not a true language-theoretic set
difference (Grammar.IR has no combinator for that). Unlike ISO
EBNF's term/exception (two different rule names), W3C's sequence := term (DASH term)? reuses the same rule name for both, so
whether the optional second term matched determines whether
:term's own raw capture is a single value or a two-element list --
handled explicitly below, not by Ichor.Actions's default fallback.