I’m trying to create a macro that calls a function with 7 parameters. 2 of these is the file name and line number of the point where the macro is called (that’s why I can’t use functions), 2 is the same for all calls in a module and 3 is specific for each call. I’d like to pass the 2 “module-specific” parameters only once, so I tried to pass them to the __using__ macro, but couldn’t get it work. This is a minimal example of what I came up with so far:
defmodule Lib do
defmacro __using__(opts) do
object_name = Keyword.get(opts, :object_name)
errors = Keyword.get(opts, :errors)
quote bind_quoted: [object_name: object_name, errors: errors] do
defmacro test_macro(term, op_code, args) do
%{file: file, line: line} = __CALLER__
quote do
IO.puts([
unquote(term),
unquote(object_name),
unquote(errors),
unquote(op_code),
unquote(args),
unquote(file),
unquote(line)
])
end
end
end
end
end
and I’m trying to use the macro this way:
defmodule MacroProblem do
use Lib, object_name: :on, errors: [:x]
def hello do
test_macro("x", :a, ["b"])
end
end
but I get this compilation error:
== Compilation error in file lib/macro_problem.ex ==
** (UndefinedFunctionError) function MacroProblem.object_name/0 is undefined (function not available)
MacroProblem.object_name/0
(stdlib) erl_eval.erl:567: :erl_eval.local_func/6
(stdlib) erl_eval.erl:232: :erl_eval.expr/5
(stdlib) erl_eval.erl:233: :erl_eval.expr/5
(stdlib) erl_eval.erl:232: :erl_eval.expr/5
(stdlib) erl_eval.erl:888: :erl_eval.expr_list/6
(stdlib) erl_eval.erl:240: :erl_eval.expr/5
expanding macro: MacroProblem.test_macro/3
I tried to quote/unquote various stuff in the code above, but only got different errors. How should this work?
It’s not your problem. Look that’s because object_name variable is not found in scope.
Simplified error example:
defmodule Example do
data = 5
def sample, do: data
end
Look that adding extra unquote/1 call solves this problem:
defmodule Example do
data = 5
def sample, do: unquote(data)
end
Following this please try something like this one:
defmodule Lib do
defmacro __using__(opts) do
quote bind_quoted: [opts: opts] do
defmacro test_macro(term, op_code, args) do
data = [args: args, op_code: op_code, term: term]
quote bind_quoted: [caller: Macro.escape(__CALLER__), data: data, opts: unquote(opts)] do
IO.inspect([
data[:term],
opts[:object_name],
opts[:errors],
data[:op_code],
data[:args],
caller.file,
caller.line
])
end
end
end
end
end
Just copied code into iex to make sure and it worked for me, so it’s really weird …
Which Erlang and Elixir version you have? I’m using Erlang in version 21.3 and Elixir in version: 1.8.1 - both are installed using asdf version manager.
I’m using Elixir 1.7.4 and Erlang/OTP 21. I’ll try with newer - I’m not sure I can actually use that in the “real” project, but let’s see if it works with the minimal example.