Invoking a macro within the invocation of another macro

I’m still having trouble with Julia macros.

I’ve defined some structs for implementing BNF grammars. I want my grammars to capture source locations. to this end, I’m trying to define a macro with which to wrap each structure definition. The macro should

  • add a slot to store the sourcelocation;

  • modify the constructor to provide a default value of nothing for that slot;

  • add a constructor that takes a source location as argument to initialize the slot;

  • define a macro that calls this second constructor with source.

With the flawed macro definitions I have, the struct for a character literal would expand thusly:

@macroexpand(@bnfnode struct CharacterLiteral <: BNFNode
    character::Char
end
)

quote
    struct CharacterLiteral <: AnotherParser.BNFNode
        #= C:\Users\Mark Nahabedian\.julia\dev\AnotherParser\src\note_BNFNode_location.jl:70 =#
        source::AnotherParser.Union{AnotherParser.Nothing, AnotherParser.LineNumberNode}
        #= C:\Users\Mark Nahabedian\.julia\dev\AnotherParser\src\note_BNFNode_location.jl:71 =#
        character::AnotherParser.Char
        #= C:\Users\Mark Nahabedian\.julia\dev\AnotherParser\src\note_BNFNode_location.jl:73 =#
        function CharacterLiteral(var"#3#character")
            #= C:\Users\Mark Nahabedian\.julia\dev\AnotherParser\src\note_BNFNode_location.jl:35 =#
            #= C:\Users\Mark Nahabedian\.julia\dev\AnotherParser\src\note_BNFNode_location.jl:36 =#
            new(nothing, var"#3#character")
        end
        function CharacterLiteral(var"#4#source"::AnotherParser.Union{AnotherParser.Nothing, AnotherParser.LineNumberNode}, var"#5#character")
            #= C:\Users\Mark Nahabedian\.julia\dev\AnotherParser\src\note_BNFNode_location.jl:56 =#
            #= C:\Users\Mark Nahabedian\.julia\dev\AnotherParser\src\note_BNFNode_location.jl:58 =#
            new(var"#4#source", var"#5#character")
        end
    end
    export CharacterLiteral, @CharacterLiteral
    macro CharacterLiteral(var"#6###args#257"...)
        CharacterLiteral(#= none:1 =#, var"#6###args#257"...)
    end
end

@macroexpand(@CharacterLiteral('a'))
CharacterLiteral(nothing, 'a')

That’s what I want.

Now thet’s try disjunction

@macroexpand(@CharacterLiteral('a'))
CharacterLiteral(nothing, 'a')
@macroexpand(@bnfnode struct Alternatives <: BNFNode
    alternatives::Tuple{Vararg{<:BNFNode}}

    function Alternatives(alternatives...)
        new(alternatives)
    end
end
)

quote
    struct Alternatives <: AnotherParser.BNFNode
        #= C:\Users\Mark Nahabedian\.julia\dev\AnotherParser\src\note_BNFNode_location.jl:70 =#
        source::AnotherParser.Union{AnotherParser.Nothing, AnotherParser.LineNumberNode}
        #= C:\Users\Mark Nahabedian\.julia\dev\AnotherParser\src\note_BNFNode_location.jl:71 =#
        alternatives::AnotherParser.Tuple{AnotherParser.Vararg{<:AnotherParser.BNFNode}}
        #= C:\Users\Mark Nahabedian\.julia\dev\AnotherParser\src\note_BNFNode_location.jl:73 =#
        function Alternatives(var"#9#alternatives"...)
            #= none:4 =#
            #= none:5 =#
            new(nothing, var"#9#alternatives")
        end
        function Alternatives(var"#10#source"::AnotherParser.Union{AnotherParser.Nothing, AnotherParser.LineNumberNode}, var"#11#alternatives"...)
            #= C:\Users\Mark Nahabedian\.julia\dev\AnotherParser\src\note_BNFNode_location.jl:56 =#
            #= C:\Users\Mark Nahabedian\.julia\dev\AnotherParser\src\note_BNFNode_location.jl:58 =#
            new(var"#10#source", var"#11#alternatives")
        end
    end
    export Alternatives, @Alternatives
    macro Alternatives(var"#12###args#258"...)
        Alternatives(#= none:1 =#, var"#12###args#258"...)
    end
end

I think that looks right.

My BNF data structures should be comoposable though. Note that below the character literal isn’t even using the macro but the simple constructor:

@macroexpand(@Alternatives())
Alternatives(nothing, ())
@macroexpand(@Alternatives(CharacterLiteral('a')))
ERROR: LoadError: MethodError: Cannot `convert` an object of type Expr to an object of type BNFNode
Closest candidates are:
  convert(::Type{T}, !Matched::T) where T at essentials.jl:205
Stacktrace:
 [1] cvt1
   @ .\essentials.jl:322 [inlined]
 [2] ntuple
   @ .\ntuple.jl:48 [inlined]
 [3] convert(#unused#::Type{Tuple{Vararg{BNFNode, N} where N}}, x::Tuple{Expr})
   @ Base .\essentials.jl:323
 [4] Alternatives(source::Nothing, alternatives::Expr)
   @ AnotherParser C:\Users\Mark Nahabedian\.julia\dev\AnotherParser\src\note_BNFNode_location.jl:58
 [5] var"@Alternatives"(__source__::LineNumberNode, __module__::Module, ##args#258::Vararg{Any, N} where N)
   @ AnotherParser C:\Users\Mark Nahabedian\.julia\dev\AnotherParser\src\BNFtypes.jl:53
 [6] #macroexpand#50
   @ .\expr.jl:112 [inlined]
 [7] top-level scope
   @ none:1
in expression starting at none:1

I get the same error with @CharacterLiteral instead of CharacterLiteral.

The error suggests that an Expr, rather than its value, is being passed to the Alternatives constructor. I don’t understand why though.

Why am I getting this error and what should I do to fix it?

Thanks.

The macro is defined here:
src/note_BNFNode_location.jl