checkpoint, still fucked debugging
This commit is contained in:
parent
a78fe0541a
commit
8d8b3607fc
329
new.exs
329
new.exs
@ -8,32 +8,33 @@ defmodule Tdd.Debug do
|
|||||||
def init do
|
def init do
|
||||||
case Process.whereis(@agent_name) do
|
case Process.whereis(@agent_name) do
|
||||||
nil -> Agent.start_link(fn -> MapSet.new() end, name: @agent_name)
|
nil -> Agent.start_link(fn -> MapSet.new() end, name: @agent_name)
|
||||||
_pid -> :ok
|
_pid -> :ok # Agent already started
|
||||||
end
|
end
|
||||||
|
|
||||||
:ok
|
:ok
|
||||||
end
|
end
|
||||||
|
|
||||||
defp add_traced_pid(pid) when is_pid(pid) do
|
defp add_traced_pid(pid) when is_pid(pid) do
|
||||||
init()
|
init() # Ensure agent is started
|
||||||
Agent.update(@agent_name, &MapSet.put(&1, pid))
|
Agent.update(@agent_name, &MapSet.put(&1, pid))
|
||||||
end
|
end
|
||||||
|
|
||||||
defp remove_traced_pid(pid) when is_pid(pid) do
|
defp remove_traced_pid(pid) when is_pid(pid) do
|
||||||
if agent_pid = Agent.whereis(@agent_name) do
|
# Use Process.whereis to avoid race condition if agent stops between calls
|
||||||
Agent.cast(agent_pid, fn state -> MapSet.delete(state, pid) end)
|
case Process.whereis(@agent_name) do
|
||||||
|
nil -> :ok # Agent not running, nothing to remove from
|
||||||
|
agent_pid -> Agent.cast(agent_pid, fn state -> MapSet.delete(state, pid) end)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
defp is_pid_traced?(pid) when is_pid(pid) do
|
def is_pid_traced?(pid) when is_pid(pid) do
|
||||||
case Agent.whereis(@agent_name) do
|
case Process.whereis(@agent_name) do
|
||||||
nil ->
|
nil ->
|
||||||
false
|
false
|
||||||
|
|
||||||
agent_pid ->
|
agent_pid ->
|
||||||
try do
|
try do
|
||||||
Agent.get(agent_pid, &MapSet.member?(&1, pid), :infinity)
|
Agent.get(agent_pid, &MapSet.member?(&1, pid), :infinity)
|
||||||
rescue
|
rescue
|
||||||
|
# Catches if agent dies or is not an agent anymore
|
||||||
_e in [Exit, ArgumentError] -> false
|
_e in [Exit, ArgumentError] -> false
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@ -43,7 +44,13 @@ defmodule Tdd.Debug do
|
|||||||
@arg_length_limit 80
|
@arg_length_limit 80
|
||||||
@total_args_length_limit 200
|
@total_args_length_limit 200
|
||||||
|
|
||||||
defp format_args_list(args_list) when is_list(args_list) do
|
# Helper function to return a unique atom for ignored arguments
|
||||||
|
# This is called from the macro-generated code.
|
||||||
|
def __internal_placeholder_for_ignored_arg__ do
|
||||||
|
:__tdd_debug_ignored_arg__ # A unique atom
|
||||||
|
end
|
||||||
|
|
||||||
|
def format_args_list(args_list) when is_list(args_list) do
|
||||||
formatted_args = Enum.map(args_list, &format_arg_value/1)
|
formatted_args = Enum.map(args_list, &format_arg_value/1)
|
||||||
combined = Enum.join(formatted_args, ", ")
|
combined = Enum.join(formatted_args, ", ")
|
||||||
|
|
||||||
@ -54,8 +61,16 @@ defmodule Tdd.Debug do
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
defp format_arg_value(arg) do
|
def format_arg_value(arg) do
|
||||||
inspected = inspect(arg, limit: :infinity, pretty: false, structs: true)
|
inspected =
|
||||||
|
case arg do
|
||||||
|
# Check if it's our specific placeholder atom for `_`
|
||||||
|
:__tdd_debug_ignored_arg__ -> "_"
|
||||||
|
# For other arguments, inspect them normally.
|
||||||
|
# This will handle runtime values for simple variables/literals,
|
||||||
|
# and ASTs for complex patterns (if that's what's passed).
|
||||||
|
_ -> inspect(arg, limit: :infinity, pretty: false, structs: true)
|
||||||
|
end
|
||||||
|
|
||||||
if String.length(inspected) > @arg_length_limit do
|
if String.length(inspected) > @arg_length_limit do
|
||||||
String.slice(inspected, 0, @arg_length_limit - 3) <> "..."
|
String.slice(inspected, 0, @arg_length_limit - 3) <> "..."
|
||||||
@ -69,29 +84,38 @@ defmodule Tdd.Debug do
|
|||||||
init()
|
init()
|
||||||
pid_to_trace = self()
|
pid_to_trace = self()
|
||||||
add_traced_pid(pid_to_trace)
|
add_traced_pid(pid_to_trace)
|
||||||
Process.flag(:trap_exit, true)
|
# Trap exits only if not already trapping, to avoid interfering with other code.
|
||||||
|
# However, monitoring is generally safer and less intrusive.
|
||||||
|
# Process.flag(:trap_exit, true) # Consider if this is truly needed or if monitoring is enough.
|
||||||
ref = Process.monitor(pid_to_trace)
|
ref = Process.monitor(pid_to_trace)
|
||||||
|
|
||||||
Process.spawn(fn ->
|
# Spawn a separate process to monitor.
|
||||||
receive do
|
# Use spawn_opt to avoid linking if this monitoring process crashes.
|
||||||
{:DOWN, ^ref, :process, ^pid_to_trace, _reason} -> remove_traced_pid(pid_to_trace)
|
Process.spawn(
|
||||||
after
|
fn ->
|
||||||
3_600_000 -> remove_traced_pid(pid_to_trace)
|
receive do
|
||||||
end
|
{:DOWN, ^ref, :process, ^pid_to_trace, _reason} ->
|
||||||
end)
|
remove_traced_pid(pid_to_trace)
|
||||||
|
after
|
||||||
|
# 1 hour timeout as a fallback
|
||||||
|
3_600_000 ->
|
||||||
|
remove_traced_pid(pid_to_trace)
|
||||||
|
end
|
||||||
|
end,
|
||||||
|
[:monitor] #:link option removed, monitor is explicit
|
||||||
|
)
|
||||||
|
|
||||||
:ok
|
:ok
|
||||||
end
|
end
|
||||||
|
|
||||||
def disable_tracing() do
|
def disable_tracing() do
|
||||||
# Ensure agent is available for removal
|
init() # Ensure agent is available for removal if it was started by another call
|
||||||
init()
|
|
||||||
remove_traced_pid(self())
|
remove_traced_pid(self())
|
||||||
|
:ok # Good practice to return :ok
|
||||||
end
|
end
|
||||||
|
|
||||||
def run(fun) when is_function(fun, 0) do
|
def run(fun) when is_function(fun, 0) do
|
||||||
enable_tracing()
|
enable_tracing()
|
||||||
|
|
||||||
try do
|
try do
|
||||||
fun.()
|
fun.()
|
||||||
after
|
after
|
||||||
@ -100,18 +124,16 @@ defmodule Tdd.Debug do
|
|||||||
end
|
end
|
||||||
|
|
||||||
# --- Process Dictionary for Depth ---
|
# --- Process Dictionary for Depth ---
|
||||||
defp get_depth do
|
defp get_depth, do: Process.get(:tdd_debug_depth, 0)
|
||||||
Process.get(:tdd_debug_depth, 0)
|
|
||||||
end
|
|
||||||
|
|
||||||
defp increment_depth do
|
def increment_depth do
|
||||||
new_depth = get_depth() + 1
|
new_depth = get_depth() + 1
|
||||||
Process.put(:tdd_debug_depth, new_depth)
|
Process.put(:tdd_debug_depth, new_depth)
|
||||||
new_depth
|
new_depth
|
||||||
end
|
end
|
||||||
|
|
||||||
defp decrement_depth do
|
def decrement_depth do
|
||||||
new_depth = max(0, get_depth() - 1)
|
new_depth = max(0, get_depth() - 1) # Ensure depth doesn't go below 0
|
||||||
Process.put(:tdd_debug_depth, new_depth)
|
Process.put(:tdd_debug_depth, new_depth)
|
||||||
new_depth
|
new_depth
|
||||||
end
|
end
|
||||||
@ -119,149 +141,195 @@ defmodule Tdd.Debug do
|
|||||||
# --- Core Macro Logic ---
|
# --- Core Macro Logic ---
|
||||||
defmacro __using__(_opts) do
|
defmacro __using__(_opts) do
|
||||||
quote do
|
quote do
|
||||||
# Make Tdd.Debug functions (like do_instrument, format_args_list) callable
|
|
||||||
# from the macros we are about to define locally in the user's module.
|
|
||||||
import Kernel, except: [def: 2, def: 1, defp: 2, defp: 1]
|
import Kernel, except: [def: 2, def: 1, defp: 2, defp: 1]
|
||||||
require Tdd.Debug
|
require Tdd.Debug
|
||||||
|
# Import Tdd.Debug's def/defp macros and other public functions/macros
|
||||||
import Tdd.Debug
|
import Tdd.Debug
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# This is an internal macro helper, not intended for direct user call
|
# The `do_instrument` macro seems to be an older/alternative version.
|
||||||
|
# The primary mechanism used by `def`/`defp` is `generate_traced_function`.
|
||||||
|
# We'll keep `do_instrument` as it was in your example, in case it's used elsewhere,
|
||||||
|
# but ensure it's not the cause of the current problem.
|
||||||
@doc false
|
@doc false
|
||||||
defmacro do_instrument(type, call, clauses, env) do
|
defmacro do_instrument(type, call, clauses, _env) do
|
||||||
# CORRECTED: Decompose the function call AST
|
|
||||||
{function_name, _meta_call, original_args_ast_nodes} = call
|
{function_name, _meta_call, original_args_ast_nodes} = call
|
||||||
# Ensure it's a list for `def foo` vs `def foo()`
|
|
||||||
original_args_ast_nodes = original_args_ast_nodes || []
|
original_args_ast_nodes = original_args_ast_nodes || []
|
||||||
|
|
||||||
# CORRECTED: Generate argument variable ASTs for runtime access.
|
# This part was problematic if original_args_ast_nodes contained `_`
|
||||||
# These vars will hold the actual values of arguments after pattern matching.
|
# and was used directly to form a list of expressions.
|
||||||
arg_vars_for_runtime_access = original_args_ast_nodes
|
arg_vars_for_runtime_access =
|
||||||
|
Enum.map(original_args_ast_nodes, fn
|
||||||
|
{:_, _, Elixir} -> quote do Tdd.Debug.__internal_placeholder_for_ignored_arg__() end
|
||||||
|
{:=, _, [var_ast, _]} -> var_ast
|
||||||
|
pattern_ast -> pattern_ast
|
||||||
|
end)
|
||||||
|
|
||||||
actual_code_ast =
|
actual_code_ast =
|
||||||
case clauses do
|
case clauses do
|
||||||
[do: block_content] -> block_content
|
# [do: block_content | _] -> block_content
|
||||||
kw when is_list(kw) -> Keyword.get(kw, :do)
|
kw when is_list(kw) -> Keyword.get(kw, :do)
|
||||||
# `do: :atom` or `do: variable`
|
|
||||||
_ -> clauses
|
_ -> clauses
|
||||||
end
|
end
|
||||||
|
|
||||||
# Keep original line numbers for stacktraces from user code
|
|
||||||
traced_body =
|
traced_body =
|
||||||
quote location: :keep do
|
quote location: :keep do
|
||||||
if Tdd.Debug.is_pid_traced?(self()) do
|
if is_pid_traced?(self()) do
|
||||||
current_print_depth = Tdd.Debug.increment_depth()
|
current_print_depth = increment_depth()
|
||||||
indent = String.duplicate(" ", current_print_depth - 1)
|
indent = String.duplicate(" ", current_print_depth - 1)
|
||||||
|
|
||||||
# CORRECTED: Use the generated vars for runtime access to get actual argument values.
|
runtime_arg_values = [unquote_splicing(arg_vars_for_runtime_access)]
|
||||||
# We unquote_splicing them into a list literal.
|
args_string = format_args_list(runtime_arg_values)
|
||||||
runtime_arg_values = arg_vars_for_runtime_access
|
|
||||||
args_string = Tdd.Debug.format_args_list(runtime_arg_values)
|
|
||||||
|
|
||||||
# __MODULE__ here refers to the module where `use Tdd.Debug` is called.
|
|
||||||
caller_module_name = Module.split(__MODULE__) |> Enum.join(".")
|
caller_module_name = Module.split(__MODULE__) |> Enum.join(".")
|
||||||
|
|
||||||
IO.puts(
|
IO.puts(
|
||||||
"#{indent}CALL: #{caller_module_name}.#{unquote(function_name)}(#{args_string})"
|
"#{indent}CALL: #{caller_module_name}.#{unquote(Macro.escape(function_name))}(#{args_string})"
|
||||||
)
|
)
|
||||||
|
|
||||||
try do
|
try do
|
||||||
# Execute original function body
|
|
||||||
result = unquote(actual_code_ast)
|
result = unquote(actual_code_ast)
|
||||||
_ = Tdd.Debug.decrement_depth()
|
_ = decrement_depth()
|
||||||
|
|
||||||
IO.puts(
|
IO.puts(
|
||||||
"#{indent}RETURN from #{caller_module_name}.#{unquote(function_name)}: #{Tdd.Debug.format_arg_value(result)}"
|
"#{indent}RETURN from #{caller_module_name}.#{unquote(Macro.escape(function_name))}: #{Tdd.Debug.format_arg_value(result)}"
|
||||||
)
|
)
|
||||||
|
|
||||||
result
|
result
|
||||||
rescue
|
rescue
|
||||||
exception_class ->
|
exception_class ->
|
||||||
_ = Tdd.Debug.decrement_depth()
|
_ = decrement_depth()
|
||||||
error_instance = __catch__(exception_class)
|
error_instance = exception_class
|
||||||
stacktrace = __STACKTRACE__
|
stacktrace = __STACKTRACE__
|
||||||
|
|
||||||
IO.puts(
|
IO.puts(
|
||||||
"#{indent}ERROR in #{caller_module_name}.#{unquote(function_name)}: #{Tdd.Debug.format_arg_value(error_instance)}"
|
"#{indent}ERROR in #{caller_module_name}.#{unquote(Macro.escape(function_name))}: #{Tdd.Debug.format_arg_value(error_instance)}"
|
||||||
)
|
)
|
||||||
|
|
||||||
reraise error_instance, stacktrace
|
reraise error_instance, stacktrace
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
# Tracing not enabled, execute original code
|
|
||||||
unquote(actual_code_ast)
|
unquote(actual_code_ast)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# Construct the final definition (def or defp) using Kernel's versions
|
|
||||||
quote do
|
quote do
|
||||||
Kernel.unquote(type)(unquote(call), do: unquote(traced_body))
|
Kernel.unquote(type)(unquote(call), do: unquote(traced_body))
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# Define the overriding def/defp macros directly in the user's module.
|
|
||||||
# These will take precedence over Kernel.def/defp.
|
|
||||||
@doc false
|
@doc false
|
||||||
defmacro def(call, clauses \\ nil) do
|
defmacro def(call, clauses \\ Keyword.new()) do # Default clauses to Keyword.new() or []
|
||||||
generate_traced_function(:def, call, clauses)
|
generate_traced_function(:def, call, clauses, __CALLER__)
|
||||||
end
|
end
|
||||||
|
|
||||||
defmacro defp(call, clauses \\ nil) do
|
@doc false
|
||||||
generate_traced_function(:defp, call, clauses)
|
defmacro defp(call, clauses \\ Keyword.new()) do # Default clauses to Keyword.new() or []
|
||||||
end
|
generate_traced_function(:defp, call, clauses, __CALLER__)
|
||||||
|
end
|
||||||
def generate_traced_function(type, call, clauses) do
|
|
||||||
{function_name, _meta_call, original_args_ast_nodes} = call
|
|
||||||
args = original_args_ast_nodes || []
|
# Capture __CALLER__ to ensure hygiene for variables generated by the macro
|
||||||
|
# if needed, though direct AST manipulation often bypasses some hygiene issues.
|
||||||
block_ast =
|
defp generate_traced_function(type, call_ast, clauses, _caller_env) do
|
||||||
case clauses do
|
{function_name_ast, _meta_call, original_args_ast_nodes} = call_ast
|
||||||
[do: block_content] -> block_content
|
args_patterns_ast = original_args_ast_nodes || []
|
||||||
kw when is_list(kw) -> Keyword.get(kw, :do)
|
|
||||||
_ -> clauses
|
original_body_ast =
|
||||||
end
|
if Keyword.keyword?(clauses) do
|
||||||
|
Keyword.get(clauses, :do, clauses)
|
||||||
quote location: :keep do
|
else
|
||||||
Kernel.unquote(type)(unquote(call)) do
|
clauses
|
||||||
if Tdd.Debug.is_pid_traced?(self()) do
|
end || quote(do: nil) # Default to an empty body if none provided
|
||||||
current_print_depth = Tdd.Debug.increment_depth()
|
|
||||||
indent = String.duplicate(" ", current_print_depth - 1)
|
# Transform argument patterns into expressions suitable for logging at runtime.
|
||||||
args_string = Tdd.Debug.format_args_list([unquote_splicing(args)])
|
logging_expressions_ast_list =
|
||||||
caller_module_name = Module.split(__MODULE__) |> Enum.join(".")
|
Enum.map(args_patterns_ast, fn arg_pattern_ast ->
|
||||||
|
case arg_pattern_ast do
|
||||||
IO.puts("#{indent}CALL: #{caller_module_name}.#{unquote(function_name)}(#{args_string})")
|
{:=, _meta_assign, [var_ast, _sub_pattern_ast]} ->
|
||||||
|
var_ast
|
||||||
try do
|
_ ->
|
||||||
result = unquote(block_ast)
|
Macro.postwalk(arg_pattern_ast, fn
|
||||||
_ = Tdd.Debug.decrement_depth()
|
current_node_ast ->
|
||||||
|
case current_node_ast do
|
||||||
IO.puts("#{indent}RETURN from #{caller_module_name}.#{unquote(function_name)}: #{Tdd.Debug.format_arg_value(result)}")
|
# Match any underscore, regardless of context if it's a bare underscore node
|
||||||
result
|
{:_, _, context} when is_atom(context) -> # context is often Elixir or nil
|
||||||
rescue
|
quote do Tdd.Debug.__internal_placeholder_for_ignored_arg__() end
|
||||||
exception_class ->
|
_ ->
|
||||||
_ = Tdd.Debug.decrement_depth()
|
current_node_ast
|
||||||
error_instance = __catch__(exception_class)
|
end
|
||||||
stacktrace = __STACKTRACE__
|
end)
|
||||||
|
end
|
||||||
IO.puts("#{indent}ERROR in #{caller_module_name}.#{unquote(function_name)}: #{Tdd.Debug.format_arg_value(error_instance)}")
|
end)
|
||||||
reraise error_instance, stacktrace
|
|
||||||
end
|
# This is the AST for the code that will become the *actual body* of the traced function.
|
||||||
else
|
traced_body_inner_ast =
|
||||||
unquote(block_ast)
|
quote do
|
||||||
end
|
if is_pid_traced?(self()) do
|
||||||
end
|
current_print_depth = increment_depth()
|
||||||
|
indent = String.duplicate(" ", current_print_depth - 1)
|
||||||
|
|
||||||
|
# Note: Macro.escape is used here because logging_expressions_ast_list
|
||||||
|
# and function_name_ast are ASTs being injected into this quote.
|
||||||
|
__runtime_arg_values_for_logging__ =
|
||||||
|
[unquote_splicing(Macro.escape(logging_expressions_ast_list, unquote: true))]
|
||||||
|
|
||||||
|
args_string = format_args_list(__runtime_arg_values_for_logging__)
|
||||||
|
caller_module_name_str = Module.split(__MODULE__) |> Enum.join(".")
|
||||||
|
|
||||||
|
printable_function_name_str =
|
||||||
|
case unquote(Macro.escape(function_name_ast, unquote: true)) do
|
||||||
|
fn_name_atom when is_atom(fn_name_atom) -> Atom.to_string(fn_name_atom)
|
||||||
|
fn_name_ast_complex -> Macro.to_string(fn_name_ast_complex)
|
||||||
|
end
|
||||||
|
|
||||||
|
IO.puts(
|
||||||
|
"#{indent}CALL: #{caller_module_name_str}.#{printable_function_name_str}(#{args_string})"
|
||||||
|
)
|
||||||
|
|
||||||
|
try do
|
||||||
|
# Original body is injected here, also escaped.
|
||||||
|
result = unquote(Macro.escape(original_body_ast, unquote: true))
|
||||||
|
_ = Tdd.Debug.decrement_depth()
|
||||||
|
IO.puts(
|
||||||
|
"#{indent}RETURN from #{caller_module_name_str}.#{printable_function_name_str}: #{Tdd.Debug.format_arg_value(result)}"
|
||||||
|
)
|
||||||
|
result
|
||||||
|
rescue
|
||||||
|
exception_class ->
|
||||||
|
_ = Tdd.Debug.decrement_depth()
|
||||||
|
error_instance = exception_class
|
||||||
|
stacktrace = __STACKTRACE__
|
||||||
|
IO.puts(
|
||||||
|
"#{indent}ERROR in #{caller_module_name_str}.#{printable_function_name_str}: #{Tdd.Debug.format_arg_value(error_instance)}"
|
||||||
|
)
|
||||||
|
reraise error_instance, stacktrace
|
||||||
|
end
|
||||||
|
else
|
||||||
|
# Tracing not enabled, execute original body directly (escaped).
|
||||||
|
unquote(Macro.escape(original_body_ast, unquote: true))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# Construct the final `Kernel.def` or `Kernel.defp` call.
|
||||||
|
# `call_ast` is the original function head.
|
||||||
|
# `traced_body_inner_ast` is the AST for the body we just constructed.
|
||||||
|
IO.inspect(call_ast, label: "call_ast")
|
||||||
|
IO.inspect(Macro.escape(call_ast, unquote: true), label: "Macro.escape(call_ast, unquote: true)")
|
||||||
|
final_definition_ast =
|
||||||
|
quote location: :keep do # unquote: false is default and implied if not set
|
||||||
|
Kernel.unquote(type)(
|
||||||
|
unquote(call_ast),
|
||||||
|
do: unquote(traced_body_inner_ast) # The body AST is passed via `do:`
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
# Uncomment for debugging the generated code:
|
||||||
|
# IO.inspect(Macro.to_string(final_definition_ast), label: "Generated for #{Macro.to_string(call_ast)}")
|
||||||
|
|
||||||
|
final_definition_ast
|
||||||
end
|
end
|
||||||
end
|
|
||||||
# REMOVE the defmacro def/2 and defmacro defp/2 from here.
|
|
||||||
# They are now defined by the __using__ macro.
|
|
||||||
|
|
||||||
# --- Your TDD Graph Printing (unrelated to the tracing error, kept for completeness) ---
|
# --- Your TDD Graph Printing (unrelated to the tracing error, kept for completeness) ---
|
||||||
@doc "Prints a formatted representation of a TDD graph starting from an ID."
|
@doc "Prints a formatted representation of a TDD graph starting from an ID."
|
||||||
def print(id) do
|
def print(id) do
|
||||||
# Assuming Tdd.Store is defined elsewhere and aliased if needed
|
alias Tdd.Store # Assuming Tdd.Store is available
|
||||||
# Or ensure it's globally available if defined in another file
|
|
||||||
alias Tdd.Store
|
|
||||||
IO.puts("--- TDD Graph (ID: #{id}) ---")
|
IO.puts("--- TDD Graph (ID: #{id}) ---")
|
||||||
do_print(id, 0, MapSet.new())
|
do_print(id, 0, MapSet.new())
|
||||||
IO.puts("------------------------")
|
IO.puts("------------------------")
|
||||||
@ -275,14 +343,12 @@ end
|
|||||||
:ok
|
:ok
|
||||||
else
|
else
|
||||||
new_visited = MapSet.put(visited, id)
|
new_visited = MapSet.put(visited, id)
|
||||||
# Assuming Tdd.Store.get_node/1 is available
|
alias Tdd.Store # Assuming Tdd.Store is available
|
||||||
case Tdd.Store.get_node(id) do
|
case Tdd.Store.get_node(id) do
|
||||||
{:ok, :true_terminal} ->
|
{:ok, :true_terminal} ->
|
||||||
IO.puts("#{prefix}ID #{id} -> TRUE")
|
IO.puts("#{prefix}ID #{id} -> TRUE")
|
||||||
|
|
||||||
{:ok, :false_terminal} ->
|
{:ok, :false_terminal} ->
|
||||||
IO.puts("#{prefix}ID #{id} -> FALSE")
|
IO.puts("#{prefix}ID #{id} -> FALSE")
|
||||||
|
|
||||||
{:ok, {var, y, n, d}} ->
|
{:ok, {var, y, n, d}} ->
|
||||||
IO.puts("#{prefix}ID #{id}: IF #{inspect(var)}")
|
IO.puts("#{prefix}ID #{id}: IF #{inspect(var)}")
|
||||||
IO.puts("#{prefix} ├─ Yes:")
|
IO.puts("#{prefix} ├─ Yes:")
|
||||||
@ -291,7 +357,6 @@ end
|
|||||||
do_print(n, indent_level + 2, new_visited)
|
do_print(n, indent_level + 2, new_visited)
|
||||||
IO.puts("#{prefix} └─ DC:")
|
IO.puts("#{prefix} └─ DC:")
|
||||||
do_print(d, indent_level + 2, new_visited)
|
do_print(d, indent_level + 2, new_visited)
|
||||||
|
|
||||||
{:error, reason} ->
|
{:error, reason} ->
|
||||||
IO.puts("#{prefix}ID #{id}: ERROR - #{reason}")
|
IO.puts("#{prefix}ID #{id}: ERROR - #{reason}")
|
||||||
end
|
end
|
||||||
@ -1349,12 +1414,29 @@ defmodule Tdd.Algo do
|
|||||||
@spec negate(non_neg_integer) :: non_neg_integer
|
@spec negate(non_neg_integer) :: non_neg_integer
|
||||||
def negate(tdd_id) do
|
def negate(tdd_id) do
|
||||||
cache_key = {:negate, tdd_id}
|
cache_key = {:negate, tdd_id}
|
||||||
|
IO.inspect(tdd_id)
|
||||||
case Store.get_op_cache(cache_key) do
|
case Store.get_op_cache(cache_key) do
|
||||||
{:ok, result_id} ->
|
{:ok, result_id} ->
|
||||||
result_id
|
result_id
|
||||||
|
|
||||||
:not_found ->
|
|
||||||
|
:not_found ->
|
||||||
|
|
||||||
|
result_id =
|
||||||
|
case Store.get_node(tdd_id) do
|
||||||
|
{:ok, :true_terminal} ->
|
||||||
|
Store.false_node_id()
|
||||||
|
|
||||||
|
{:ok, :false_terminal} ->
|
||||||
|
Store.true_node_id()
|
||||||
|
|
||||||
|
{:ok, {var, y, n, d}} ->
|
||||||
|
Store.find_or_create_node(var, negate(y), negate(n), negate(d))
|
||||||
|
end
|
||||||
|
|
||||||
|
Store.put_op_cache(cache_key, result_id)
|
||||||
|
result_id
|
||||||
|
{ :error, :not_found } ->
|
||||||
result_id =
|
result_id =
|
||||||
case Store.get_node(tdd_id) do
|
case Store.get_node(tdd_id) do
|
||||||
{:ok, :true_terminal} ->
|
{:ok, :true_terminal} ->
|
||||||
@ -3016,6 +3098,7 @@ defmodule CompilerAlgoTests do
|
|||||||
|
|
||||||
# --- Section: Basic Subtyping ---
|
# --- Section: Basic Subtyping ---
|
||||||
IO.puts("\n--- Section: Basic Subtyping ---")
|
IO.puts("\n--- Section: Basic Subtyping ---")
|
||||||
|
Tdd.Debug.enable_tracing()
|
||||||
test_subtype(":foo <: atom", true, {:literal, :foo}, :atom)
|
test_subtype(":foo <: atom", true, {:literal, :foo}, :atom)
|
||||||
test_subtype("atom <: :foo", false, :atom, {:literal, :foo})
|
test_subtype("atom <: :foo", false, :atom, {:literal, :foo})
|
||||||
test_subtype(":foo <: integer", false, {:literal, :foo}, :integer)
|
test_subtype(":foo <: integer", false, {:literal, :foo}, :integer)
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user