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