defmodule CPSolver.Constraint do
alias CPSolver.Variable.Interface
alias CPSolver.Propagator
@callback new(args :: list()) :: Constraint.t()
@callback propagators(args :: list()) :: [atom()]
@callback arguments(args :: list()) :: list()
defmacro __using__(_) do
quote do
@behaviour CPSolver.Constraint
alias CPSolver.Constraint
alias CPSolver.Common
def new(args) do
Constraint.new(__MODULE__, arguments(args))
end
def arguments(args) do
args
end
defoverridable new: 1, arguments: 1
end
end
def new(constraint_impl, args) do
(Enum.empty?(args) && throw({constraint_impl, :no_args})) ||
{constraint_impl, args}
end
def constraint_to_propagators(constraint, reducer_fun \\ &Function.identity/1)
def constraint_to_propagators({constraint_mod, args}, reducer_fun) when is_list(args) do
List.foldr(constraint_mod.propagators(args), [], fn p, plist_acc ->
case reducer_fun.(p) do
nil -> plist_acc
p_result ->
[p_result | plist_acc]
end
end)
end
def constraint_to_propagators(constraint, reducer_fun) when is_tuple(constraint) do
[constraint_mod | args] = Tuple.to_list(constraint)
constraint_to_propagators({constraint_mod, args}, reducer_fun)
end
def post(constraint) when is_tuple(constraint) do
constraint_to_propagators(constraint,
fn p ->
case Propagator.filter(p, reset?: true, changes: %{}) do
:fail -> throw({:fail, p.id})
%{state: _state, active?: true, changes: _changes} -> p
%{active?: false} -> nil
end
end)
end
def extract_variables(constraint) do
constraint
|> constraint_to_propagators()
|> Enum.map(fn p ->
p
|> Propagator.variables()
|> Enum.map(fn var -> Interface.variable(var) end)
end)
|> List.flatten()
|> Enum.uniq()
end
end