Hi all, I’ve been scratching my head on this all morning, and I can’t figure what’s missing… I’m trying to define types based on the contents of an xml file and its schema. This is not unlike Trouble understanding macros: creating a struct on the fly - #6 by JonathanAnderson but I did not manage to make it work anyway.
I figured that, in order to create one type per XML element, I could loop over the contents of the XML file to generate all the types using a macro.
As an example, I created a small XML string and parsed it with EzXML
. I’m trying to use the “Name” property as a type name and the rest of the attributes as properties.
fruitsxml = parsexml("""
<Fruits>
<Fruit Name = "Apple" Description = "Apples are indeed fruits"/>
<Fruit Name = "Tomato" Description = "Yes tomatoes are fruits !"/>
</Fruits>
""")
Now to the macro. I need to retrieve the value of the “Name” property of my XML element so I need to escape it first then interpolate it, then get the property. Same thing for the description.
However when I try to use the associated expression in a subsequent quote
block, I get a ERROR: syntax: invalid type signature
. So I assume some expression is not properly expanded or else there.
macro generate_fruit(fruit)
fruit_name = :($(esc(fruit))["Name"])
fruit_description = :($(esc(fruit))["Description"])
ex = quote
struct $fruit_name
description::String
function $fruit_name()
return new($fruit_description)
end
end
end
return esc(ex)
end
firstfruit = firstelement(root(fruitsxml))
@macroexpand @generate_fruit(firstfruit)
The macro expansion gives
quote
struct ($(Expr(:escape, :firstfruit)))["Name"]
description::String
function (($(Expr(:escape, :firstfruit)))["Name"])()
return new(($(Expr(:escape, :firstfruit)))["Description"])
end
end
end
while I would like the actual values instead of the expressions, is there some eval
magic to add there ?
While testing other syntaxes I tried
macro generate_fruit_quote(fruit)
quote
fruit_name = $(esc(fruit))["Name"]
fruit_description = $(esc(fruit))["Description"]
ex = quote
struct $fruit_name
description::String
function $fruit_name()
return new($fruit_description)
end
end
end
return esc(ex)
end
end
For which @macroexpand
gave
:($(Expr(:escape, quote
struct "Apple"
description::String
function ("Apple")()
return new("Apples are indeed fruits")
end
end
end)))
Which shows that the underlying variables are almost properly expanded inside the struct
definition, but not the expression itself which is quoted.
Could someone explain the proper way to do this ?
EDIT n°1 i was missing a Symbol
in fruit_name
to get a proper type name
EDIT n°2 I think I’ve got it now. If I understand correctly, I just missed an eval
in my initial implementation instead of the esc
… I someone could clarify that would be perfect though.
To make it cleaner, I put everything inside a module
which leads to
module MyFruits
using EzXML
fruitsxml = parsexml("""
<Fruits>
<Fruit Name = "Apple" Description = "Apples are indeed fruits"/>
<Fruit Name = "Tomato" Description = "Yes tomatoes are fruits !"/>
</Fruits>
""")
macro generate_fruit(fruit)
fruit_name_type = eval(:(Symbol($(fruit)["Name"])))
fruit_description = :($(fruit)["Description"])
ex = quote
struct $fruit_name_type
description::String
function $fruit_name_type()
return new($fruit_description)
end
end
end
return ex
end
for efruit in eachelement(root(fruitsxml))
@eval @generate_fruit $efruit
end
end
Upon evaluation this gives me a Main.MyFruits
module with an Apple
and a Tomato
types