语句表构成
Statement list composition
我需要将一个语句列表拆分成多个部分,如下所示:
import macros
macro test: stmt =
var first = quote do:
var x = 1
var second = quote do:
echo x
result = newStmtList()
first.copyChildrenTo(result)
second.copyChildrenTo(result)
echo result.repr
test
但是编译器告诉我这个:
[..]
var x = 1
echo x
minimalist.nim(14, 0) Info: instantiation from here
minimalist.nim(7, 13) Error: undeclared identifier: 'x'
echo x
考虑到 x
是在节点列表中声明的,这非常令人困惑。我如何使它正常工作? (如果不是很明显,出于其他原因我确实需要将 AST 分成多个部分)
如果您比较 AST,您会发现哪里出了问题:
dumpTree:
var x = 1
echo x
这会打印:
StmtList
VarSection
IdentDefs
Ident !"x"
Empty
IntLit 1
Command
Ident !"echo"
Ident !"x"
那么在你的例子中:
macro test: stmt =
var first = quote do:
var x = 1
var second = quote do:
echo x
result = newStmtList()
first.copyChildrenTo(result)
second.copyChildrenTo(result)
echo result.treeRepr
test
这会打印:
StmtList
VarSection
IdentDefs
Sym "x"
Empty
IntLit 1
Command
Sym "echo"
Ident !"x"
注意到区别了吗?一旦 "x" 是一个 Ident,另一次是 Sym。这些符号不应该被查找,也许这应该在编译器中改变。但是现在您可以自己解决问题并再次将所有 Syms 替换为 Idents 以强制进行新查找:
import macros
proc symsToIdents(n): PNimrodNode {.compiletime.} =
if n.kind == nnkSym:
return newIdentNode($n)
result = n
for i in 0 .. <result.len:
result[i] = symsToIdents(n[i])
macro test: stmt =
var first = quote do:
var x = 1
var second = quote do:
echo x
result = newStmtList()
first.copyChildrenTo(result)
second.copyChildrenTo(result)
result = symsToIdents(result)
test
编辑:现在已报告为可能的错误:https://github.com/nim-lang/Nim/issues/1843
默认情况下,Nim 将对引用块内的代码执行宏卫生规则。这意味着在一个块中引入的符号名称在另一个块中将不可见。您可以通过为共享符号引入一个变量来解决这个问题,如下所示:
import macros
macro test: stmt =
var x = genSym()
var first = quote do:
var `x` = 1
var second = quote do:
echo `x`
result = newStmtList()
first.copyChildrenTo(result)
second.copyChildrenTo(result)
test
在幕后,quote 由应用于非脏模板的 getAst
操作提供支持。如果您自己使用较低级别的机制,也可以像这样禁用引用模板的卫生行为:
import macros
macro test: stmt =
template first {.dirty.} =
var x = 1
template second {.dirty.} =
echo x
result = newStmtList()
getAst(first()).copyChildrenTo(result)
getAst(second()).copyChildrenTo(result)
test
这将得到相同的结果。请记住,quote
最终会获得一个标志,用于控制引用代码的脏度。
我需要将一个语句列表拆分成多个部分,如下所示:
import macros
macro test: stmt =
var first = quote do:
var x = 1
var second = quote do:
echo x
result = newStmtList()
first.copyChildrenTo(result)
second.copyChildrenTo(result)
echo result.repr
test
但是编译器告诉我这个:
[..]
var x = 1
echo x
minimalist.nim(14, 0) Info: instantiation from here
minimalist.nim(7, 13) Error: undeclared identifier: 'x'
echo x
考虑到 x
是在节点列表中声明的,这非常令人困惑。我如何使它正常工作? (如果不是很明显,出于其他原因我确实需要将 AST 分成多个部分)
如果您比较 AST,您会发现哪里出了问题:
dumpTree:
var x = 1
echo x
这会打印:
StmtList
VarSection
IdentDefs
Ident !"x"
Empty
IntLit 1
Command
Ident !"echo"
Ident !"x"
那么在你的例子中:
macro test: stmt =
var first = quote do:
var x = 1
var second = quote do:
echo x
result = newStmtList()
first.copyChildrenTo(result)
second.copyChildrenTo(result)
echo result.treeRepr
test
这会打印:
StmtList
VarSection
IdentDefs
Sym "x"
Empty
IntLit 1
Command
Sym "echo"
Ident !"x"
注意到区别了吗?一旦 "x" 是一个 Ident,另一次是 Sym。这些符号不应该被查找,也许这应该在编译器中改变。但是现在您可以自己解决问题并再次将所有 Syms 替换为 Idents 以强制进行新查找:
import macros
proc symsToIdents(n): PNimrodNode {.compiletime.} =
if n.kind == nnkSym:
return newIdentNode($n)
result = n
for i in 0 .. <result.len:
result[i] = symsToIdents(n[i])
macro test: stmt =
var first = quote do:
var x = 1
var second = quote do:
echo x
result = newStmtList()
first.copyChildrenTo(result)
second.copyChildrenTo(result)
result = symsToIdents(result)
test
编辑:现在已报告为可能的错误:https://github.com/nim-lang/Nim/issues/1843
默认情况下,Nim 将对引用块内的代码执行宏卫生规则。这意味着在一个块中引入的符号名称在另一个块中将不可见。您可以通过为共享符号引入一个变量来解决这个问题,如下所示:
import macros
macro test: stmt =
var x = genSym()
var first = quote do:
var `x` = 1
var second = quote do:
echo `x`
result = newStmtList()
first.copyChildrenTo(result)
second.copyChildrenTo(result)
test
在幕后,quote 由应用于非脏模板的 getAst
操作提供支持。如果您自己使用较低级别的机制,也可以像这样禁用引用模板的卫生行为:
import macros
macro test: stmt =
template first {.dirty.} =
var x = 1
template second {.dirty.} =
echo x
result = newStmtList()
getAst(first()).copyChildrenTo(result)
getAst(second()).copyChildrenTo(result)
test
这将得到相同的结果。请记住,quote
最终会获得一个标志,用于控制引用代码的脏度。