Code templates, quoting, and variable substitution

I’m having trouble with code templates, quoting, and variables.

I’m using the NativeSVG package. Some of its SVG element definitions don’t allow content even though they should. In particular, I’d like to add a title child to rect so that my rects can have tooltips.

I made a local branch of NativeSVG to work in. It’s easy enough to change the flag associated with :rect in the definition of PRIMITIVES to enable child content.

The code in NativeSVG that defines a function for each SVG element that can have children is

for primitive in keys(filter(d -> last(d), PRIMITIVES))
    eval(quote
        function $primitive(f::Function, io::IOBuffer = BUFFER; kwargs...)
            print(io, "<", $primitive)
            for (arg, val) in kwargs
                print(io, " ", replacenotallowed(arg), "=\"", val, "\"")
            end
            println(io, ">")
            f()
            println(io, "</", $primitive, ">")
        end
    end)
end

I’m also trying to abstract out the code that writes the SVG into a single function that can be called by those element definitions and is available to users if they require some element not considered by NativeSVG:

function element(f::Function, tagname::Symbol, io::IOBuffer = BUFFER; kwargs...)
    print(io, "<", tagname)
    for (arg, val) in kwargs
        print(io, " ", replacenotallowed(arg), "=\"", val, "\"")
    end
    println(io, ">")
    f()
    println(io, "</", tagname, ">")
end

for primitive in keys(filter(d -> last(d), PRIMITIVES))
    eval(quote
        function $primitive(f::Function, io::IOBuffer = BUFFER; kwargs...)
          element(f, $primitive, io; kwargs...)
        end
    end)
end

When I run my NativeSVG’s unit tests though, I get the error

     Testing Running tests...
ERROR: LoadError: MethodError: no method matching element(::NativeSVG.var"#6#7"{var"#1#3"}, ::typeof(svg), ::IOBuffer; xmlns="http://www.w3.org/2000/svg", width="300", height="200")
Closest candidates are:
  element(::Function, ::Symbol, ::IOBuffer; kwargs...) at c:\Users\Mark Nahabedian\.julia\dev\NativeSVG.jl\src\svg.jl:82
  element(::Function, ::Symbol) at c:\Users\Mark Nahabedian\.julia\dev\NativeSVG.jl\src\svg.jl:82 got unsupported keyword arguments "xmlns", "width", "height"
Stacktrace:
 [1] svg(f::Function, io::IOBuffer; kwargs::Base.Iterators.Pairs{Symbol, String, Tuple{Symbol, Symbol, Symbol}, NamedTuple{(:xmlns, :width, :height), Tuple{String, String, String}}})
   @ NativeSVG ~\.julia\dev\NativeSVG.jl\src\svg.jl:105

The top stack trame svg is a function defined by this code.

I don’t understand why svg is calling element without ‘f’ and with a var. Presumably the 2nd argument ::typeof(svg) is the :svg that was substituted for $primitive. Why did it not recognize that as Symbol?

Can someone explain what I’m doing wrong?

Thanks.

When you write:

        quote
        function $primitive(f::Function, io::IOBuffer = BUFFER; kwargs...)
          element(f, $primitive, io; kwargs...)
        end

you’re producing code that looks something like:

function svg(f...)
  element(f, svg, ...)
end

where svg is a symbol in the resulting code (just like element, and f are), but not actually a quoted symbol itself.

Or, in simpler form, you’re doing this:

julia> name = :a
:a

julia> quote
         $name
       end
quote
    #= REPL[7]:2 =#
    a
end

when what you want is this:

julia> quote
         $(QuoteNode(name))
       end
quote
    #= REPL[8]:2 =#
    :a
end

I think element(f, $(QuoteNode(primitive)), io; kwargs...) should resolve the issue.

1 Like

Thanks Robin. That did the trick.

I don’t understand why the call to print in the original code did’t have the same problem though. Would it be because print presumably acceptsAny and it could find a string representation for what ot got?

Right, you can print just about anything, and if you print a function, it just prints the name of that function:

julia> function foo()
         println("<", foo, ">")
       end
foo (generic function with 1 method)

julia> foo()
<foo>

but that’s not a particularly relaible way to do things. After all, some future version of Julia might “improve” function printing to something like Function: "foo" or whatever, which would have completely broken the old code.