Executing a method given its name as a string

I would like to define a macro that would execute a function having been provided its name in a string-valued variable. So the following code gives a naive implementation:

macro invoke_callback(c,args...) =
quote
eval(Symbol($c))($args...)
end
end

However, while it works in the REPL for testing, it doesn’t work in more complex situations where the expression “c” and/or its contents are defined in a different module to the invocation site of the macro. I think this is because the eval method that is used is the one from the module in which the macro is invoked, and so it doesn’t necessarily have access to the module from which expression “c” has been passed.

So the question is, is there an elegant way to access the appropriate eval, or should I just go about this a different way, e.g by using a Dict{String,Function}?

Probably :slightly_smiling_face:

Can you explain what you are actually trying to do? If you want to execute a callback, you can just pass the function itself rather than trying to find it from its name as a string. Is there some reason that doesn’t work in your case?

1 Like

Here is a simple test case for using the macro defined in the first post which is assumed defined in another module:

module A
q(x) = println("q called with $x")
end

module B
tt = "A.q"
@invoke_callback tt 5
end

Should print q called with 5

Some more background:
I’m trying to port the pure Python Lark parser to Julia. Lark provides “Transformer” and “Visitor” classes which walk the parse tree and execute methods on each visited node according to the grammar rule that produced that node. Users of Lark write a Transformer/Visitor subclass with methods named after the grammar rules. The python code managing transformation/visitation finds the method to call based on the string-valued rule name. I am just emulating this behaviour.

As for alternative approaches, the Lark class hierarchy maps nicely to Julia abstract types, as the Lark classes are defined by how they manage transformations or visitations, rather than by content. The user-defined subclass in Python becomes the concrete Julia type at the bottom of the type tree. Therefore, a simple alternative to the string -> function conundrum I have raised would be for the user-defined class to include a Dict{String,Function} field and then the user-defined methods populate this dictionary. This could be made simple by defining a couple of macros for use when writing the user methods. It feels a bit like reinventing method tables or classes, however, and makes me think there might be a more elegant way.

I’m not overly fussed with taking the alternative route, but it would be quite educational to know how this might be achieved with macro magic. I can’t help feeling that a few judicious $ and esc might solve the problem.

I assume you want to keep the same grammer format as the python version?

You can parse the grammer inside the macro and emit julia AST which will then be eval'd by the runtime. (Normally calling eval in a macro is a sign that you’re doing something wrong :slight_smile: )

Here’s a sketch:

julia> module A
           f(str) = @info "Called A.f($(repr(str)))"
       end

julia> macro parse_grammer(grammer)
           piece = split(grammer)[1]  # This is a stand in for some arbitrary DSL parsing
           ast = Meta.parse(piece) # Can utilize Meta.parse if some DSL parts are julia code
           quote
               # Here you emit some function or callable object
               # representing your parser. The compiler will use the usual
               # scoping rules to resolve `esc(ast)` in the scope where
               # @parse_grammer is invoked
               function myparser(str)
                   $(esc(ast))(str)
               end
           end
       end

julia> parser = @parse_grammer "A.f blah blah"
#30#myparser (generic function with 1 method)

julia> parser("string you're trying to parse")
[ Info: Called A.f("string you're trying to parse")