This might be entirely misguided as I'm new to writing macros, but, is there a way to return many nodes from a macro, instead of a "tree" with a single root node?
For example, consider the following:
import std/macros
macro manyItems: untyped =
newLit(78)
let s = @[2, 3, 4, manyItems(), 5]
The above works and inserts 78 in the middle of the seq. Now say I want to insert (arbitrarily) many items?
import std/macros
macro manyItems: untyped =
newStmtList(newLit(78), newLit(87))
let s = @[2, 3, 4, manyItems(), 5]
This doesn't work. I can anticipate that a statement list is not the correct "root" to use here. But, is there a way to generate many nodes in a "flat" manner, to insert multiple nodes in the children of the Bracket tree?
Unfortunately, I don't think there is a way to do this. I wish there was. It would allow for things like this:
type
Node {.variant.} = ref object
case kind: NodeKind # the NodeKind type will be automatically generated
of nkRoot: left, right: Node
of nkLeaf: value: int
You can't do it with a normal @[] literal, but you can make a macro taking advantage of tuple elements having different types.
import std/macros
template flattener {.pragma.}
proc doFlatten[T: tuple](x: T{nkTupleConstr}): T {.flattener.} = x
macro manyItems: untyped =
result = newCall(bindSym"doFlatten", newTree(nnkTupleConstr,
newLit(78), newLit(87),
newCall(bindSym"doFlatten", newTree(nnkTupleConstr, newLit(99)))))
macro flatten(arr: varargs[typed]): untyped =
result = newTree(nnkBracket)
proc addFlattened(s: NimNode, n: NimNode) =
if n.kind in nnkCallKinds and
# just to check if `doFlatten` is being called
(let callee = n[0]; let prag = bindSym"flattener";
getAst(hasCustomPragma(callee, prag)).intVal == 1):
for v in n[1]:
addFlattened(s, v)
else:
s.add(n)
for a in arr:
result.addFlattened(a)
result = newCall("@", result)
let s = flatten(2, 3, 4, manyItems(), 5)
echo s # @[2, 3, 4, 78, 87, 99, 5]
import std/[macros, options]
proc erRepeat(body: NimNode): seq[NimNode] =
body.expectLen 3
body[2].expectKind nnkIntLit
for i in 1 .. body[2].intVal:
result.add body[1]
proc erIncrease(body: NimNode): seq[NimNode] =
body.expectLen 3
body[2].expectKind nnkIntLit
for i in 0 ..< body[2].intVal:
# newLit(i) results compile error
var lit = newNimNode(nnkIntLit)
lit.intVal = i
result.add infix(body[1], "+", lit)
proc evalReplaceImpl(node: NimNode): NimNode =
result = node.copyNimNode
for n in node:
if n.kind == nnkCall:
let res =
if n[0].eqIdent "erRepeat":
some(erRepeat(n.evalReplaceImpl))
elif n[0].eqIdent "erIncrease":
some(erIncrease(n.evalReplaceImpl))
else:
none(seq[NimNode])
if res.isSome:
for i in res.get:
result.add i
continue
result.add evalReplaceImpl(n)
macro evalReplace*(body: untyped): untyped =
## Recursively evaluate specific procs in all `NimNode`subtrees in `node`.
evalReplaceImpl body
# Test code
doAssert evalReplace([1, 2, erRepeat(123, 3), 3, 4, erIncrease(5, 4), 8 + 1]) == [1, 2, 123, 123, 123, 3, 4, 5, 6, 7, 8, 9]
evalReplace:
doAssert max(erIncrease(1.0, 2)) == 2.0
doAssert min(erRepeat('a', 2)) == 'a'
var x = 0
erRepeat(inc x, 3)
doAssert x == 3
import std/[sequtils]
doAssert evalReplace(concat(@[0], erRepeat(@[erIncrease(erRepeat(2, 2))], 2))) == @[0, 2, 3, 2, 3]