Ichor (Ichor v0.2.1)

Copy Markdown View Source

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

There are three genuinely different ways to go from grammar text to a running parser: __using__/1 below (compile-time codegen, rerun on every mix compile), Mix.Tasks.Ichor.Gen (the same codegen, run once ahead of time to a checked-in .ex file -- the recommended default for anything shipping to production, since it's the only one of the three where ichor itself never needs to be present at runtime, mix release builds included), and Grammar.VM called directly on an Aether.Parser.parse/2 + Grammar.Analysis.run/1 result (no codegen at all, for a grammar not known until your own program is already running). See the tutorial's "Which path is right for you?" section for a full worked comparison of all three.

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

generate(source, file, actions_module)

@spec generate(String.t(), String.t() | nil, module()) :: Macro.t()

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

generate_from_grammar(grammar, actions_module)

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