I recommend you read the hygiene section in the docs.
I also recommend (again) using @macroexpand
to see what your macro is doing so you can look for what to change. The output of the macro should be the a quoted code that you originally wanted.
If I use your macro as defined and test on the example you provided this is what we get:
julia> @macroexpand @simsolver MySolver begin
@param a = 1.0
@param b = false
@param c
end
quote # REPL[15], line 4:
struct MySolverParam # REPL[15], line 6:
end # REPL[15], line 8:
struct MySolver # REPL[15], line 9:
params::(Main.Dict){Main.Symbol, MySolverParam} # REPL[15], line 11:
MySolver(#84#params::(Main.Dict){Main.Symbol, MySolverParam}) = begin # REPL[15], line 11:
new(#84#params)
end
end # REPL[15], line 15:
function #82#MySolver(#91#params...) # REPL[15], line 17:
#85#dict = (Main.Dict){Main.Symbol, MySolverParam}() # REPL[15], line 20:
for (#86#varname, #87#varparams) = #91#params # REPL[15], line 21:
#88#kwargs = [#89#k => #90#v for (#89#k, #90#v) = (Main.zip)((Main.keys)(#87#varparams), #87#varparams)] # REPL[15], line 22:
(Main.push!)(#85#dict, #86#varname => MySolverParam(; #88#kwargs...))
end # REPL[15], line 25:
#82#MySolver(#85#dict)
end
end
All those #SomeNumber#Then_what_I_originally_wrote
is due to macro hygiene.
Now I’ll change a little bit your macro to:
julia> macro simsolver(solver, body)
paramtype = Symbol(solver,"Param")
esc(quote
struct $paramtype
# how to copy every line starting with @param to here?
end
struct $solver
params::Dict{Symbol,$paramtype}
$solver(params::Dict{Symbol,$paramtype}) = new(params)
end
# this outer constructor is not visible for some reason?
function $solver(params...)
# build dictionary for inner constructor
dict = Dict{Symbol,$paramtype}()
# convert named tuples to solver parameters
for (varname, varparams) in params
kwargs = [k => v for (k,v) in zip(keys(varparams), varparams)]
push!(dict, varname => $paramtype(; kwargs...))
end
$solver(dict)
end
end)
end
@simsolver (macro with 1 method)
Note I’m using esc()
on the whole quote. Now testing it again with @macroexpand
:
julia> @macroexpand @simsolver MySolver begin
@param a = 1.0
@param b = false
@param c
end
quote # REPL[17], line 4:
struct MySolverParam # REPL[17], line 6:
end # REPL[17], line 8:
struct MySolver # REPL[17], line 9:
params::Dict{Symbol, MySolverParam} # REPL[17], line 11:
MySolver(params::Dict{Symbol, MySolverParam}) = begin # REPL[17], line 11:
new(params)
end
end # REPL[17], line 15:
function MySolver(params...) # REPL[17], line 17:
dict = Dict{Symbol, MySolverParam}() # REPL[17], line 20:
for (varname, varparams) = params # REPL[17], line 21:
kwargs = [k => v for (k, v) = zip(keys(varparams), varparams)] # REPL[17], line 22:
push!(dict, varname => MySolverParam(; kwargs...))
end # REPL[17], line 25:
MySolver(dict)
end
end
Whoa! Now it is much closer to what we want!
Now, how to copy the list to the struct? I don’t know…
My recommendation is to play a little bit with julia Expr
to figure that out. First create a expression for your intended use of the macro:
julia> ex = :(@simsolver MySolver begin; @param a = 1.0; @param b = false; @param c; end)
:(@simsolver MySolver begin # REPL[40], line 1:
@param a = 1.0 # REPL[40], line 1:
@param b = false # REPL[40], line 1:
@param c
end)
Then use the head
and args
fields of the expression type:
julia> ex.head
:macrocall
julia> ex.args
3-element Array{Any,1}:
Symbol("@simsolver")
:MySolver
quote # REPL[40], line 1:
@param a = 1.0 # REPL[40], line 1:
@param b = false # REPL[40], line 1:
@param c
end
julia> ex.args[1]
Symbol("@simsolver")
julia> ex.args[2]
:MySolver
julia> ex.args[3]
quote # REPL[40], line 1:
@param a = 1.0 # REPL[40], line 1:
@param b = false # REPL[40], line 1:
@param c
end
So your expression will generate a macro call (as expected) with three argumetns: The name of the macro and the two parameters provided: the symbol :MySolver
and the expression given by the quote.
Now note the third argument is a expression itself. So we can do .head
and .args
on it also:
julia> ex.args[3].head
:block
julia> ex.args[3].args
6-element Array{Any,1}:
:( # REPL[40], line 1:)
:(@param a = 1.0)
:( # REPL[40], line 1:)
:(@param b = false)
:( # REPL[40], line 1:)
:(@param c)
We are getting there! The arguments are again all Expr
themselves. Let’s go to the second one:
julia> ex.args[3].args[2].head
:macrocall
julia> ex.args[3].args[2].args
2-element Array{Any,1}:
Symbol("@param")
:(a = 1.0)
There it is, one of the parameters!
julia> ex.args[3].args[2].args[2]
:(a = 1.0)
Now. remember ex.args[3]
will be the second argument, named body
in the macro definition. So you will access it by doing $(body.args[2].args[2]
. Let’s try adding that to your macro definition to see if it works…
julia> macro simsolver(solver, body)
paramtype = Symbol(solver,"Param")
esc(quote
struct $paramtype
$(body.args[2].args[2])
end
struct $solver
params::Dict{Symbol,$paramtype}
$solver(params::Dict{Symbol,$paramtype}) = new(params)
end
# this outer constructor is not visible for some reason?
function $solver(params...)
# build dictionary for inner constructor
dict = Dict{Symbol,$paramtype}()
# convert named tuples to solver parameters
for (varname, varparams) in params
kwargs = [k => v for (k,v) in zip(keys(varparams), varparams)]
push!(dict, varname => $paramtype(; kwargs...))
end
$solver(dict)
end
end)
end
@simsolver (macro with 1 method)
julia> @macroexpand @simsolver MySolver begin
@param a = 1.0
@param b = false
@param c
end
quote # REPL[86], line 4:
struct MySolverParam # REPL[86], line 5:
a = 1.0
end # REPL[86], line 8:
struct MySolver # REPL[86], line 9:
params::Dict{Symbol, MySolverParam} # REPL[86], line 11:
MySolver(params::Dict{Symbol, MySolverParam}) = begin # REPL[86], line 11:
new(params)
end
end # REPL[86], line 15:
function MySolver(params...) # REPL[86], line 17:
dict = Dict{Symbol, MySolverParam}() # REPL[86], line 20:
for (varname, varparams) = params # REPL[86], line 21:
kwargs = [k => v for (k, v) = zip(keys(varparams), varparams)] # REPL[86], line 22:
push!(dict, varname => MySolverParam(; kwargs...))
end # REPL[86], line 25:
MySolver(dict)
end
end
There it is! Now, of course, for your actual usage you will have to iterate your body
input to look for the Symbol("@param")
and do the filtering.
Summarizing:
- Use
@macroexpand
A LOT
- Analyze the expressions you are passing to the macro to make your plan to extract the info you want.
Ps:You can also use dump()
to analyze expressions but the quantity of info can be overwhelming:
julia> dump(ex)
Expr
head: Symbol macrocall
args: Array{Any}((3,))
1: Symbol @simsolver
2: Symbol MySolver
3: Expr
head: Symbol block
args: Array{Any}((6,))
1: Expr
head: Symbol line
args: Array{Any}((2,))
1: Int64 1
2: Symbol REPL[89]
typ: Any
2: Expr
head: Symbol macrocall
args: Array{Any}((2,))
1: Symbol @param
2: Expr
head: Symbol =
args: Array{Any}((2,))
1: Symbol a
2: Float64 1.0
typ: Any
typ: Any
3: Expr
head: Symbol line
args: Array{Any}((2,))
1: Int64 1
2: Symbol REPL[89]
typ: Any
4: Expr
head: Symbol macrocall
args: Array{Any}((2,))
1: Symbol @param
2: Expr
head: Symbol =
args: Array{Any}((2,))
1: Symbol b
2: Bool false
typ: Any
typ: Any
5: Expr
head: Symbol line
args: Array{Any}((2,))
1: Int64 1
2: Symbol REPL[89]
typ: Any
6: Expr
head: Symbol macrocall
args: Array{Any}((2,))
1: Symbol @param
2: Symbol c
typ: Any
typ: Any
typ: Any