I am faced with an issue that I can’t seem to figure out. Here is the source code (sorry I couldn’t come up with a more simple example):
macro handfunc(expr, kwargs...)
expr = unblock(expr) # cleans expression
expr = rmlines(expr) # cleans expression
var = esc(expr.args[1]) # pulls variable name
eq = esc(expr.args[2]) # pulls expression
func_args = QuoteNode(expr.args[2]) #pulls function arguments to pass to function below
found_func = InteractiveUtils.gen_call_with_extracted_types_and_kwargs(__module__, :code_expr, (expr.args[2],)) # returns expression
return quote
x = handfunc($found_func, $func_args) # handfunc returns expression
latex = $Main.eval(x) # need to evaluate it (but want to do it in calling scope)
$var = $eq # evaluates expression
latex # returns latex rendering
end
end
Here is a snip of what it does (it currently works in main scope):
It basically parses through the called function body and returns the latex rendering of it, while also evaluating the Ix = Handcalcs.calc_Ix(b, h) portion as well.
Here is my issue. I would like to return just the function and then the macro would automatically evaluate the returned expression, but I can’t since found_func is an expression that, if inside the function, wouldn’t be evaluated.
Basically, it seems like I am left with trying to use the eval function, but that is for global scope and I am not sure how to get it to evaluate in calling scope.
Is there a way to build an expression that would evaluate both?
Or is there a way to get eval to work for calling scope?
Don’t use eval — especially not in macros. Their job is to generate code, and it’ll get evaluated soon enough.
I think you want to call handfunc outside the quote block and then interpolate it in twice, once normally (which will be code that executes) and once into a quote node (to get the expression itself).
macro handfunc(expr, kwargs...)
expr = unblock(expr) # cleans expression
expr = rmlines(expr) # cleans expression
var = esc(expr.args[1]) # pulls variable name
eq = esc(expr.args[2]) # pulls expression
func_args = QuoteNode(expr.args[2]) #pulls function arguments to pass to function below
found_func = InteractiveUtils.gen_call_with_extracted_types_and_kwargs(__module__, :code_expr, (expr.args[2],)) # returns expression
ret_expr = handfunc(found_func, func_args)
return quote
latex = $ret_expr
$var = $eq # evaluates expression
latex # returns latex rendering
end
end
I just tried one interpolation first. This results in an error though because the found_func is an expression that needs to be evaluated first.
Before:
I don’t understand the code you have there so I can’t really comment on it in detail. But when you get errors due to expressions like $(Expr(:escape, :calc_Ix)) it usually means that you are escaping some piece of code twice.
Escaping has to be done at exactly the right granularity, ask yourself which of your code should appear in place of the macro as if the user had written it (often this is a verbatim or almost verbatim copy of the actual code written by the user) and which of the code is helper code that relates only to your module and shouldn’t in any way interact with the other top-level user code. That’s the stuff you do not want to escape.
So when you have a double escaping error, your logic was flawed because marked a piece of code as user code, interpolated it into some other code, but then also marked that code as user code. In this case, the inner interpolated code should not have been escaped first because it was going to be anyway, as part of the escaped outer code. (Admittedly, this is sometimes an annoying complexity when writing macros, because you have to know for each expression fragment you generate what the final context it ends up in has to be, and if you’re allowed to escape or not.)
Yeah I don’t know… That code you are referring to is generated by a function in the InteractiveUtils library which is code I did not write. Even the function parameters I pass to it is copied from CodeTacking.jl. That code works great when I interpolate it into the quote.
The problem is (at least I think) I need to pass the interpolated code from that InteractiveUtils function to the function I wrote (handfunc). But my function also returns an expression, but since it is within a quote, it doesn’t evaluate when macro is called… That is why I am using the eval function to force it to evaluate, but eval is global scope only…
I guess a general question: Can you within a macro, use a function that generates an expression. This expression then needs to be evaluated somehow. Let’s call the evaluated expression x. Then use that x within another function that generates an expression that then needs to be evaluated?
At a fundamental level, macros should just generate the code to do what you want — they aren’t the ones to do it.
So yes, your macro can output code that defines a function and then calls it.
But I think you’re getting tied in knots because you’re currently outputting code that happens to evaluate to an expression. But that might be backwards — you probably want to just construct the expression at macro-time (outside the quote). Then you can reference the expression in two ways: once that defines the function and once quoted-ly.
To simplify this, here’s a macro only deals with functions of two arguments named x and y:
julia> macro xy(expr)
quote
fun = ($(esc(:x)),$(esc(:y)))->$(esc(expr))
ret = fun($(esc(:x)), $(esc(:y)))
Expr(:(=), $(QuoteNode(expr)), ret)
end
end
@xy (macro with 1 method)
julia> let x=1, y=2
@xy x+y^2
end
:(x + y ^ 2 = 5)
julia> let x=3, y=2
@xy x+y^2
end
:(x + y ^ 2 = 7)