@Tarmean I now also did what you did, and I am posting my code here. I don't 
update the original post, because it would require me to either update the 
entire code there and rewrite things, or break compatibility with the example 
and the macro code. So I just post it here:
    
    
    import macros
    
    macro implementInterface(interfaceName: typed) : untyped =
      let
        interfaceNameStr = $interfaceName.symbol
        vtableSymbol = interfaceName.symbol.getImpl[2][2][1][1][0]
        vtableRecordList = vtableSymbol.symbol.getImpl[2][2]
      
      let
        objectConstructor = nnkObjConstr.newTree(vtableSymbol)
      
      for identDefs in vtableRecordList:
        let
          methodName = identDefs[0]
          params = identDefs[1][0]
          lambdaBody = quote do:
            cast[ptr T](this).`methodName`()
          call = lambdaBody[0]
        
        for i in 2 ..< len(params):
          let param = params[i]
          param.expectKind(nnkIdentDefs)
          for j in 0 .. len(param) - 3:
            call.add param[j]
        
        # leave out () when not needed
        if call.len == 1:
          lambdaBody[0] = call[0]
        
        methodName.expectKind nnkIdent
        
        objectConstructor.add nnkExprColonExpr.newTree(
          methodName,
          nnkLambda.newTree(
            newEmptyNode(),newEmptyNode(),newEmptyNode(),
            params.copy,
            newEmptyNode(),newEmptyNode(),
            lambdaBody
          )
        )
      
      let
        getVtableReturnStatement =
          nnkReturnStmt.newTree(newCall("addr", newIdentNode("theVtable")))
        globalVtableIdent = newIdentNode("theVtable")
        getVtableProcIdent = newIdentNode("get" & interfaceNameStr & "Vtable")
        getVtableProcDeclaration = quote do:
          proc `getVtableProcIdent`[T](): ptr BananasVtable =
            var `globalVtableIdent` {.global.} = `objectConstructor`
            `getVtableReturnStatement`
      
      result = newStmtList()
      result.add getVtableProcDeclaration
      
      let castIdent = newIdentNode("to" & $interfaceName.symbol)
      
      result.add quote do:
        converter `castIdent`[T](this: ptr T) : `interfaceName` = 
`interfaceName`(
          objet : this,
          vtable : `getVtableProcIdent`[T]()
        )
      
      result.add quote do:
        converter `castIdent`[T](this: var T) : `interfaceName` = 
`interfaceName`(
          objet : this.addr,
          vtable : `getVtableProcIdent`[T]()
        )
      
      result.add quote do:
        converter `castIdent`(this: `interfaceName`): `interfaceName` = this
      
      echo result.repr
    
    
    macro createInterface*(name : untyped, methods : untyped) : untyped =
      name.expectKind nnkIdent
      
      
      let
        nameStr = $name.ident
        vtableRecordList = nnkRecList.newTree
        vtableIdent = newIdentNode(nameStr & "Vtable")
        vtableTypeDef = nnkTypeSection.newTree(
          nnkTypeDef.newTree(
            vtableIdent,
            newEmptyNode(),
            nnkObjectTy.newTree(
              newEmptyNode(),
              newEmptyNode(),
              vtableRecordList
            )
          )
        )
      
      var newMethods = newSeq[NimNode]()
      
      for meth in methods:
        meth.expectKind(nnkProcDef)
        let
          methodIdent = meth[0]
          params = meth[3]
          thisParam = params[1]
          thisIdent = thisParam[0]
          thisType  = thisParam[1]
        
        if thisType != name:
          error thisType.repr & " != " & name.repr
        
        let vtableEntryParams = params.copy
        vtableEntryParams[1][1] = newIdentNode("pointer")
        
        vtableRecordList.add(
          nnkIdentDefs.newTree(
            methodIdent,
            nnkProcTy.newTree(
              vtableEntryParams,
              newEmptyNode(),
            ),
            newEmptyNode()
          )
        )
        
        let call = nnkCall.newTree(
          nnkDotExpr.newTree( nnkDotExpr.newTree(thisIdent, 
newIdentNode("vtable")), methodIdent  ),
          nnkDotExpr.newTree( thisIdent, newIdentNode("objet") ),
        )
        
        for i in 2 ..< len(params):
          let param = params[i]
          param.expectKind(nnkIdentDefs)
          for j in 0 .. len(param) - 3:
            call.add param[j]
        
        meth[6] = nnkStmtList.newTree(call)
        
        newMethods.add(meth)
      
      result = newStmtList()
      result.add(vtableTypeDef)
      result.add quote do:
        type `name` = object
          objet : pointer
          vtable: ptr `vtableIdent`
      
      for meth in newMethods:
        result.add meth
      
      result.add newCall(bindSym"implementInterface", name)
      
      # optional if you want to see what you generated
      # echo result.repr
    
    

And here is an example how to use it. Doesn't make a lot of sense, but yea it's 
just to show that it works.
    
    
    import interfacemacros
    
    type
      MyBananas = object
        a,b : int
      OtherBananas = object
        foo,bar: int
    
    proc foo(banan : ptr MyBananas) : int =
      banan.a + banan.b
    
    proc bar(banan : ptr MyBananas) : int =
      banan.a * banan.b
    
    proc baz(banan : ptr Mybananas, a,b : int, c : float) : float =
      echo "baz MyBananas"
      float((a+banan.a) * (b+banan.b)) / c
    
    proc baz(banan : ptr OtherBananas, a,b : int, c : float) : float =
      float((a+banan.foo) * (b+banan.bar)) / c
    
    createInterface(Bananas):
      proc foo(this : Bananas) : int
      proc bar(this : Bananas) : int
      proc baz(this : Bananas, a,b : int, c : float) : float
    
    proc foobar(mbi: Bananas): int =
      mbi.foo + mbi.bar
    
    proc bazinga(args : varargs[Bananas, toBananas]): int =
      echo "got ", args.len, " args"
      for arg in args:
        result += arg.foo * arg.bar
        echo "baz in bazinga ", arg.baz(7,8,3.14)
    
    proc main() =
      var
        banan =      MyBananas(a: 17, b: 4)
        otherbanan = OtherBananas(foo: 17, bar: 4)
      
      echo "hallo welt!"
      echo "foobar1: ", foobar(banan)
      echo "foobar2: ", foobar(otherbanan)
      
      echo "bazinga: ", bazinga(banan.addr, otherbanan.addr)
      
      # according to the documentation the following two statements should be 
identical, but only the second one compiles
      # echo bazinga(banan, otherbanan)
      # echo bazinga([toBananas(banan), toBananas(otherbanan)])
    
    main()
    
    

Reply via email to