Rodrigo Stv
Rodrigo Stv

Reputation: 425

Eixir metaprogramming - defining macro available at compile time from within macro

I am studying Elixir metaprogramming and I am toying around with making a macro that allows me to define REST resources. The interface would be like this:

defmodule Router do
  use Resources

  resource "cars"
  resource "animals"
end

I got as far as defining a module attribute using the Module module, but I can't get the following to work:

defmodule Resource do
  defmacro __using__(_opts) do
    quote do
      Module.put_attribute __MODULE__, :stack, [1, 2, 3]
      defmacro resource(name) do
        stack = Module.get_attribute __MODULE__, :stack
        Module.put_attribute __MODULE__, :stack, [name|stack]
      end
    end
  end
end

The following does not compile:

defmodule Domain  do
  use Resource

  resource "foo"

  def run do
    IO.inspect @stack
  end
end

If I remove the resource line, it prints [1, 2, 3] correctly.

The resource/1 macro is visible from run/0.

How could I get the code in Router to work, so that calling resource "xxx" pushes "xxx" to the stack on the @stack module attribute?

Upvotes: 0

Views: 73

Answers (1)

Sheharyar
Sheharyar

Reputation: 75740

You need to define the second Macro separately, not within the __using__ macro. You can use the first macro to import Resource and define the initial @stack, so you can then use the resource macro in your Module.

You also don't need to call Module.get_attribute and Module.put_attribute, and just use @stack everywhere.

Try this:

defmodule Resource do
  defmacro __using__(_opts) do
    quote do
      import Resource
      @stack [1,2,3]
    end
  end

  defmacro resource(name) do
    quote do
      @stack [unquote(name) | @stack]
    end
  end
end

Calling Domain.run should now give you ["foo", 1, 2, 3]. You should also go over the Official guide on building your own Domain Specific Languages in Elixir.

Upvotes: 1

Related Questions