Hi there,
I want to write macros that takes code written in one place and inserts it elsewhere. In use this would look something like:
registerHook post_foo:
echo “foo done. x=“, x
...
proc foo =
let x = 10
runHooks post_foo
My approach for the registerHook macro has been:
Is this possible with macros? Any help appreciated.
Version 1, exactly what you asked.
import tables, macros
var hooks {.compileTime.}: Table[string, NimNode]
macro registerHook(name: untyped{ident}, body: untyped): untyped =
result = newStmtList()
result.add newProc(
name = name,
body = body,
# need a dirty template to capture x
procType = nnkTemplateDef,
pragmas = nnkPragma.newTree(ident"dirty")
)
# We could use a hashing scheme for idents instead of a string
# but let's keep it simple for now
# See https://github.com/mratsim/compute-graph-optim/blob/master/e07_part1_var_ADTs_tables.nim#L5-L7
hooks[$name] = name
macro runHooks(hook: untyped): untyped =
result = newCall(hooks[$hook])
registerHook post_foo:
# need a dirty template to capture x
echo "foo done. x=", x
proc foo =
let x = 10
runHooks post_foo
proc main() =
foo()
echo "The end."
main()
And second version, with run_hook as pragma
import tables, macros
var hooks {.compileTime.}: Table[string, NimNode]
macro registerHook(name: untyped{ident}, body: untyped): untyped =
result = newStmtList()
result.add newProc(
name = name,
body = body,
# need a dirty template to capture x
procType = nnkTemplateDef,
pragmas = nnkPragma.newTree(ident"dirty")
)
# We could use a hashing scheme for idents instead of a string
# but let's keep it simple for now
# See https://github.com/mratsim/compute-graph-optim/blob/master/e07_part1_var_ADTs_tables.nim#L5-L7
hooks[$name] = name
macro runHook(pragma: untyped, moddedProc: untyped): untyped =
result = moddedProc
result[6].expectKind(nnkStmtList)
result[6].add newCall(pragma)
registerHook post_foo:
# need a dirty template to capture x
echo "foo done. x=", x
proc foo {.runHook: post_foo.}=
let x = 10
proc main() =
foo()
echo "The end."
main()
Thanks mratsim, the use of {.compileTime.} was what I was missing :)
One follow-up question: what does the untyped{ident} syntax mean? Is it restricting the argument to valid identifiers?
Thanks
I created a cookbook repo to store some recipes, I had (I have plenty sprayed over my repos!).
Yours is the first one: https://github.com/status-im/nim-cookbook/blob/264cc90d/macros_register_hooks.nim
Awesome, I’ll be watching the repo!
For posterity, I also made a version that supports any number of hooks registered under one name, based on the compile-time table version.
import tables, macros
var HOOKS {.compileTime.}: Table[string, seq[NimNode]]
macro add_hook*(name: untyped{ident}, body: untyped): untyped =
discard HOOKS.has_key_or_put($name, @[])
HOOKS[$name].add new_block_stmt(new_empty_node(), body)
macro run_hooks*(name: untyped): untyped =
result = new_stmt_list()
if HOOKS.has_key($name):
for hook in HOOKS[$name]:
result.add hook