A minor minor variant that works with `var` parameters instead of `ptr`:
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[var 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
when defined(interfacedebug):
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)
when defined(interfacedebug):
echo result.repr
Usage:
import interfacemacros
type
MyBananas = object
a,b : int
OtherBananas = object
foo,bar: int
proc foo(banan : var MyBananas) : int =
banan.a + banan.b
proc bar(banan : var MyBananas) : int =
banan.a * banan.b
proc baz(banan : var Mybananas, a,b : int, c : float) : float =
echo "baz MyBananas"
float((a+banan.a) * (b+banan.b)) / c
proc baz(banan : var 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)
echo "baz in bazinga 1 ", arg.baz(7,8,21)
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, otherbanan)
main()
@Krux02: Since I get that you are not interested in publishing this, would you
mind if I did?