fuckd
This commit is contained in:
parent
8d8b3607fc
commit
3c7edc67da
243
new.exs
243
new.exs
@ -19,9 +19,8 @@ defmodule Tdd.Debug do
|
|||||||
end
|
end
|
||||||
|
|
||||||
defp remove_traced_pid(pid) when is_pid(pid) do
|
defp remove_traced_pid(pid) when is_pid(pid) do
|
||||||
# Use Process.whereis to avoid race condition if agent stops between calls
|
|
||||||
case Process.whereis(@agent_name) do
|
case Process.whereis(@agent_name) do
|
||||||
nil -> :ok # Agent not running, nothing to remove from
|
nil -> :ok
|
||||||
agent_pid -> Agent.cast(agent_pid, fn state -> MapSet.delete(state, pid) end)
|
agent_pid -> Agent.cast(agent_pid, fn state -> MapSet.delete(state, pid) end)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@ -34,8 +33,7 @@ defmodule Tdd.Debug do
|
|||||||
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 # More specific rescue
|
||||||
_e in [Exit, ArgumentError] -> false
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@ -44,11 +42,9 @@ defmodule Tdd.Debug do
|
|||||||
@arg_length_limit 80
|
@arg_length_limit 80
|
||||||
@total_args_length_limit 200
|
@total_args_length_limit 200
|
||||||
|
|
||||||
# Helper function to return a unique atom for ignored arguments
|
# MODIFIED: Return the specific atoms format_arg_value expects
|
||||||
# This is called from the macro-generated code.
|
def __internal_placeholder_for_ignored_arg__, do: :__tdd_debug_ignored_arg__
|
||||||
def __internal_placeholder_for_ignored_arg__ do
|
def __internal_placeholder_for_complex_pattern__, do: :__tdd_debug_complex_pattern__
|
||||||
:__tdd_debug_ignored_arg__ # A unique atom
|
|
||||||
end
|
|
||||||
|
|
||||||
def format_args_list(args_list) when is_list(args_list) do
|
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)
|
||||||
@ -64,14 +60,13 @@ defmodule Tdd.Debug do
|
|||||||
def format_arg_value(arg) do
|
def format_arg_value(arg) do
|
||||||
inspected =
|
inspected =
|
||||||
case arg do
|
case arg do
|
||||||
# Check if it's our specific placeholder atom for `_`
|
|
||||||
:__tdd_debug_ignored_arg__ -> "_"
|
:__tdd_debug_ignored_arg__ -> "_"
|
||||||
# For other arguments, inspect them normally.
|
:__tdd_debug_complex_pattern__ -> "<pattern>"
|
||||||
# This will handle runtime values for simple variables/literals,
|
_ -> inspect(arg, limit: @arg_length_limit, pretty: false, structs: true) # Use limit here too
|
||||||
# and ASTs for complex patterns (if that's what's passed).
|
|
||||||
_ -> inspect(arg, limit: :infinity, pretty: false, structs: true)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# The inspect limit is now applied within the inspect call itself for individual args mostly.
|
||||||
|
# This outer limit is more for the overall string representation from inspect if it didn't truncate.
|
||||||
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) <> "..."
|
||||||
else
|
else
|
||||||
@ -84,34 +79,29 @@ 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)
|
||||||
# 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)
|
||||||
|
|
||||||
# Spawn a separate process to monitor.
|
# Spawn linked to ensure monitor process dies if current process dies unexpectedly
|
||||||
# Use spawn_opt to avoid linking if this monitoring process crashes.
|
|
||||||
Process.spawn(
|
Process.spawn(
|
||||||
fn ->
|
fn ->
|
||||||
receive do
|
receive do
|
||||||
{:DOWN, ^ref, :process, ^pid_to_trace, _reason} ->
|
{:DOWN, ^ref, :process, ^pid_to_trace, _reason} ->
|
||||||
remove_traced_pid(pid_to_trace)
|
remove_traced_pid(pid_to_trace)
|
||||||
after
|
# after # Consider if timeout is strictly needed or if DOWN message is sufficient
|
||||||
# 1 hour timeout as a fallback
|
# 3_600_000 -> # 1 hour timeout
|
||||||
3_600_000 ->
|
# remove_traced_pid(pid_to_trace)
|
||||||
remove_traced_pid(pid_to_trace)
|
|
||||||
end
|
end
|
||||||
end,
|
end,
|
||||||
[:monitor] #:link option removed, monitor is explicit
|
[:monitor]
|
||||||
|
# Removed [:monitor] option as spawn_link and Process.monitor achieve desired effect
|
||||||
)
|
)
|
||||||
|
|
||||||
:ok
|
:ok
|
||||||
end
|
end
|
||||||
|
|
||||||
def disable_tracing() do
|
def disable_tracing() do
|
||||||
init() # Ensure agent is available for removal if it was started by another call
|
# init() # Not strictly necessary here as remove_traced_pid handles agent not existing
|
||||||
remove_traced_pid(self())
|
remove_traced_pid(self())
|
||||||
:ok # Good practice to return :ok
|
:ok
|
||||||
end
|
end
|
||||||
|
|
||||||
def run(fun) when is_function(fun, 0) do
|
def run(fun) when is_function(fun, 0) do
|
||||||
@ -126,14 +116,14 @@ defmodule Tdd.Debug do
|
|||||||
# --- Process Dictionary for Depth ---
|
# --- Process Dictionary for Depth ---
|
||||||
defp get_depth, do: Process.get(:tdd_debug_depth, 0)
|
defp get_depth, do: Process.get(:tdd_debug_depth, 0)
|
||||||
|
|
||||||
def increment_depth do
|
def increment_depth do # Made private as it's an internal helper
|
||||||
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
|
||||||
|
|
||||||
def decrement_depth do
|
def decrement_depth do # Made private
|
||||||
new_depth = max(0, get_depth() - 1) # Ensure depth doesn't go below 0
|
new_depth = max(0, get_depth() - 1)
|
||||||
Process.put(:tdd_debug_depth, new_depth)
|
Process.put(:tdd_debug_depth, new_depth)
|
||||||
new_depth
|
new_depth
|
||||||
end
|
end
|
||||||
@ -143,44 +133,51 @@ defmodule Tdd.Debug do
|
|||||||
quote do
|
quote do
|
||||||
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 # Imports def/defp from this module, and helper functions
|
||||||
import Tdd.Debug
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# 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
|
||||||
|
# As per your note, this macro might need updates.
|
||||||
|
# Main issues to check if used:
|
||||||
|
# 1. Hygiene of `actual_code_ast`: use Macro.escape/2.
|
||||||
|
# 2. Robustness of `arg_vars_for_runtime_access` for all pattern types (similar to generate_traced_function).
|
||||||
|
# Kept original logic for now based on your description.
|
||||||
{function_name, _meta_call, original_args_ast_nodes} = call
|
{function_name, _meta_call, original_args_ast_nodes} = call
|
||||||
original_args_ast_nodes = original_args_ast_nodes || []
|
original_args_ast_nodes = original_args_ast_nodes || []
|
||||||
|
|
||||||
# This part was problematic if original_args_ast_nodes contained `_`
|
|
||||||
# and was used directly to form a list of expressions.
|
|
||||||
arg_vars_for_runtime_access =
|
arg_vars_for_runtime_access =
|
||||||
Enum.map(original_args_ast_nodes, fn
|
Enum.map(original_args_ast_nodes, fn
|
||||||
{:_, _, Elixir} -> quote do Tdd.Debug.__internal_placeholder_for_ignored_arg__() end
|
{:_, _, ctx} when is_atom(ctx) or is_nil(ctx) ->
|
||||||
{:=, _, [var_ast, _]} -> var_ast
|
quote do Tdd.Debug.__internal_placeholder_for_ignored_arg__() end
|
||||||
pattern_ast -> pattern_ast
|
{:=, _, [var_ast, _]} ->
|
||||||
|
var_ast
|
||||||
|
{var_name, _meta, ctx} = pattern_ast when is_atom(var_name) and (is_atom(ctx) or is_nil(ctx)) and
|
||||||
|
not (var_name in [:_ , :%{}, :{}, :|, :<<>>, :fn, :->, :&, :^]) ->
|
||||||
|
pattern_ast
|
||||||
|
{constructor, _, _} = pattern_ast when constructor in [:%{}, :{}, :|, :<<>>] ->
|
||||||
|
quote do Tdd.Debug.__internal_placeholder_for_complex_pattern__() end
|
||||||
|
pattern_ast ->
|
||||||
|
pattern_ast
|
||||||
end)
|
end)
|
||||||
|
|
||||||
actual_code_ast =
|
actual_code_ast =
|
||||||
case clauses do
|
case clauses do
|
||||||
# [do: block_content | _] -> block_content
|
|
||||||
kw when is_list(kw) -> Keyword.get(kw, :do)
|
kw when is_list(kw) -> Keyword.get(kw, :do)
|
||||||
_ -> clauses
|
_ -> clauses
|
||||||
end
|
end
|
||||||
|
|
||||||
traced_body =
|
traced_body =
|
||||||
quote location: :keep do
|
quote location: :keep do
|
||||||
if is_pid_traced?(self()) do
|
if Tdd.Debug.is_pid_traced?(self()) do
|
||||||
current_print_depth = increment_depth()
|
current_print_depth = Tdd.Debug.increment_depth() # Qualified
|
||||||
indent = String.duplicate(" ", current_print_depth - 1)
|
indent = String.duplicate(" ", current_print_depth - 1)
|
||||||
|
|
||||||
runtime_arg_values = [unquote_splicing(arg_vars_for_runtime_access)]
|
runtime_arg_values = [unquote_splicing(arg_vars_for_runtime_access)]
|
||||||
args_string = format_args_list(runtime_arg_values)
|
IO.inspect(runtime_arg_values, label: "runtime_arg_values")
|
||||||
|
IO.puts("runtime_arg_values 1")
|
||||||
|
args_string = Tdd.Debug.format_args_list(runtime_arg_values) # Qualified
|
||||||
caller_module_name = Module.split(__MODULE__) |> Enum.join(".")
|
caller_module_name = Module.split(__MODULE__) |> Enum.join(".")
|
||||||
|
|
||||||
IO.puts(
|
IO.puts(
|
||||||
@ -188,24 +185,26 @@ defmodule Tdd.Debug do
|
|||||||
)
|
)
|
||||||
|
|
||||||
try do
|
try do
|
||||||
result = unquote(actual_code_ast)
|
# Potential hygiene issue here if actual_code_ast clashes with macro vars
|
||||||
_ = decrement_depth()
|
result = unquote(actual_code_ast) # Consider Macro.escape(actual_code_ast, unquote: true)
|
||||||
|
_ = Tdd.Debug.decrement_depth() # Qualified
|
||||||
IO.puts(
|
IO.puts(
|
||||||
"#{indent}RETURN from #{caller_module_name}.#{unquote(Macro.escape(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)}" # Qualified
|
||||||
)
|
)
|
||||||
result
|
result
|
||||||
rescue
|
rescue
|
||||||
exception_class ->
|
exception_class ->
|
||||||
_ = decrement_depth()
|
_ = Tdd.Debug.decrement_depth() # Qualified
|
||||||
error_instance = exception_class
|
error_instance = exception_class
|
||||||
stacktrace = __STACKTRACE__
|
stacktrace = __STACKTRACE__
|
||||||
IO.puts(
|
IO.puts(
|
||||||
"#{indent}ERROR in #{caller_module_name}.#{unquote(Macro.escape(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)}" # Qualified
|
||||||
)
|
)
|
||||||
reraise error_instance, stacktrace
|
reraise error_instance, stacktrace
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
unquote(actual_code_ast)
|
IO.puts("runtime_arg_values 2")
|
||||||
|
unquote(actual_code_ast) # Consider Macro.escape
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -215,68 +214,87 @@ defmodule Tdd.Debug do
|
|||||||
end
|
end
|
||||||
|
|
||||||
@doc false
|
@doc false
|
||||||
defmacro def(call, clauses \\ Keyword.new()) do # Default clauses to Keyword.new() or []
|
defmacro def(call, clauses \\ Keyword.new()) do
|
||||||
generate_traced_function(:def, call, clauses, __CALLER__)
|
generate_traced_function(:def, call, clauses, __CALLER__)
|
||||||
end
|
end
|
||||||
|
|
||||||
@doc false
|
@doc false
|
||||||
defmacro defp(call, clauses \\ Keyword.new()) do # Default clauses to Keyword.new() or []
|
defmacro defp(call, clauses \\ Keyword.new()) do
|
||||||
generate_traced_function(:defp, call, clauses, __CALLER__)
|
generate_traced_function(:defp, call, clauses, __CALLER__)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
defp generate_traced_function(type, call_ast, clauses, _caller_env) do
|
||||||
# Capture __CALLER__ to ensure hygiene for variables generated by the macro
|
{function_name_ast, _meta_call, original_args_patterns_ast} = call_ast
|
||||||
# if needed, though direct AST manipulation often bypasses some hygiene issues.
|
args_patterns_ast = original_args_patterns_ast || []
|
||||||
defp generate_traced_function(type, call_ast, clauses, _caller_env) do
|
|
||||||
{function_name_ast, _meta_call, original_args_ast_nodes} = call_ast
|
|
||||||
args_patterns_ast = original_args_ast_nodes || []
|
|
||||||
|
|
||||||
original_body_ast =
|
original_body_ast =
|
||||||
if Keyword.keyword?(clauses) do
|
(if Keyword.keyword?(clauses) do
|
||||||
Keyword.get(clauses, :do, clauses)
|
Keyword.get(clauses, :do, clauses) # clauses could be `[do: actual_body]` or just `actual_body`
|
||||||
else
|
else
|
||||||
clauses
|
clauses
|
||||||
end || quote(do: nil) # Default to an empty body if none provided
|
end) || quote(do: nil) # Default to a nil body if nothing provided
|
||||||
|
|
||||||
# Transform argument patterns into expressions suitable for logging at runtime.
|
# --- REVISED logging_expressions_ast_list ---
|
||||||
logging_expressions_ast_list =
|
logging_expressions_ast_list =
|
||||||
Enum.map(args_patterns_ast, fn arg_pattern_ast ->
|
Enum.map(args_patterns_ast, fn arg_pattern_ast_outer ->
|
||||||
case arg_pattern_ast do
|
core_pattern_ast_for_logging =
|
||||||
{:=, _meta_assign, [var_ast, _sub_pattern_ast]} ->
|
case arg_pattern_ast_outer do
|
||||||
|
{:when, _, [pattern, _guard]} -> pattern
|
||||||
|
other -> other
|
||||||
|
end
|
||||||
|
|
||||||
|
cond do
|
||||||
|
match?({:_, _, ctx} when is_atom(ctx) or is_nil(ctx), core_pattern_ast_for_logging) ->
|
||||||
|
quote do Tdd.Debug.__internal_placeholder_for_ignored_arg__() end
|
||||||
|
|
||||||
|
match?({:=, _, [{_var_ast, _, _}, _sub_pattern_ast]}, core_pattern_ast_for_logging) ->
|
||||||
|
{:=, _, [var_ast, _]} = core_pattern_ast_for_logging
|
||||||
var_ast
|
var_ast
|
||||||
_ ->
|
|
||||||
Macro.postwalk(arg_pattern_ast, fn
|
match?({constructor, _, _args} when constructor in [:%{}, :{}, :|, :<<>>], core_pattern_ast_for_logging) ->
|
||||||
current_node_ast ->
|
quote do Tdd.Debug.__internal_placeholder_for_complex_pattern__() end
|
||||||
case current_node_ast do
|
|
||||||
# Match any underscore, regardless of context if it's a bare underscore node
|
match?({name_atom, _, context} when is_atom(name_atom) and name_atom != :_ and (is_atom(context) or is_nil(context)), core_pattern_ast_for_logging) ->
|
||||||
{:_, _, context} when is_atom(context) -> # context is often Elixir or nil
|
{name_atom_inner, _meta, context_inner} = core_pattern_ast_for_logging
|
||||||
quote do Tdd.Debug.__internal_placeholder_for_ignored_arg__() end
|
if is_nil(context_inner) and name_atom_inner not in [:true, :false, :nil] do
|
||||||
_ ->
|
name_atom_inner
|
||||||
current_node_ast
|
else
|
||||||
end
|
core_pattern_ast_for_logging
|
||||||
end)
|
end
|
||||||
|
|
||||||
|
# Macro.is_literal(core_pattern_ast_for_logging) ->
|
||||||
|
# core_pattern_ast_for_logging
|
||||||
|
|
||||||
|
# Fallback for unhandled complex patterns or other ASTs.
|
||||||
|
# If it's a variable AST that somehow slipped through (e.g. context wasn't atom/nil for a var),
|
||||||
|
# it might be okay to pass `core_pattern_ast_for_logging` directly.
|
||||||
|
# However, to be safe and avoid `_` or other non-value ASTs, placeholder is better.
|
||||||
|
true ->
|
||||||
|
# This case implies a pattern that is not `_`, not `var=pattern`, not a common structure,
|
||||||
|
# not a simple var/literal atom, and not a known literal.
|
||||||
|
# It's likely a more complex pattern we haven't explicitly handled for logging.
|
||||||
|
quote do Tdd.Debug.__internal_placeholder_for_complex_pattern__() end
|
||||||
end
|
end
|
||||||
end)
|
end)
|
||||||
|
# --- END REVISED logging_expressions_ast_list ---
|
||||||
|
|
||||||
# This is the AST for the code that will become the *actual body* of the traced function.
|
|
||||||
traced_body_inner_ast =
|
traced_body_inner_ast =
|
||||||
quote do
|
quote do
|
||||||
if is_pid_traced?(self()) do
|
if Tdd.Debug.is_pid_traced?(self()) do
|
||||||
current_print_depth = increment_depth()
|
current_print_depth = Tdd.Debug.increment_depth() # Qualified
|
||||||
indent = String.duplicate(" ", current_print_depth - 1)
|
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__ =
|
__runtime_arg_values_for_logging__ =
|
||||||
[unquote_splicing(Macro.escape(logging_expressions_ast_list, unquote: true))]
|
[unquote_splicing(logging_expressions_ast_list)]
|
||||||
|
|
||||||
args_string = format_args_list(__runtime_arg_values_for_logging__)
|
args_string = Tdd.Debug.format_args_list(__runtime_arg_values_for_logging__) # Qualified
|
||||||
caller_module_name_str = Module.split(__MODULE__) |> Enum.join(".")
|
caller_module_name_str = Module.split(__MODULE__) |> Enum.join(".")
|
||||||
|
|
||||||
|
__Printable_fn_name_intermediate__ = unquote(function_name_ast)
|
||||||
printable_function_name_str =
|
printable_function_name_str =
|
||||||
case unquote(Macro.escape(function_name_ast, unquote: true)) do
|
case __Printable_fn_name_intermediate__ do
|
||||||
fn_name_atom when is_atom(fn_name_atom) -> Atom.to_string(fn_name_atom)
|
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)
|
fn_name_ast_complex -> Macro.to_string(fn_name_ast_complex) # Handles operators etc.
|
||||||
end
|
end
|
||||||
|
|
||||||
IO.puts(
|
IO.puts(
|
||||||
@ -284,58 +302,55 @@ defmodule Tdd.Debug do
|
|||||||
)
|
)
|
||||||
|
|
||||||
try do
|
try do
|
||||||
# Original body is injected here, also escaped.
|
|
||||||
result = unquote(Macro.escape(original_body_ast, unquote: true))
|
result = unquote(Macro.escape(original_body_ast, unquote: true))
|
||||||
_ = Tdd.Debug.decrement_depth()
|
_ = Tdd.Debug.decrement_depth() # Qualified
|
||||||
IO.puts(
|
IO.puts(
|
||||||
"#{indent}RETURN from #{caller_module_name_str}.#{printable_function_name_str}: #{Tdd.Debug.format_arg_value(result)}"
|
"#{indent}RETURN from #{caller_module_name_str}.#{printable_function_name_str}: #{Tdd.Debug.format_arg_value(result)}" # Qualified
|
||||||
)
|
)
|
||||||
result
|
result
|
||||||
rescue
|
rescue
|
||||||
exception_class ->
|
exception_class ->
|
||||||
_ = Tdd.Debug.decrement_depth()
|
_ = Tdd.Debug.decrement_depth() # Qualified
|
||||||
error_instance = exception_class
|
error_instance = exception_class
|
||||||
stacktrace = __STACKTRACE__
|
stacktrace = __STACKTRACE__
|
||||||
IO.puts(
|
IO.puts(
|
||||||
"#{indent}ERROR in #{caller_module_name_str}.#{printable_function_name_str}: #{Tdd.Debug.format_arg_value(error_instance)}"
|
"#{indent}ERROR in #{caller_module_name_str}.#{printable_function_name_str}: #{Tdd.Debug.format_arg_value(error_instance)}" # Qualified
|
||||||
)
|
)
|
||||||
reraise error_instance, stacktrace
|
reraise error_instance, stacktrace
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
# Tracing not enabled, execute original body directly (escaped).
|
|
||||||
unquote(Macro.escape(original_body_ast, unquote: true))
|
unquote(Macro.escape(original_body_ast, unquote: true))
|
||||||
end
|
end
|
||||||
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 =
|
final_definition_ast =
|
||||||
quote location: :keep do # unquote: false is default and implied if not set
|
quote location: :keep do
|
||||||
Kernel.unquote(type)(
|
Kernel.unquote(type)(
|
||||||
unquote(call_ast),
|
unquote(call_ast), # This unquotes the function head {name, meta, args_patterns}
|
||||||
do: unquote(traced_body_inner_ast) # The body AST is passed via `do:`
|
do: unquote(traced_body_inner_ast)
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
# Uncomment for debugging the generated code:
|
# For debugging the macro itself:
|
||||||
# IO.inspect(Macro.to_string(final_definition_ast), label: "Generated for #{Macro.to_string(call_ast)}")
|
# require Logger
|
||||||
|
# Logger.debug("Generated AST for #{type} #{Macro.to_string(call_ast)}:\n#{Macro.to_string(final_definition_ast)}")
|
||||||
|
|
||||||
final_definition_ast
|
final_definition_ast
|
||||||
end
|
end
|
||||||
|
|
||||||
# --- Your TDD Graph Printing (unrelated to the tracing error, kept for completeness) ---
|
# --- TDD Graph Printing (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
|
||||||
|
# It's better to alias Tdd.Store at the top of the module if consistently used,
|
||||||
|
# or pass it as an argument if it's a dependency.
|
||||||
|
# For now, keeping alias local to this function if Tdd.Store is not used elsewhere in Debug.
|
||||||
alias Tdd.Store # Assuming Tdd.Store is available
|
alias Tdd.Store # Assuming Tdd.Store is available
|
||||||
IO.puts("--- TDD Graph (ID: #{id}) ---")
|
IO.puts("--- TDD Graph (ID: #{id}) ---")
|
||||||
do_print(id, 0, MapSet.new())
|
do_print(id, 0, MapSet.new(), Tdd.Store) # Pass Store if it's an external module
|
||||||
IO.puts("------------------------")
|
IO.puts("------------------------")
|
||||||
end
|
end
|
||||||
|
|
||||||
defp do_print(id, indent_level, visited) do
|
defp do_print(id, indent_level, visited, store_module) do # Accept store_module
|
||||||
prefix = String.duplicate(" ", indent_level)
|
prefix = String.duplicate(" ", indent_level)
|
||||||
|
|
||||||
if MapSet.member?(visited, id) do
|
if MapSet.member?(visited, id) do
|
||||||
@ -343,8 +358,8 @@ defmodule Tdd.Debug do
|
|||||||
:ok
|
:ok
|
||||||
else
|
else
|
||||||
new_visited = MapSet.put(visited, id)
|
new_visited = MapSet.put(visited, id)
|
||||||
alias Tdd.Store # Assuming Tdd.Store is available
|
# alias Tdd.Store # Removed from here
|
||||||
case Tdd.Store.get_node(id) do
|
case store_module.get_node(id) do # Use passed module
|
||||||
{:ok, :true_terminal} ->
|
{:ok, :true_terminal} ->
|
||||||
IO.puts("#{prefix}ID #{id} -> TRUE")
|
IO.puts("#{prefix}ID #{id} -> TRUE")
|
||||||
{:ok, :false_terminal} ->
|
{:ok, :false_terminal} ->
|
||||||
@ -352,11 +367,11 @@ defmodule Tdd.Debug do
|
|||||||
{: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:")
|
||||||
do_print(y, indent_level + 2, new_visited)
|
do_print(y, indent_level + 2, new_visited, store_module)
|
||||||
IO.puts("#{prefix} ├─ No:")
|
IO.puts("#{prefix} ├─ No:")
|
||||||
do_print(n, indent_level + 2, new_visited)
|
do_print(n, indent_level + 2, new_visited, store_module)
|
||||||
IO.puts("#{prefix} └─ DC:")
|
IO.puts("#{prefix} └─ DC:")
|
||||||
do_print(d, indent_level + 2, new_visited)
|
do_print(d, indent_level + 2, new_visited, store_module)
|
||||||
{:error, reason} ->
|
{:error, reason} ->
|
||||||
IO.puts("#{prefix}ID #{id}: ERROR - #{reason}")
|
IO.puts("#{prefix}ID #{id}: ERROR - #{reason}")
|
||||||
end
|
end
|
||||||
@ -1846,8 +1861,8 @@ defmodule Tdd.Compiler do
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
defp is_recursive_spec?({:list_of, _}), do: true
|
defp is_recursive_spec?({:list_of, _a}), do: true
|
||||||
defp is_recursive_spec?(_), do: false
|
defp is_recursive_spec?(_a), do: false
|
||||||
|
|
||||||
# NEW: The logic for simple, non-recursive types.
|
# NEW: The logic for simple, non-recursive types.
|
||||||
defp compile_non_recursive_spec(spec, context) do
|
defp compile_non_recursive_spec(spec, context) do
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user