bartblast

bartblast

Creator of Hologram

How to get the AST that would be generated by the given macro?

Suppose we’ve got the following module:

defmodule TestModule do
  defmacro test_macro(param1, param2, param3) do
    quote do
      param1 + param2 + unqoute(param3)
    end
  end
end

How can I get the AST that would be generated by the given macro?

Let’s say that param1 = 1, param2 = 2, param3 = 3
Given those params I’d like to get the following AST:

{:+, [context: Elixir, import: Kernel],
 [
   {:+, [context: Elixir, import: Kernel],
    [{:param1, [], Elixir}, {:param2, [], Elixir}]},
   3
 ]}

I tried to solve this problem with Macro.expand/2 and Code.eval_quoted/3 but wasn’t able to.

Can anyone let me know how to do this or give some hints?

Marked As Solved

ityonemo

ityonemo

I did a little bit of investigation… When you do a defmacro, elixir will turn your macro into a function using the following convention:

  defmacro foo(a) do...

becomes the function :"MACRO-foo"/2 under the hood. First argument is what becomes __CALLER__ which should be of type Macro.Env.t; second argument is a (or generally the macro arguments concatenated). This is how __CALLER__ gets into the macro system. Output is the ast (which is what you are looking for) So, in theory you could do a fair job of reproducing even nested macros, if you carefully keep track of ENV. But at that point you are basically rebuilding the elixir ast parser =P, and there are a lot of things that are deliberately kept as “private” aka reserved fields in ENV that have zero forward compatibility guarantees.

Also Liked

ityonemo

ityonemo

If you are doing an elixir to js transpiler, I recommend using the BEAM bytecode. You can obtain bytecode by doing :beam_disasm on the module binary.

hauleth

hauleth

What you are looking for is Macro.expand_once/2 or Macro.expand/2 if you want to expand it fully. Just beware, that it is no safer than Code.eval_quoted/3, so do not use it on untrusted input.

ityonemo

ityonemo

the problem is that macros are HIGHLY contextual and the “middle” of the macro code (everything between the defmacro and the resulting quoted retval), may not be able to evaluated without having access to a bunch of internal information, if for example, functions like Module.get_attribute (see note “This function can only be used on modules that have not yet been compiled”) and then if you have a macro inside of a macro, you will need to track the rolling environment, and then there’s the magic __CONTEXT__ special form, which quite frankly I’m not entirely sure how it works under the hood. I guess that’s why you’re trying to obtain the AST information at the point of egress, but note that even the AST emitted by module ITSELF might have macros inside of it, so even if you obtain the AST in a late-interception fashion, you still need something that can “execute” macros.

I think this complexity might have been what killed ElixirScript – elixir_script v0.32.1, which I have a ton of respect for. If you decide to go with reading bytecode, which honestly is IMO way simpler than elixir AST, let me know, I am very much intereseted in resurrecting these elixir on the FE type projects. I am thinking about tinkering with a WASM-based bytecode interpreter, (not to be confused with lumen, which is a beam-to-wasm compiler).

Last Post!

ityonemo

ityonemo

yes, but do keep in mind that any macro that uses Module.get_attribute (and that is a common use case, esp. for using macros that aren’t “abuse of using”) will fall flat.

Where Next?

Popular in Questions Top

vertexbuffer
Hello, can anybody help here..? I have a list of players and I what to delete an element, but every for loop the list is reverting to ori...
New
vonH
In asking this question I am more interested about the expressiveness of the language itself and less concerned about the availability of...
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
komlanvi
Hi everyone, I was playing with phoenix liveView but I run into an issue. I have a form and want to validate each input text when the te...
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
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
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

Other popular topics Top

nobody
Hi! In PHP: $_SERVER[‘SERVER_ADDR’] - in Elixir? Searched the docs for ip address and the web, no good results. Thanks!
New
hariharasudhan94
Lets say I have map like this fetching from my database %{"_id" => #BSON.ObjectId<58eb1a7a9ad169198c3dXXXX>, "email" => ...
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
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_101
I am VERY much an elixir newbie. I have taken one elixir course and one phoenix course on Udemy. During that course, I saw the instructor...
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

We're in Beta

About us Mission Statement