Ichor reads grammar definitions -- its own language Aether, plus ABNF,
BNF, EBNF, and PEG importers -- and turns each one into a working
Lexer + Parser + Executor for whatever language the grammar describes,
via either an interpreted VM backend (Grammar.VM) or compile-time
native codegen (__using__/1 below).
This top-level module holds __using__/1, the native codegen
backend's own entry point:
defmodule Calculator do
use Ichor, grammar: "calculator.aether", actions: Calculator.Actions
end
Calculator.run("2 + 3 * 4") #=> {:ok, 14}grammar: is a path to a .aether file, resolved relative to the
use-ing module's own source file (so a grammar file can live
alongside the module that compiles it); grammar_source: takes
literal grammar text directly instead, for callers that don't want a
separate file on disk. Exactly one of the two is required. actions:
bakes in a compile-time-known module reference -- parse/1,
tokenize/1, and run/1,2 all get added directly to the use-ing
module, via generate/3 below (dispatching further to
Grammar.Native/Grammar.Native.LR/Grammar.Native.GLR by
@engine).
generate/3 is also Mix.Tasks.Ichor.Gen's entry point: the same
parse-analyze-codegen pipeline, just called from a Mix task instead of
from macro expansion, so a grammar can be compiled to a plain,
ordinary .ex file once and checked in, instead of every use Ichor
caller re-parsing and re-analyzing the same grammar on every compile.
A grammar-with-macros implementation (anything needing to evaluate a
raw capture node that didn't come from the original parse -- LISP's
own defmacro/expansion, most notably) wants Ichor.Actions.evaluate_node/3,
not anything here: unlike generate/3/__using__/1, which are
genuinely compile-time-only, that's a runtime entry point, and lives
in ichor_runtime alongside the rest of Ichor.Actions.
Summary
Functions
Parses and analyzes source (a full .aether grammar), then
generates the quoted module body for actions_module -- whichever of
Grammar.Native, Grammar.Native.LR, or Grammar.Native.GLR the
grammar's own @engine pragma selects. file is used only for
error messages (line/column context); pass nil if source didn't
come from a file.
Like generate/3, but for a grammar that's already an %Aether.Grammar{}
-- not yet run through Grammar.Analysis -- rather than raw .aether
text. Ichor.GrammarImport is the caller: a grammar assembled from an
imported ABNF/BNF/EBNF/PEG ruleset never goes through Aether.Parser
at all, so generate/3's own parse step doesn't apply to it, but
everything after parsing (analysis, engine dispatch) is identical.
Functions
Parses and analyzes source (a full .aether grammar), then
generates the quoted module body for actions_module -- whichever of
Grammar.Native, Grammar.Native.LR, or Grammar.Native.GLR the
grammar's own @engine pragma selects. file is used only for
error messages (line/column context); pass nil if source didn't
come from a file.
Shared by __using__/1 (which splices the result straight into the
caller's module) and Mix.Tasks.Ichor.Gen (which wraps it in its own
defmodule and writes it to disk as ordinary source).
Raises CompileError if source fails to parse or fails analysis
(unresolved rule references, a non-peg engine picked for a grammar
Analysis flags, an LR/GLR grammar with unresolvable conflicts, etc.).
@spec generate_from_grammar(Aether.Grammar.t(), module()) :: Macro.t()
Like generate/3, but for a grammar that's already an %Aether.Grammar{}
-- not yet run through Grammar.Analysis -- rather than raw .aether
text. Ichor.GrammarImport is the caller: a grammar assembled from an
imported ABNF/BNF/EBNF/PEG ruleset never goes through Aether.Parser
at all, so generate/3's own parse step doesn't apply to it, but
everything after parsing (analysis, engine dispatch) is identical.
Raises CompileError on the same conditions generate/3 does, minus
the parse step.