defmodule Tilly.BDD.Node do @moduledoc """ Defines the structure of BDD nodes and provides basic helper functions. BDD nodes can be one of the following Elixir terms: - `true`: Represents the universal set BDD. - `false`: Represents the empty set BDD. - `{:leaf, leaf_value_id}`: Represents a leaf node. `leaf_value_id`'s interpretation depends on the specific BDD's `ops_module`. - `{:split, element_id, positive_child_id, ignore_child_id, negative_child_id}`: Represents an internal decision node. `element_id` is the value being split upon. `positive_child_id`, `ignore_child_id`, `negative_child_id` are IDs of other BDD nodes. """ @typedoc "A BDD node representing the universal set." @type true_node :: true @typedoc "A BDD node representing the empty set." @type false_node :: false @typedoc "A BDD leaf node." @type leaf_node(leaf_value) :: {:leaf, leaf_value} @typedoc "A BDD split node." @type split_node(element, node_id) :: {:split, element, node_id, node_id, node_id} @typedoc "Any valid BDD node structure." @type t(element, leaf_value, node_id) :: true_node() | false_node() | leaf_node(leaf_value) | split_node(element, node_id) # --- Smart Constructors (Low-Level) --- @doc "Creates a true BDD node." @spec mk_true() :: true_node() def mk_true, do: true @doc "Creates a false BDD node." @spec mk_false() :: false_node() def mk_false, do: false @doc "Creates a leaf BDD node." @spec mk_leaf(leaf_value :: any()) :: leaf_node(any()) def mk_leaf(leaf_value_id), do: {:leaf, leaf_value_id} @doc "Creates a split BDD node." @spec mk_split( element_id :: any(), positive_child_id :: any(), ignore_child_id :: any(), negative_child_id :: any() ) :: split_node(any(), any()) def mk_split(element_id, positive_child_id, ignore_child_id, negative_child_id) do {:split, element_id, positive_child_id, ignore_child_id, negative_child_id} end # --- Predicates --- @doc "Checks if the node is a true node." @spec is_true?(node :: t(any(), any(), any())) :: boolean() def is_true?(true), do: true def is_true?(_other), do: false @doc "Checks if the node is a false node." @spec is_false?(node :: t(any(), any(), any())) :: boolean() def is_false?(false), do: true def is_false?(_other), do: false @doc "Checks if the node is a leaf node." @spec is_leaf?(node :: t(any(), any(), any())) :: boolean() def is_leaf?({:leaf, _value}), do: true def is_leaf?(_other), do: false @doc "Checks if the node is a split node." @spec is_split?(node :: t(any(), any(), any())) :: boolean() def is_split?({:split, _el, _p, _i, _n}), do: true def is_split?(_other), do: false # --- Accessors --- @doc """ Returns the value of a leaf node. Raises an error if the node is not a leaf node. """ @spec value(leaf_node :: leaf_node(any())) :: any() def value({:leaf, value_id}), do: value_id def value(other), do: raise(ArgumentError, "Not a leaf node: #{inspect(other)}") @doc """ Returns the element of a split node. Raises an error if the node is not a split node. """ @spec element(split_node :: split_node(any(), any())) :: any() def element({:split, element_id, _, _, _}), do: element_id def element(other), do: raise(ArgumentError, "Not a split node: #{inspect(other)}") @doc """ Returns the positive child ID of a split node. Raises an error if the node is not a split node. """ @spec positive_child(split_node :: split_node(any(), any())) :: any() def positive_child({:split, _, p_child_id, _, _}), do: p_child_id def positive_child(other), do: raise(ArgumentError, "Not a split node: #{inspect(other)}") @doc """ Returns the ignore child ID of a split node. Raises an error if the node is not a split node. """ @spec ignore_child(split_node :: split_node(any(), any())) :: any() def ignore_child({:split, _, _, i_child_id, _}), do: i_child_id def ignore_child(other), do: raise(ArgumentError, "Not a split node: #{inspect(other)}") @doc """ Returns the negative child ID of a split node. Raises an error if the node is not a split node. """ @spec negative_child(split_node :: split_node(any(), any())) :: any() def negative_child({:split, _, _, _, n_child_id}), do: n_child_id def negative_child(other), do: raise(ArgumentError, "Not a split node: #{inspect(other)}") end