Generating a macro call from a macro with the macro name as a symbol

I am trying to write a macro that expands into a call to another macro. Moreover, that other macro isn’t known statically—it’s determined at macro expansion time. I’m having difficulty constructing an AST with the macro name in a variable. Here’s what I’ve tried:

macro make_time_call(expr)
  time_macro = Symbol("@time")
  return Expr(:macrocall, [time_macro, LineNumberNode(1), 42])
end

@make_time_call 42

Gives me this error:

ERROR: LoadError: ArgumentError: "macrocall": too few arguments (expected 3)
Stacktrace:
 [1] include(fname::String)
   @ Base.MainInclude ./client.jl:478
 [2] top-level scope
   @ REPL[50]:1
in expression starting at /Users/ashton/…/tst.jl:6

Which seems really strange to me: no where am I calling a function called macrocall—I’m just trying to create an AST node for a macro call. I’ve reproduced the three functions as best I can.

Then I tried using what looked like a solution here:

macro make_time_call(expr)
  time_macro = Symbol("@time")
  return esc(:(@time $expr))     #  works
end

@make_time_call 42

and that works as expected—I get the (short!) runtime for evaluating 42. But I need to be able to switch what macro is called; I can’t just have @time sitting in there. So I tried this:

macro make_time_call(expr)
  time_macro = Symbol("@time")
  return esc(:($time_macro $expr))
end

@make_time_call 42

and I get this error:

ERROR: LoadError: UndefVarError: `$` not defined
Stacktrace:
 [1] top-level scope
   @ ~/…/tst.jl:6
 [2] include(fname::String)
   @ Base.MainInclude ./client.jl:478
 [3] top-level scope
   @ REPL[50]:1
in expression starting at /Users/ashton/…/tst.jl:6

Why does it think that $ is a variable that’s not defined?!

How can I call a macro given a symbol?

Hello @awiersdorf, and welcome to the Julia community!

Your initial implementation was almost right, with one small mistake. The Expr constructor expects the arguments of the expression to be passed in splatted form, not as a single list. So with a simple modification, i.e., just remove the brackets, we can make your solution work:

macro make_time_call(expr)
  time_macro = Symbol("@time")
  return Expr(:macrocall, time_macro, LineNumberNode(1), 42)
end

julia> @make_time_call 42
  0.000001 seconds
42

That being said, I would probably pass __source__ instead of LineNumbeNode(1) as the first argument, like so:

macro make_time_call(expr)
  time_macro = Symbol("@time")
  return Expr(:macrocall, time_macro, __source__, 42)
end

HTH!

2 Likes

That worked! Thank you so much!

1 Like