Works with unquote, cannot make it work with separate macro

I have two versions. The version using the local unquote construct does exactly what I intend. Whenever I try to migrate it to a macro defined in another file it fails. I have tried many variations. I tried replicating what was in the record.ex of the language.I even have tried just building the syntax tree by hand. How is the macro supposed to be defined?

[code]defmodule Works do
require ConstantExtractor

create one function representing each name/value pair

for {name, value} <- ConstantExtractor.extractAllConstants(“include/asn1/S1AP.hrl”) do
def unquote({name, [context: MODULE], MODULE}), do: unquote(value)
end
end

defmodule Fails do
require ConstantExtractor

create one function representing each name/value pair

for {name, value} <- ConstantExtractor.extractAllConstants(“include/asn1/S1AP.hrl”) do
ConstantExtractor.defconst name, value
end
end

defmodule ConstantExtractor do # failing version

…

defmacro defconst(name, value) do
quote do
defmacro(unquote(name)()) do
(unquote(value))
end
end
end
end[/code]

Fails with:
lib/asn1/s1ap.ex:15: warning: variable name is unused
lib/asn1/s1ap.ex:15: warning: variable value is unused
…
== Compilation error on file lib/asn1/s1ap.ex ==
** (CompileError) lib/asn1/s1ap.ex:17: invalid syntax in defmacro name()
lib/asn1/s1ap.ex:17: anonymous fn/2 in :elixir_compiler_2.MODULE/1
(elixir) lib/enum.ex:1473: Enum."-reduce/3-lists^foldl/2-0-"/3
lib/asn1/s1ap.ex:15: (module)

1 Like

This also works:

[code]defmodule Test do
defmacro test(key, value) do
def unquote(key)() do
unquote(value)
end
end
end

defmodule Example do
require Test

Test.test(:hello, :world)
end[/code]

This is as I suspected - if I call with literals the macro works. If I call it from within a loop, instead I get some variable contexts and it no longer works. How to fix this?

Thanks.

1 Like

This works:

defmacro defconst(key, entry) do quote bind_quoted: [key: key, entry: entry] do def unquote(key)() do unquote(entry) end end end

Not exactly sure why…

1 Like

Just a quick guess, but you are passing name to your function, and just yesterday we had the discussion about Macro.escape/2. So I’d try to escape name and value before passing them. I don’t know exactly thow, if you need to do it before calling the macro, or if you can do it inside of the macro (before quote-block).

Perhaps, it might also be, that you need to use Macro.expand/2. I’m always confused about how and when I need which of the functions from Macro module, so as soon as something doesn’t work without them randomly until it works, or I just change my plan and try to do something similar without using metaprogramming at all :wink:

1 Like

Hello, NobbZ.

I guess bind_quoted defines bindings inside the caller context, and then I can use the rebound values inside the macro. Record.defrecord does it, it made my solution work too…

I tried working with Macro.escape but had no satisfactory results so far…

Thanks. :slight_smile:

1 Like

PS - if I had read the documentation for the quote special form, I might have noticed the solution in the section “Binding and unquote fragments”

http://elixir-lang.org/docs/stable/elixir/Kernel.SpecialForms.html#quote/2

defmacro defkv(kv) do quote bind_quoted: [kv: kv] do Enum.each kv, fn {k, v} -> def unquote(k)(), do: unquote(v) end end end

1 Like