Is anyone interested in a macro that could automatically transform an iterator into the factory proc version recommended in the manual?
I've been tinkering with one since this discussion and have come up with something that I think is reasonable. It transforms your code into a proc + macro pair that allows the transformation to be transparent to the caller. One limitation is that you can't use auto as a return type (might be a compiler bug, or might be by design).
It can transform something like this, which doesn't work currently:
iterator recCountDown*(n: int): int =
if n > 0:
yield n
for e in recCountDown(n - 1):
yield e
Into something like this:
macro recCountDown*(x: ForLoopStmt): untyped =
let expr`gensym2 = x`gensym2[0]
let call`gensym2 = x`gensym2[1]
let body`gensym2 = x`gensym2[2]
call`gensym2[0] = ident(":tmp_939524152")
template toItr(expr`gensym2, call`gensym2, body`gensym2) =
let itr`gensym2 = call`gensym2
for expr`gensym2 in itr`gensym2():
body`gensym2
result = getAst(toItr(expr`gensym2, call`gensym2, body`gensym2))
proc :tmp_939524152*(n: int): iterator (): int =
result = iterator (): int =
if n > 0:
yield n
for e in recCountDown(n - 1):
yield e
and then you can simply call it like a normal iterator
for i in recCountDown(100):
echo i
# Outputs 100 down to 1
And all you have to do to get this functionality is annote your iterator like so:
iterator recCountDown*(n: int): int {.recursive.} =
if n > 0:
yield n
for e in recCountDown(n - 1):
yield e
The proc generated is random, so there won't be any conflicts.
Here is the macro:
import macros
macro recursive*(iterDef: untyped): untyped =
iterDef.expectKind nnkIteratorDef
let
body = iterDef.body
iterDefProto = iterDef.copy()
iterDefProto.body = newEmptyNode()
let
iterDefName = iterDefProto[0]
iterRetType = iterDefProto.params[0]
# Create a new proc def and copy everything to it
let procDef = newNimNode(nnkProcDef)
for i in 0..<iterDefProto.len:
procDef.add(iterDef[i])
procDef.params[0] = nnkIteratorTy.newTree(
nnkFormalParams.newTree(
iterRetType
),
newEmptyNode()
)
# Use gensym to generate a random name
let
symName = genSym(nskProc)
procName = ident($symName.toStrLit)
procStringName = newLit($procName.toStrLit)
let bodyComment = if body[0].kind == nnkCommentStmt: body[0] else: nnkCommentStmt.newTree()
procDef.name = procName
let procDefProto = procDef.copy()
procDefProto.body = newEmptyNode()
procDef.body = quote do:
result = iterator(): `iterRetType` =
`body`
result = quote do:
macro `iterDefName`(x: ForLoopStmt): untyped =
`bodyComment`
let expr = x[0]
let call = x[1]
let body = x[2]
call[0] = ident(`procStringName`)
template toItr(expr, call, body) =
let itr = call
for expr in itr():
body
result = getAst(toItr(expr, call, body))
`procDef`
Nim playground: https://play.nim-lang.org/#ix=3qyK
Advantages:
Disadvantages:
What do people think? Should this be added to something like fusion? Could more improvements be made?
Does this work for:
for i,j in myRecItr(x,y): echo i,j
? Maybe good improvement if not.