import macros, strutils
macro pgen(name: untyped, body: untyped): untyped =
quote do:
proc `name`(y: string): string =
`body`
pgen(test1):
result = "Hello World"
pgen(test2):
result = y.toLowerAscii()
test2("Hello World")
pgen.nim(11, 5) template/generic instantiation from here
pgen.nim(12, 10) Error: undeclared identifier: 'y'
I tried using {.inject.} to get y to be usable, but haven't had much success. Any pointers on what I'm doing wrong?
That's because the y in the body and the y in the macro do not have the same scope.
Nim macros are hygienic so even if you use the same name, if they are in different scope they will get a unique identifier in the end.
You can use this to replace all occurences of y in the original body by one create in the macro scope:
import strutils
template pgen(name, identifier, body: untyped): untyped =
proc name(`identifier`{.inject.}: string):string =
`body`
import macros, strutils
proc replaceNodes*(ast: NimNode, replacing: NimNode, to_replace: NimNode): NimNode =
## Replace an array of identifiers
##
## Args:
## - The full syntax tree
## - an array of replacement value
## - an array of identifiers to replace
proc inspect(node: NimNode): NimNode =
case node.kind:
of {nnkIdent, nnkSym}:
for i, c in to_replace:
if node.eqIdent($c):
return replacing[i]
return node
of nnkEmpty:
return node
of nnkLiterals:
return node
else:
var rTree = node.kind.newTree()
for child in node:
rTree.add inspect(child)
return rTree
result = inspect(ast)
macro pgen(name: untyped, body: untyped): untyped =
## This is our input value
let y = newIdentNode("y")
let replacing = nnkBracket.newTree(y)
## Let's replace all occurence of y in the body
let to_replace = nnkBracket.newTree(ident"y")
let newBody = body.replaceNodes(replacing, to_replace)
quote do:
proc `name`(`y`: string): string = # Notice that we inject `y` here as well, so all y are the same now.
`newBody`
pgen(test1):
result = "Hello World"
pgen(test2):
result = y.toLowerAscii()
echo test2("Hello World")
You can also inject the symbol I've found:
import macros, strutils
macro pgen(name: untyped, body: untyped): untyped =
quote do:
proc `name`(y: string): string =
let y {.inject.} = y
`body`
pgen(test1):
result = "Hello World"
pgen(test2):
result = toLowerAscii(y)
echo test2("Hello World")
Wow, nice one
then this is better than macros:
import strutils
template pgen(name: untyped, body: untyped): untyped =
proc `name`(y: string): string =
let y {.inject.} = y
`body`
pgen(test1):
result = "Hello World"
pgen(test2):
result = toLowerAscii(y)
echo test2("Hello World")
Instead of injecting you can use {.dirty.}
import strutils
template pgen(name, body): untyped {.dirty.} =
proc `name`(y: string): string =
body
pgen(test1):
result = "Hello World"
pgen(test2):
result = y.toLowerAscii()
echo test2("Hello World")
Awesome! Thanks all for the solutions. Rebinding the parameter and adding the inject pragma works perfectly.
Additionally, thanks mratsim for the detailed explanation.