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

fireproofsocks
I’m working on defining a simple Ecto schema for a table (in PostGres), but I don’t see where I can define a column as NOT NULL. Conside...
New
vrod
I am using the Starship cross-shell prompt – it seems pretty nice, but I get some errors: [WARN] - (starship::utils): Executing command ...
New
aalberti333
As the title describes, I’m trying to run Enum.map() over a list of key/value pairs, where the value is a map. My data looks like this: ...
New
New
quazar
How to set Jason to encode all fields in ecto schema, I don’t care about security and implementing only is taking long list of attributes...
New
Jim
As a follow up to my earlier question: I have the code compiling and running but not getting a successful login from the rest server. ...
New
johnnyicon
Hi all, I've just started learning Elixir and Phoenix Framework, so please pardon my n00bness at this stage. I'm trying to use Postg...
New
skosch
To my knowledge, put_in, Map.update etc. all have the one limitation of not automatically creating intermediate keys when needed (for exa...
New
jay1
Why is it that the mnesia database isn’t the most preferred database for use in Elixir/Phoenix?
New
idi527
I’ve been re-reading swift book again and noticed that multiline strings there don’t have a trailing line break, unlike in elixir iex(2)...
New

Other popular topics Top

belgoros
I’m not a pro in using Regex and can’t figure out why the following behaviour happens, especially if we take into account the difference ...
New
lastday4you
I wanted to check elixir version in phoenix because i found that my elixir is 1.5 but when i use Enum.chunk_by it said the function is un...
New
JDanielMartinez
Hi! May someone helps me, please! I have two apps into an umbrella project: the first one is Database, which manages queries, and the se...
New
chrisalley
ExUnit now has describe blocks which is a welcome addition coming from RSpec. In the docs, it states that nested hierarchies of describe ...
New
hariharasudhan94
I would like to know what is the best IDE for elixir development?
New
New
sacepums
Hey guys. I'm new to elixir and im really stocked about it. But I ran into a bit of problem - I need to convert a date sting, for examp...
New
script
If I have a string “1000 cfu/ml” . I want to remove the characters and / and space . So the string is like this "1000" What is the ...
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
fayddelight
I tried installing elixir 1.11.2 erlang 23.3.4 via asdf in my zsh shell. Enabled the versions locally and globally. When I list them ...
New

We're in Beta

About us Mission Statement