Macro inside macro compilation error

Hello everyone!
I am new to Elixir and need help to solve this little problem.

I have two macro:

defmacro state(state_id, do: expr) do
    quote do
        def process_state({:next, {unquote(state_id), state_info}=var!(state)}, update) do
            case update do
                unquote(expr)
            end
        end
    end
end

defmacro command(cmd_text, do: expr) do
    quote do
        {:message, %{text: text} , _} ->
            unquote(expr)
    end
end

I use ‘command’ macro in ‘state’ macro like this:

state :state_new do
    command "help", do 
        {:ok, ""}
     end
end

And have this error message:

== Compilation error on file lib/user/server.ex ==
** (CompileError) lib/user/server.ex:42: expected -> clauses for do in case
    (stdlib) lists.erl:1354: :lists.mapfoldl/3
    lib/user/server.ex:42: (module)

Looks like state macro not expanded? Why?

Because state gets the complete AST. If you wan’t it to get expanded, you need to do it manually by using Macro.expand/2 or Macro.expand_once/2, depending on your needs.

2 Likes

Thank You. It is works now!

I change code to:

defmacro state(state_id, do: expr) do
    quote do
        def process_state({:next, {unquote(state_id), state_info}=var!(state)}, update) do
            case update do
                unquote(Macro.expand(expr, __CALLER__))
            end
        end
    end
end

defmacro command(cmd_text, do: expr) do
    quote do
        {:message, %{text: text} , _} ->
            unquote(expr)
    end
end

And as i say it works for code:

state :state_new do
    command "help", do 
        {:ok, ""}
     end
end

But not for code

state :state_new do
    command "help", do 
        {:ok, ""}
     end
    command "next", do 
        {:ok, ""}
     end
end

expand can’t process expr and output original expr. What can i do to make this code works?

I’m not quite sure what you mean by this, but as in the documentation of escape/2 is written, we only expand the given node, if you only have a single item in the list your node is {:command, ...}, while when you have 2 items or even more, your node will be {:__block__, ...}, which is not an expandable macro. In this case you will have traverse the AST on your own. Be careful though. It might totally make sense to not expand bliendly!

In general, I think it would make more sense to generally handle command in state, since a command without state doesn’t make any sense. It is only valid inside a state block.

Hello and thank you again for reply.
What did you mean in “generally handle command in state”, something like

command :state, "cmd" do
    ...
end

or anything else?

No, I mean you should not introduce a macro command, but traverse the list of items in the :__block__, and handle :command entries there in some walk-function.

You can steal the basic idea of this from my dot dsl submission on exercism:

I have to admit, I am to tired to tailor an example that actually works with your syntax. Maybe tomorrow, but I do not promise anything.

Thank You! I understand basic idea, but i need to learn more about macro & AST.

And after all I can’t understand why command not expanded automatically? Many examples over internet use macro inside macro without walking AST or use Macro.expand (but they not generate ‘case’ statement).

Because Macro.expand/2 and co do not walk the AST, they only try to expand the root-node. And since __block__ is not a macro, there is nothing to expand.

I can’t tell you much about that examples, or explain why they work, without seeing them. Perhaps you can link to them and we go through the differences?

If you want to go deeper into macros and metaprogramming, I’d suggest to read Metaprogramming Elixir by @chrismccord. I’m only through the first quarter , and it will take me probably about another year for each remaining one, but it really helped me much, even when I read only this little.

This code works well:

defmodule T do
    defmacro ma(do: expr) do
        v = Macro.var(:a, nil)
        quote do
            def f() do
                unquote(v) = 1
                unquote(expr)
            end
        end
    end

    defmacro mb(do: expr) do
        v = Macro.var(:b, nil)
        quote do
            unquote(v) = 5
            unquote(expr)
        end
    end
end

defmodule Example do
    require T

    T.ma do
        T.mb do
            IO.puts(a+b)
        end
        T.mb do
            IO.puts(a+b*2)
        end
    end
end

This is output:

Erlang/OTP 19 [erts-8.3] [source-d5c06c6] [64-bit] [smp:8:8] [async-threads:10] [hipe] [kernel-poll:false]

Interactive Elixir (1.4.1) - press Ctrl+C to exit (type h() ENTER for help)
iex(1)> Example.f()
6
11
:ok
iex(2)> 

In my opinion it is very similar, but without case statement.

Basically this is because macros are expanded from the outer to the inner. After expanding the outer macro the inner syntax needs to stay valid. And exactly thats the issue with your code.

case x do
  command "Foo", do: nil
end

is not valid syntax anymore.

Thanks a lot for your help!
If this is not a problem, the code example for my case would be very helpful, but i try to do it myself.