genasts.nim 3.4 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788
  1. import macros
  2. type GenAstOpt* = enum
  3. kDirtyTemplate,
  4. # When set, uses a dirty template in implementation of `genAst`. This
  5. # is occasionally useful as workaround for issues such as #8220, see
  6. # `strformat limitations <strformat.html#limitations>`_ for details.
  7. # Default is unset, to avoid hijacking of uncaptured local symbols by
  8. # symbols in caller scope.
  9. kNoNewLit,
  10. # don't call call newLit automatically in `genAst` capture parameters
  11. macro genAstOpt*(options: static set[GenAstOpt], args: varargs[untyped]): untyped =
  12. ## Accepts a list of captured variables `a=b` or `a` and a block and returns the
  13. ## AST that represents it. Local `{.inject.}` symbols (e.g. procs) are captured
  14. ## unless `kDirtyTemplate in options`.
  15. runnableExamples:
  16. # This example shows how one could write a simplified version of `unittest.check`.
  17. import std/[macros, strutils]
  18. macro check2(cond: bool): untyped =
  19. assert cond.kind == nnkInfix, "$# not implemented" % $cond.kind
  20. result = genAst(cond, s = repr(cond), lhs = cond[1], rhs = cond[2]):
  21. # each local symbol we access must be explicitly captured
  22. if not cond:
  23. doAssert false, "'$#'' failed: lhs: '$#', rhs: '$#'" % [s, $lhs, $rhs]
  24. let a = 3
  25. check2 a*2 == a+3
  26. if false: check2 a*2 < a+1 # would error with: 'a * 2 < a + 1'' failed: lhs: '6', rhs: '4'
  27. runnableExamples:
  28. # This example goes in more details about the capture semantics.
  29. macro fun(a: string, b: static bool): untyped =
  30. let c = 'z'
  31. var d = 11 # implicitly {.gensym.} and needs to be captured for use in `genAst`.
  32. proc localFun(): auto = 12 # implicitly {.inject.}, doesn't need to be captured.
  33. genAst(a, b, c = true):
  34. # `a`, `b` are captured explicitly, `c` is a local definition masking `c = 'z'`.
  35. const b2 = b # macro static param `b` is forwarded here as a static param.
  36. # `echo d` would give: `var not init` because `d` is not captured.
  37. (a & a, b, c, localFun()) # localFun can be called without capture.
  38. assert fun("ab", false) == ("abab", false, true, 12)
  39. let params = newTree(nnkFormalParams, newEmptyNode())
  40. let pragmas =
  41. if kDirtyTemplate in options:
  42. nnkPragma.newTree(ident"dirty")
  43. else:
  44. newEmptyNode()
  45. template newLitMaybe(a): untyped =
  46. when (a is type) or (typeof(a) is (proc | iterator | func | NimNode)):
  47. a # `proc` actually also covers template, macro
  48. else: newLit(a)
  49. # using `_` as workaround, see https://github.com/nim-lang/Nim/issues/2465#issuecomment-511076669
  50. let name = genSym(nskTemplate, "_fun")
  51. let call = newCall(name)
  52. for a in args[0..^2]:
  53. var varName: NimNode
  54. var varVal: NimNode
  55. case a.kind
  56. of nnkExprEqExpr:
  57. varName = a[0]
  58. varVal = a[1]
  59. of nnkIdent:
  60. varName = a
  61. varVal = a
  62. else: error("invalid argument kind: " & $a.kind, a)
  63. if kNoNewLit notin options: varVal = newCall(bindSym"newLitMaybe", varVal)
  64. params.add newTree(nnkIdentDefs, varName, newEmptyNode(), newEmptyNode())
  65. call.add varVal
  66. result = newStmtList()
  67. result.add nnkTemplateDef.newTree(
  68. name,
  69. newEmptyNode(),
  70. newEmptyNode(),
  71. params,
  72. pragmas,
  73. newEmptyNode(),
  74. args[^1])
  75. result.add newCall(bindSym"getAst", call)
  76. template genAst*(args: varargs[untyped]): untyped =
  77. ## Convenience wrapper around `genAstOpt`.
  78. genAstOpt({}, args)