How to use a closure parameter in a template hook?

Viewed 95

I have the following code definition:

type Tree* = ref ...

type Parser* = ref object
  hooks: Table[string, proc(it: var Tree)]

template hook*(this: var Parser, rule: string, body: untyped) =
  this.hooks[rule] = proc(it: var Tree) =
    body

But I get an error "Error: undeclared identifier: 'it'" when trying to use it like:

let gp: Parser = ...

gp.hook("Value"):
  echo $it

I expect this to be translated to:

gp.hooks["Value"] = proc(it: var Tree) =
  echo $it

And this works fine.

2 Answers

Variables, types, formal parameters, and others in templates are gensym'd by default. gensym means to generate a guaranteed unique symbol to avoid name clashes outside of the template.

You can see how it is gensym'd with the following example:

template mkproc() =
  proc foo(it: var Tree) =
    echo it

mkproc()
foo()

Which will give you this compilation error:

Error: type mismatch got <> but expected one of:
proc foo(it`gensym123456: var Tree)

Where it is indeed gensym'd.

To fix hook, like your solution, is to tag a variable with {.inject.} since the parameter can't be tagged.

template hook*(this: var Parser, rule: string, body: untyped) =
  this.hooks[rule] = proc(it: var Tree) =
    var it {.inject.} = it
    body

Lastly, templates used like hook can benefit from {.dirty.}. This will tag all symbols as {.inject.} including the proc parameters.

template hook*(this: var Parser, rule: string, body: untyped) {.dirty.} =
  this.hooks[rule] = proc(it: var Tree) =
    body

parser.hook "rule":
  echo it

OK, I found the solution:

template hook*(this: var Parser, rule: string, body: untyped) =
  this.hooks[rule] = proc(tree: var Tree) =
    var it {.inject.} = tree
    body

But this is unexpected since 'it' is already in a scope!

Related