# `Ichor.EBNF.W3C.Actions`
[🔗](https://github.com/joetjen/ichor/blob/v0.3.0/lib/ichor/ebnf/w3c_actions.ex#L1)

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.

---

*Consult [api-reference.md](api-reference.md) for complete listing*
