如何在模板钩子中使用闭包参数?

How to use a closure parameter in a template hook?

我有以下代码定义:

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

但是我在尝试使用它时遇到错误 "Error: undeclared identifier: 'it'":

let gp: Parser = ...

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

我希望将其翻译成:

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

这很好用。

好的,我找到了解决方案:

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

但这出乎意料,因为 'it' 已经在范围内!

模板中的变量、类型、形式参数和其他内容在默认情况下是生成符号的。 gensym 表示生成保证唯一的符号,以避免模板外的名称冲突。

您可以通过以下示例了解它是如何生成的:

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

mkproc()
foo()

这会给你这个编译错误:

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

其中 it 确实是 gensym'd。

要修复 hook,就像您的解决方案一样,是用 {.inject.} 标记变量,因为无法标记参数。

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

最后,像 hook 这样使用的模板可以受益于 {.dirty.}。这会将所有符号标记为 {.inject.},包括 proc 参数。

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

parser.hook "rule":
  echo it