Ichor.GrammarImport (Ichor v0.2.0)

Copy Markdown View Source

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

style()

@type style() :: :abnf | :bnf | :ebnf_iso | :peg

Functions

assemble(ruleset, root, token_names)

@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.

import_grammar(style, source, opts \\ [])

@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 rule source itself 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.

styles()

@spec styles() :: [style()]

The style names import_grammar/3/Mix.Tasks.Ichor.Gen accept, matched against --style/@style/a file extension.