Ichor.GrammarImport (Ichor v0.7.2)

Copy Markdown View Source

Turns an ABNF/BNF/ISO-EBNF/W3C-EBNF/PEG ruleset (as Ichor.ABNF/ Ichor.BNF/Ichor.EBNF.ISO/Ichor.EBNF.W3C/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{}.

Which notation source, read from path, is written in, and the source to hand that notation's reader.

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 | :ebnf_w3c | :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.

styled(source, path)

@spec styled(String.t(), Path.t()) ::
  {:ok, style() | :aether, String.t()} | {:error, Ichor.Error.t()}

Which notation source, read from path, is written in, and the source to hand that notation's reader.

An @style line wins over the extension (@style abnf, bnf, ebnf, peg or aether), and is taken out of the source, since it is valid syntax in none of them. Without one, the extension says: .abnf, .bnf, .ebnf or .iso.ebnf (ISO), .w3c.ebnf (W3C), .peg, and Aether for .aether or anything else.

One definition for every reader of a grammar file -- mix ichor.gen and the Ichor Conformance Suite's adapter alike -- so the two can never disagree about what a file is.

iex> Ichor.GrammarImport.styled("a <- 'a'", "g.peg")
{:ok, :peg, "a <- 'a'"}

styles()

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

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