Current section
Files
Jump to
Current section
Files
lib/wongi/engine/beta/negative.ex
# credo:disable-for-this-file Credo.Check.Refactor.Nesting
defmodule Wongi.Engine.Beta.Negative do
@moduledoc false
alias Wongi.Engine.Token
alias Wongi.Engine.WME
@derive Inspect
defstruct [:ref, :parent_ref, :template, :tests, :assignments]
def new(parent_ref, template, tests, assignments) do
%__MODULE__{
ref: make_ref(),
parent_ref: parent_ref,
template: template,
tests: tests,
assignments: assignments
}
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)
|> case do
{:ok, _} -> true
:error -> false
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
defimpl Wongi.Engine.Beta do
alias Wongi.Engine.Beta
alias Wongi.Engine.Beta.Common
alias Wongi.Engine.Rete
def ref(%@for{ref: ref}), do: ref
def parent_refs(%@for{parent_ref: parent_ref}), do: [parent_ref]
def seed(%@for{template: template} = neg, beta, rete) do
tokens = Rete.tokens(rete, neg)
wmes = Rete.select(rete, template)
rete =
Enum.reduce(tokens, rete, fn token, rete ->
if Enum.empty?(Rete.neg_join_results(rete, token)) do
Beta.beta_activate(beta, Token.new(beta, [token], nil), rete)
else
rete
end
end)
Enum.reduce(wmes, rete, fn wme, rete ->
alpha_activate(neg, wme, rete, betas: [beta])
end)
end
def equivalent?(
%@for{parent_ref: r, template: t, tests: ts},
%@for{parent_ref: r, template: t, tests: ts},
_rete
),
do: true
def equivalent?(_, _, _), do: false
def alpha_activate(neg, wme, rete),
do: alpha_activate(neg, wme, rete, betas: Rete.beta_subscriptions(rete, neg))
defp alpha_activate(neg, wme, rete, betas: betas) do
tokens =
rete
|> Rete.tokens(neg)
|> Enum.reject(&Token.ancestral_wme?(&1, wme))
Enum.reduce(tokens, rete, fn token, rete ->
if @for.match(neg, token, wme) do
rete =
rete
|> Rete.add_neg_join_result(token, wme)
Common.beta_deactivate(betas, token, rete)
else
rete
end
end)
end
def alpha_deactivate(neg, wme, rete) do
Rete.neg_join_results(rete, wme)
|> Enum.reduce(rete, fn {token, wme}, rete ->
Rete.tokens(rete, neg)
|> Enum.filter(fn t -> t == token end)
|> Enum.reduce(rete, fn token, rete ->
rete = Rete.remove_neg_join_result(rete, token, wme)
if Enum.empty?(Rete.neg_join_results(rete, token)) do
Rete.beta_subscriptions(rete, neg)
|> Enum.reduce(rete, fn beta, rete ->
Beta.beta_activate(beta, Token.new(beta, [token], nil), rete)
end)
else
rete
end
end)
end)
end
def beta_activate(neg, 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)
wmes = Rete.select(rete, @for.specialize(neg, token))
rete =
Enum.reduce(wmes, rete, fn wme, rete ->
if @for.match(neg, token, wme) do
Rete.add_neg_join_result(rete, token, wme)
else
rete
end
end)
if Enum.empty?(Rete.neg_join_results(rete, token)) do
betas = Rete.beta_subscriptions(rete, neg)
Enum.reduce(betas, rete, fn beta, rete ->
Beta.beta_activate(beta, Token.new(beta, [token], nil), rete)
end)
else
rete
end
end
def beta_deactivate(neg, token, rete) do
rete =
rete
|> Rete.remove_token(token)
rete
|> Rete.beta_subscriptions(neg)
|> Common.beta_deactivate(token, rete)
end
end
end