checkpoint but debug is still kinda fucked
This commit is contained in:
parent
8a6f84238e
commit
a42c981fa0
295
debug.exs
Normal file
295
debug.exs
Normal file
@ -0,0 +1,295 @@
|
|||||||
|
defmodule Tdd.Debug do
|
||||||
|
@moduledoc """
|
||||||
|
Provides macros to wrap `def` and `defp` for simple function call/return tracing.
|
||||||
|
Logs arguments as a list and return values using `IO.inspect`.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# --- Agent for Tracing State ---
|
||||||
|
@agent_name Tdd.Debug.StateAgent
|
||||||
|
|
||||||
|
def init_agent_if_needed do
|
||||||
|
case Process.whereis(@agent_name) do
|
||||||
|
nil -> Agent.start_link(fn -> MapSet.new() end, name: @agent_name)
|
||||||
|
_pid -> :ok
|
||||||
|
end
|
||||||
|
:ok
|
||||||
|
end
|
||||||
|
|
||||||
|
def add_traced_pid(pid) when is_pid(pid) do
|
||||||
|
init_agent_if_needed()
|
||||||
|
Agent.update(@agent_name, &MapSet.put(&1, pid))
|
||||||
|
end
|
||||||
|
|
||||||
|
def remove_traced_pid(pid) when is_pid(pid) do
|
||||||
|
case Process.whereis(@agent_name) do
|
||||||
|
nil -> :ok
|
||||||
|
agent_pid -> Agent.cast(agent_pid, fn state -> MapSet.delete(state, pid) end)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def is_pid_traced?(pid) when is_pid(pid) do
|
||||||
|
case Process.whereis(@agent_name) do
|
||||||
|
nil ->
|
||||||
|
false
|
||||||
|
agent_pid ->
|
||||||
|
try do
|
||||||
|
Agent.get(agent_pid, &MapSet.member?(&1, pid), :infinity)
|
||||||
|
rescue
|
||||||
|
_e in [Exit, ArgumentError] -> false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# --- Tracing Control Functions ---
|
||||||
|
@doc "Enables function call tracing for the current process."
|
||||||
|
def enable_tracing do
|
||||||
|
pid_to_trace = self()
|
||||||
|
add_traced_pid(pid_to_trace)
|
||||||
|
ref = Process.monitor(pid_to_trace)
|
||||||
|
|
||||||
|
Process.spawn(fn ->
|
||||||
|
receive do
|
||||||
|
{:DOWN, ^ref, :process, ^pid_to_trace, _reason} ->
|
||||||
|
remove_traced_pid(pid_to_trace)
|
||||||
|
after
|
||||||
|
3_600_000 -> # 1 hour safety timeout
|
||||||
|
remove_traced_pid(pid_to_trace)
|
||||||
|
end
|
||||||
|
end, [:monitor])
|
||||||
|
:ok
|
||||||
|
end
|
||||||
|
|
||||||
|
@doc "Disables function call tracing for the current process."
|
||||||
|
def disable_tracing do
|
||||||
|
remove_traced_pid(self())
|
||||||
|
:ok
|
||||||
|
end
|
||||||
|
|
||||||
|
@doc "Runs the given 0-arity function with tracing enabled, then disables it."
|
||||||
|
def run(fun) when is_function(fun, 0) do
|
||||||
|
enable_tracing()
|
||||||
|
try do
|
||||||
|
fun.()
|
||||||
|
after
|
||||||
|
disable_tracing()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# --- Process Dictionary for Call Depth ---
|
||||||
|
defp get_depth, do: Process.get(:tdd_debug_depth, 0)
|
||||||
|
def increment_depth do
|
||||||
|
new_depth = get_depth() + 1
|
||||||
|
Process.put(:tdd_debug_depth, new_depth)
|
||||||
|
new_depth
|
||||||
|
end
|
||||||
|
def decrement_depth do
|
||||||
|
new_depth = max(0, get_depth() - 1)
|
||||||
|
Process.put(:tdd_debug_depth, new_depth)
|
||||||
|
new_depth
|
||||||
|
end
|
||||||
|
|
||||||
|
# --- Core Macro Logic ---
|
||||||
|
defmacro __using__(_opts) do
|
||||||
|
quote do
|
||||||
|
import Kernel, except: [def: 1, def: 2, defp: 1, defp: 2]
|
||||||
|
import Tdd.Debug
|
||||||
|
require Tdd.Debug
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
@doc false
|
||||||
|
defmacro def(call, clauses \\ Keyword.new()) do
|
||||||
|
generate_traced_function(:def, call, clauses, __CALLER__)
|
||||||
|
end
|
||||||
|
|
||||||
|
@doc false
|
||||||
|
defmacro defp(call, clauses \\ Keyword.new()) do
|
||||||
|
generate_traced_function(:defp, call, clauses, __CALLER__)
|
||||||
|
end
|
||||||
|
defp is_simple_variable_ast?(ast_node) do
|
||||||
|
case ast_node do
|
||||||
|
{var_name, _meta, _context} when is_atom(var_name) ->
|
||||||
|
var_name != :_
|
||||||
|
_ -> false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
defp generate_traced_function(type, call_ast, clauses, caller_env) do
|
||||||
|
require Macro # Good practice
|
||||||
|
|
||||||
|
{function_name_ast, meta_call, original_args_patterns_ast_nullable} = call_ast
|
||||||
|
original_args_patterns_ast_list = original_args_patterns_ast_nullable || []
|
||||||
|
|
||||||
|
original_body_ast =
|
||||||
|
(if Keyword.keyword?(clauses) do
|
||||||
|
Keyword.get(clauses, :do, clauses)
|
||||||
|
else
|
||||||
|
clauses
|
||||||
|
end) || quote(do: nil)
|
||||||
|
|
||||||
|
mapped_and_generated_vars_tuples =
|
||||||
|
Enum.map(Enum.with_index(original_args_patterns_ast_list), fn {original_pattern_ast, index} ->
|
||||||
|
# __td_arg_N__ is for logging, make it hygienic with `nil` context (or __MODULE__)
|
||||||
|
td_arg_var = Macro.var(String.to_atom("__td_arg_#{index}__"), nil)
|
||||||
|
|
||||||
|
{final_pattern_for_head, rhs_for_td_arg_assignment} =
|
||||||
|
case original_pattern_ast do
|
||||||
|
# 1. Ignored variable: `_`
|
||||||
|
# AST: {:_, meta, context_module_or_nil}
|
||||||
|
{:_, _, _} = underscore_ast ->
|
||||||
|
{underscore_ast, quote(do: :__td_ignored_argument__)}
|
||||||
|
|
||||||
|
# 2. Assignment pattern: `var = pattern` or `var = _`
|
||||||
|
# AST: {:=, meta, [lhs, rhs_of_assign]}
|
||||||
|
{:=, _meta_assign, [lhs_of_assign, _rhs_of_assign]} = assignment_pattern_ast ->
|
||||||
|
if is_simple_variable_ast?(lhs_of_assign) do
|
||||||
|
{assignment_pattern_ast, lhs_of_assign} # Head uses `var = pattern`, log `var`
|
||||||
|
else
|
||||||
|
# LHS is complex (e.g., `%{key: v} = pattern`), capture the whole value.
|
||||||
|
captured_val_var = Macro.unique_var(String.to_atom("tdc_assign_#{index}"), Elixir)
|
||||||
|
new_head_pattern = quote do unquote(captured_val_var) = unquote(assignment_pattern_ast) end
|
||||||
|
{new_head_pattern, captured_val_var}
|
||||||
|
end
|
||||||
|
|
||||||
|
# 3. Default argument: `pattern_before_default \\ default_value`
|
||||||
|
# AST: {:\|, meta, [pattern_before_default, default_value_ast]}
|
||||||
|
{:\\, _meta_default, [pattern_before_default, _default_value_ast]} = default_arg_pattern_ast ->
|
||||||
|
cond do
|
||||||
|
# 3a. `var \\ default`
|
||||||
|
is_simple_variable_ast?(pattern_before_default) ->
|
||||||
|
{default_arg_pattern_ast, pattern_before_default}
|
||||||
|
|
||||||
|
# 3b. `(var = inner_pattern) \\ default`
|
||||||
|
match?({:=, _, [lhs_inner_assign, _]}, pattern_before_default) and
|
||||||
|
is_simple_variable_ast?(pattern_before_default |> elem(2) |> Enum.at(0)) ->
|
||||||
|
{:=, _, [lhs_inner_assign, _]}= pattern_before_default
|
||||||
|
# `lhs_inner_assign` is the var on the left of `=`
|
||||||
|
{default_arg_pattern_ast, lhs_inner_assign}
|
||||||
|
|
||||||
|
# 3c. `(complex_pattern) \\ default` or `(_ = inner_pattern) \\ default` etc.
|
||||||
|
true ->
|
||||||
|
captured_val_var = Macro.unique_var(String.to_atom("tdc_def_#{index}"), Elixir)
|
||||||
|
new_head_pattern = quote do unquote(captured_val_var) = unquote(default_arg_pattern_ast) end
|
||||||
|
{new_head_pattern, captured_val_var}
|
||||||
|
end
|
||||||
|
|
||||||
|
# 4. Simple variable `var` (checked using our helper)
|
||||||
|
# or other complex patterns/literals not caught above.
|
||||||
|
ast_node ->
|
||||||
|
if is_simple_variable_ast?(ast_node) do
|
||||||
|
{ast_node, ast_node} # Head uses `var`, log `var`
|
||||||
|
else
|
||||||
|
# It's a complex pattern (e.g., `%{a:x}`, `[h|t]`) or a literal not assignable to.
|
||||||
|
captured_val_var = Macro.unique_var(String.to_atom("tdc_pat_#{index}"), Elixir)
|
||||||
|
new_head_pattern = quote do unquote(captured_val_var) = unquote(ast_node) end
|
||||||
|
{new_head_pattern, captured_val_var}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
assignment_ast = quote do unquote(td_arg_var) = unquote(rhs_for_td_arg_assignment) end
|
||||||
|
{final_pattern_for_head, assignment_ast, td_arg_var}
|
||||||
|
end)
|
||||||
|
|
||||||
|
{new_args_patterns_for_head_list, assignments_for_logging_vars_ast_list, generated_vars_to_log_asts} =
|
||||||
|
if mapped_and_generated_vars_tuples == [],
|
||||||
|
do: {[], [], []},
|
||||||
|
else: mapped_and_generated_vars_tuples |> Enum.map(&Tuple.to_list(&1)) |> Enum.zip()|> Enum.map(&Tuple.to_list(&1))
|
||||||
|
|> then(fn [a, b, c] -> {a, b, c} end)
|
||||||
|
# Enum.unzip(mapped_and_generated_vars_tuples)
|
||||||
|
|
||||||
|
new_call_ast = {function_name_ast, meta_call, new_args_patterns_for_head_list}
|
||||||
|
|
||||||
|
traced_body_inner_ast =
|
||||||
|
quote do
|
||||||
|
unquote_splicing(assignments_for_logging_vars_ast_list)
|
||||||
|
|
||||||
|
if Tdd.Debug.is_pid_traced?(self()) do
|
||||||
|
current_print_depth = Tdd.Debug.increment_depth()
|
||||||
|
indent = String.duplicate(" ", current_print_depth - 1)
|
||||||
|
runtime_arg_values = [unquote_splicing(generated_vars_to_log_asts)]
|
||||||
|
|
||||||
|
actual_module_name_str = Atom.to_string(unquote(caller_env.module))
|
||||||
|
|
||||||
|
# The function_name_ast is resolved at macro expansion time.
|
||||||
|
# If it's `def foo(...)`, `unquote(function_name_ast)` becomes `:foo`.
|
||||||
|
# If `def unquote(name_var)(...)`, it resolves `name_var`.
|
||||||
|
resolved_fn_name = unquote(function_name_ast)
|
||||||
|
printable_function_name_str =
|
||||||
|
if is_atom(resolved_fn_name) do
|
||||||
|
Atom.to_string(resolved_fn_name)
|
||||||
|
else
|
||||||
|
Macro.to_string(resolved_fn_name) # For complex names / operators if AST passed
|
||||||
|
end
|
||||||
|
|
||||||
|
IO.puts(
|
||||||
|
"#{indent}CALL: #{actual_module_name_str}.#{printable_function_name_str}"
|
||||||
|
)
|
||||||
|
IO.puts(
|
||||||
|
"#{indent} ARGS: #{inspect(runtime_arg_values)}"
|
||||||
|
)
|
||||||
|
|
||||||
|
try do
|
||||||
|
result = unquote(original_body_ast)
|
||||||
|
_ = Tdd.Debug.decrement_depth()
|
||||||
|
IO.puts(
|
||||||
|
"#{indent}RETURN from #{actual_module_name_str}.#{printable_function_name_str}: #{inspect(result)}"
|
||||||
|
)
|
||||||
|
result
|
||||||
|
rescue
|
||||||
|
exception_class ->
|
||||||
|
error_instance = exception_class
|
||||||
|
stacktrace = __STACKTRACE__
|
||||||
|
_ = Tdd.Debug.decrement_depth()
|
||||||
|
IO.puts(
|
||||||
|
"#{indent}ERROR in #{actual_module_name_str}.#{printable_function_name_str}: #{inspect(error_instance)}"
|
||||||
|
)
|
||||||
|
reraise error_instance, stacktrace
|
||||||
|
end
|
||||||
|
else
|
||||||
|
unquote(original_body_ast)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
final_definition_ast =
|
||||||
|
quote location: :keep do
|
||||||
|
Kernel.unquote(type)(
|
||||||
|
unquote(new_call_ast),
|
||||||
|
do: unquote(traced_body_inner_ast)
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
final_definition_ast
|
||||||
|
end
|
||||||
|
|
||||||
|
# --- TDD Graph Printing (Kept as it was, not directly related to call tracing simplification) ---
|
||||||
|
@doc "Prints a formatted representation of a TDD graph structure."
|
||||||
|
def print_tdd_graph(id, store_module \\ Tdd.Store) do
|
||||||
|
IO.puts("--- TDD Graph (ID: #{id}) ---")
|
||||||
|
do_print_tdd_node(id, 0, MapSet.new(), store_module)
|
||||||
|
IO.puts("------------------------")
|
||||||
|
end
|
||||||
|
|
||||||
|
defp do_print_tdd_node(id, indent_level, visited, store_module) do
|
||||||
|
prefix = String.duplicate(" ", indent_level)
|
||||||
|
|
||||||
|
if MapSet.member?(visited, id) do
|
||||||
|
IO.puts("#{prefix}ID #{id} -> [Seen, recursive link]")
|
||||||
|
:ok
|
||||||
|
else
|
||||||
|
new_visited = MapSet.put(visited, id)
|
||||||
|
case store_module.get_node(id) do # Assumes store_module.get_node/1 exists
|
||||||
|
{:ok, :true_terminal} -> IO.puts("#{prefix}ID #{id} -> TRUE")
|
||||||
|
{:ok, :false_terminal} -> IO.puts("#{prefix}ID #{id} -> FALSE")
|
||||||
|
{:ok, {var, y_id, n_id, dc_id}} ->
|
||||||
|
IO.puts("#{prefix}ID #{id}: IF #{inspect(var)}")
|
||||||
|
IO.puts("#{prefix} ├─ Yes (to ID #{y_id}):")
|
||||||
|
do_print_tdd_node(y_id, indent_level + 2, new_visited, store_module)
|
||||||
|
IO.puts("#{prefix} ├─ No (to ID #{n_id}):")
|
||||||
|
do_print_tdd_node(n_id, indent_level + 2, new_visited, store_module)
|
||||||
|
IO.puts("#{prefix} └─ DC (to ID #{dc_id}):")
|
||||||
|
do_print_tdd_node(dc_id, indent_level + 2, new_visited, store_module)
|
||||||
|
{:error, reason} ->
|
||||||
|
IO.puts("#{prefix}ID #{id}: ERROR - #{reason}")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
309
new.exs
309
new.exs
@ -1,298 +1,5 @@
|
|||||||
defmodule Tdd.Debug do
|
|
||||||
@moduledoc """
|
Code.require_file("./debug.exs")
|
||||||
Provides macros to wrap `def` and `defp` for simple function call/return tracing.
|
|
||||||
Logs arguments as a list and return values using `IO.inspect`.
|
|
||||||
"""
|
|
||||||
|
|
||||||
# --- Agent for Tracing State ---
|
|
||||||
@agent_name Tdd.Debug.StateAgent
|
|
||||||
|
|
||||||
def init_agent_if_needed do
|
|
||||||
case Process.whereis(@agent_name) do
|
|
||||||
nil -> Agent.start_link(fn -> MapSet.new() end, name: @agent_name)
|
|
||||||
_pid -> :ok
|
|
||||||
end
|
|
||||||
:ok
|
|
||||||
end
|
|
||||||
|
|
||||||
def add_traced_pid(pid) when is_pid(pid) do
|
|
||||||
init_agent_if_needed()
|
|
||||||
Agent.update(@agent_name, &MapSet.put(&1, pid))
|
|
||||||
end
|
|
||||||
|
|
||||||
def remove_traced_pid(pid) when is_pid(pid) do
|
|
||||||
case Process.whereis(@agent_name) do
|
|
||||||
nil -> :ok
|
|
||||||
agent_pid -> Agent.cast(agent_pid, fn state -> MapSet.delete(state, pid) end)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def is_pid_traced?(pid) when is_pid(pid) do
|
|
||||||
case Process.whereis(@agent_name) do
|
|
||||||
nil ->
|
|
||||||
false
|
|
||||||
agent_pid ->
|
|
||||||
try do
|
|
||||||
Agent.get(agent_pid, &MapSet.member?(&1, pid), :infinity)
|
|
||||||
rescue
|
|
||||||
_e in [Exit, ArgumentError] -> false
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# --- Tracing Control Functions ---
|
|
||||||
@doc "Enables function call tracing for the current process."
|
|
||||||
def enable_tracing do
|
|
||||||
pid_to_trace = self()
|
|
||||||
add_traced_pid(pid_to_trace)
|
|
||||||
ref = Process.monitor(pid_to_trace)
|
|
||||||
|
|
||||||
Process.spawn(fn ->
|
|
||||||
receive do
|
|
||||||
{:DOWN, ^ref, :process, ^pid_to_trace, _reason} ->
|
|
||||||
remove_traced_pid(pid_to_trace)
|
|
||||||
after
|
|
||||||
3_600_000 -> # 1 hour safety timeout
|
|
||||||
remove_traced_pid(pid_to_trace)
|
|
||||||
end
|
|
||||||
end, [:monitor])
|
|
||||||
:ok
|
|
||||||
end
|
|
||||||
|
|
||||||
@doc "Disables function call tracing for the current process."
|
|
||||||
def disable_tracing do
|
|
||||||
remove_traced_pid(self())
|
|
||||||
:ok
|
|
||||||
end
|
|
||||||
|
|
||||||
@doc "Runs the given 0-arity function with tracing enabled, then disables it."
|
|
||||||
def run(fun) when is_function(fun, 0) do
|
|
||||||
enable_tracing()
|
|
||||||
try do
|
|
||||||
fun.()
|
|
||||||
after
|
|
||||||
disable_tracing()
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# --- Process Dictionary for Call Depth ---
|
|
||||||
defp get_depth, do: Process.get(:tdd_debug_depth, 0)
|
|
||||||
def increment_depth do
|
|
||||||
new_depth = get_depth() + 1
|
|
||||||
Process.put(:tdd_debug_depth, new_depth)
|
|
||||||
new_depth
|
|
||||||
end
|
|
||||||
def decrement_depth do
|
|
||||||
new_depth = max(0, get_depth() - 1)
|
|
||||||
Process.put(:tdd_debug_depth, new_depth)
|
|
||||||
new_depth
|
|
||||||
end
|
|
||||||
|
|
||||||
# --- Core Macro Logic ---
|
|
||||||
defmacro __using__(_opts) do
|
|
||||||
quote do
|
|
||||||
import Kernel, except: [def: 1, def: 2, defp: 1, defp: 2]
|
|
||||||
import Tdd.Debug
|
|
||||||
require Tdd.Debug
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
@doc false
|
|
||||||
defmacro def(call, clauses \\ Keyword.new()) do
|
|
||||||
generate_traced_function(:def, call, clauses, __CALLER__)
|
|
||||||
end
|
|
||||||
|
|
||||||
@doc false
|
|
||||||
defmacro defp(call, clauses \\ Keyword.new()) do
|
|
||||||
generate_traced_function(:defp, call, clauses, __CALLER__)
|
|
||||||
end
|
|
||||||
defp is_simple_variable_ast?(ast_node) do
|
|
||||||
case ast_node do
|
|
||||||
{var_name, _meta, _context} when is_atom(var_name) ->
|
|
||||||
var_name != :_
|
|
||||||
_ -> false
|
|
||||||
end
|
|
||||||
end
|
|
||||||
defp generate_traced_function(type, call_ast, clauses, caller_env) do
|
|
||||||
require Macro # Good practice
|
|
||||||
|
|
||||||
{function_name_ast, meta_call, original_args_patterns_ast_nullable} = call_ast
|
|
||||||
original_args_patterns_ast_list = original_args_patterns_ast_nullable || []
|
|
||||||
|
|
||||||
original_body_ast =
|
|
||||||
(if Keyword.keyword?(clauses) do
|
|
||||||
Keyword.get(clauses, :do, clauses)
|
|
||||||
else
|
|
||||||
clauses
|
|
||||||
end) || quote(do: nil)
|
|
||||||
|
|
||||||
mapped_and_generated_vars_tuples =
|
|
||||||
Enum.map(Enum.with_index(original_args_patterns_ast_list), fn {original_pattern_ast, index} ->
|
|
||||||
# __td_arg_N__ is for logging, make it hygienic with `nil` context (or __MODULE__)
|
|
||||||
td_arg_var = Macro.var(String.to_atom("__td_arg_#{index}__"), nil)
|
|
||||||
|
|
||||||
{final_pattern_for_head, rhs_for_td_arg_assignment} =
|
|
||||||
case original_pattern_ast do
|
|
||||||
# 1. Ignored variable: `_`
|
|
||||||
# AST: {:_, meta, context_module_or_nil}
|
|
||||||
{:_, _, _} = underscore_ast ->
|
|
||||||
{underscore_ast, quote(do: :__td_ignored_argument__)}
|
|
||||||
|
|
||||||
# 2. Assignment pattern: `var = pattern` or `var = _`
|
|
||||||
# AST: {:=, meta, [lhs, rhs_of_assign]}
|
|
||||||
{:=, _meta_assign, [lhs_of_assign, _rhs_of_assign]} = assignment_pattern_ast ->
|
|
||||||
if is_simple_variable_ast?(lhs_of_assign) do
|
|
||||||
{assignment_pattern_ast, lhs_of_assign} # Head uses `var = pattern`, log `var`
|
|
||||||
else
|
|
||||||
# LHS is complex (e.g., `%{key: v} = pattern`), capture the whole value.
|
|
||||||
captured_val_var = Macro.unique_var(String.to_atom("tdc_assign_#{index}"), Elixir)
|
|
||||||
new_head_pattern = quote do unquote(captured_val_var) = unquote(assignment_pattern_ast) end
|
|
||||||
{new_head_pattern, captured_val_var}
|
|
||||||
end
|
|
||||||
|
|
||||||
# 3. Default argument: `pattern_before_default \\ default_value`
|
|
||||||
# AST: {:\|, meta, [pattern_before_default, default_value_ast]}
|
|
||||||
{:\\, _meta_default, [pattern_before_default, _default_value_ast]} = default_arg_pattern_ast ->
|
|
||||||
cond do
|
|
||||||
# 3a. `var \\ default`
|
|
||||||
is_simple_variable_ast?(pattern_before_default) ->
|
|
||||||
{default_arg_pattern_ast, pattern_before_default}
|
|
||||||
|
|
||||||
# 3b. `(var = inner_pattern) \\ default`
|
|
||||||
match?({:=, _, [lhs_inner_assign, _]}, pattern_before_default) and
|
|
||||||
is_simple_variable_ast?(pattern_before_default |> elem(2) |> Enum.at(0)) ->
|
|
||||||
{:=, _, [lhs_inner_assign, _]}= pattern_before_default
|
|
||||||
# `lhs_inner_assign` is the var on the left of `=`
|
|
||||||
{default_arg_pattern_ast, lhs_inner_assign}
|
|
||||||
|
|
||||||
# 3c. `(complex_pattern) \\ default` or `(_ = inner_pattern) \\ default` etc.
|
|
||||||
true ->
|
|
||||||
captured_val_var = Macro.unique_var(String.to_atom("tdc_def_#{index}"), Elixir)
|
|
||||||
new_head_pattern = quote do unquote(captured_val_var) = unquote(default_arg_pattern_ast) end
|
|
||||||
{new_head_pattern, captured_val_var}
|
|
||||||
end
|
|
||||||
|
|
||||||
# 4. Simple variable `var` (checked using our helper)
|
|
||||||
# or other complex patterns/literals not caught above.
|
|
||||||
ast_node ->
|
|
||||||
if is_simple_variable_ast?(ast_node) do
|
|
||||||
{ast_node, ast_node} # Head uses `var`, log `var`
|
|
||||||
else
|
|
||||||
# It's a complex pattern (e.g., `%{a:x}`, `[h|t]`) or a literal not assignable to.
|
|
||||||
captured_val_var = Macro.unique_var(String.to_atom("tdc_pat_#{index}"), Elixir)
|
|
||||||
new_head_pattern = quote do unquote(captured_val_var) = unquote(ast_node) end
|
|
||||||
{new_head_pattern, captured_val_var}
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
assignment_ast = quote do unquote(td_arg_var) = unquote(rhs_for_td_arg_assignment) end
|
|
||||||
{final_pattern_for_head, assignment_ast, td_arg_var}
|
|
||||||
end)
|
|
||||||
|
|
||||||
{new_args_patterns_for_head_list, assignments_for_logging_vars_ast_list, generated_vars_to_log_asts} =
|
|
||||||
if mapped_and_generated_vars_tuples == [],
|
|
||||||
do: {[], [], []},
|
|
||||||
else: mapped_and_generated_vars_tuples |> Enum.map(&Tuple.to_list(&1)) |> Enum.zip()|> Enum.map(&Tuple.to_list(&1))
|
|
||||||
|> then(fn [a, b, c] -> {a, b, c} end)
|
|
||||||
# Enum.unzip(mapped_and_generated_vars_tuples)
|
|
||||||
|
|
||||||
new_call_ast = {function_name_ast, meta_call, new_args_patterns_for_head_list}
|
|
||||||
|
|
||||||
traced_body_inner_ast =
|
|
||||||
quote do
|
|
||||||
unquote_splicing(assignments_for_logging_vars_ast_list)
|
|
||||||
|
|
||||||
if Tdd.Debug.is_pid_traced?(self()) do
|
|
||||||
current_print_depth = Tdd.Debug.increment_depth()
|
|
||||||
indent = String.duplicate(" ", current_print_depth - 1)
|
|
||||||
runtime_arg_values = [unquote_splicing(generated_vars_to_log_asts)]
|
|
||||||
|
|
||||||
actual_module_name_str = Atom.to_string(unquote(caller_env.module))
|
|
||||||
|
|
||||||
# The function_name_ast is resolved at macro expansion time.
|
|
||||||
# If it's `def foo(...)`, `unquote(function_name_ast)` becomes `:foo`.
|
|
||||||
# If `def unquote(name_var)(...)`, it resolves `name_var`.
|
|
||||||
resolved_fn_name = unquote(function_name_ast)
|
|
||||||
printable_function_name_str =
|
|
||||||
if is_atom(resolved_fn_name) do
|
|
||||||
Atom.to_string(resolved_fn_name)
|
|
||||||
else
|
|
||||||
Macro.to_string(resolved_fn_name) # For complex names / operators if AST passed
|
|
||||||
end
|
|
||||||
|
|
||||||
IO.puts(
|
|
||||||
"#{indent}CALL: #{actual_module_name_str}.#{printable_function_name_str}"
|
|
||||||
)
|
|
||||||
IO.puts(
|
|
||||||
"#{indent} ARGS: #{inspect(runtime_arg_values)}"
|
|
||||||
)
|
|
||||||
|
|
||||||
try do
|
|
||||||
result = unquote(original_body_ast)
|
|
||||||
_ = Tdd.Debug.decrement_depth()
|
|
||||||
IO.puts(
|
|
||||||
"#{indent}RETURN from #{actual_module_name_str}.#{printable_function_name_str}: #{inspect(result)}"
|
|
||||||
)
|
|
||||||
result
|
|
||||||
rescue
|
|
||||||
exception_class ->
|
|
||||||
error_instance = exception_class
|
|
||||||
stacktrace = __STACKTRACE__
|
|
||||||
_ = Tdd.Debug.decrement_depth()
|
|
||||||
IO.puts(
|
|
||||||
"#{indent}ERROR in #{actual_module_name_str}.#{printable_function_name_str}: #{inspect(error_instance)}"
|
|
||||||
)
|
|
||||||
reraise error_instance, stacktrace
|
|
||||||
end
|
|
||||||
else
|
|
||||||
unquote(original_body_ast)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
final_definition_ast =
|
|
||||||
quote location: :keep do
|
|
||||||
Kernel.unquote(type)(
|
|
||||||
unquote(new_call_ast),
|
|
||||||
do: unquote(traced_body_inner_ast)
|
|
||||||
)
|
|
||||||
end
|
|
||||||
|
|
||||||
final_definition_ast
|
|
||||||
end
|
|
||||||
|
|
||||||
# --- TDD Graph Printing (Kept as it was, not directly related to call tracing simplification) ---
|
|
||||||
@doc "Prints a formatted representation of a TDD graph structure."
|
|
||||||
def print_tdd_graph(id, store_module \\ Tdd.Store) do
|
|
||||||
IO.puts("--- TDD Graph (ID: #{id}) ---")
|
|
||||||
do_print_tdd_node(id, 0, MapSet.new(), store_module)
|
|
||||||
IO.puts("------------------------")
|
|
||||||
end
|
|
||||||
|
|
||||||
defp do_print_tdd_node(id, indent_level, visited, store_module) do
|
|
||||||
prefix = String.duplicate(" ", indent_level)
|
|
||||||
|
|
||||||
if MapSet.member?(visited, id) do
|
|
||||||
IO.puts("#{prefix}ID #{id} -> [Seen, recursive link]")
|
|
||||||
:ok
|
|
||||||
else
|
|
||||||
new_visited = MapSet.put(visited, id)
|
|
||||||
case store_module.get_node(id) do # Assumes store_module.get_node/1 exists
|
|
||||||
{:ok, :true_terminal} -> IO.puts("#{prefix}ID #{id} -> TRUE")
|
|
||||||
{:ok, :false_terminal} -> IO.puts("#{prefix}ID #{id} -> FALSE")
|
|
||||||
{:ok, {var, y_id, n_id, dc_id}} ->
|
|
||||||
IO.puts("#{prefix}ID #{id}: IF #{inspect(var)}")
|
|
||||||
IO.puts("#{prefix} ├─ Yes (to ID #{y_id}):")
|
|
||||||
do_print_tdd_node(y_id, indent_level + 2, new_visited, store_module)
|
|
||||||
IO.puts("#{prefix} ├─ No (to ID #{n_id}):")
|
|
||||||
do_print_tdd_node(n_id, indent_level + 2, new_visited, store_module)
|
|
||||||
IO.puts("#{prefix} └─ DC (to ID #{dc_id}):")
|
|
||||||
do_print_tdd_node(dc_id, indent_level + 2, new_visited, store_module)
|
|
||||||
{:error, reason} ->
|
|
||||||
IO.puts("#{prefix}ID #{id}: ERROR - #{reason}")
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
defmodule Tdd.TypeSpec do
|
defmodule Tdd.TypeSpec do
|
||||||
@moduledoc """
|
@moduledoc """
|
||||||
@ -640,7 +347,6 @@ defmodule Tdd.TypeSpec do
|
|||||||
end
|
end
|
||||||
|
|
||||||
defmodule Tdd.Store do
|
defmodule Tdd.Store do
|
||||||
use Tdd.Debug
|
|
||||||
@moduledoc """
|
@moduledoc """
|
||||||
Manages the state of the TDD system's node graph and operation cache.
|
Manages the state of the TDD system's node graph and operation cache.
|
||||||
|
|
||||||
@ -824,7 +530,7 @@ defmodule Tdd.Store do
|
|||||||
end
|
end
|
||||||
|
|
||||||
defmodule Tdd.Variable do
|
defmodule Tdd.Variable do
|
||||||
use Tdd.Debug
|
# use Tdd.Debug
|
||||||
@moduledoc """
|
@moduledoc """
|
||||||
Defines the canonical structure for all Tdd predicate variables.
|
Defines the canonical structure for all Tdd predicate variables.
|
||||||
|
|
||||||
@ -876,9 +582,6 @@ defmodule Tdd.Variable do
|
|||||||
end
|
end
|
||||||
|
|
||||||
# --- Category 5: List Properties ---
|
# --- Category 5: List Properties ---
|
||||||
# All are now 4-element tuples. The sorting will be correct.
|
|
||||||
# @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}
|
|
||||||
|
|
||||||
@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()
|
||||||
@ -900,10 +603,6 @@ defmodule Tdd.Predicate.Info do
|
|||||||
@doc "Returns a map of traits for a given predicate variable."
|
@doc "Returns a map of traits for a given predicate variable."
|
||||||
@spec get_traits(term()) :: map() | nil
|
@spec get_traits(term()) :: map() | nil
|
||||||
|
|
||||||
# --- THIS IS THE FIX ---
|
|
||||||
# Add explicit negative implications for primary types. When a value is proven
|
|
||||||
# to be of one primary type, it is implicitly proven NOT to be of the others.
|
|
||||||
|
|
||||||
def get_traits({0, :is_atom, _, _}) do
|
def get_traits({0, :is_atom, _, _}) do
|
||||||
%{
|
%{
|
||||||
type: :primary,
|
type: :primary,
|
||||||
@ -1016,7 +715,7 @@ end
|
|||||||
|
|
||||||
# in a new file, e.g., lib/tdd/consistency/engine.ex
|
# in a new file, e.g., lib/tdd/consistency/engine.ex
|
||||||
defmodule Tdd.Consistency.Engine do
|
defmodule Tdd.Consistency.Engine do
|
||||||
use Tdd.Debug
|
# use Tdd.Debug
|
||||||
@moduledoc """
|
@moduledoc """
|
||||||
A rule-based engine for checking the semantic consistency of a set of assumptions.
|
A rule-based engine for checking the semantic consistency of a set of assumptions.
|
||||||
|
|
||||||
|
|||||||
308
scratch.exs
Normal file
308
scratch.exs
Normal file
@ -0,0 +1,308 @@
|
|||||||
|
defmodule Tdd.Debug do
|
||||||
|
@moduledoc """
|
||||||
|
Provides macros to wrap `def` and `defp` for simple function call/return tracing.
|
||||||
|
Logs arguments as a list and return values using `IO.inspect`.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# --- Agent for Tracing State ---
|
||||||
|
@agent_name Tdd.Debug.StateAgent
|
||||||
|
|
||||||
|
def init_agent_if_needed do
|
||||||
|
case Process.whereis(@agent_name) do
|
||||||
|
nil -> Agent.start_link(fn -> MapSet.new() end, name: @agent_name)
|
||||||
|
_pid -> :ok
|
||||||
|
end
|
||||||
|
:ok
|
||||||
|
end
|
||||||
|
|
||||||
|
def add_traced_pid(pid) when is_pid(pid) do
|
||||||
|
init_agent_if_needed()
|
||||||
|
Agent.update(@agent_name, &MapSet.put(&1, pid))
|
||||||
|
end
|
||||||
|
|
||||||
|
def remove_traced_pid(pid) when is_pid(pid) do
|
||||||
|
case Process.whereis(@agent_name) do
|
||||||
|
nil -> :ok
|
||||||
|
agent_pid -> Agent.cast(agent_pid, fn state -> MapSet.delete(state, pid) end)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def is_pid_traced?(pid) when is_pid(pid) do
|
||||||
|
case Process.whereis(@agent_name) do
|
||||||
|
nil ->
|
||||||
|
false
|
||||||
|
agent_pid ->
|
||||||
|
try do
|
||||||
|
Agent.get(agent_pid, &MapSet.member?(&1, pid), :infinity)
|
||||||
|
rescue
|
||||||
|
_e in [Exit, ArgumentError] -> false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# --- Tracing Control Functions ---
|
||||||
|
@doc "Enables function call tracing for the current process."
|
||||||
|
def enable_tracing do
|
||||||
|
pid_to_trace = self()
|
||||||
|
add_traced_pid(pid_to_trace)
|
||||||
|
ref = Process.monitor(pid_to_trace)
|
||||||
|
|
||||||
|
Process.spawn(fn ->
|
||||||
|
receive do
|
||||||
|
{:DOWN, ^ref, :process, ^pid_to_trace, _reason} ->
|
||||||
|
remove_traced_pid(pid_to_trace)
|
||||||
|
after
|
||||||
|
3_600_000 -> # 1 hour safety timeout
|
||||||
|
remove_traced_pid(pid_to_trace)
|
||||||
|
end
|
||||||
|
end, [:monitor])
|
||||||
|
:ok
|
||||||
|
end
|
||||||
|
|
||||||
|
@doc "Disables function call tracing for the current process."
|
||||||
|
def disable_tracing do
|
||||||
|
remove_traced_pid(self())
|
||||||
|
:ok
|
||||||
|
end
|
||||||
|
|
||||||
|
@doc "Runs the given 0-arity function with tracing enabled, then disables it."
|
||||||
|
def run(fun) when is_function(fun, 0) do
|
||||||
|
enable_tracing()
|
||||||
|
try do
|
||||||
|
fun.()
|
||||||
|
after
|
||||||
|
disable_tracing()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# --- Process Dictionary for Call Depth ---
|
||||||
|
defp get_depth, do: Process.get(:tdd_debug_depth, 0)
|
||||||
|
def increment_depth do
|
||||||
|
new_depth = get_depth() + 1
|
||||||
|
Process.put(:tdd_debug_depth, new_depth)
|
||||||
|
new_depth
|
||||||
|
end
|
||||||
|
def decrement_depth do
|
||||||
|
new_depth = max(0, get_depth() - 1)
|
||||||
|
Process.put(:tdd_debug_depth, new_depth)
|
||||||
|
new_depth
|
||||||
|
end
|
||||||
|
|
||||||
|
# --- Core Macro Logic ---
|
||||||
|
defmacro __using__(_opts) do
|
||||||
|
quote do
|
||||||
|
import Kernel, except: [def: 1, def: 2, defp: 1, defp: 2]
|
||||||
|
import Tdd.Debug
|
||||||
|
require Tdd.Debug
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
@doc false
|
||||||
|
defmacro def(call, clauses \\ Keyword.new()) do
|
||||||
|
generate_traced_function(:def, call, clauses, __CALLER__)
|
||||||
|
end
|
||||||
|
|
||||||
|
@doc false
|
||||||
|
defmacro defp(call, clauses \\ Keyword.new()) do
|
||||||
|
generate_traced_function(:defp, call, clauses, __CALLER__)
|
||||||
|
end
|
||||||
|
defp is_simple_variable_ast?(ast_node) do
|
||||||
|
case ast_node do
|
||||||
|
{var_name, _meta, _context} when is_atom(var_name) ->
|
||||||
|
var_name != :_
|
||||||
|
_ -> false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
defp generate_traced_function(type, call_ast, clauses, caller_env) do
|
||||||
|
require Macro # Good practice
|
||||||
|
|
||||||
|
{function_name_ast, meta_call, original_args_patterns_ast_nullable} = call_ast
|
||||||
|
original_args_patterns_ast_list = original_args_patterns_ast_nullable || []
|
||||||
|
|
||||||
|
original_body_ast =
|
||||||
|
(if Keyword.keyword?(clauses) do
|
||||||
|
Keyword.get(clauses, :do, clauses)
|
||||||
|
else
|
||||||
|
clauses
|
||||||
|
end) || quote(do: nil)
|
||||||
|
|
||||||
|
mapped_and_generated_vars_tuples =
|
||||||
|
Enum.map(Enum.with_index(original_args_patterns_ast_list), fn {original_pattern_ast, index} ->
|
||||||
|
# __td_arg_N__ is for logging, make it hygienic with `nil` context (or __MODULE__)
|
||||||
|
td_arg_var = Macro.var(String.to_atom("__td_arg_#{index}__"), nil)
|
||||||
|
|
||||||
|
{final_pattern_for_head, rhs_for_td_arg_assignment} =
|
||||||
|
case original_pattern_ast do
|
||||||
|
# 1. Ignored variable: `_`
|
||||||
|
# AST: {:_, meta, context_module_or_nil}
|
||||||
|
{:_, _, _} = underscore_ast ->
|
||||||
|
{underscore_ast, quote(do: :__td_ignored_argument__)}
|
||||||
|
|
||||||
|
# 2. Assignment pattern: `var = pattern` or `var = _`
|
||||||
|
# AST: {:=, meta, [lhs, rhs_of_assign]}
|
||||||
|
{:=, _meta_assign, [lhs_of_assign, _rhs_of_assign]} = assignment_pattern_ast ->
|
||||||
|
if is_simple_variable_ast?(lhs_of_assign) do
|
||||||
|
{assignment_pattern_ast, lhs_of_assign} # Head uses `var = pattern`, log `var`
|
||||||
|
else
|
||||||
|
# LHS is complex (e.g., `%{key: v} = pattern`), capture the whole value.
|
||||||
|
captured_val_var = Macro.unique_var(String.to_atom("tdc_assign_#{index}"), Elixir)
|
||||||
|
new_head_pattern = quote do unquote(captured_val_var) = unquote(assignment_pattern_ast) end
|
||||||
|
{new_head_pattern, captured_val_var}
|
||||||
|
end
|
||||||
|
|
||||||
|
# 3. Default argument: `pattern_before_default \\ default_value`
|
||||||
|
# AST: {:\|, meta, [pattern_before_default, default_value_ast]}
|
||||||
|
{:\\, _meta_default, [pattern_before_default, _default_value_ast]} = default_arg_pattern_ast ->
|
||||||
|
cond do
|
||||||
|
# 3a. `var \\ default`
|
||||||
|
is_simple_variable_ast?(pattern_before_default) ->
|
||||||
|
{default_arg_pattern_ast, pattern_before_default}
|
||||||
|
|
||||||
|
# 3b. `(var = inner_pattern) \\ default`
|
||||||
|
match?({:=, _, [lhs_inner_assign, _]}, pattern_before_default) and
|
||||||
|
is_simple_variable_ast?(pattern_before_default |> elem(2) |> Enum.at(0)) ->
|
||||||
|
{:=, _, [lhs_inner_assign, _]}= pattern_before_default
|
||||||
|
# `lhs_inner_assign` is the var on the left of `=`
|
||||||
|
{default_arg_pattern_ast, lhs_inner_assign}
|
||||||
|
|
||||||
|
# 3c. `(complex_pattern) \\ default` or `(_ = inner_pattern) \\ default` etc.
|
||||||
|
true ->
|
||||||
|
captured_val_var = Macro.unique_var(String.to_atom("tdc_def_#{index}"), Elixir)
|
||||||
|
new_head_pattern = quote do unquote(captured_val_var) = unquote(default_arg_pattern_ast) end
|
||||||
|
{new_head_pattern, captured_val_var}
|
||||||
|
end
|
||||||
|
|
||||||
|
# 4. Simple variable `var` (checked using our helper)
|
||||||
|
# or other complex patterns/literals not caught above.
|
||||||
|
ast_node ->
|
||||||
|
if is_simple_variable_ast?(ast_node) do
|
||||||
|
{ast_node, ast_node} # Head uses `var`, log `var`
|
||||||
|
else
|
||||||
|
# It's a complex pattern (e.g., `%{a:x}`, `[h|t]`) or a literal not assignable to.
|
||||||
|
captured_val_var = Macro.unique_var(String.to_atom("tdc_pat_#{index}"), Elixir)
|
||||||
|
new_head_pattern = quote do unquote(captured_val_var) = unquote(ast_node) end
|
||||||
|
{new_head_pattern, captured_val_var}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
assignment_ast = quote do unquote(td_arg_var) = unquote(rhs_for_td_arg_assignment) end
|
||||||
|
{final_pattern_for_head, assignment_ast, td_arg_var}
|
||||||
|
end)
|
||||||
|
|
||||||
|
{new_args_patterns_for_head_list, assignments_for_logging_vars_ast_list, generated_vars_to_log_asts} =
|
||||||
|
if mapped_and_generated_vars_tuples == [],
|
||||||
|
do: {[], [], []},
|
||||||
|
else: mapped_and_generated_vars_tuples |> Enum.map(&Tuple.to_list(&1)) |> Enum.zip()|> Enum.map(&Tuple.to_list(&1))
|
||||||
|
|> then(fn [a, b, c] -> {a, b, c} end)
|
||||||
|
# Enum.unzip(mapped_and_generated_vars_tuples)
|
||||||
|
|
||||||
|
new_call_ast = {function_name_ast, meta_call, new_args_patterns_for_head_list}
|
||||||
|
|
||||||
|
traced_body_inner_ast =
|
||||||
|
quote do
|
||||||
|
unquote_splicing(assignments_for_logging_vars_ast_list)
|
||||||
|
|
||||||
|
if Tdd.Debug.is_pid_traced?(self()) do
|
||||||
|
current_print_depth = Tdd.Debug.increment_depth()
|
||||||
|
indent = String.duplicate(" ", current_print_depth - 1)
|
||||||
|
runtime_arg_values = [unquote_splicing(generated_vars_to_log_asts)]
|
||||||
|
|
||||||
|
actual_module_name_str = Atom.to_string(unquote(caller_env.module))
|
||||||
|
|
||||||
|
# The function_name_ast is resolved at macro expansion time.
|
||||||
|
# If it's `def foo(...)`, `unquote(function_name_ast)` becomes `:foo`.
|
||||||
|
# If `def unquote(name_var)(...)`, it resolves `name_var`.
|
||||||
|
resolved_fn_name = unquote(function_name_ast)
|
||||||
|
printable_function_name_str =
|
||||||
|
if is_atom(resolved_fn_name) do
|
||||||
|
Atom.to_string(resolved_fn_name)
|
||||||
|
else
|
||||||
|
Macro.to_string(resolved_fn_name) # For complex names / operators if AST passed
|
||||||
|
end
|
||||||
|
|
||||||
|
IO.puts(
|
||||||
|
"#{indent}CALL: #{actual_module_name_str}.#{printable_function_name_str}"
|
||||||
|
)
|
||||||
|
IO.puts(
|
||||||
|
"#{indent} ARGS: #{inspect(runtime_arg_values)}"
|
||||||
|
)
|
||||||
|
|
||||||
|
try do
|
||||||
|
result = unquote(original_body_ast)
|
||||||
|
_ = Tdd.Debug.decrement_depth()
|
||||||
|
IO.puts(
|
||||||
|
"#{indent}RETURN from #{actual_module_name_str}.#{printable_function_name_str}: #{inspect(result)}"
|
||||||
|
)
|
||||||
|
result
|
||||||
|
rescue
|
||||||
|
exception_class ->
|
||||||
|
error_instance = exception_class
|
||||||
|
stacktrace = __STACKTRACE__
|
||||||
|
_ = Tdd.Debug.decrement_depth()
|
||||||
|
IO.puts(
|
||||||
|
"#{indent}ERROR in #{actual_module_name_str}.#{printable_function_name_str}: #{inspect(error_instance)}"
|
||||||
|
)
|
||||||
|
reraise error_instance, stacktrace
|
||||||
|
end
|
||||||
|
else
|
||||||
|
unquote(original_body_ast)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
final_definition_ast =
|
||||||
|
quote location: :keep do
|
||||||
|
Kernel.unquote(type)(
|
||||||
|
unquote(new_call_ast),
|
||||||
|
do: unquote(traced_body_inner_ast)
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
final_definition_ast
|
||||||
|
end
|
||||||
|
|
||||||
|
# --- TDD Graph Printing (Kept as it was, not directly related to call tracing simplification) ---
|
||||||
|
@doc "Prints a formatted representation of a TDD graph structure."
|
||||||
|
def print_tdd_graph(id, store_module \\ Tdd.Store) do
|
||||||
|
IO.puts("--- TDD Graph (ID: #{id}) ---")
|
||||||
|
do_print_tdd_node(id, 0, MapSet.new(), store_module)
|
||||||
|
IO.puts("------------------------")
|
||||||
|
end
|
||||||
|
|
||||||
|
defp do_print_tdd_node(id, indent_level, visited, store_module) do
|
||||||
|
prefix = String.duplicate(" ", indent_level)
|
||||||
|
|
||||||
|
if MapSet.member?(visited, id) do
|
||||||
|
IO.puts("#{prefix}ID #{id} -> [Seen, recursive link]")
|
||||||
|
:ok
|
||||||
|
else
|
||||||
|
new_visited = MapSet.put(visited, id)
|
||||||
|
case store_module.get_node(id) do # Assumes store_module.get_node/1 exists
|
||||||
|
{:ok, :true_terminal} -> IO.puts("#{prefix}ID #{id} -> TRUE")
|
||||||
|
{:ok, :false_terminal} -> IO.puts("#{prefix}ID #{id} -> FALSE")
|
||||||
|
{:ok, {var, y_id, n_id, dc_id}} ->
|
||||||
|
IO.puts("#{prefix}ID #{id}: IF #{inspect(var)}")
|
||||||
|
IO.puts("#{prefix} ├─ Yes (to ID #{y_id}):")
|
||||||
|
do_print_tdd_node(y_id, indent_level + 2, new_visited, store_module)
|
||||||
|
IO.puts("#{prefix} ├─ No (to ID #{n_id}):")
|
||||||
|
do_print_tdd_node(n_id, indent_level + 2, new_visited, store_module)
|
||||||
|
IO.puts("#{prefix} └─ DC (to ID #{dc_id}):")
|
||||||
|
do_print_tdd_node(dc_id, indent_level + 2, new_visited, store_module)
|
||||||
|
{:error, reason} ->
|
||||||
|
IO.puts("#{prefix}ID #{id}: ERROR - #{reason}")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
defmodule Asd do
|
||||||
|
use Tdd.Debug
|
||||||
|
def kekistan(dupsko, {:sommething, _}) do
|
||||||
|
IO.inspect("inside ")
|
||||||
|
dupsko + 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
Process.sleep(100)
|
||||||
|
Tdd.Debug.enable_tracing()
|
||||||
|
# Asd.kekistan(1, 2)
|
||||||
|
Asd.kekistan(1, {:sommething, 2})
|
||||||
Loading…
x
Reference in New Issue
Block a user