The intermediate representation (IR) shared by every compile phase of Rete.
Internal. Produced by Rete.DSL.Parser, refined in place by the later phases, and
escaped into the defining module for the network builder to read at runtime.
quoted DSL
|> Rete.DSL.Parser.parse_production/4 # AST -> IR
|> Rete.DSL.Normalize.normalize_lhs/1 # gates -> conditions
|> Rete.Compiler.Sort.sort/1 # topological condition order
|> Rete.DSL.Bindings.classify/2 # join keys, guard splitting
|> Rete.IR.escape/1 # emitted into the ruleset module
|> Rete.Compiler.build/2 # the network, at build timeEvery phase consumes and produces %Rete.IR.Production{}, so a struct carries fields a
later phase fills in. Each struct records which are nil after parsing.
:__ast__ holds the raw quoted fragments the later phases need. It is compile-time
only, and escape/1 drops it so quoted AST never reaches the compiled module.
A condition's :type is the declared fact type. It is never a runtime check baked
into the alpha expression, which matches a fact of any type on purpose. The alpha index
applies the taxonomy. See docs/design/ir.md §2.
Summary
Functions
All variables a condition makes visible downstream.
Turns a parsed production into quoted code that rebuilds it inside the defining module.
The {code, fun} pairs of every expression of a production.
Every Rete.IR.Expr reachable from a production or condition, in LHS order.
The variables a classified LHS makes visible to the right hand side, split
into {guaranteed, optional}.
The name of the function a production's right hand side compiles to.
Types
@type condition() :: Rete.IR.Fact.t() | Rete.IR.Coll.t() | Rete.IR.Test.t() | Rete.IR.Gate.t() | Rete.IR.Negation.t() | Rete.IR.CompoundNegation.t()
A single LHS condition.
One element of a left hand side.
Either a single condition or a disjunction of conjunctions. A branch is itself a list of elements, so branches may nest.
@type lhs() :: [element()]
The left hand side of a production.
An ordered list, never flattened to DNF, which explodes combinatorially. A
disjunction fans out from the current parents and re-converges before the next element.
The parser emits only plain conditions and Rete.IR.Gate placeholders.
Functions
All variables a condition makes visible downstream.
:bind plus the fact or collection binding. A Rete.IR.Test and a negation bind
nothing.
iex> Rete.IR.bound_vars(%Rete.IR.Fact{bind: [:id], fact_binding: :f})
[:id, :f]
iex> Rete.IR.bound_vars(%Rete.IR.Negation{condition: %Rete.IR.Fact{bind: [:id]}})
[]
@spec escape(Rete.IR.Production.t()) :: Macro.t()
Turns a parsed production into quoted code that rebuilds it inside the defining module.
Splice the result into the module body after the expression functions are defined,
because it captures them by name. :__ast__ is dropped, :fun and :rhs become
Function.capture/3 calls, and :opts is kept unescaped so option values are
evaluated in the module scope.
@spec expr_data(Rete.IR.Production.t()) :: [{atom(), (... -> any())}]
The {code, fun} pairs of every expression of a production.
@spec exprs(Rete.IR.Production.t() | element()) :: [Rete.IR.Expr.t()]
Every Rete.IR.Expr reachable from a production or condition, in LHS order.
An alpha comes before the join filter of the same condition. Not deduplicated: use
Enum.uniq_by(& &1.name) when emitting functions.
The variables a classified LHS makes visible to the right hand side, split
into {guaranteed, optional}.
Both lists are sorted and disjoint, and together they are the production's :bind. A
guaranteed binding is in every token that reaches the RHS. An optional one is
bound on some branch of a disjunction but not all, so the RHS reads it with Map.get/2.
Run this on the classified LHS. On a raw parsed one it answers with the union of a
Rete.IR.Gate's arguments, the over-approximation it exists to avoid.
iex> alias Rete.IR
iex> user = %IR.Fact{bind: [:id]}
iex> admin = %IR.Fact{bind: [:level]}
iex> IR.lhs_bindings([{:or, [[user], [admin]]}])
{[], [:id, :level]}
The name of the function a production's right hand side compiles to.
iex> Rete.IR.rhs_name(:loyalty)
:__rhs_loyalty__Deliberately not the production's own name. A query is called by name, so the name has to stay free for the arity 2 function that runs it.