Macros are just functions that take AST as parameters and are expected to return AST. Generally AST is created using quote blocks. But you can quote any code whether in a macro of not. So you to get the AST of the code you show, it would be a matter of:
defmodule TestModule do
defmacro test_macro(param1, param2, param3) do
quote do
param1 + param2 + unqoute(param3)
end
end
end
Note that:
In this case param1 and param2 in the quote block have nothing to do with the parameters passed to the macro. They are variable references that would need to be referenceable in the calling site.
unquote will dereference the parameter param3. If your intent was to get the AST for unquote(param3) then change the quote do block to be quote unquote: false do
Do note that due to macro hygene, simply having param1 as a bound variable at the call site will actually not work without opting out of hygene with var!
@bartblast you can see the AST by putting an IO.inspect after your quote do block
defmodule TestModule do
defmacro test_macro(param1, param2, param3) do
param1 + param2 + unqoute(param3)
end
|> IO.inspect
end
@kip, @benwilson512
Sorry, I forgot about wrapping the code inside the macro with quote, but that was my intent (I edited the module code).
I need to get the AST programmatically without touching the current code.
So, I’d like to implement the following function that can be called anywhere:
defmodule Helpers do
def get_macro_generated_ast(module, function, args) do
…
end
end
And calling this function like this: Helpers.get_macro_generated_ast(TestModule, :test_macro, [1, 2, 3])
Would return the AST which i specified in my first post.
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.
defmodule TestModule do
defmacro test_macro(param1, param2, param3) do
p1_and_p2 = param1 + param2
quote do
unquote(p1_and_p2) + unquote(param3)
end
end
def testing do
test_macro(1, 2, 3)
end
end
elixirc text.ex mix run run.exs --beam Elixir.TestModule.beam
defmodule TestModule do
def(MACRO-test_macro(&("CALLER"), _param1, _param2, _param3)) do
_p1_and_p2 = _param1 + _param2
{:+, [context: TestModule, import: Kernel], [_p1_and_p2, _param3]}
end
def testing() do
3 + 3
end
end
@hauleth
I’m writing Elixir to JavaScript transpiler as part of a project I’m working on. When I transpile a specific Elixir module I need to expand all the macros and use directives before transforming the AST to IR. If such macro expansion can’t be done with Elixir standard lib, I’m still able to get the macros AST, resolve the bindings and inject the generated AST into the calling module, but this would be more time-consuming with my custom approach, so I hoped I could do this differently, just with the standard lib somehow.
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).