some progress with recursive checks
This commit is contained in:
parent
bcddae26cb
commit
658332ace1
347
new.exs
347
new.exs
@ -517,6 +517,57 @@ defmodule Tdd.Store do
|
|||||||
Process.put(@op_cache_key, Map.put(cache, cache_key, result))
|
Process.put(@op_cache_key, Map.put(cache, cache_key, result))
|
||||||
:ok
|
:ok
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Creates a unique, temporary placeholder node for a recursive spec.
|
||||||
|
Returns the ID of this placeholder.
|
||||||
|
"""
|
||||||
|
@spec create_placeholder(TypeSpec.t()) :: non_neg_integer()
|
||||||
|
def create_placeholder(spec) do
|
||||||
|
# The variable is a unique tuple that won't conflict with real predicates.
|
||||||
|
# The children are meaningless (-1) as they will be replaced.
|
||||||
|
find_or_create_node({:placeholder, spec}, -1, -1, -1)
|
||||||
|
end
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Updates a node's details directly. This is a special-purpose, mutable-style
|
||||||
|
operation used exclusively by the compiler to "tie the knot" for recursive types.
|
||||||
|
It updates both the forward and reverse lookup tables.
|
||||||
|
"""
|
||||||
|
@spec update_node_in_place(
|
||||||
|
non_neg_integer(),
|
||||||
|
new_details ::
|
||||||
|
{:ok,
|
||||||
|
{term(), non_neg_integer(), non_neg_integer(), non_neg_integer()}
|
||||||
|
| :true_terminal
|
||||||
|
| :false_terminal}
|
||||||
|
) :: :ok
|
||||||
|
def update_node_in_place(id, {:ok, new_details}) do
|
||||||
|
# Get current state
|
||||||
|
nodes = Process.get(@nodes_key)
|
||||||
|
node_by_id = Process.get(@node_by_id_key)
|
||||||
|
|
||||||
|
# 1. Find and remove the old reverse mapping from the `nodes` table.
|
||||||
|
# The node at `id` is a placeholder, so its details are what we created above.
|
||||||
|
old_details = Map.get(node_by_id, id)
|
||||||
|
nodes = Map.delete(nodes, old_details)
|
||||||
|
|
||||||
|
# 2. Add the new reverse mapping to the `nodes` table.
|
||||||
|
# But only if the new details correspond to a non-terminal node.
|
||||||
|
nodes =
|
||||||
|
case new_details do
|
||||||
|
{_v, _y, _n, _d} -> Map.put(nodes, new_details, id)
|
||||||
|
_ -> nodes
|
||||||
|
end
|
||||||
|
|
||||||
|
# 3. Update the main `node_by_id` table.
|
||||||
|
node_by_id = Map.put(node_by_id, id, new_details)
|
||||||
|
|
||||||
|
# 4. Save the updated tables.
|
||||||
|
Process.put(@nodes_key, nodes)
|
||||||
|
Process.put(@node_by_id_key, node_by_id)
|
||||||
|
:ok
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
defmodule Tdd.Variable do
|
defmodule Tdd.Variable do
|
||||||
@ -1097,10 +1148,9 @@ defmodule Tdd.Algo do
|
|||||||
current_state = {tdd_id, sorted_assumptions}
|
current_state = {tdd_id, sorted_assumptions}
|
||||||
|
|
||||||
# Coinductive base case: if we have seen this exact state before in this
|
# Coinductive base case: if we have seen this exact state before in this
|
||||||
# call stack, we are in a loop. For a least-fixed-point type (like list_of),
|
# call stack, we are in a loop.
|
||||||
# this represents an un-satisfiable recursive constraint, which is `none`.
|
|
||||||
if MapSet.member?(context, current_state) do
|
if MapSet.member?(context, current_state) do
|
||||||
Store.false_node_id()
|
Store.true_node_id()
|
||||||
else
|
else
|
||||||
new_context = MapSet.put(context, current_state)
|
new_context = MapSet.put(context, current_state)
|
||||||
assumptions = Map.new(sorted_assumptions)
|
assumptions = Map.new(sorted_assumptions)
|
||||||
@ -1169,7 +1219,50 @@ defmodule Tdd.Algo do
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@doc """
|
||||||
|
Recursively traverses a TDD graph from `root_id`, creating a new graph
|
||||||
|
where all references to `from_id` are replaced with `to_id`.
|
||||||
|
|
||||||
|
This is a pure function used to "tie the knot" in recursive type compilation.
|
||||||
|
"""
|
||||||
|
@spec substitute(root_id :: non_neg_integer(), from_id :: non_neg_integer(), to_id :: non_neg_integer()) ::
|
||||||
|
non_neg_integer()
|
||||||
|
def substitute(root_id, from_id, to_id) do
|
||||||
|
# Handle the trivial case where the root is the node to be replaced.
|
||||||
|
if root_id == from_id, do: to_id, else: do_substitute(root_id, from_id, to_id)
|
||||||
|
end
|
||||||
|
|
||||||
|
# The private helper uses memoization to avoid re-computing identical sub-graphs.
|
||||||
|
defp do_substitute(root_id, from_id, to_id) do
|
||||||
|
cache_key = {:substitute, root_id, from_id, to_id}
|
||||||
|
|
||||||
|
case Store.get_op_cache(cache_key) do
|
||||||
|
{:ok, result_id} ->
|
||||||
|
result_id
|
||||||
|
|
||||||
|
:not_found ->
|
||||||
|
result_id =
|
||||||
|
case Store.get_node(root_id) do
|
||||||
|
# Terminal nodes are unaffected unless they are the target of substitution.
|
||||||
|
{:ok, :true_terminal} -> Store.true_node_id()
|
||||||
|
{:ok, :false_terminal} -> Store.false_node_id()
|
||||||
|
|
||||||
|
# For internal nodes, recursively substitute in all children.
|
||||||
|
{:ok, {var, y, n, d}} ->
|
||||||
|
new_y = substitute(y, from_id, to_id)
|
||||||
|
new_n = substitute(n, from_id, to_id)
|
||||||
|
new_d = substitute(d, from_id, to_id)
|
||||||
|
Store.find_or_create_node(var, new_y, new_n, new_d)
|
||||||
|
|
||||||
|
# This case should not be hit if the graph is well-formed.
|
||||||
|
{:error, reason} ->
|
||||||
|
raise "substitute encountered an error getting node #{root_id}: #{reason}"
|
||||||
|
end
|
||||||
|
|
||||||
|
Store.put_op_cache(cache_key, result_id)
|
||||||
|
result_id
|
||||||
|
end
|
||||||
|
end
|
||||||
# defp do_simplify(tdd_id, assumptions) do
|
# defp do_simplify(tdd_id, assumptions) do
|
||||||
# IO.inspect([tdd_id, assumptions], label: "do_simplify(tdd_id, assumptions)")
|
# IO.inspect([tdd_id, assumptions], label: "do_simplify(tdd_id, assumptions)")
|
||||||
# # First, check if the current assumption set is already a contradiction.
|
# # First, check if the current assumption set is already a contradiction.
|
||||||
@ -1364,10 +1457,18 @@ defmodule Tdd.Compiler do
|
|||||||
alias Tdd.Variable
|
alias Tdd.Variable
|
||||||
alias Tdd.Store
|
alias Tdd.Store
|
||||||
alias Tdd.Algo
|
alias Tdd.Algo
|
||||||
|
@doc """
|
||||||
@doc "The main entry point. Takes a spec and returns its TDD ID."
|
The main public entry point. Takes a spec and returns its TDD ID.
|
||||||
|
This now delegates to a private function with a context for recursion.
|
||||||
|
"""
|
||||||
@spec spec_to_id(TypeSpec.t()) :: non_neg_integer()
|
@spec spec_to_id(TypeSpec.t()) :: non_neg_integer()
|
||||||
def spec_to_id(spec) do
|
def spec_to_id(spec) do
|
||||||
|
# Start with an empty context map.
|
||||||
|
spec_to_id(spec, %{})
|
||||||
|
end
|
||||||
|
# This is the new core compilation function with a context map.
|
||||||
|
# The context tracks `{spec => placeholder_id}` for in-progress compilations.
|
||||||
|
defp spec_to_id(spec, context) do
|
||||||
normalized_spec = TypeSpec.normalize(spec)
|
normalized_spec = TypeSpec.normalize(spec)
|
||||||
cache_key = {:spec_to_id, normalized_spec}
|
cache_key = {:spec_to_id, normalized_spec}
|
||||||
|
|
||||||
@ -1376,54 +1477,107 @@ defmodule Tdd.Compiler do
|
|||||||
id
|
id
|
||||||
|
|
||||||
:not_found ->
|
:not_found ->
|
||||||
# Do the raw compilation first.
|
# Check if we are in a recursive call for a spec we're already compiling.
|
||||||
raw_id = do_spec_to_id(normalized_spec)
|
case Map.get(context, normalized_spec) do
|
||||||
# THEN, do a final semantic simplification pass. This is the fix.
|
placeholder_id when is_integer(placeholder_id) ->
|
||||||
id = Algo.simplify(raw_id)
|
placeholder_id
|
||||||
Store.put_op_cache(cache_key, id)
|
|
||||||
id
|
nil ->
|
||||||
|
# This is a new spec. Decide which compilation path to take.
|
||||||
|
if is_recursive_spec?(normalized_spec) do
|
||||||
|
# Use the full knot-tying logic only for recursive types.
|
||||||
|
compile_recursive_spec(normalized_spec, context)
|
||||||
|
else
|
||||||
|
# Use the simple, direct path for all other types.
|
||||||
|
compile_non_recursive_spec(normalized_spec, context)
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
defp is_recursive_spec?({:list_of, _}), do: true
|
||||||
|
defp is_recursive_spec?(_), do: false
|
||||||
|
|
||||||
# This helper does the raw, structural compilation. It does NOT call simplify.
|
# NEW: The logic for simple, non-recursive types.
|
||||||
defp do_spec_to_id(spec) do
|
defp compile_non_recursive_spec(spec, context) do
|
||||||
|
# Just compile the body directly. Pass context in case it contains recursive children.
|
||||||
|
raw_id = do_spec_to_id(spec, context)
|
||||||
|
final_id = Algo.simplify(raw_id)
|
||||||
|
|
||||||
|
# Cache and return.
|
||||||
|
Store.put_op_cache({:spec_to_id, spec}, final_id)
|
||||||
|
final_id
|
||||||
|
end
|
||||||
|
|
||||||
|
# NEW: The full knot-tying logic, now isolated.
|
||||||
|
defp compile_recursive_spec(spec, context) do
|
||||||
|
# 1. Create a placeholder and update the context.
|
||||||
|
placeholder_id = Store.create_placeholder(spec)
|
||||||
|
new_context = Map.put(context, spec, placeholder_id)
|
||||||
|
|
||||||
|
# 2. Compile the body. It will be built with pointers to the placeholder.
|
||||||
|
body_id = do_spec_to_id(spec, new_context)
|
||||||
|
|
||||||
|
# 3. THIS IS THE CRUCIAL FIX:
|
||||||
|
# Instead of mutating the store, we create a NEW graph where all
|
||||||
|
# internal pointers to `placeholder_id` are replaced with `body_id`.
|
||||||
|
# This makes the graph point to its own root, creating the cycle.
|
||||||
|
final_id = Algo.substitute(body_id, placeholder_id, body_id)
|
||||||
|
|
||||||
|
# 4. Simplify the resulting (now correctly cyclic) graph.
|
||||||
|
simplified_id = Algo.simplify(final_id)
|
||||||
|
|
||||||
|
# 5. Cache the result and return it.
|
||||||
|
Store.put_op_cache({:spec_to_id, spec}, simplified_id)
|
||||||
|
simplified_id
|
||||||
|
end
|
||||||
|
# This helper does the raw, structural compilation.
|
||||||
|
# It now takes and passes down the context.
|
||||||
|
defp do_spec_to_id(spec, context) do
|
||||||
case spec do
|
case spec do
|
||||||
|
# Pass context on all recursive calls to spec_to_id/2
|
||||||
:any -> Store.true_node_id()
|
:any -> Store.true_node_id()
|
||||||
:none -> Store.false_node_id()
|
:none -> Store.false_node_id()
|
||||||
:atom -> create_base_type_tdd(Variable.v_is_atom())
|
:atom -> create_base_type_tdd(Variable.v_is_atom())
|
||||||
:integer -> create_base_type_tdd(Variable.v_is_integer())
|
:integer -> create_base_type_tdd(Variable.v_is_integer())
|
||||||
:list -> create_base_type_tdd(Variable.v_is_list())
|
:list -> create_base_type_tdd(Variable.v_is_list())
|
||||||
:tuple -> create_base_type_tdd(Variable.v_is_tuple())
|
:tuple -> create_base_type_tdd(Variable.v_is_tuple())
|
||||||
{:literal, val} when is_atom(val) -> compile_value_equality(:atom, Variable.v_atom_eq(val))
|
{:literal, val} when is_atom(val) -> compile_value_equality(:atom, Variable.v_atom_eq(val), context)
|
||||||
{:literal, val} when is_integer(val) -> compile_value_equality(:integer, Variable.v_int_eq(val))
|
{:literal, val} when is_integer(val) -> compile_value_equality(:integer, Variable.v_int_eq(val), context)
|
||||||
{:literal, []} -> compile_value_equality(:list, Variable.v_list_is_empty())
|
{:literal, []} -> compile_value_equality(:list, Variable.v_list_is_empty(), context)
|
||||||
{:integer_range, min, max} -> compile_integer_range(min, max)
|
{:integer_range, min, max} -> compile_integer_range(min, max, context)
|
||||||
|
|
||||||
{:union, specs} ->
|
{:union, specs} ->
|
||||||
Enum.map(specs, &spec_to_id/1)
|
Enum.map(specs, &spec_to_id(&1, context))
|
||||||
|> Enum.reduce(Store.false_node_id(), fn id, acc ->
|
|> Enum.reduce(Store.false_node_id(), fn id, acc ->
|
||||||
Algo.apply(:sum, &op_union_terminals/2, id, acc)
|
Algo.apply(:sum, &op_union_terminals/2, id, acc)
|
||||||
end)
|
end)
|
||||||
|
|
||||||
{:intersect, specs} ->
|
{:intersect, specs} ->
|
||||||
Enum.map(specs, &spec_to_id/1)
|
Enum.map(specs, &spec_to_id(&1, context))
|
||||||
|> Enum.reduce(Store.true_node_id(), fn id, acc ->
|
|> Enum.reduce(Store.true_node_id(), fn id, acc ->
|
||||||
Algo.apply(:intersect, &op_intersect_terminals/2, id, acc)
|
Algo.apply(:intersect, &op_intersect_terminals/2, id, acc)
|
||||||
end)
|
end)
|
||||||
|
|
||||||
{:negation, sub_spec} ->
|
{:negation, sub_spec} ->
|
||||||
Algo.negate(spec_to_id(sub_spec))
|
Algo.negate(spec_to_id(sub_spec, context))
|
||||||
|
|
||||||
{:cons, head_spec, tail_spec} ->
|
{:cons, head_spec, tail_spec} ->
|
||||||
id_head = spec_to_id(head_spec)
|
id_head = spec_to_id(head_spec, context)
|
||||||
id_tail = spec_to_id(tail_spec)
|
id_tail = spec_to_id(tail_spec, context)
|
||||||
compile_cons_from_ids(id_head, id_tail)
|
compile_cons_from_ids(id_head, id_tail, context)
|
||||||
|
|
||||||
{:tuple, elements} ->
|
{:tuple, elements} ->
|
||||||
compile_tuple(elements)
|
compile_tuple(elements, context)
|
||||||
|
|
||||||
{:list_of, element_spec} ->
|
# --- REMOVE THE SPECIAL CASE ---
|
||||||
compile_list_of(element_spec)
|
# The main `spec_to_id/2` logic now handles ALL recursive types.
|
||||||
|
{:list_of, _element_spec} ->
|
||||||
|
# We need to compile the logical definition: [] | cons(E, list_of(E))
|
||||||
|
# The TypeSpec normalizer doesn't do this expansion, so we do it here.
|
||||||
|
{:ok, {:list_of, element_spec}} = {:ok, spec}
|
||||||
|
recursive_def = {:union, [{:literal, []}, {:cons, element_spec, spec}]}
|
||||||
|
# Now compile this definition. The main `spec_to_id/2` will handle the knot-tying.
|
||||||
|
spec_to_id(recursive_def, context)
|
||||||
|
|
||||||
_ ->
|
_ ->
|
||||||
raise "Tdd.Compiler: Cannot compile unknown spec: #{inspect(spec)}"
|
raise "Tdd.Compiler: Cannot compile unknown spec: #{inspect(spec)}"
|
||||||
@ -1434,15 +1588,14 @@ defmodule Tdd.Compiler do
|
|||||||
|
|
||||||
defp create_base_type_tdd(var), do: Store.find_or_create_node(var, Store.true_node_id(), Store.false_node_id(), Store.false_node_id())
|
defp create_base_type_tdd(var), do: Store.find_or_create_node(var, Store.true_node_id(), Store.false_node_id(), Store.false_node_id())
|
||||||
|
|
||||||
defp compile_value_equality(base_type_spec, value_var) do
|
defp compile_value_equality(base_type_spec, value_var, context) do
|
||||||
eq_node = create_base_type_tdd(value_var)
|
eq_node = create_base_type_tdd(value_var)
|
||||||
# Note: spec_to_id is safe here because it's on non-recursive base types.
|
base_node_id = spec_to_id(base_type_spec, context)
|
||||||
base_node_id = spec_to_id(base_type_spec)
|
|
||||||
Algo.apply(:intersect, &op_intersect_terminals/2, base_node_id, eq_node)
|
Algo.apply(:intersect, &op_intersect_terminals/2, base_node_id, eq_node)
|
||||||
end
|
end
|
||||||
|
|
||||||
defp compile_integer_range(min, max) do
|
defp compile_integer_range(min, max, context) do
|
||||||
base_id = spec_to_id(:integer)
|
base_id = spec_to_id(:integer, context)
|
||||||
lt_min_tdd = if min != :neg_inf, do: create_base_type_tdd(Variable.v_int_lt(min))
|
lt_min_tdd = if min != :neg_inf, do: create_base_type_tdd(Variable.v_int_lt(min))
|
||||||
gte_min_tdd = if lt_min_tdd, do: Algo.negate(lt_min_tdd), else: spec_to_id(:any)
|
gte_min_tdd = if lt_min_tdd, do: Algo.negate(lt_min_tdd), else: spec_to_id(:any)
|
||||||
id_with_min = Algo.apply(:intersect, &op_intersect_terminals/2, base_id, gte_min_tdd)
|
id_with_min = Algo.apply(:intersect, &op_intersect_terminals/2, base_id, gte_min_tdd)
|
||||||
@ -1455,15 +1608,15 @@ defmodule Tdd.Compiler do
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
defp compile_cons_from_ids(h_id, t_id) do
|
defp compile_cons_from_ids(h_id, t_id, context) do # Pass context
|
||||||
# Build `list & !is_empty` manually and safely.
|
# Build `list & !is_empty` manually and safely.
|
||||||
id_list = create_base_type_tdd(Variable.v_is_list())
|
id_list = create_base_type_tdd(Variable.v_is_list())
|
||||||
id_is_empty = create_base_type_tdd(Variable.v_list_is_empty())
|
id_is_empty = create_base_type_tdd(Variable.v_list_is_empty())
|
||||||
id_not_is_empty = Algo.negate(id_is_empty)
|
id_not_is_empty = Algo.negate(id_is_empty)
|
||||||
non_empty_list_id = Algo.apply(:intersect, &op_intersect_terminals/2, id_list, id_not_is_empty)
|
non_empty_list_id = Algo.apply(:intersect, &op_intersect_terminals/2, id_list, id_not_is_empty)
|
||||||
|
|
||||||
head_checker = sub_problem(&Variable.v_list_head_pred/1, h_id)
|
head_checker = sub_problem(&Variable.v_list_head_pred/1, h_id, context) # Pass context
|
||||||
tail_checker = sub_problem(&Variable.v_list_tail_pred/1, t_id)
|
tail_checker = sub_problem(&Variable.v_list_tail_pred/1, t_id, context) # Pass context
|
||||||
|
|
||||||
[non_empty_list_id, head_checker, tail_checker]
|
[non_empty_list_id, head_checker, tail_checker]
|
||||||
|> Enum.reduce(Store.true_node_id(), fn id, acc ->
|
|> Enum.reduce(Store.true_node_id(), fn id, acc ->
|
||||||
@ -1471,9 +1624,9 @@ defmodule Tdd.Compiler do
|
|||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
|
|
||||||
defp compile_tuple(elements) do
|
defp compile_tuple(elements, context) do
|
||||||
size = length(elements)
|
size = length(elements)
|
||||||
base_id = spec_to_id(:tuple)
|
base_id = spec_to_id(:tuple, context)
|
||||||
size_tdd = create_base_type_tdd(Variable.v_tuple_size_eq(size))
|
size_tdd = create_base_type_tdd(Variable.v_tuple_size_eq(size))
|
||||||
initial_id = Algo.apply(:intersect, &op_intersect_terminals/2, base_id, size_tdd)
|
initial_id = Algo.apply(:intersect, &op_intersect_terminals/2, base_id, size_tdd)
|
||||||
|
|
||||||
@ -1482,57 +1635,101 @@ defmodule Tdd.Compiler do
|
|||||||
|> Enum.reduce(initial_id, fn {elem_spec, index}, acc_id ->
|
|> Enum.reduce(initial_id, fn {elem_spec, index}, acc_id ->
|
||||||
elem_id = spec_to_id(elem_spec)
|
elem_id = spec_to_id(elem_spec)
|
||||||
elem_key_constructor = &Variable.v_tuple_elem_pred(index, &1)
|
elem_key_constructor = &Variable.v_tuple_elem_pred(index, &1)
|
||||||
elem_checker = sub_problem(elem_key_constructor, elem_id)
|
elem_checker = sub_problem(elem_key_constructor, elem_id, context) # Pass context
|
||||||
Algo.apply(:intersect, &op_intersect_terminals/2, acc_id, elem_checker)
|
Algo.apply(:intersect, &op_intersect_terminals/2, acc_id, elem_checker)
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
|
|
||||||
defp sub_problem(sub_key_constructor, tdd_id) do
|
defp do_sub_problem(sub_key_constructor, tdd_id, context) do
|
||||||
|
# The `if` block is now a standard multi-clause `cond` or `case` at the top.
|
||||||
|
# Let's use a `cond` to make the guard explicit.
|
||||||
|
cond do
|
||||||
|
# Guard against invalid IDs from placeholder children.
|
||||||
|
tdd_id < 0 ->
|
||||||
|
Store.false_node_id()
|
||||||
|
|
||||||
|
# If it's a valid ID, proceed with the main logic.
|
||||||
|
true ->
|
||||||
|
case Store.get_node(tdd_id) do
|
||||||
|
{:ok, :true_terminal} ->
|
||||||
|
Store.true_node_id()
|
||||||
|
|
||||||
|
{:ok, :false_terminal} ->
|
||||||
|
Store.false_node_id()
|
||||||
|
|
||||||
|
# Handle placeholders by operating on their spec, not their TDD structure.
|
||||||
|
{:ok, {{:placeholder, spec}, _, _, _}} ->
|
||||||
|
dummy_var = sub_key_constructor.(:dummy)
|
||||||
|
|
||||||
|
case dummy_var do
|
||||||
|
{5, :c_head, :dummy, _} ->
|
||||||
|
{:list_of, element_spec} = spec
|
||||||
|
spec_to_id(element_spec, context)
|
||||||
|
|
||||||
|
{5, :d_tail, :dummy, _} ->
|
||||||
|
tdd_id
|
||||||
|
|
||||||
|
{4, :b_element, index, :dummy} ->
|
||||||
|
{:tuple, elements} = spec
|
||||||
|
spec_to_id(Enum.at(elements, index), context)
|
||||||
|
|
||||||
|
_ ->
|
||||||
|
raise "sub_problem encountered an unhandled recursive predicate on a placeholder: #{inspect(dummy_var)}"
|
||||||
|
end
|
||||||
|
|
||||||
|
# The normal, non-placeholder case
|
||||||
|
{:ok, {var, y, n, d}} ->
|
||||||
|
Store.find_or_create_node(
|
||||||
|
sub_key_constructor.(var),
|
||||||
|
sub_problem(sub_key_constructor, y, context),
|
||||||
|
sub_problem(sub_key_constructor, n, context),
|
||||||
|
sub_problem(sub_key_constructor, d, context)
|
||||||
|
)
|
||||||
|
|
||||||
|
# This case should now be unreachable.
|
||||||
|
{:error, :not_found} ->
|
||||||
|
raise "sub_problem received an unknown tdd_id: #{tdd_id}"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp sub_problem(sub_key_constructor, tdd_id, context) do
|
||||||
cache_key = {:sub_problem, sub_key_constructor, tdd_id}
|
cache_key = {:sub_problem, sub_key_constructor, tdd_id}
|
||||||
|
# Note: context is not part of the cache key. This is a simplification that
|
||||||
|
# assumes the result of a sub_problem is independent of the wider compilation
|
||||||
|
# context, which is true for our use case.
|
||||||
case Store.get_op_cache(cache_key) do
|
case Store.get_op_cache(cache_key) do
|
||||||
{:ok, result_id} -> result_id
|
{:ok, result_id} ->
|
||||||
|
result_id
|
||||||
|
|
||||||
:not_found ->
|
:not_found ->
|
||||||
result_id = do_sub_problem(sub_key_constructor, tdd_id)
|
result_id = do_sub_problem(sub_key_constructor, tdd_id, context)
|
||||||
Store.put_op_cache(cache_key, result_id)
|
Store.put_op_cache(cache_key, result_id)
|
||||||
result_id
|
result_id
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
defp do_sub_problem(sub_key_constructor, tdd_id) do
|
# defp compile_list_of(element_spec) do
|
||||||
case Store.get_node(tdd_id) do
|
# norm_elem_spec = TypeSpec.normalize(element_spec)
|
||||||
{:ok, :true_terminal} -> Store.true_node_id()
|
# cache_key = {:compile_list_of, norm_elem_spec}
|
||||||
{:ok, :false_terminal} -> Store.false_node_id()
|
# case Store.get_op_cache(cache_key) do
|
||||||
{:ok, {var, y, n, d}} ->
|
# {:ok, id} -> id
|
||||||
Store.find_or_create_node(
|
# :not_found ->
|
||||||
sub_key_constructor.(var),
|
# id = do_compile_list_of(norm_elem_spec)
|
||||||
sub_problem(sub_key_constructor, y),
|
# Store.put_op_cache(cache_key, id)
|
||||||
sub_problem(sub_key_constructor, n),
|
# id
|
||||||
sub_problem(sub_key_constructor, d)
|
# end
|
||||||
)
|
# end
|
||||||
end
|
#
|
||||||
end
|
# defp do_compile_list_of(element_spec) do
|
||||||
|
# id_elem = spec_to_id(element_spec)
|
||||||
defp compile_list_of(element_spec) do
|
# id_empty_list = spec_to_id({:literal, []})
|
||||||
norm_elem_spec = TypeSpec.normalize(element_spec)
|
# step_function = fn id_x ->
|
||||||
cache_key = {:compile_list_of, norm_elem_spec}
|
# id_cons = compile_cons_from_ids(id_elem, id_x)
|
||||||
case Store.get_op_cache(cache_key) do
|
# Algo.apply(:sum, &op_union_terminals/2, id_empty_list, id_cons)
|
||||||
{:ok, id} -> id
|
# end
|
||||||
:not_found ->
|
# loop_until_stable(Store.false_node_id(), step_function)
|
||||||
id = do_compile_list_of(norm_elem_spec)
|
# end
|
||||||
Store.put_op_cache(cache_key, id)
|
|
||||||
id
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
defp do_compile_list_of(element_spec) do
|
|
||||||
id_elem = spec_to_id(element_spec)
|
|
||||||
id_empty_list = spec_to_id({:literal, []})
|
|
||||||
step_function = fn id_x ->
|
|
||||||
id_cons = compile_cons_from_ids(id_elem, id_x)
|
|
||||||
Algo.apply(:sum, &op_union_terminals/2, id_empty_list, id_cons)
|
|
||||||
end
|
|
||||||
loop_until_stable(Store.false_node_id(), step_function)
|
|
||||||
end
|
|
||||||
|
|
||||||
# THIS IS THE FINAL, CORRECTED LOOP
|
# THIS IS THE FINAL, CORRECTED LOOP
|
||||||
defp loop_until_stable(prev_id, step_function, iteration \\ 0) do
|
defp loop_until_stable(prev_id, step_function, iteration \\ 0) do
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user