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

Expert LSP Core Team

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

sen
Hi All, I set a environment variables in dev.exs , like below code. when i start server, how can i set the ${enable} value? thanks. d...
New
sergio
In Ruby, I can go: User.find_by(email: "foobar@email.com").update(email: "hello@email.com") How can I do something similar in Elixir? ...
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
jerry
Good day to you all. I have been struggling to get a query involving like and ilike to work. Can anyone assist me on this, please? pro...
New
stefanchrobot
What’s the safe way to decode a JSON string into a struct? I want to avoid calling String.to_atom. Jason.decode can give me a map with st...
New
hariharasudhan94
lets say i have a sample like a = 20; b = 10; if (a > b) do {:ok, "a"} end if (a < b) do {:ok, b} end if (a == b) do {:ok, "equa...
New
jaysoifer
Is there a way to rollback a specific migration and only that one (“skipping” all the other ones)? Would mix ecto.rollback -v 200809061...
New
pmjoe
I have a relationship of love and hate with Elixir. Lots of things are just absolutely right, but there are some things that are kind of ...
New
SoCreat
i’m a new one to elixir which editor can i use vs code? or atom? Thanks! :smiley:
New
yawaramin
In the Dialyzer docs ( dialyzer — OTP 29.0.2 (dialyzer 6.0.1) ), there is a way to turn off a specific warning for a function: -dialyzer...
New

Other popular topics Top

JeremM34
Hello, how can I check the Phoenix version ? Thanks !
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
AngeloChecked
What learn first? Rust or Elixir Hi Elixir community! I’m here because i want learn a new language. I’m a junior developer and mainly i ...
New
pmjoe
I have a relationship of love and hate with Elixir. Lots of things are just absolutely right, but there are some things that are kind of ...
New
alice
Hey, Just curious what are the main benefits of Elixir compared to Clojure? When is Elixir more useful than Clojure and vice versa? Th...
New
gausby
I asked this very same question on twitter and got some interesting feedback, but I thought it would be a good question to ask here as we...
1207 39297 209
New
bsollish-terakeet
Credo is smart enough to check for (something like) this: assert length(the_list) == 0 with this response: Checking if an enum is empt...
New
ashish173
I am using Ecto timestamps with postgres, I can see the timestamps() use the :naive_dateime but for my use case I wanted to store the ti...
New
hariharasudhan94
I would like to know what is the best IDE for elixir development?
New
openscript
Hello! Sorry for this astonishing simple question, but I’m really stuck. I try to set up the intellij-elixir plugin, but I don’t know ho...
New

Latest on Elixir Forum

We're in Beta

About us Mission Statement