Is anyone interested in a macro that could automatically transform an iterator 
into the factory proc version recommended in [the 
manual](https://nim-lang.github.io/Nim/manual.html#iterators-and-the-for-statement-firstminusclass-iterators)?

I've been tinkering with one since [this 
discussion](https://forum.nim-lang.org/t/8127) 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
    
    
    Run

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
    
    
    Run

and then you can simply call it like a normal iterator
    
    
    for i in recCountDown(100):
      echo i
    # Outputs 100 down to 1
    
    
    Run

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
    
    
    Run

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`
    
    
    Run

Nim playground: <https://play.nim-lang.org/#ix=3qyK>

Advantages:

  * No need to do much extra to your code
  * hides the complexity from the user



Disadvantages:

  * hides the complexity from the user (could be a disadvantage as well)
  * can't use auto return type (not a big deal IMO)
  * goto definition doesn't work, since the definition is hidden in the macro 
ast



What do people think? Should this be added to something like fusion? Could more 
improvements be made? 

Reply via email to