I’d like to share variables between nested macros in a convenient manner, but this seems to be difficult. I will try to illustrate the situation with some code examples.
To create custom properties to Julia types one can do the following:
struct S
field
end
function Base.getproperty(this::S, sym::Symbol)
if sym === Symbol("property")
return this.field * 2
end
if sym === Symbol("fun")
return function(n::Int)
return this.field * n
end
end
return getfield(this, sym)
end
s = S(10)
s.field
s.property
s.fun(3)
However, the branching Base.getproperty
is not too pretty. What I’d like instead is the following (which should have the same behavior as above):
@defs S begin
@def property this.field * 2
@def fun function(n::Int)
return this.field * n
end
end
This looks significantly better, but this requires sharing of variables between macros:
module PropertyMacros
export @defs, @def
macro defs(type, body)
type = esc(type)
body = esc(body)
return quote
function Base.getproperty(this::$type, sym::Symbol)
$body
return getfield(this, sym)
end
end
end
macro def(symbol, body)
symb = string(symb)
body = esc(body)
return quote
# NOTE: "sym" has been defined in defs !
if sym === Symbol($symb)
return $body
end
end
end
end
Great, this seems to be what I want, however, there is one problem. The def
macro does not
know about sym
and will always convert it to PropertyMacros.sym
and thus breaking the
functionality.
Using @macroexpand
on the “pretty” code gives the following (I cleaned the generated comments):
using PropertyMacros
@macroexpand @defs S begin
@def property this.field * 2
@def fun function(n::Int)
return this.field * n
end
end
=>
# NOTE:
# The function signature defines: var"#8#sym"
# However, the variable used in the ifs is: Main.PropertyMacros.sym
quote
function (Main.PropertyMacros.Base).getproperty(var"#7#this"::S, var"#8#sym"::Main.PropertyMacros.Symbol)
begin
begin
if Main.PropertyMacros.sym === Main.PropertyMacros.Symbol("property")
return this.field * 2
end
end
begin
if Main.PropertyMacros.sym === Main.PropertyMacros.Symbol("fun")
return function (n::Int,)
return this.field * n
end
end
end
end
return Main.PropertyMacros.getfield(var"#7#this", var"#8#sym")
end
end
The problem with the generated code is that var"#8#sym" != Main.PropertyMacros.sym
(same
problem actually applies to the this
identifier).
The easy solution to this problem would be to just tell the macro generator to not transform some identifiers or to just inject strings, like this fictional example: injectstring("sym")
, but without the quotes.
Another solution would be to somehow specify macro dependencies, but this feature is unlikely implemented in Julia.
I did successfully implement this behavior using raw strings, Meta.parse
, esc
and @eval
, but that code is quite ugly.
Is this problem solvable at the moment without using raw strings? Any help would be much appreciated!