I have a macro that creates a type and associated functions, and I'm calling that from a template. I want to use identifier construction to append a suffix to the template parameter, but the whole backticked AST (instead of the constructed identifier) gets passed in to the macro. Is there some way to generate an identifier before the macro is evaluated? I've included a simplified example below.
macro defthing(name: untyped): untyped =
return parseStmt("let $1 = 1000".format($name.ident))
template calldefthing(name: untyped): untyped {.dirty.} =
defthing name # argument is an identifier
defthing `name Extra` # argument is not an identifier, I'd like to evaluate the `name Extra` before the macro is called
calldefthing x
This works for me on 0.18 and devel
import macros
import strutils
macro defthing(name: untyped): untyped =
return parseStmt("let $1 = 1000".format(ident($name)))
template calldefthing(name: untyped): untyped =
# defthing name # argument is an identifier
defthing `name Extra` # argument is not an identifier, I'd like to evaluate the `name Extra` before the macro is called
calldefthing x
Yes, that compiles, but the macro produces the code
let x = 1000
instead of the code
let xExtra = 1000
I'm trying to get the identifier to be constructed before passing it into the macro, but I'm not sure if that's possible.
Normally to do identifier construction of variable you need to use {.inject.}
However untyped macro will capture everything and you will have to deal with the inject pragma in your macro.
Instead forget about the template and do identifier construction directly in it.
import macros
static: echo "Target macro: "
dumpASTgen():
let xExtra = 1000
static: echo "#############"
macro defthing(name: untyped): untyped =
echo name.treerepr
result = newStmtList()
result.add nnkLetSection.newTree(
nnkIdentDefs.newTree(
newIdentNode($name & "Extra"),
newEmptyNode(),
newLit(1000)
)
)
defthing awesome
echo "xExtra is: " & $awesomeExtra
Now building on your example, if you want to match normal Nim syntax and macro calls. Here are 3 ways to do that from within macros and templates:
import macros
macro defthing(name: untyped): untyped =
echo name.treerepr
result = newStmtList()
result.add nnkLetSection.newTree(
nnkIdentDefs.newTree(
newIdentNode($name & "Extra"),
newEmptyNode(),
newLit(1000)
)
)
# 1. Extra stuff in macro v1
template extraThing(): untyped =
echo "wow extra from template with normal Nim syntax"
result.add getAST(extraThing())
# 2. Extra stuff in macro v2"
result.add quote do:
echo "yet more thing to do in macro with normal Nim syntax"
template yetyetAnother(name: untyped): untyped =
# 3. Extra stuff in template
defthing(name)
echo xExtra * 1337
yetyetAnother(x)
Edit: Here's something that works:
import macros
macro defthing(name: untyped): untyped =
# used this instead of parseStmt
result = quote do:
let `name` = 1000
template calldefthing(name: untyped): untyped =
# defthing name # argument is an identifier
defthing `name Extra` # argument is not an identifier, I'd like to evaluate the `name Extra` before the macro is called
calldefthing x
echo $xExtra
Forget the previous version of this post, I'm starting to think that there's some kind of "cached state" thing in the compilation process that gives me inconclusive results for code changes.Thanks for the suggestions.
@mratsim I had no idea you could put a template inside a macro and get its evaluated AST. Cool! This will be very useful.
@Lando Interesting, I guess this works because quote doesn't touch the passed in backticked identifier and this is then returned intact to the template? Maybe I should treat identifiers-like NimNodes as more of a black box in the future to allow this sort of thing.
In a quote do block, `quoted` identifiers are interpolated with the normal Nim syntax.
You actually have to use the normal Nim syntax, which wasn't really evident in my own example because echo exists in both.
Basically quote do is equivalent to template + getAST:
import macros
macro defthing(): untyped =
# macro level
let interpol = 123
# 2. Extra stuff in macro v2"
result = newStmtList()
result.add quote do:
# "Runtime level"
echo "yet more thing to do in macro with normal Nim syntax"
let a = 456 + `interpol`
echo a
proc foo(x: int): bool =
x mod 2 == 0
echo foo(a)
echo foo(`interpol`)
defthing()
Interesting, I guess this works because quote doesn't touch the passed in backticked identifier and this is then returned intact to the template?
Yes. The different meanings of backtick-quoting ("make an identifier" in the template, "evaluate in local scope and put here" in the quote block) can be pretty confusing:
import macros
macro defthing(name: untyped): untyped =
# used this instead of parseStmt
result = quote do:
let `name` = 1000
echo "generated code: " & result.repr
template calldefthing(name: untyped): untyped =
# defthing name # argument is an identifier
defthing `name Extra`
calldefthing x
echo $xExtra