mirror of
https://github.com/nim-lang/Nim.git
synced 2026-04-25 00:35:26 +00:00
new genAst as replacement for quote do (#17426)
* new `macros.genAst`: fixes all issues with `quote do` * add changelog entry * add workaround for https://github.com/nim-lang/Nim/issues/2465#issuecomment-511076669 * add test for #9607 * add kNoExposeLocalInjects option * add test case for nested application of genAst * genAst: automatically call newLit when needed * allow skipping `{}`: genAst: foo * add test that shows this fixes #11986 * add examples showing mixin; add examples showing passing types, macros, templates * move to std/genasts * improve docs
This commit is contained in:
87
lib/std/genasts.nim
Normal file
87
lib/std/genasts.nim
Normal file
@@ -0,0 +1,87 @@
|
||||
import macros
|
||||
|
||||
type GenAstOpt* = enum
|
||||
kDirtyTemplate,
|
||||
# When set, uses a dirty template in implementation of `genAst`. This
|
||||
# is occasionally useful as workaround for issues such as #8220, see
|
||||
# `strformat limitations <strformat.html#limitations>`_ for details.
|
||||
# Default is unset, to avoid hijacking of uncaptured local symbols by
|
||||
# symbols in caller scope.
|
||||
kNoNewLit,
|
||||
# don't call call newLit automatically in `genAst` capture parameters
|
||||
|
||||
macro genAstOpt*(options: static set[GenAstOpt], args: varargs[untyped]): untyped =
|
||||
## Accepts a list of captured variables `a=b` or `a` and a block and returns the
|
||||
## AST that represents it. Local `{.inject.}` symbols (e.g. procs) are captured
|
||||
## unless `kDirtyTemplate in options`.
|
||||
runnableExamples:
|
||||
# This example shows how one could write a simplified version of `unittest.check`.
|
||||
import std/[macros, strutils]
|
||||
macro check2(cond: bool): untyped =
|
||||
assert cond.kind == nnkInfix, "$# not implemented" % $cond.kind
|
||||
result = genAst(cond, s = repr(cond), lhs = cond[1], rhs = cond[2]):
|
||||
# each local symbol we access must be explicitly captured
|
||||
if not cond:
|
||||
doAssert false, "'$#'' failed: lhs: '$#', rhs: '$#'" % [s, $lhs, $rhs]
|
||||
let a = 3
|
||||
check2 a*2 == a+3
|
||||
if false: check2 a*2 < a+1 # would error with: 'a * 2 < a + 1'' failed: lhs: '6', rhs: '4'
|
||||
|
||||
runnableExamples:
|
||||
# This example goes in more details about the capture semantics.
|
||||
macro fun(a: string, b: static bool): untyped =
|
||||
let c = 'z'
|
||||
var d = 11 # implicitly {.gensym.} and needs to be captured for use in `genAst`.
|
||||
proc localFun(): auto = 12 # implicitly {.inject.}, doesn't need to be captured.
|
||||
genAst(a, b, c = true):
|
||||
# `a`, `b` are captured explicitly, `c` is a local definition masking `c = 'z'`.
|
||||
const b2 = b # macro static param `b` is forwarded here as a static param.
|
||||
# `echo d` would give: `var not init` because `d` is not captured.
|
||||
(a & a, b, c, localFun()) # localFun can be called without capture.
|
||||
assert fun("ab", false) == ("abab", false, true, 12)
|
||||
|
||||
let params = newTree(nnkFormalParams, newEmptyNode())
|
||||
let pragmas =
|
||||
if kDirtyTemplate in options:
|
||||
nnkPragma.newTree(ident"dirty")
|
||||
else:
|
||||
newEmptyNode()
|
||||
|
||||
template newLitMaybe(a): untyped =
|
||||
when (a is type) or (typeof(a) is (proc | iterator | func | NimNode)):
|
||||
a # `proc` actually also covers template, macro
|
||||
else: newLit(a)
|
||||
|
||||
# using `_` as workaround, see https://github.com/nim-lang/Nim/issues/2465#issuecomment-511076669
|
||||
let name = genSym(nskTemplate, "_fun")
|
||||
let call = newCall(name)
|
||||
for a in args[0..^2]:
|
||||
var varName: NimNode
|
||||
var varVal: NimNode
|
||||
case a.kind
|
||||
of nnkExprEqExpr:
|
||||
varName = a[0]
|
||||
varVal = a[1]
|
||||
of nnkIdent:
|
||||
varName = a
|
||||
varVal = a
|
||||
else: error("invalid argument kind: " & $a.kind, a)
|
||||
if kNoNewLit notin options: varVal = newCall(bindSym"newLitMaybe", varVal)
|
||||
|
||||
params.add newTree(nnkIdentDefs, varName, newEmptyNode(), newEmptyNode())
|
||||
call.add varVal
|
||||
|
||||
result = newStmtList()
|
||||
result.add nnkTemplateDef.newTree(
|
||||
name,
|
||||
newEmptyNode(),
|
||||
newEmptyNode(),
|
||||
params,
|
||||
pragmas,
|
||||
newEmptyNode(),
|
||||
args[^1])
|
||||
result.add newCall(bindSym"getAst", call)
|
||||
|
||||
template genAst*(args: varargs[untyped]): untyped =
|
||||
## Convenience wrapper around `genAstOpt`.
|
||||
genAstOpt({}, args)
|
||||
Reference in New Issue
Block a user