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 anilvalue)
Any ideas? Or elegant alternative approaches to pruning a node matching a pattern from a tree?
Marked As Solved
mhanberg
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
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”.
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
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
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.
Popular in Questions
Other popular topics
Categories:
Sub Categories:
Forums
Popular Tags
- #ecto
- #liveview
- #troubleshooting
- #learning-elixir
- #deployment
- #library
- #erlang
- #testing
- #genserver
- #mix
- #absinthe
- #remote-other
- #otp
- #plug
- #how-to-question
- #macros
- #postgres
- #channels
- #elixirconf
- #exunit
- #discussion
- #code-sync
- #javascript
- #podcasts
- #onsite
- #dialyzer
- #docker
- #authentication
- #umbrella
- #full-time-contract
- #podcasts-by-brainlid
- #ecto-query
- #elixir-ls
- #phoenix_html
- #iex
- #blog-post
- #graphql
- #genstage
- #ai
- #websockets
- #supervisor
- #advent-of-code
- #elixirconf-us
- #distillery
- #processes
- #forms
- #api
- #metaprogramming
- #security
- #performance








