Ichor.Actions behaviour (IchorRuntime v0.1.0)

Copy Markdown View Source

How a parsed Aether AST turns into an actual result -- a sandboxed program's final value, a config's map/struct, a query's result set, or transpiled output. One mechanism, used differently per grammar: a grammar's Actions module only implements the rules/tokens it needs custom behavior for (@optional_callbacks); everything else falls back to the defaults below.

handle_rule/handle_token are never invoked directly by user code -- Grammar.VM builds the raw capture tree during matching, and this module's evaluate/5 walks it, calling into the actions module (or the default fallback) as it goes, threading context throughout.

evaluate_node/3 is the companion entry point for a raw capture node that didn't come from the original parse -- a grammar with macro-like features (LISP's own defmacro/expansion is the reference case) needs to evaluate the result of its own expansion through this same handle_rule/handle_token dispatch, not skip it. Not Lisp-specific in principle: any grammar with macro-like features needs this same re-entry point. Genuinely a runtime concern (called from generated/hand-written Actions code while actually running a parse), unlike Ichor.generate/3/Ichor.__using__/1, which only ever run at compile time -- that split is exactly why this lives here, in ichor_runtime, rather than alongside those two in ichor proper.

Summary

Functions

Eagerly invokes every capture's eval, in order, threading context through -- the plain bottom-up fold that's correct for grammars with no special-form semantics (most config/markup/query grammars, as opposed to something like LISP's if/quote, which need to not evaluate an untaken branch at all). A list-valued capture (the same name captured more than once, e.g. inside a *) resolves to a list of values in the same order. Halts and returns immediately on the first error.

The single entry point tying Grammar.VM's raw capture tree to a grammar's Actions module: evaluates the root rule, then runs finalize/1 over whatever context that produced. Returns the final context alongside the value -- needed whenever more than one top-level match must thread context forward (e.g. loading a multi-form source file one top-level form at a time); callers that only need the value can just ignore it.

Evaluates a raw capture node (as found on any Ichor.Capture.node, or built directly by something like a macro's unreify) against actions_module, starting from context. See this module's own moduledoc for when you need this instead of an ordinary .eval thunk.

Types

capture_shapes()

@type capture_shapes() :: %{optional(atom()) => MapSet.t(atom())}

captures()

@type captures() :: %{optional(atom()) => Ichor.Capture.t() | [Ichor.Capture.t()]}

context()

@type context() :: term()

Callbacks

finalize(context)

(optional)
@callback finalize(context :: context()) :: :ok | {:error, [Ichor.Error.t()]}

handle_rule(rule, captures, context)

(optional)
@callback handle_rule(rule :: atom(), captures :: captures(), context :: context()) ::
  {:ok, term(), context()} | {:error, Ichor.Error.t()}

handle_token(token, text, context)

(optional)
@callback handle_token(token :: atom(), text :: String.t(), context :: context()) ::
  {:ok, term()} | {:error, Ichor.Error.t()}

Functions

eval_all(captures, context)

@spec eval_all(captures(), context()) ::
  {:ok, %{optional(atom()) => term()}, context()} | {:error, Ichor.Error.t()}

Eagerly invokes every capture's eval, in order, threading context through -- the plain bottom-up fold that's correct for grammars with no special-form semantics (most config/markup/query grammars, as opposed to something like LISP's if/quote, which need to not evaluate an untaken branch at all). A list-valued capture (the same name captured more than once, e.g. inside a *) resolves to a list of values in the same order. Halts and returns immediately on the first error.

evaluate(root_rule, raw_captures, actions_module, initial_context, capture_shapes \\ %{})

@spec evaluate(atom(), map(), module(), context(), capture_shapes()) ::
  {:ok, term(), context()} | {:error, Ichor.Error.t() | [Ichor.Error.t()]}

The single entry point tying Grammar.VM's raw capture tree to a grammar's Actions module: evaluates the root rule, then runs finalize/1 over whatever context that produced. Returns the final context alongside the value -- needed whenever more than one top-level match must thread context forward (e.g. loading a multi-form source file one top-level form at a time); callers that only need the value can just ignore it.

capture_shapes (from Grammar.VM.RuleCompiler.capture_shapes/1) says, per rule, which capture names sit under a */+/{n,m} -- needed so e.g. an op: that matched zero times still shows up as [], not as a missing key indistinguishable from "this rule shape never has an :op" (see that function's docs for why this can't just be inferred from the raw tree at dispatch time).

evaluate_node(arg, actions_module, context)

@spec evaluate_node(Ichor.Capture.node_t(), module(), context()) ::
  {:ok, term(), context()} | {:error, Ichor.Error.t()}

Evaluates a raw capture node (as found on any Ichor.Capture.node, or built directly by something like a macro's unreify) against actions_module, starting from context. See this module's own moduledoc for when you need this instead of an ordinary .eval thunk.