defmodule Retex.Node.BetaMemory do @moduledoc """ A BetaMemory works like a two input node in Rete. It is simply a join node between two tests that have passed successfully. The activation of a BetaMemory happens if the two parents (left and right) have been activated and the bindings are matching for both of them. """ defstruct id: nil @type t :: %Retex.Node.BetaMemory{} def new(id) do %__MODULE__{id: id} end defimpl Retex.Protocol.Activation do alias Retex.Protocol.Activation def activate(neighbor, rete, wme, bindings, _tokens) do [left, right] = Graph.in_neighbors(rete.graph, neighbor) with true <- Activation.active?(left, rete), true <- Activation.active?(right, rete), left_tokens <- Map.get(rete.tokens, left.id), right_tokens <- Map.get(rete.tokens, right.id), new_tokens <- matching_tokens(neighbor, wme, right_tokens, left_tokens), true <- Enum.any?(new_tokens) do rete |> Retex.create_activation(neighbor, wme) |> Retex.add_token(neighbor, wme, bindings, new_tokens) |> Retex.continue_traversal(bindings, neighbor, wme) else _anything -> Retex.stop_traversal(rete, %{}) end end defp matching_tokens(_, _, left, nil), do: left defp matching_tokens(_, _, nil, right), do: right defp matching_tokens(node, wme, left, right) do for %{bindings: left_bindings} <- left, %{bindings: right_bindings} <- right do if variables_match?(left_bindings, right_bindings) do [ %{ Retex.Token.new() | wmem: wme, node: node.id, bindings: Map.merge(left_bindings, right_bindings) } ] else [] end end |> List.flatten() end defp variables_match?(left, right) do left_bindings_match_in_right_bindings?(left, right) && right_bindings_match_in_left_bindings?(right, left) end defp left_bindings_match_in_right_bindings?(left, right) do Enum.reduce_while(left, true, fn {key, left_value}, true -> if Map.get(right, key, left_value) == left_value, do: {:cont, true}, else: {:halt, false} end) end defp right_bindings_match_in_left_bindings?(right, left) do Enum.reduce_while(right, true, fn {key, right_value}, true -> if Map.get(left, key, right_value) == right_value, do: {:cont, true}, else: {:halt, false} end) end @spec active?(%{id: any}, Retex.t()) :: boolean() def active?(%{id: id}, %Retex{activations: activations}) do Enum.any?(Map.get(activations, id, [])) end end end