Search code examples
functionerlangelixirabstract-syntax-tree

How to get Elixir AST from local function


I want to get the AST from a local function that is passed as an argument like doSomething(fn a -> a * 10 end)

So I tried

  def test do
    inspectAST(fn a,b -> a + b + 42 end)
    inspectAST(quote do: fn a,b -> a + b + 42 end)
    inspectFunctionInfo(fn a,b -> a + b + 42 end)
    :ok
  end

  def inspectAST(this_is_AST) do
    IO.inspect(this_is_AST)
    IO.inspect("--------------------------------")
  end

  def inspectFunctionInfo(fun) do
    IO.inspect(Function.info(fun))
    IO.inspect("--------------------------------")
  end

Results :

iex(3)> Utils.test
#Function<0.30424669/2 in Utils.test/0>
"--------------------------------"
{:fn, [],
 [
   {:->, [],
    [
      [{:a, [], Utils}, {:b, [], Utils}],
      {:+, [context: Utils, import: Kernel],
       [
         {:+, [context: Utils, import: Kernel],
          [{:a, [], Utils}, {:b, [], Utils}]},
         42
       ]}
    ]}
 ]}
"--------------------------------"
[
  pid: #PID<0.485.0>,
  module: Utils,
  new_index: 1,
  new_uniq: <<58, 7, 203, 172, 99, 108, 54, 80, 24, 151, 75, 56, 73, 174, 138,
    177>>,
  index: 1,
  uniq: 30424669,
  name: :"-test/0-fun-1-",
  arity: 2,
  env: [],
  type: :local
]
"--------------------------------"

What I want is the result from inspectAST(quote do: fn a,b -> a + b + 42 end) ( the AST ) but I would like to send the function like inspectAST(fn a,b -> a + b + 42 end) without the quote do:

If anyone has some idea about this, your help would be really appreciated :)


Solution

  • If you want a "function" to be called on the AST, not values, it should be a macro, not a function.

    The following should be a good base for what you want to achieve:

      defmacro inspectAST(ast) do
        quote do
          IO.inspect(unquote(Macro.escape(ast)))
          :ok
        end
      end
    
      def test do
        inspectAST(fn a,b -> a + b + 42 end)
      end
    
    • you need to use defmacro to treat your parameter as AST, not a regular value
    • we want IO.inspect to happen at runtime, not compile time, so we need to return the AST for it using quote
    • what we want to inspect is the content of the ast variable, so we are using unquote
    • but unquote will unescape ast as well, so we need to escape it once more using Macro.escape/2

    Please note that the macro should be defined before calling it, so defmacro has to be before test, not after.

    For more advanced explanations about macros, you should check this serie of articles which is very detailed.