defmodule Retex.Node.PNode do
@moduledoc """
Production node. This is like a production node in Rete algorithm. It is activated if all
the conditions in a rule are matching and contains the action that can be executed as consequence.
"""
defstruct action: nil, id: nil, raw_action: nil, bindings: %{}, filters: []
@type t() :: %__MODULE__{}
def new(action, filters \\ []) do
item = %__MODULE__{action: action, raw_action: action, filters: filters}
%{item | id: Retex.hash(item)}
end
defimpl Retex.Protocol.Activation do
def activate(
%{filters: filters} = neighbor,
%Retex{tokens: tokens, graph: graph, activations: activations} = rete,
_wme,
_bindings,
_tokens
) do
[parent] =
parents =
Graph.in_neighbors(graph, neighbor)
|> Enum.filter(fn node ->
Map.get(activations, node.id)
end)
tokens = Map.get(tokens, parent.id)
if Enum.all?(parents, &Map.get(activations, &1.id)) do
bindings = tokens |> Enum.reduce(%{}, fn t, acc -> Map.merge(acc, t.bindings) end)
productions = apply_filters([Retex.replace_bindings(neighbor, bindings)], filters)
new_rete = %{
rete
| agenda: [productions | rete.agenda] |> List.flatten() |> Enum.uniq()
}
Retex.stop_traversal(new_rete, %{})
else
Retex.stop_traversal(rete, %{})
end
end
def active?(_, _) do
raise "Not implemented"
end
def apply_filters(nodes, filters) do
Enum.filter(nodes, fn node -> pass_test?(filters, node) end)
end
defp pass_test?(filters, node) do
Enum.reduce_while(filters, true, fn filter, _ ->
if test_pass?(node, filter), do: {:cont, true}, else: {:halt, false}
end)
end
def test_pass?(%Retex.Node.PNode{bindings: bindings}, %Retex.Fact.Filter{
predicate: predicate,
value: value,
variable: variable
}) do
case Map.get(bindings, variable, :_undefined) do
:_undefined -> true
current_value -> apply(Kernel, predicate, [current_value, value])
end
end
end
end