Turns an ABNF/BNF/ISO-EBNF/PEG ruleset (as Ichor.ABNF/Ichor.BNF/
Ichor.EBNF.ISO/Ichor.PEG produce them -- %{rule_name_atom => Grammar.IR.expr()}, explicitly documented as "not a runnable
Aether.Grammar") into one that actually is: picks a root (the
source's own first-declared rule, unless overridden), assembles a
full %Aether.Grammar{}, and (for ABNF specifically) fills in RFC
5234 Appendix B's implicit "core rules" (ALPHA, DIGIT, CRLF,
...) for anything the source references but never defines itself --
extremely common in real ABNF, which generally assumes those are
always available.
Mix.Tasks.Ichor.Gen's own entry point for a grammar whose source
isn't .aether text at all; nothing here is native-Aether-specific,
so it's equally usable standalone by anything else that wants a
runnable grammar out of one of these formats.
None of ABNF, ISO EBNF, or PEG (as specified, and as this project's
own importers implement them) have Aether's own named-capture syntax
(name:expr) or its @skip auto-splicing convenience -- both are
Aether-specific authoring conveniences, not properties of the target
formats -- so every assembled grammar is @noskip (whitespace
tolerance has to already be written explicitly into the imported
rules, matching how real ABNF/EBNF/PEG source is actually written),
and an Ichor.Actions module written against one only ever sees the
default fallback shape (Ichor.Node/passthrough), never a named
capture.
Summary
Functions
Builds a grammar from ruleset, root (an atom naming the entry
rule), and token_names (the subset of ruleset keys that are
lexical/terminal -- everything else becomes a parser rule; [] is a
perfectly good default, since every bare Literal/CharClass/Any
leaf inside a designated rule still gets auto-promoted into its own
synthetic token below, exactly as Aether.Parser already does for
hand-written Aether source -- pre-declaring a name as a token only
matters if you want it to read as one by that name in generated
code, not for correctness). @noskip always -- see this module's own
moduledoc for why.
Imports source (written in style) into a runnable %Aether.Grammar{}.
The style names import_grammar/3/Mix.Tasks.Ichor.Gen accept, matched against --style/@style/a file extension.
Types
Functions
@spec assemble(%{required(atom()) => Grammar.IR.expr()}, atom(), [atom()]) :: Aether.Grammar.t()
Builds a grammar from ruleset, root (an atom naming the entry
rule), and token_names (the subset of ruleset keys that are
lexical/terminal -- everything else becomes a parser rule; [] is a
perfectly good default, since every bare Literal/CharClass/Any
leaf inside a designated rule still gets auto-promoted into its own
synthetic token below, exactly as Aether.Parser already does for
hand-written Aether source -- pre-declaring a name as a token only
matters if you want it to read as one by that name in generated
code, not for correctness). @noskip always -- see this module's own
moduledoc for why.
@spec import_grammar(style(), String.t(), keyword()) :: {:ok, Aether.Grammar.t()} | {:error, Ichor.Error.t() | [Ichor.Error.t()]}
Imports source (written in style) into a runnable %Aether.Grammar{}.
opts:
:root-- the entry rule's name (an atom; matched against the ruleset's own key casing -- e.g.:foo, never:"Foo"). Defaults to the first rulesourceitself declares.
Returns {:error, Ichor.Error.t()} if source fails to parse, if it
declares no rules at all and no :root override was given, or if an
explicit :root names a rule the ruleset doesn't actually have.
@spec styles() :: [style()]
The style names import_grammar/3/Mix.Tasks.Ichor.Gen accept, matched against --style/@style/a file extension.