Call a macro within a macro

I am trying to define a macro that calls another macro.

To take an example, suppose a package defines a macro @formula as follows:

type Formula
    lhs::Union{Symbol, Expr, Void}
    rhs::Union{Symbol, Expr, Integer}
end
macro formula(ex)
    if (ex.head === :call && ex.args[1] === :(~))
        lhs = Base.Meta.quot(ex.args[2])
        rhs = Base.Meta.quot(ex.args[3])
       return Expr(:call, :Formula, lhs, rhs)
    else
        error("expected ~, got $(ex.args[1])")
    end
end
@formula(y~x)
#> Formula(:y, :x)

My goal is to define a macro @formula2 that simply calls @formula

macro formula2(expr)
   Expr(:macrocall, Symbol("@formula"), expr)
end
@formula2(y~x)
#> ERROR: expected ~, got $(Expr(:globalref, Main, :~))

This simple definition does not work: @formula2(y~x) does not give the same output as @formula(y~x). How could I define @formula2 to obtain exactly the same output as @formula?

Perhaps:

macro formula2(expr)
    :(@formula($(esc(expr))))
end
macro formula2(expr)
    :(@formula $(esc(expr)))
end
4 Likes

Thanks yuyichao! Could you explain a little bit what is happening? Is there some documentation about it?

All user input must be escaped once and exactly once.
https://docs.julialang.org/en/latest/manual/metaprogramming.html#Hygiene-1

3 Likes

Hi @yuyichao , @kristoffer.carlsson

I just encounter a similar problem.

To make it simple:

I have a inner macro:

macro m1(ex)  :(println($ex + 1)) end

and an outer macro:

macro m2(ex)  :(@m1 $(esc(ex))) end

But

julia> for i in 1:3
       @m2 i
       end
ERROR: UndefVarError: i not defined
Stacktrace:
 [1] macro expansion at ./REPL[10]:2 [inlined]
 [2] top-level scope at ./<missing>:0

How can the inner macro access i?

Thanks.

Use esc in the first macro, too, eg

macro m1(ex)
    :(println($(esc(ex)) + 1))
end

macro m2(ex)
    :(@m1 $(esc(ex)))
end
1 Like

Wow…

Thanks!

With my usual apology to resurrecting a dormant thread, this thread came up high in a google search and I wanted to link it to the more relevant thread:

In short, I wanted to call @show within a macro, and the seemingly correct macro requires an esc around the whole returned expression:

macro sourceshow(expr)
    si = Base.CoreLogging.@_sourceinfo
    fileline = "$(si[2]):$(si[3])"
    esc(:(begin
        println($fileline)
        @show($expr)
    end
    ))
end

function f()
    y = 4
    @sourceshow y
end

@show macroexpand(Main, :(@sourceshow y))

f()
macro sourceshow(expr)
    si = Base.CoreLogging.@_sourceinfo
    fileline = "$(si[2]):$(si[3])"
    esc(:(begin
        println($fileline)
        @show($expr)
    end
    ))
end

function f()
    y = 4
    @sourceshow y
end

@show macroexpand(Main, :(@sourceshow y))

f()

I don’t see any hygiene issues with @sourceshow