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