For 循环宏

当宏只有一个输入参数,而且这个参数的类型是特殊的 system.ForLoopStmt 时, 这个宏可以重写整个 for 循环:

  1. import std/macros
  2. macro example(loop: ForLoopStmt) =
  3. result = newTree(nnkForStmt) # 创建一个新的 For 循环。
  4. result.add loop[^3] # 这是 "item" 。
  5. result.add loop[^2][^1] # 这是 "[1, 2, 3]" 。
  6. result.add newCall(bindSym"echo", loop[0])
  7. for item in example([1, 2, 3]): discard

展开为:

  1. for item in items([1, 2, 3]):
  2. echo item

再举一个例子:

  1. import std/macros
  2. macro enumerate(x: ForLoopStmt): untyped =
  3. expectKind x, nnkForStmt
  4. # 检查是否指定了计数的起始值
  5. var countStart = if x[^2].len == 2: newLit(0) else: x[^2][1]
  6. result = newStmtList()
  7. # 我们把第一个 for 循环变量修改为整数计数器:
  8. result.add newVarStmt(x[0], countStart)
  9. var body = x[^1]
  10. if body.kind != nnkStmtList:
  11. body = newTree(nnkStmtList, body)
  12. body.add newCall(bindSym"inc", x[0])
  13. var newFor = newTree(nnkForStmt)
  14. for i in 1..x.len-3:
  15. newFor.add x[i]
  16. # 将 enumerate(X) 转换为 'X'
  17. newFor.add x[^2][^1]
  18. newFor.add body
  19. result.add newFor
  20. # 现在将整个宏包装到代码块里从而创建一个新的作用域
  21. result = quote do:
  22. block: `result`
  23. for a, b in enumerate(items([1, 2, 3])):
  24. echo a, " ", b
  25. # 如果不将宏包装到代码块里,我们就需要为这里的 `a` 和 `b` 选择不同的名称
  26. # 以免犯重复定义的错误。
  27. for a, b in enumerate(10, [1, 2, 3, 5]):
  28. echo a, " ", b