checkpoint before next attempt at recursive types - noice debugging here
This commit is contained in:
parent
ff557cb221
commit
bcddae26cb
245
new.exs
245
new.exs
@ -575,7 +575,6 @@ defmodule Tdd.Variable do
|
|||||||
# @spec v_list_all_elements_are(TypeSpec.t()) :: term()
|
# @spec v_list_all_elements_are(TypeSpec.t()) :: term()
|
||||||
# def v_list_all_elements_are(element_spec), do: {5, :a_all_elements, element_spec, nil}
|
# def v_list_all_elements_are(element_spec), do: {5, :a_all_elements, element_spec, nil}
|
||||||
|
|
||||||
|
|
||||||
@doc "Predicate: The list is the empty list `[]`."
|
@doc "Predicate: The list is the empty list `[]`."
|
||||||
@spec v_list_is_empty() :: term()
|
@spec v_list_is_empty() :: term()
|
||||||
def v_list_is_empty, do: {5, :b_is_empty, nil, nil}
|
def v_list_is_empty, do: {5, :b_is_empty, nil, nil}
|
||||||
@ -1369,7 +1368,6 @@ defmodule Tdd.Compiler do
|
|||||||
@doc "The main entry point. Takes a spec and returns its TDD ID."
|
@doc "The main entry point. Takes a spec and returns its TDD ID."
|
||||||
@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
|
||||||
# Memoization wrapper for the entire compilation process.
|
|
||||||
normalized_spec = TypeSpec.normalize(spec)
|
normalized_spec = TypeSpec.normalize(spec)
|
||||||
cache_key = {:spec_to_id, normalized_spec}
|
cache_key = {:spec_to_id, normalized_spec}
|
||||||
|
|
||||||
@ -1378,76 +1376,44 @@ defmodule Tdd.Compiler do
|
|||||||
id
|
id
|
||||||
|
|
||||||
:not_found ->
|
:not_found ->
|
||||||
id = do_spec_to_id(normalized_spec)
|
# Do the raw compilation first.
|
||||||
|
raw_id = do_spec_to_id(normalized_spec)
|
||||||
|
# THEN, do a final semantic simplification pass. This is the fix.
|
||||||
|
id = Algo.simplify(raw_id)
|
||||||
Store.put_op_cache(cache_key, id)
|
Store.put_op_cache(cache_key, id)
|
||||||
id
|
id
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# The core compilation logic.
|
# This helper does the raw, structural compilation. It does NOT call simplify.
|
||||||
defp do_spec_to_id(spec) do
|
defp do_spec_to_id(spec) do
|
||||||
raw_id =
|
|
||||||
case spec do
|
case spec do
|
||||||
# --- Base Types ---
|
:any -> Store.true_node_id()
|
||||||
:any ->
|
:none -> Store.false_node_id()
|
||||||
Store.true_node_id()
|
:atom -> create_base_type_tdd(Variable.v_is_atom())
|
||||||
|
:integer -> create_base_type_tdd(Variable.v_is_integer())
|
||||||
|
:list -> create_base_type_tdd(Variable.v_is_list())
|
||||||
|
: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_integer(val) -> compile_value_equality(:integer, Variable.v_int_eq(val))
|
||||||
|
{:literal, []} -> compile_value_equality(:list, Variable.v_list_is_empty())
|
||||||
|
{:integer_range, min, max} -> compile_integer_range(min, max)
|
||||||
|
|
||||||
:none ->
|
|
||||||
Store.false_node_id()
|
|
||||||
|
|
||||||
:atom ->
|
|
||||||
create_base_type_tdd(Variable.v_is_atom())
|
|
||||||
|
|
||||||
:integer ->
|
|
||||||
create_base_type_tdd(Variable.v_is_integer())
|
|
||||||
|
|
||||||
:list ->
|
|
||||||
create_base_type_tdd(Variable.v_is_list())
|
|
||||||
|
|
||||||
:tuple ->
|
|
||||||
create_base_type_tdd(Variable.v_is_tuple())
|
|
||||||
|
|
||||||
# --- Literal Types ---
|
|
||||||
{:literal, val} when is_atom(val) ->
|
|
||||||
compile_value_equality(:atom, Variable.v_atom_eq(val))
|
|
||||||
|
|
||||||
{:literal, val} when is_integer(val) ->
|
|
||||||
compile_value_equality(:integer, Variable.v_int_eq(val))
|
|
||||||
|
|
||||||
{:literal, []} ->
|
|
||||||
compile_value_equality(:list, Variable.v_list_is_empty())
|
|
||||||
|
|
||||||
# --- Integer Range ---
|
|
||||||
{:integer_range, min, max} ->
|
|
||||||
compile_integer_range(min, max)
|
|
||||||
|
|
||||||
# --- Set-Theoretic Combinators ---
|
|
||||||
{:union, specs} ->
|
{:union, specs} ->
|
||||||
ids = Enum.map(specs, &spec_to_id/1)
|
Enum.map(specs, &spec_to_id/1)
|
||||||
|
|> Enum.reduce(Store.false_node_id(), fn id, acc ->
|
||||||
Enum.reduce(ids, 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} ->
|
||||||
ids = Enum.map(specs, &spec_to_id/1)
|
Enum.map(specs, &spec_to_id/1)
|
||||||
|
|> Enum.reduce(Store.true_node_id(), fn id, acc ->
|
||||||
Enum.reduce(ids, 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))
|
||||||
|
|
||||||
# --- RECURSIVE TYPES ---
|
|
||||||
#
|
|
||||||
# {:list_of, element_spec} ->
|
|
||||||
# id_list = spec_to_id(:list)
|
|
||||||
# # The element_spec MUST be normalized to be a canonical part of the variable.
|
|
||||||
norm_elem_spec = TypeSpec.normalize(element_spec)
|
|
||||||
var = Variable.v_list_all_elements_are(norm_elem_spec)
|
|
||||||
# id_check = create_base_type_tdd(var)
|
|
||||||
# Algo.apply(:intersect, &op_intersect_terminals/2, id_list, id_check)
|
|
||||||
{:cons, head_spec, tail_spec} ->
|
{:cons, head_spec, tail_spec} ->
|
||||||
id_head = spec_to_id(head_spec)
|
id_head = spec_to_id(head_spec)
|
||||||
id_tail = spec_to_id(tail_spec)
|
id_tail = spec_to_id(tail_spec)
|
||||||
@ -1456,47 +1422,30 @@ defmodule Tdd.Compiler do
|
|||||||
{:tuple, elements} ->
|
{:tuple, elements} ->
|
||||||
compile_tuple(elements)
|
compile_tuple(elements)
|
||||||
|
|
||||||
|
{:list_of, element_spec} ->
|
||||||
|
compile_list_of(element_spec)
|
||||||
|
|
||||||
# --- Default ---
|
|
||||||
_ ->
|
_ ->
|
||||||
raise "Tdd.Compiler: Cannot compile unknown spec: #{inspect(spec)}"
|
raise "Tdd.Compiler: Cannot compile unknown spec: #{inspect(spec)}"
|
||||||
end
|
end
|
||||||
|
|
||||||
# CRUCIAL: Every constructed TDD must be passed through simplify
|
|
||||||
# to ensure it's in its canonical, semantically-reduced form.
|
|
||||||
Algo.simplify(raw_id)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
# ------------------------------------------------------------------
|
# --- Private Helpers ---
|
||||||
# Private Compilation Helpers
|
|
||||||
# ------------------------------------------------------------------
|
|
||||||
|
|
||||||
defp create_base_type_tdd(var) 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())
|
||||||
Store.find_or_create_node(
|
|
||||||
var,
|
|
||||||
Store.true_node_id(),
|
|
||||||
Store.false_node_id(),
|
|
||||||
Store.false_node_id()
|
|
||||||
)
|
|
||||||
end
|
|
||||||
|
|
||||||
defp compile_value_equality(base_type_spec, value_var) do
|
defp compile_value_equality(base_type_spec, value_var) 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)
|
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) do
|
||||||
base_id = spec_to_id(:integer)
|
base_id = spec_to_id(:integer)
|
||||||
|
lt_min_tdd = if min != :neg_inf, do: create_base_type_tdd(Variable.v_int_lt(min))
|
||||||
id_with_min =
|
gte_min_tdd = if lt_min_tdd, do: Algo.negate(lt_min_tdd), else: spec_to_id(:any)
|
||||||
if min == :neg_inf do
|
id_with_min = Algo.apply(:intersect, &op_intersect_terminals/2, base_id, gte_min_tdd)
|
||||||
base_id
|
|
||||||
else
|
|
||||||
lt_min_tdd = create_base_type_tdd(Variable.v_int_lt(min))
|
|
||||||
gte_min_tdd = Algo.negate(lt_min_tdd)
|
|
||||||
Algo.apply(:intersect, &op_intersect_terminals/2, base_id, gte_min_tdd)
|
|
||||||
end
|
|
||||||
|
|
||||||
if max == :pos_inf do
|
if max == :pos_inf do
|
||||||
id_with_min
|
id_with_min
|
||||||
@ -1506,49 +1455,18 @@ defmodule Tdd.Compiler do
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# ------------------------------------------------------------------
|
|
||||||
# Helpers for Compositional Recursive Types (`cons`, `tuple`)
|
|
||||||
# ------------------------------------------------------------------
|
|
||||||
|
|
||||||
defp sub_problem(sub_key_constructor, tdd_id) do
|
|
||||||
cache_key = {:sub_problem, sub_key_constructor, tdd_id}
|
|
||||||
|
|
||||||
case Store.get_op_cache(cache_key) do
|
|
||||||
{:ok, result_id} ->
|
|
||||||
result_id
|
|
||||||
|
|
||||||
:not_found ->
|
|
||||||
result_id = do_sub_problem(sub_key_constructor, tdd_id)
|
|
||||||
Store.put_op_cache(cache_key, result_id)
|
|
||||||
result_id
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
defp do_sub_problem(sub_key_constructor, tdd_id) do
|
|
||||||
case Store.get_node(tdd_id) do
|
|
||||||
{:ok, :true_terminal} ->
|
|
||||||
Store.true_node_id()
|
|
||||||
|
|
||||||
{:ok, :false_terminal} ->
|
|
||||||
Store.false_node_id()
|
|
||||||
|
|
||||||
{:ok, {var, y, n, d}} ->
|
|
||||||
lifted_var = sub_key_constructor.(var)
|
|
||||||
lifted_y = sub_problem(sub_key_constructor, y)
|
|
||||||
lifted_n = sub_problem(sub_key_constructor, n)
|
|
||||||
lifted_d = sub_problem(sub_key_constructor, d)
|
|
||||||
Store.find_or_create_node(lifted_var, lifted_y, lifted_n, lifted_d)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
defp compile_cons_from_ids(h_id, t_id) do
|
defp compile_cons_from_ids(h_id, t_id) do
|
||||||
non_empty_list_id = spec_to_id({:intersect, [:list, {:negation, {:literal, []}}]})
|
# Build `list & !is_empty` manually and safely.
|
||||||
|
id_list = create_base_type_tdd(Variable.v_is_list())
|
||||||
|
id_is_empty = create_base_type_tdd(Variable.v_list_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)
|
||||||
|
|
||||||
head_checker = sub_problem(&Variable.v_list_head_pred/1, h_id)
|
head_checker = sub_problem(&Variable.v_list_head_pred/1, h_id)
|
||||||
tail_checker = sub_problem(&Variable.v_list_tail_pred/1, t_id)
|
tail_checker = sub_problem(&Variable.v_list_tail_pred/1, t_id)
|
||||||
|
|
||||||
ids_to_intersect = [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(ids_to_intersect, 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)
|
||||||
end
|
end
|
||||||
@ -1569,19 +1487,38 @@ defmodule Tdd.Compiler do
|
|||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
|
|
||||||
# ------------------------------------------------------------------
|
defp sub_problem(sub_key_constructor, tdd_id) do
|
||||||
# Helper for Self-Recursive Types (`list_of`) via Fixed-Point Iteration
|
cache_key = {:sub_problem, sub_key_constructor, tdd_id}
|
||||||
# ------------------------------------------------------------------
|
case Store.get_op_cache(cache_key) do
|
||||||
|
{:ok, result_id} -> result_id
|
||||||
|
:not_found ->
|
||||||
|
result_id = do_sub_problem(sub_key_constructor, tdd_id)
|
||||||
|
Store.put_op_cache(cache_key, result_id)
|
||||||
|
result_id
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp do_sub_problem(sub_key_constructor, tdd_id) do
|
||||||
|
case Store.get_node(tdd_id) do
|
||||||
|
{:ok, :true_terminal} -> Store.true_node_id()
|
||||||
|
{:ok, :false_terminal} -> Store.false_node_id()
|
||||||
|
{:ok, {var, y, n, d}} ->
|
||||||
|
Store.find_or_create_node(
|
||||||
|
sub_key_constructor.(var),
|
||||||
|
sub_problem(sub_key_constructor, y),
|
||||||
|
sub_problem(sub_key_constructor, n),
|
||||||
|
sub_problem(sub_key_constructor, d)
|
||||||
|
)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
defp compile_list_of(element_spec) do
|
defp compile_list_of(element_spec) do
|
||||||
cache_key = {:compile_list_of, element_spec}
|
norm_elem_spec = TypeSpec.normalize(element_spec)
|
||||||
|
cache_key = {:compile_list_of, norm_elem_spec}
|
||||||
case Store.get_op_cache(cache_key) do
|
case Store.get_op_cache(cache_key) do
|
||||||
{:ok, id} ->
|
{:ok, id} -> id
|
||||||
id
|
|
||||||
|
|
||||||
:not_found ->
|
:not_found ->
|
||||||
id = do_compile_list_of(element_spec)
|
id = do_compile_list_of(norm_elem_spec)
|
||||||
Store.put_op_cache(cache_key, id)
|
Store.put_op_cache(cache_key, id)
|
||||||
id
|
id
|
||||||
end
|
end
|
||||||
@ -1590,50 +1527,50 @@ defmodule Tdd.Compiler do
|
|||||||
defp do_compile_list_of(element_spec) do
|
defp do_compile_list_of(element_spec) do
|
||||||
id_elem = spec_to_id(element_spec)
|
id_elem = spec_to_id(element_spec)
|
||||||
id_empty_list = spec_to_id({:literal, []})
|
id_empty_list = spec_to_id({:literal, []})
|
||||||
|
|
||||||
step_function = fn id_x ->
|
step_function = fn id_x ->
|
||||||
id_cons = compile_cons_from_ids(id_elem, id_x)
|
id_cons = compile_cons_from_ids(id_elem, id_x)
|
||||||
Algo.apply(:sum, &op_union_terminals/2, id_empty_list, id_cons)
|
Algo.apply(:sum, &op_union_terminals/2, id_empty_list, id_cons)
|
||||||
end
|
end
|
||||||
|
|
||||||
loop_until_stable(Store.false_node_id(), step_function)
|
loop_until_stable(Store.false_node_id(), step_function)
|
||||||
end
|
end
|
||||||
|
|
||||||
# --- THIS IS THE FIX ---
|
# THIS IS THE FINAL, CORRECTED LOOP
|
||||||
defp loop_until_stable(prev_id, step_function) do
|
defp loop_until_stable(prev_id, step_function, iteration \\ 0) do
|
||||||
# At each step, we must simplify the result to get its canonical form.
|
IO.puts("\n--- Fixed-Point Iteration: #{iteration} ---")
|
||||||
# This ensures we are comparing semantic equality, not just structural.
|
IO.inspect(prev_id, label: "prev_id")
|
||||||
|
# Tdd.Debug.print(prev_id) # Optional: uncomment for full graph
|
||||||
|
|
||||||
raw_next_id = step_function.(prev_id)
|
raw_next_id = step_function.(prev_id)
|
||||||
|
IO.inspect(raw_next_id, label: "raw_next_id (after step_function)")
|
||||||
|
Tdd.Debug.print(raw_next_id) # Let's see the raw graph
|
||||||
|
|
||||||
|
# Crucially, simplify after each step for canonical comparison.
|
||||||
next_id = Algo.simplify(raw_next_id)
|
next_id = Algo.simplify(raw_next_id)
|
||||||
|
IO.inspect(next_id, label: "next_id (after simplify)")
|
||||||
|
Tdd.Debug.print(next_id) # Let's see the simplified graph
|
||||||
|
|
||||||
if next_id == prev_id do
|
if next_id == prev_id do
|
||||||
|
IO.puts("--- Fixed-Point Reached! ---")
|
||||||
next_id
|
next_id
|
||||||
else
|
else
|
||||||
loop_until_stable(next_id, step_function)
|
# Add a safety break for debugging
|
||||||
|
if iteration > 2 do
|
||||||
|
IO.inspect Process.info(self(), :current_stacktrace)
|
||||||
|
raise "Fixed-point iteration did not converge after 2 steps. Halting."
|
||||||
|
end
|
||||||
|
loop_until_stable(next_id, step_function, iteration + 1)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# ------------------------------------------------------------------
|
# --- Private Functions for Terminal Logic ---
|
||||||
# Private Functions for Terminal Logic
|
defp op_union_terminals(:true_terminal, _), do: :true_terminal
|
||||||
# ------------------------------------------------------------------
|
defp op_union_terminals(_, :true_terminal), do: :true_terminal
|
||||||
|
defp op_union_terminals(t, :false_terminal), do: t
|
||||||
defp op_union_terminals(u1_details, u2_details) do
|
defp op_union_terminals(:false_terminal, t), do: t
|
||||||
case {u1_details, u2_details} do
|
defp op_intersect_terminals(:false_terminal, _), do: :false_terminal
|
||||||
{:true_terminal, _} -> :true_terminal
|
defp op_intersect_terminals(_, :false_terminal), do: :false_terminal
|
||||||
{_, :true_terminal} -> :true_terminal
|
defp op_intersect_terminals(t, :true_terminal), do: t
|
||||||
{:false_terminal, t2} -> t2
|
defp op_intersect_terminals(:true_terminal, t), do: t
|
||||||
{t1, :false_terminal} -> t1
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
defp op_intersect_terminals(u1_details, u2_details) do
|
|
||||||
case {u1_details, u2_details} do
|
|
||||||
{:false_terminal, _} -> :false_terminal
|
|
||||||
{_, :false_terminal} -> :false_terminal
|
|
||||||
{:true_terminal, t2} -> t2
|
|
||||||
{t1, :true_terminal} -> t1
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
####
|
####
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user