Recursive macro call

As an exercise, I’m trying to make a macro that turns `@x a b c` into `a = b = c = 1234`.

I tried to write it recursively, but I don’t know how to fix my error

Definition:

``````julia> macro x(s::Symbol, ss...)
quote
\$(esc(s)) = @x \$(esc(ss))...
end
end

julia> macro x()
1234
end
``````

Call:

``````julia> @x a b c
ERROR: LoadError: MethodError: no method matching @x(::LineNumberNode, ::Module, ::Expr)
Closest candidates are:
@x(::LineNumberNode, ::Module) at REPL[9]:2
@x(::LineNumberNode, ::Module, ::Symbol, ::Any...) at REPL[21]:2
in expression starting at REPL[21]:3
``````

Any tips?

(The self-assigned exercise is to write it recursively)

Generally one doesn’t write macros as themselves being recursive. Instead, you write a function that works on the input expression, and that function is recursive. The macro calls the recursive function and returns the final expression.

2 Likes

This works…

``````julia> macro xx(s, ss...)
quote
\$(esc(s)) = @xx \$(esc.(ss)...)
end
end;

julia> macro xx()
1234
end
@xx (macro with 2 methods)

julia> (@macroexpand @xx a b c) |> Base.remove_linenums!
quote
a = begin
b = begin
c = 1234
end
end
end
``````

although I would do:

``````julia> macro y(s::Symbol, ss...)
_y(s, ss...)
end;

julia> _y(s, ss...) = :(\$(esc(s)) = \$(_y(ss...)));

julia> _y() = 1234
_y (generic function with 2 methods)

julia> @macroexpand @y a b c
:(a = (b = (c = 1234)))
``````
5 Likes

After the 5th or 6th guess at where to put the `esc` :

``````macro x(s::Symbol, ss...)
quote
\$(esc(s)) = \$(esc(:(@x \$(ss...))))
end
end

macro x()
1234
end
``````
1 Like

Thanks everyone! And @mcabbott thank you for the tip to call out to functions

You probably don’t want to escape `@x` as well, since that will look for the macro definition in the macro caller’s scope, so if you are defining the macro in a different module and don’t explicitly import it, this will fail. @mcabbott’s solution doesn’t have this problem, because only the arguments are escaped.

Good point. Actually I tried @mcabbott’s solution but didn’t think to remove the `::Symbol`, which I guess is crucial (it errors otherwise) for reasons that are currently mysterious to me…