@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()