Current section
Files
Jump to
Current section
Files
lib/wongi/engine/beta/join.ex
# credo:disable-for-this-file Credo.Check.Refactor.Nesting
defmodule Wongi.Engine.Beta.Join do
@moduledoc false
alias Wongi.Engine.Beta
alias Wongi.Engine.Filter
alias Wongi.Engine.Token
alias Wongi.Engine.WME
@derive Inspect
defstruct [:ref, :parent_ref, :template, :tests, :assignments, :filters]
def new(parent_ref, template, tests, assignments, opts \\ []) do
%__MODULE__{
ref: make_ref(),
parent_ref: parent_ref,
template: template,
tests: tests,
assignments: assignments,
filters: opts[:when]
}
end
def match(%__MODULE__{tests: tests, assignments: assignments}, token, wme) do
[:subject, :predicate, :object]
|> Enum.reduce_while({:ok, %{}}, fn field, {:ok, new_assignments} ->
case Map.fetch(tests, field) do
{:ok, var} ->
value = wme[field]
case Token.fetch(token, var, new_assignments) do
{:ok, ^value} ->
{:cont, {:ok, assign(new_assignments, field, value, assignments)}}
_ ->
{:halt, :error}
end
:error ->
{:cont, {:ok, assign(new_assignments, field, wme, assignments)}}
end
end)
end
defp assign(acc, field, %WME{} = wme, assignment_decls) do
assign(acc, field, wme[field], assignment_decls)
end
defp assign(acc, field, value, assignment_decls) do
case Map.fetch(assignment_decls, field) do
{:ok, var} ->
Map.put(acc, var, value)
:error ->
acc
end
end
def specialize(%__MODULE__{template: template, tests: tests}, token) do
tests
|> Enum.reduce(template, fn {field, var}, template ->
case Token.fetch(token, var) do
{:ok, value} ->
Map.put(template, field, value)
:error ->
template
end
end)
end
def filter_pass?(%__MODULE__{filters: nil}, _), do: true
def filter_pass?(%__MODULE__{filters: filters}, token), do: filter_pass?(filters, token)
def filter_pass?(filters, token) when is_list(filters),
do: Enum.all?(filters, &filter_pass?(&1, token))
def filter_pass?(filter, token), do: Filter.pass?(filter, token)
defimpl Beta do
alias Wongi.Engine.Beta.Common
alias Wongi.Engine.Rete
def ref(%@for{ref: ref}) do
ref
end
def parent_refs(%@for{parent_ref: parent_ref}) do
[parent_ref]
end
def seed(%@for{template: template} = join, beta, rete) do
wmes = Rete.select(rete, template)
tokens = Rete.tokens(rete, join)
Enum.reduce(wmes, rete, fn wme, rete ->
Enum.reduce(tokens, rete, &propagate_matching(join, &1, wme, [beta], &2))
end)
end
def equivalent?(
%@for{parent_ref: r, template: t, tests: ts, assignments: a, filters: f},
%@for{parent_ref: r, template: t, tests: ts, assignments: a, filters: f},
_rete
),
do: true
def equivalent?(_, _, _), do: false
def alpha_activate(join, wme, rete) do
betas = Rete.beta_subscriptions(rete, join)
tokens =
rete
|> Rete.tokens(join)
|> Enum.reject(&Token.ancestral_wme?(&1, wme))
Enum.reduce(tokens, rete, &propagate_matching(join, &1, wme, betas, &2))
end
def alpha_deactivate(%@for{} = join, wme, rete) do
rete
|> Rete.beta_subscriptions(join)
|> Common.beta_deactivate(wme, rete)
end
def beta_activate(%@for{} = join, token, rete) do
# return early if already has a duplicate token?
# is it possible or some artifact of the ruby impl?
rete = Rete.add_token(rete, token)
betas = Rete.beta_subscriptions(rete, join)
wmes = Rete.select(rete, @for.specialize(join, token))
Enum.reduce(wmes, rete, &propagate_matching(join, token, &1, betas, &2))
end
def beta_deactivate(join, token, rete) do
rete =
rete
|> Rete.remove_token(token)
rete
|> Rete.beta_subscriptions(join)
|> Common.beta_deactivate(token, rete)
end
defp propagate_matching(join, token, wme, betas, rete) do
case @for.match(join, token, wme) do
{:ok, assignments} ->
token_ctor = fn beta ->
token = Token.new(beta, [token], wme, assignments)
if @for.filter_pass?(join, token) do
token
end
end
Common.beta_activate(betas, token_ctor, rete)
_ ->
rete
end
end
end
end