Last active
October 6, 2015 01:43
-
-
Save gmpreussner/927349ef5a5b0d4294c1 to your computer and use it in GitHub Desktop.
MIDL interface glue code generator
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import macros | |
macro midlInterface*(head: expr; body: stmt): stmt {.immediate.} = | |
## Generates the glue code required for MIDL interface types. | |
## | |
## For example, the following declaration... | |
## | |
## midlInterface IUnknown: | |
## proc queryInterface*(riid: Iid; outObj: ptr pointer): HResult | |
## proc addRef*(): int | |
## proc release*(): int | |
## | |
## ... will be rewritten as: | |
## | |
## type | |
## IUnknownVtable* {.inheritable.} = object | |
## queryInterface: proc (this: pointer; riid: Iid; | |
## outObj: ptr pointer): HResult {.stdcall.} | |
## addRef: proc (this: ptr IUnknown): int {.stdcall.} | |
## release: proc (this: ptr IUnknown): int {.stdcall.} | |
## | |
## IUnknown* = object | |
## vtable: ptr IUnknownVtable | |
## | |
## IUnknownConcept* = concept x | |
## x.vtable of ptr IUnknownVtable | |
## | |
## proc queryInterface*(this: ptr IUnknownConcept; riid: Iid; | |
## outObj: ptr pointer): HResult = | |
## this.vtable.queryInterface(this, riid, outObj) | |
## | |
## It is also possible to declare derived interfaces via: | |
## | |
## midlInterface IDerived of IUnknown | |
## | |
## head | |
## The macro's head expression containing the interface type and, | |
## optionally, a base type. | |
## body | |
## The statement body containing the interface procedures. | |
## result | |
## The interface implementation statement. | |
var typeName, baseName: NimNode | |
let procPointers = newNimNode(nnkRecList) | |
result = newStmtList() | |
# extract interface and base type | |
if head.kind == nnkIdent: | |
# `head` is expression `typeName` | |
baseName = nil | |
typeName = head | |
elif head.kind == nnkInfix and $head[0] == "of": | |
# `head` is expression `typeName of baseName` | |
typeName = head[1] | |
baseName = head[2] | |
else: | |
quit "Invalid node: " & head.lispRepr | |
# process statements in the body | |
for node in body.children: | |
case node.kind: | |
of nnkMethodDef, nnkProcDef: | |
# generate procedure pointer for vtable | |
# | |
# dumpTree: | |
# procName: proc (this: ptr typeName): ResultType {.stdcall.} | |
# ------------------------------------------------------------- | |
# IdentDefs | |
# Ident !"procName" | |
# ProcTy | |
# FormalParams | |
# Ident !"ResultType" | |
# IdentDefs | |
# Ident !"this" | |
# Ident !"pointer" | |
# Empty | |
# ... | |
# Pragma | |
# Ident !"stdcall" | |
let pointerParams = copyNimTree(node.params) | |
pointerParams.insert(1, | |
newIdentDefs( | |
newIdentNode("this"), | |
newIdentNode("pointer") | |
) | |
) | |
procPointers.add( | |
newIdentDefs( | |
copyNimNode(node.name[1]), | |
newNimNode(nnkProcTy).add( | |
copyNimTree(pointerParams), | |
newNimNode(nnkPragma).add( | |
newIdentNode("stdcall") | |
) | |
) | |
) | |
) | |
# inject `this: typeNameConcept` pointer into procedure arguments | |
# | |
# dumpTree: | |
# proc procName*(this: ptr typeNameConcept, ...): ResultType | |
# ------------------------------------------------------------- | |
# ProcDef | |
# Postfix | |
# Ident !"*" | |
# Ident !"procName" | |
# Empty | |
# Empty | |
# FormalParams | |
# Ident !"ResultType" | |
# IdentDefs <---- | |
# Ident !"this" | |
# PtrTy | |
# Ident !"typeNameConcept" | |
# Empty | |
# ... | |
# Empty | |
# Empty | |
# Empty | |
let procParams = copyNimTree(node.params) | |
procParams.insert(1, | |
newIdentDefs( | |
newIdentNode("this"), | |
newNimNode(nnkPtrTy).add( | |
newIdentNode($typeName & "Concept") | |
) | |
) | |
) | |
node.params = procParams | |
# inject `this` pointer and procedure implementation | |
# | |
# dumpTree: | |
# proc procName*(...): ResultType | |
# this.vtable.procName(this, param1, param2, paramN) | |
# ---------------------------------------------------- | |
# ProcDef | |
# Postfix | |
# Ident !"*" | |
# Ident !"procName" | |
# Empty | |
# Empty | |
# FormalParams | |
# ... | |
# Empty | |
# Empty | |
# StmtList <---- | |
# Call | |
# DotExpr | |
# DotExpr | |
# Ident !"this" | |
# Ident !"vtable" | |
# Ident !"procName" | |
# Ident !"this" | |
# Ident !"param1" | |
# Ident !"param2" | |
# Ident !"paramN" | |
let call = newCall( | |
newDotExpr( | |
newDotExpr( | |
newIdentNode("this"), | |
newIdentNode("vtable") | |
), | |
copyNimNode(node.name[1]) | |
) | |
) | |
for i in 1..<procParams.len: | |
call.add(procParams[i][0]) | |
node.body = newStmtList(call) | |
else: | |
discard | |
result.add(node) | |
# generate virtual function table | |
# | |
# dumpTree: | |
# typeNameVtable* {.inheritable.} = object of baseNameVtable | |
# ... | |
# ---------------------------------------------------------------------- | |
# TypeDef | |
# PragmaDef | |
# PragmaExpr | |
# Postfix | |
# Ident !"*" | |
# Ident !"typeName" | |
# Pragma | |
# Ident !"inheritable" | |
# Empty | |
# ObjectTy | |
# Empty | |
# OfInherit | or Empty if no base type | |
# Ident !"baseNameVtable" | | |
# RecList | |
# ... | |
let vtableNode = newNimNode(nnkTypeDef).add( | |
newNimNode(nnkPragmaExpr).add( | |
newIdentNode($typeName & "Vtable").postfix("*"), | |
newNimNode(nnkPragma).add( | |
newIdentNode("inheritable") | |
) | |
), | |
newNimNode(nnkEmpty), | |
newNimNode(nnkObjectTy).add( | |
newNimNode(nnkEmpty), | |
if baseName == nil: | |
newNimNode(nnkEmpty) | |
else: | |
newNimNode(nnkOfInherit).add( | |
newIdentNode($baseName & "Vtable") | |
), | |
procPointers | |
) | |
) | |
# generate interface declaration | |
# | |
# dumpTree: | |
# type | |
# typeName* = object | |
# vtable: typeNameVtable | |
# ---------------------------- | |
# TypeDef | |
# Postfix | |
# Ident !"*" | |
# Ident !"typeName" | |
# Empty | |
# ObjectTy | |
# Empty | |
# Empty | |
# RecList | |
# IdentDefs | |
# Ident !"vtable" | |
# PtrTy | |
# Ident !"typeNameVtable" | |
# Empty | |
let interfaceNode = newNimNode(nnkTypeDef).add( | |
typeName.postfix("*"), | |
newNimNode(nnkEmpty), | |
newNimNode(nnkObjectTy).add( | |
newNimNode(nnkEmpty), | |
newNimNode(nnkEmpty), | |
newNimNode(nnkRecList).add( | |
newIdentDefs( | |
newIdentNode("vtable"), | |
newNimNode(nnkPtrTy).add( | |
newIdentNode($typeName & "Vtable") | |
), | |
newNimNode(nnkEmpty), | |
) | |
) | |
) | |
) | |
# generate concept declaration | |
# | |
# dumpTree: | |
# typeNameConcept* = concept x | |
# x.vtable of ptr typeNameVtable | |
# ---------------------------------- | |
# TypeDef | |
# Postfix | |
# Ident !"*" | |
# Ident !"typeNameConcept" | |
# Empty | |
# TypeClassTy | |
# Arglist | |
# Ident !"x" | |
# Empty | |
# Empty | |
# StmtList | |
# Infix | |
# Ident !"of" | |
# DotExpr | |
# Ident !"x" | |
# Ident !"vtable" | |
# PtrTy | |
# Ident !"typeNameVtable" | |
let conceptNode = newNimNode(nnkTypeDef).add( | |
newIdentNode($typeName & "Concept").postfix("*"), | |
newNimNode(nnkEmpty), | |
newNimNode(nnkTypeClassTy).add( | |
newNimNode(nnkArglist).add( | |
newIdentNode("x") | |
), | |
newNimNode(nnkEmpty), | |
newNimNode(nnkEmpty), | |
newStmtList( | |
newDotExpr( | |
newIdentNode("x"), | |
newIdentNode("vtable") | |
).infix("of", | |
newNimNode(nnkPtrTy).add( | |
newIdentNode($typeName & "Vtable") | |
) | |
) | |
) | |
) | |
) | |
# assemble type section | |
result.insert(0, | |
newNimNode(nnkTypeSection).add( | |
vtableNode, | |
interfaceNode, | |
conceptNode | |
) | |
) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Example usage: