christhekeele

christhekeele

Any idea how to emit an empty AST node in a macro?

I’m doing some questionable meta-programming, and could use your help!

I’m using Macro.prewalk to traverse some source code, with the goal of pruning entire expressions matching a pattern as I go. I would like replace the matching expression with a functionally “no-op” AST node, but so far everything I try displays as a value upon being passed to Macro.to_string, including:

  • nil
  • {}
  • []
  • {:__block__, [], []} (this becomes a nil value)

Any ideas? Or elegant alternative approaches to pruning a node matching a pattern from a tree?

Marked As Solved

mhanberg

mhanberg

Creator of elixir-tools

It is also possible to use Macro.traverse/4 and on the pre order phase, replace the node with a sentinel like :__remove_me__, and in the post order phase, find that node in the args (as someone said above) and delete it.

The post order phase function being as simple as

            fn
              {node, meta, args}, acc when is_list(args) ->
                args = List.delete(args, :__remove_me__)
                {{node, meta, args}, acc}

              node, acc ->
                {node, acc}
            end

Also Liked

zachallaun

zachallaun

On my phone so can’t give a good example, but I’d recommend looking into Sourceror’s Zipper, which is a higher level structure that you can traverse over but has support for “remove the current node”.

linky

alias Sourceror.Zipper, as: Z

ast
|> Z.zip() # ast to zipper
|> Z.traverse(fn zipper ->
  if should_remove?(Z.node(zipper)) do
    Z.remove(zipper)
  else
    zipper
  end
end)
|> Z.node() # back to ast
lud

lud

If you want to remove matching clauses, you would generally remove them from a list of clauses:

iex(1)> quote do
...(1)> case val do
...(1)> {:ok, v} -> v
...(1)> {:error, e} -> raise e
...(1)> end
...(1)> end
{:case, [],
 [
   {:val, [], Elixir},
   [
     do: [
       {:->, [],
        [
          [
            ok: {:v,
             [
               if_undefined: :apply,
               context: Elixir,
               imports: [{0, IEx.Helpers}, {1, IEx.Helpers}]
             ], Elixir}
          ],
          {:v,
           [
             if_undefined: :apply,
             context: Elixir,
             imports: [{0, IEx.Helpers}, {1, IEx.Helpers}]
           ], Elixir}
        ]},
       {:->, [],
        [
          [error: {:e, [], Elixir}],
          {:raise, [context: Elixir, imports: [{1, Kernel}, {2, Kernel}]],
           [{:e, [], Elixir}]}
        ]}
     ]
   ]
 ]}
iex(2)> pruned = {:case, [],
 [
...(2)>    {:val, [], Elixir},
...(2)>    [
     do: [
...(2)>        {:->, [],
...(2)>         [
...(2)>           [
...(2)>             ok: {:v,
...(2)>              [
...(2)>                if_undefined: :apply,
...(2)>                context: Elixir,
...(2)>                imports: [{0, IEx.Helpers}, {1, IEx.Helpers}]
...(2)>              ], Elixir}
...(2)>           ],
...(2)>           {:v,
...(2)>            [
...(2)>              if_undefined: :apply,
...(2)>              context: Elixir,
...(2)>              imports: [{0, IEx.Helpers}, {1, IEx.Helpers}]
...(2)>            ], Elixir}
...(2)>         ]}
...(2)>      ]
...(2)>    ]
...(2)>  ]}
iex(3)> Macro.to_string(pruned) |> IO.puts()
case val do
  {:ok, v} -> v
end
:ok

But if for instance you have a case with a single clause, you cannot remove it, you must remove the case expression entirely. And if the value of that expression is used, then you must remove that too, etc.

A quick and dirty hack would be to replace your clause by a value that could never match, like a ref. You inject never_match = make_ref()and then you replace the match clauses by ^never_match.

But it is a dirty hack. What are you trying to accomplish?

kip

kip

ex_cldr Core Team

I believe that [] would be the nearest to a no-op. Pretty sure I’ve used that for the same purpose but on my phone now so difficult to find.

Where Next?

Popular in Questions Top

yawaramin
In the Dialyzer docs ( http://erlang.org/doc/man/dialyzer.html#requesting-or-suppressing-warnings-in-source-files ), there is a way to tu...
New
New
dokuzbir
Hello, I am trying to convert my lists to string without losing brackets.For start i have 3 map. They look like these buyer = %{ id: ...
New
hariharasudhan94
I would like to know what is the best IDE for elixir development?
New
mgjohns61585
Could someone help me? I'm making my first elixir program, number guessing game. I can't figure out how to convert the user's guess from ...
New
ovidiubadita
Hey all, I discovered Elixir and I love it. I always wanted to learn a functional programming and I intended to go for Haskell, but afte...
New
Patoshizzle
After calling mix ecto.create I get this error: 17:00:32.162 [error] GenServer #PID<0.412.0> terminating ** (Postgrex.Error) FATAL...
New
Exadra37
Sometimes I want to check if the input into a function is not a blank string. My first approach: defmodule Example do def do_stuff(s...
New
Fl4m3Ph03n1x
About me? ( if you have nothing better to do than reading about some random guy in the internet :stuck_out_tongue: ) Hello all, this is ...
New
joeerl
Hello again - after a longish gap I’ve decided I really must dig into Elixir and see what’s been happening here - so I have a few questio...
New

Other popular topics Top

JakeBecker
TL;DR: I’ve just released an implementation of Microsoft’s IDE-independent Language Server Protocol for Elixir. It adds language support ...
1140 51847 244
New
yawaramin
In the Dialyzer docs ( http://erlang.org/doc/man/dialyzer.html#requesting-or-suppressing-warnings-in-source-files ), there is a way to tu...
New
ycv005
I have followed this StackOverflow post to install the specific version of Erlang. And When I am running mix ecto.setup then getting fol...
New
jononomo
I am trying to figure out how Mix knows whether the environment is test, dev, or prod -- where is this set? Thanks.
New
mcarvalho
What is the difference between System.get_env and Application.get_env? For example, what are best practices to use one versus another.
New
aadeshere1
I have a another noob question about loop. Since elixir is immutable, while loop is not directly possible. total = 10 while total != 0 ...
New
rms.mrcs
Hi, I need to transform a list of numbers into a map where the keys are the indexes and the values are the original values of the list....
New
Patoshizzle
After calling mix ecto.create I get this error: 17:00:32.162 [error] GenServer #PID<0.412.0> terminating ** (Postgrex.Error) FATAL...
New
lucidguppy
I have a super simple question about elixir - how would I take a file like this foo bar baz and output a new file that enumerates th...
New
joeerl
Hello again - after a longish gap I’ve decided I really must dig into Elixir and see what’s been happening here - so I have a few questio...
New

We're in Beta

About us Mission Statement