KoviRobi

KoviRobi

Hi,

I’ve written the following to debug function calls, not sure if it’s useful for anyone else, and if so should I put it somewhere?

iex(1)> require Debug; import Debug
Debug

iex(2)> inspect_call(round(2.3*4.5))
round(10.35) -> 10

iex(3)> inspect_call(inspect({:foo, 1+2}))
inspect({:foo, 3}) -> "{:foo, 3}"
"{:foo, 3}"

iex(4)> inspect_call(IO.inspect(1) + IO.inspect(2))
1
2
+(1, 2) -> 3
3

It’s hopefully a fairly easily understandable piece of macro, though only evaluating the arguments once did make it more complicated. The first pattern-matching is taken from the Macro.decompose_call/1 function.

defmodule Debug do
  defmacro inspect_call(block) do
    {name, args, rebuild} =
      case block do
        {:{}, _metadata_a, args} when is_list(args) ->
          raise CompileError,
            description: "Using inspect_call on a map literal is not supported",
            file: __CALLER__.file,
            line: __CALLER__.line

        {{:., metadata_a, [remote, function]}, metadata_b, args}
        when is_tuple(remote) or is_atom(remote) ->
          {
            Macro.to_string({{:., metadata_a, [remote, function]}, [no_parens: true], []}),
            args,
            fn new_args -> {{:., metadata_a, [remote, function]}, metadata_b, new_args} end
          }

        {name, metadata_a, args} when is_atom(name) and is_atom(args) ->
          {to_string(name), [], fn _new_args -> {name, metadata_a, args} end}

        {name, metadata_a, args} when is_atom(name) and is_list(args) ->
          {to_string(name), args, fn new_args -> {name, metadata_a, new_args} end}

        _ ->
          raise CompileError,
            description:
              "Cannot understand function call #{Macro.to_string(block)}",
            file: __CALLER__.file,
            line: __CALLER__.line
      end

    eval_args =
      for n <- 1..length(args) do
        Macro.unique_var(:"eval_arg_#{n}", __MODULE__)
      end

    return_value = Macro.unique_var(:return_value, __MODULE__)

    quote do
      unquote_splicing(
        for {e_a, a} <- Enum.zip(eval_args, args) do
          quote do
            unquote(e_a) = unquote(a)
          end
        end
      )

      IO.write(
        unquote(name) <>
          "(" <>
          (unquote(eval_args)
           |> Enum.map(&inspect/1)
           |> Enum.join(", ")) <>
          ")"
      )

      unquote(return_value) = unquote(rebuild.(eval_args))

      IO.puts(" -> " <> inspect(unquote(return_value)))

      unquote(return_value)
    end
  end
end

Showing Posts 1 to 5

hauleth

hauleth

There is already such macro in Kernel

KoviRobi

KoviRobi OP

You know, I’ve heard of dbg but looking at the docs I thought it just did the same as inspect, with also printing the file and line so I didn’t investigate it further

KoviRobi

KoviRobi OP

Playing around with it, dbg seems to be able to do this, after I run Application.put_env(:elixir, :dbg_callback, {Macro, :dbg, []}). Though if I may segue a bit into using the Elixir debugger:

The reason I want to avoid actually doing a breakpoint is because I don’t know how to work around IEx breakpoints forgetting values. E.g.

Erlang/OTP 25 [erts-13.2] [source] [64-bit] [smp:24:24] [ds:24:24:10] [async-threads:1] [jit:ns]

Interactive Elixir (1.14.3) - press Ctrl+C to exit (type h() ENTER for help)
iex(1)> x = 123 # open some hardware
123
iex(2)> dbg(IO.inspect(1) + IO.inspect(2))
Break reached: iex:2
pry(1)> cont
warning: variable "cont" does not exist and is being expanded to "cont()", please use parentheses to remove the ambiguity or change the variable name
  iex:1

** (CompileError) iex:1: undefined function cont/0 (there is no such import)
    (elixir 1.14.3) src/elixir.erl:376: :elixir.quoted_to_erl/4
    (elixir 1.14.3) src/elixir.erl:277: :elixir.eval_forms/4
    (elixir 1.14.3) lib/module/parallel_checker.ex:110: Module.ParallelChecker.verify/1
    (iex 1.14.3) lib/iex/evaluator.ex:329: IEx.Evaluator.eval_and_inspect/3
    (iex 1.14.3) lib/iex/evaluator.ex:303: IEx.Evaluator.eval_and_inspect_parsed/3
    (iex 1.14.3) lib/iex/evaluator.ex:292: IEx.Evaluator.parse_eval_inspect/3
    (iex 1.14.3) lib/iex/evaluator.ex:187: IEx.Evaluator.loop/1
pry(1)> continue
1
2
IO.inspect(1) + IO.inspect(2) #=> 3

3

Interactive Elixir (1.14.3) - press Ctrl+C to exit (type h() ENTER for help)
iex(1)> x
warning: variable "x" does not exist and is being expanded to "x()", please use parentheses to remove the ambiguity or change the variable name
  iex:1

** (CompileError) iex:1: undefined function x/0 (there is no such import)
    (elixir 1.14.3) src/elixir.erl:376: :elixir.quoted_to_erl/4
    (elixir 1.14.3) src/elixir.erl:277: :elixir.eval_forms/4
    (elixir 1.14.3) lib/module/parallel_checker.ex:110: Module.ParallelChecker.verify/1
    (iex 1.14.3) lib/iex/evaluator.ex:329: IEx.Evaluator.eval_and_inspect/3
    (iex 1.14.3) lib/iex/evaluator.ex:303: IEx.Evaluator.eval_and_inspect_parsed/3
    (iex 1.14.3) lib/iex/evaluator.ex:292: IEx.Evaluator.parse_eval_inspect/3
    (iex 1.14.3) lib/iex/evaluator.ex:187: IEx.Evaluator.loop/1

Is there a way to avoid IEx not forgetting my variables after a breakpoint? It is a big problem when the variable it forgets is some device I have opened (which seems to stay open in the background so I can’t even open it again).

And if I can ask something else, is there a way to add hooks to breakpoints? I.e. to execute a command when the breakpoint gets triggered, and then continue instead of waiting for user input.

sabiwara

sabiwara

Elixir Core Team

Good news: starting from Elixir 1.15, prying for dbg in IEx becomes opt-in :tada:.

Sebb

Sebb

this will be the best feature since dbg

— All posts loaded —

Where Next? Top

Trending in Guides/Tuts Top

Other Trending Topics Top

budgie
A little off-topic, but I feel like people here have a good head on their shoulders. I used to be quite good at making software. Was luc...
New
KristerV
Hey. Is there anyone here who creates agents in their apps? Not talking about using agents, but creating them. I’m finding it pretty diff...
New
mudasobwa
I fully migrated to my own harness from Anthropic/Gemini and I think it’s time to share it. Welcome DSH, the DeepSeek Harness, fully writ...
New
jimsynz
Beam Bots (or just BB for short) is a framework for building fault-tolerant robotics applications in Elixir using familiar OTP patterns. ...
New
juhalehtonen
There has been a thread to discuss the Stack Overflow Developer Survey on this forum every year since 2018, so here’s yet another one for...
New
budgie
I love Elixir. It’s one of 2 programming languages I’ve ever fallen in love with. But I don’t use it anymore. Serverless was the promis...
New

We're in Beta

About us Mission Statement

Options

Thread Display Mode




Thread Preview

Skip Thread Previews