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