add NimQmlMacros.nim
This commit is contained in:
parent
fae26cedad
commit
b02540d1c1
|
@ -0,0 +1,360 @@
|
||||||
|
## Contains helper macros for NimQml
|
||||||
|
|
||||||
|
import macros
|
||||||
|
import strutils
|
||||||
|
import typetraits
|
||||||
|
import tables
|
||||||
|
|
||||||
|
static:
|
||||||
|
let nimFromQtVariant = {
|
||||||
|
"int" : "intVal",
|
||||||
|
"string" : "stringVal",
|
||||||
|
"bool" : "boolVal" ,
|
||||||
|
}.toTable
|
||||||
|
|
||||||
|
let nim2QtMeta = {
|
||||||
|
"bool": "Bool",
|
||||||
|
"int " : "Int",
|
||||||
|
"string" : "QString",
|
||||||
|
"pointer" : "VoidStar",
|
||||||
|
"QVariant": "QVariant",
|
||||||
|
"" : "Void", # no return, which is represented by an nnkEmpty node
|
||||||
|
}.toTable
|
||||||
|
|
||||||
|
proc getNodeOf*(tree: PNimrodNode, kind: TNimrodNodeKind): PNimrodNode {.compileTime.} =
|
||||||
|
## recursively looks for a node of kind, ``kind``, in the tree provided as ``tree``
|
||||||
|
## Returnsthe first node that satisfies this condition
|
||||||
|
for i in 0.. <tree.len:
|
||||||
|
var child = tree[i]
|
||||||
|
if child of PNimrodNode and child.kind == kind:
|
||||||
|
return child
|
||||||
|
var candidate = getNodeOf(child, kind)
|
||||||
|
if not candidate.isNil:
|
||||||
|
return candidate
|
||||||
|
|
||||||
|
static:
|
||||||
|
type Context* = ref object of RootObj
|
||||||
|
type NullContext* = ref object of Context
|
||||||
|
|
||||||
|
type NodeModifier*[T] = proc(context: T, a: var PNimrodNode): PNimrodNode
|
||||||
|
|
||||||
|
# had to remove type bound on hook due to recent regression with generics
|
||||||
|
proc hookOnNode*[T](context: T, code: PNimrodNode, hook: NodeModifier,
|
||||||
|
recursive: bool = false): PNimrodNode {.compileTime.} =
|
||||||
|
## Iterates over the tree, ``code``, calling ``hook`` on each ``PNimrodNode``
|
||||||
|
## encountered. If ``recursive`` is true, it will recurse over the tree, otherwise
|
||||||
|
## it will only visit ``code``s children. ``hook`` should return a replacement for
|
||||||
|
## the node that was passed in via it's return value.
|
||||||
|
if code.len == 0:
|
||||||
|
return code
|
||||||
|
var newCode = newNimNode(code.kind)
|
||||||
|
for i in 0.. <code.len:
|
||||||
|
var child = code[i].copy()
|
||||||
|
child = hook(context, child)
|
||||||
|
if recursive:
|
||||||
|
child = hookOnNode(context,child,hook,true)
|
||||||
|
if child != nil:
|
||||||
|
newCode.add child
|
||||||
|
return newCode
|
||||||
|
|
||||||
|
proc removeOpenSym*(context: NullContext, a: var PNimrodNode): PNimrodNode {.compileTime.} =
|
||||||
|
## replaces: ``nnkOpenSymChoice`` and ``nnkSym`` nodes with idents corresponding to the
|
||||||
|
## symbols string representation.
|
||||||
|
if a.kind == nnkOpenSymChoice:
|
||||||
|
return ident($a[0].symbol)
|
||||||
|
elif a.kind == nnkSym:
|
||||||
|
return ident($a.symbol)
|
||||||
|
return a
|
||||||
|
|
||||||
|
proc newTemplate*(name = newEmptyNode(); params: openArray[PNimrodNode] = [newEmptyNode()];
|
||||||
|
body: PNimrodNode = newStmtList()): PNimrodNode {.compileTime.} =
|
||||||
|
## shortcut for creating a new template
|
||||||
|
##
|
||||||
|
## The ``params`` array must start with the return type of the template,
|
||||||
|
## followed by a list of IdentDefs which specify the params.
|
||||||
|
result = newNimNode(nnkTemplateDef).add(
|
||||||
|
name,
|
||||||
|
newEmptyNode(),
|
||||||
|
newEmptyNode(),
|
||||||
|
newNimNode(nnkFormalParams).add(params), ##params
|
||||||
|
newEmptyNode(), ## pragmas
|
||||||
|
newEmptyNode(),
|
||||||
|
body)
|
||||||
|
|
||||||
|
template declareSuperTemplate*(parent: typedesc, typ: typedesc): typedesc =
|
||||||
|
template superType*(ofType: typedesc[typ]): typedesc[parent] =
|
||||||
|
parent
|
||||||
|
|
||||||
|
proc getTypeName*(a: PNimrodNode): PNimrodNode {.compileTime.} =
|
||||||
|
## returns the node containing the name of an object in a
|
||||||
|
## given type definition block
|
||||||
|
expectMinLen a, 1
|
||||||
|
expectKind a, nnkTypeDef
|
||||||
|
var testee = a
|
||||||
|
if testee[0].kind == nnkPragmaExpr:
|
||||||
|
testee = testee[0]
|
||||||
|
if testee[0].kind in {nnkIdent}:
|
||||||
|
return testee[0]
|
||||||
|
elif testee[0].kind in {nnkPostfix}:
|
||||||
|
return testee[0][1]
|
||||||
|
|
||||||
|
proc genSuperTemplate*(typeDecl: PNimrodNode): PNimrodNode {.compileTime.} =
|
||||||
|
## generates a template, with name: superType, that returns the super type
|
||||||
|
## of the object defined in the type defintion, ``typeDecl``. ``typeDecl``
|
||||||
|
## must contain an object inheriting from a base type.
|
||||||
|
expectKind typeDecl, nnkTypeDef
|
||||||
|
let inheritStmt = typeDecl.getNodeOf(nnkOfInherit)
|
||||||
|
let typeName = getTypeName(typeDecl)
|
||||||
|
if inheritStmt == nil: error("you must declare a super type for " & $typeName)
|
||||||
|
# ident of superType (have to deal with generics)
|
||||||
|
let superType = if inheritStmt[0].kind == nnkIdent: inheritStmt[0] else: inheritStmt[0].getNodeOf(nnkIdent)
|
||||||
|
let superTemplate = getAst declareSuperTemplate(superType, typeName)
|
||||||
|
return superTemplate[0]
|
||||||
|
|
||||||
|
proc getSuperType*(typeDecl: PNimrodNode): PNimrodNode {.compileTime.} =
|
||||||
|
## returns ast containing superType info, may not be an ident if generic
|
||||||
|
let inheritStmt = typeDecl.getNodeOf(nnkOfInherit)
|
||||||
|
if inheritStmt.isNil: return newEmptyNode()
|
||||||
|
return inheritStmt[0]
|
||||||
|
|
||||||
|
proc getPragmaName*(child: PNimrodNode): PNimrodNode {.compileTime.} =
|
||||||
|
## name of child in a nnkPragma section
|
||||||
|
if child.kind == nnkIdent:
|
||||||
|
return child
|
||||||
|
# assumes first ident is name of pragma
|
||||||
|
let ident = child.getNodeOf(nnkIdent)
|
||||||
|
result = ident
|
||||||
|
|
||||||
|
proc removePragma*(pragma: PNimrodNode, toRemove: string): PNimrodNode {.compileTime.} =
|
||||||
|
## removes a pragma from pragma definition, `pragma`, with name `toRemove`
|
||||||
|
expectKind pragma, nnkPragma
|
||||||
|
result = newNimNode(nnkPragma)
|
||||||
|
for i in 0.. <pragma.len:
|
||||||
|
let child = pragma[i]
|
||||||
|
if $child.getPragmaName == toRemove:
|
||||||
|
continue
|
||||||
|
result.add child
|
||||||
|
if result.len == 0:
|
||||||
|
return newEmptyNode()
|
||||||
|
|
||||||
|
proc hasPragma*(node: PNimrodNode, pragmaName: string): bool {.compileTime.} =
|
||||||
|
## Returns ``true`` iff the method, or proc definition: ``node``, has a pragma
|
||||||
|
## ``pragmaName``
|
||||||
|
doAssert node.kind in {nnkMethodDef, nnkProcDef}
|
||||||
|
result = false
|
||||||
|
let pragma = node.pragma
|
||||||
|
if pragma.kind == nnkEmpty:
|
||||||
|
# denotes no pragma set
|
||||||
|
return false
|
||||||
|
for child in pragma.children():
|
||||||
|
if $child.getPragmaName() == pragmaName:
|
||||||
|
return true
|
||||||
|
|
||||||
|
proc getArgType*(arg: PNimrodNode): PNimrodNode {.compileTime.} =
|
||||||
|
## returns the ``PNimrodNode`` representing a parameters type
|
||||||
|
if arg[1].kind == nnkIdent:
|
||||||
|
arg[1]
|
||||||
|
else:
|
||||||
|
arg[1].getNodeOf(nnkIdent)
|
||||||
|
|
||||||
|
proc getArgName*(arg: PNimrodNode): PNimrodNode {.compileTime.} =
|
||||||
|
## returns the ``PNimrodNode`` representing a parameters name
|
||||||
|
if arg[0].kind == nnkIdent:
|
||||||
|
arg[0]
|
||||||
|
else:
|
||||||
|
arg[0].getNodeOf(nnkIdent)
|
||||||
|
|
||||||
|
proc addSignalBody(signal: PNimrodNode): PNimrodNode {.compileTime.} =
|
||||||
|
# e.g: produces: emit(MyQObject, "nameChanged")
|
||||||
|
expectKind signal, nnkMethodDef
|
||||||
|
result = newStmtList()
|
||||||
|
# if exported, will use postfix
|
||||||
|
let name = if signal.name.kind == nnkIdent: signal.name else: signal.name[1]
|
||||||
|
let params = signal.params
|
||||||
|
# type signal defined on is the 1st arg
|
||||||
|
let self = getArgName(params[1])
|
||||||
|
var args = newSeq[PNimrodNode]()
|
||||||
|
args.add(self)
|
||||||
|
args.add newLit($name)
|
||||||
|
if params.len > 2: # more args than just type
|
||||||
|
for i in 2.. <params.len:
|
||||||
|
args.add getArgName params[i]
|
||||||
|
result.add newCall("emit", args)
|
||||||
|
|
||||||
|
template declareOnSlotCalled(typ: typedesc): stmt =
|
||||||
|
method onSlotCalled(myQObject: typ, slotName: string, args: openarray[QVariant]) =
|
||||||
|
discard
|
||||||
|
|
||||||
|
template prototypeCreate(typ: typedesc): stmt =
|
||||||
|
template create(myQObject: var typ) =
|
||||||
|
var super = (typ.superType())(myQObject)
|
||||||
|
procCall create(super)
|
||||||
|
|
||||||
|
proc doRemoveOpenSym(a: var PNimrodNode): PNimrodNode {.compileTime.} =
|
||||||
|
hookOnNode(NullContext(),a, removeOpenSym, true)
|
||||||
|
|
||||||
|
proc templateBody*(a: PNimrodNode): PNimrodNode {.compileTime.} =
|
||||||
|
expectKind a, nnkTemplateDef
|
||||||
|
result = a[6]
|
||||||
|
|
||||||
|
proc genArgTypeArray(params: PNimrodNode): PNimrodNode {.compileTime.} =
|
||||||
|
expectKind params, nnkFormalParams
|
||||||
|
result = newNimNode(nnkBracket)
|
||||||
|
for i in 0 .. <params.len:
|
||||||
|
if i == 1:
|
||||||
|
# skip "self" param eg: myQObject: MyQObject
|
||||||
|
continue
|
||||||
|
let pType = if i != 0: getArgType params[i] else: params[i]
|
||||||
|
let pTypeString = if pType.kind == nnkEmpty: "" else: $pType
|
||||||
|
# function that maps Qvariant type to nim type
|
||||||
|
let qtMeta = nim2QtMeta[pTypeString]
|
||||||
|
if qtMeta == nil: error(pTypeString & " not supported yet")
|
||||||
|
let metaDot = newDotExpr(ident "QMetaType", ident qtMeta)
|
||||||
|
result.add metaDot
|
||||||
|
|
||||||
|
proc getIdentDefName*(a: PNimrodNode): PNimrodNode {.compileTime.} =
|
||||||
|
## returns object field name from ident def
|
||||||
|
expectKind a, nnkIdentDefs
|
||||||
|
if a[0].kind == nnkIdent:
|
||||||
|
return a[0]
|
||||||
|
elif a[0].kind == nnkPostFix:
|
||||||
|
return a[0][1]
|
||||||
|
|
||||||
|
macro QtType*(qtDecl: stmt): stmt {.immediate.} =
|
||||||
|
expectKind(qtDecl, nnkStmtList)
|
||||||
|
#echo treeRepr qtDecl
|
||||||
|
result = newStmtList()
|
||||||
|
var slots = newSeq[PNimrodNode]()
|
||||||
|
var properties = newSeq[PNimrodNode]()
|
||||||
|
var signals = newSeq[PNimrodNode]()
|
||||||
|
# assume only one type per section for now
|
||||||
|
var typ: PNimrodNode
|
||||||
|
for it in qtDecl.children():
|
||||||
|
if it.kind == nnkTypeSection:
|
||||||
|
# add the typesection unchanged
|
||||||
|
let typeDecl = it.findChild(it.kind == nnkTypeDef)
|
||||||
|
let superType = typeDecl.getSuperType()
|
||||||
|
if superType.kind != nnkEmpty:
|
||||||
|
let superName = if superType.kind == nnkIdent: superType else: superType.getNodeOf(nnkIdent)
|
||||||
|
if $superName != "QtProperty":
|
||||||
|
if typ != nil:
|
||||||
|
error("only support one type declaration")
|
||||||
|
typ = typeDecl.getTypeName
|
||||||
|
result.add it
|
||||||
|
result.add genSuperTemplate(typeDecl)
|
||||||
|
else:
|
||||||
|
# process later
|
||||||
|
properties.add(it)
|
||||||
|
elif it.kind == nnkMethodDef:
|
||||||
|
if it.hasPragma("slot"):
|
||||||
|
let pragma = it.pragma()
|
||||||
|
it.pragma = pragma.removePragma("slot")
|
||||||
|
slots.add it # we need to gensome code later
|
||||||
|
result.add it
|
||||||
|
elif it.hasPragma("signal"):
|
||||||
|
let pragma = it.pragma()
|
||||||
|
it.pragma = pragma.removePragma("signal")
|
||||||
|
it.body = addSignalBody(it)
|
||||||
|
result.add it
|
||||||
|
signals.add it
|
||||||
|
|
||||||
|
## define onSlotCalled
|
||||||
|
var slotProto = (getAst declareOnSlotCalled(typ))[0]
|
||||||
|
var caseStmt = newNimNode(nnkCaseStmt)
|
||||||
|
caseStmt.add ident("slotName")
|
||||||
|
for slot in slots:
|
||||||
|
var ofBranch = newNimNode(nnkOfBranch)
|
||||||
|
# for exported procedures - strip * marker
|
||||||
|
let slotName = ($slot.name).replace("*","")
|
||||||
|
ofBranch.add newLit slotName
|
||||||
|
let params = slot.params
|
||||||
|
let hasReturn = not (params[0].kind == nnkEmpty)
|
||||||
|
var branchStmts = newStmtList()
|
||||||
|
let paramCount = params.len -1 # don't include ret (arg 0)
|
||||||
|
var args = newSeq[PNimrodNode]()
|
||||||
|
# first params always the object
|
||||||
|
args.add ident "myQObject"
|
||||||
|
for i in 2.. <params.len:
|
||||||
|
let pType = getArgType params[i]
|
||||||
|
# function that maps Qvariant type to nim type
|
||||||
|
let mapper = nimFromQtVariant[$pType]
|
||||||
|
let argAccess = newNimNode(nnkBracketExpr)
|
||||||
|
.add (ident "args")
|
||||||
|
.add newIntLitNode(i-1)
|
||||||
|
let dot = newDotExpr(argAccess, ident mapper)
|
||||||
|
args.add dot
|
||||||
|
var call = newCall(ident slotName, args)
|
||||||
|
if hasReturn:
|
||||||
|
# eg: args[0].strVal = getName(myQObject)
|
||||||
|
let retType = params[0]
|
||||||
|
let mapper = nimFromQtVariant[$retType]
|
||||||
|
let argAccess = newNimNode(nnkBracketExpr)
|
||||||
|
.add (ident "args")
|
||||||
|
.add newIntLitNode(0)
|
||||||
|
let dot = newDotExpr(argAccess, ident mapper)
|
||||||
|
call = newAssignment(dot, call)
|
||||||
|
branchStmts.add call
|
||||||
|
ofBranch.add branchStmts
|
||||||
|
caseStmt.add ofBranch
|
||||||
|
# add else: discard
|
||||||
|
caseStmt.add newNimNode(nnkElse)
|
||||||
|
.add newStmtList().add newNimNode(nnkDiscardStmt).add newNimNode(nnkEmpty)
|
||||||
|
slotProto.body = newStmtList().add caseStmt
|
||||||
|
result.add slotProto
|
||||||
|
|
||||||
|
# generate create method
|
||||||
|
var createProto = (getAst prototypeCreate(typ))[0]
|
||||||
|
# the template creates loads of openSyms - replace these with idents
|
||||||
|
createProto = doRemoveOpenSym(createProto)
|
||||||
|
var createBody = createProto.templateBody
|
||||||
|
for slot in slots:
|
||||||
|
let params = slot.params
|
||||||
|
let regSlotDot = newDotExpr(ident "myQObject", ident "registerSlot")
|
||||||
|
let name = ($slot.name).replace("*","")
|
||||||
|
let argTypesArray = genArgTypeArray(params)
|
||||||
|
let call = newCall(regSlotDot, newLit name, argTypesArray)
|
||||||
|
createBody.add call
|
||||||
|
for signal in signals:
|
||||||
|
let params = signal.params
|
||||||
|
let regSigDot = newDotExpr(ident "myQObject", ident "registerSignal")
|
||||||
|
let name = ($signal.name).replace("*","")
|
||||||
|
let argTypesArray = genArgTypeArray(params)
|
||||||
|
let call = newCall(regSigDot, newLit name, argTypesArray)
|
||||||
|
createBody.add call
|
||||||
|
for property in properties:
|
||||||
|
let inherit = property.getNodeOf(nnkOfInherit)
|
||||||
|
# OfInherit
|
||||||
|
# BracketExpr
|
||||||
|
# Ident !"QtProperty"
|
||||||
|
# Ident !"string"
|
||||||
|
let nimPropType = inherit[0][1]
|
||||||
|
let qtPropMeta = nim2QtMeta[$nimPropType]
|
||||||
|
if qtPropMeta == nil: error($nimPropType & " not supported")
|
||||||
|
let metaDot = newDotExpr(ident "QMetaType", ident qtPropMeta)
|
||||||
|
let typeDef = property.getNodeOf(nnkTypeDef)
|
||||||
|
let typeName = typeDef.getTypeName()
|
||||||
|
var read, write, notify: PNimrodNode
|
||||||
|
# fields
|
||||||
|
let recList = typeDef.getNodeof(nnkRecList)
|
||||||
|
for identDef in recList.children:
|
||||||
|
let name = identDef.getIdentDefName()
|
||||||
|
case $name
|
||||||
|
of "read":
|
||||||
|
read = identDef[2]
|
||||||
|
of "write":
|
||||||
|
write = identDef[2]
|
||||||
|
of "notify":
|
||||||
|
notify = identDef[2]
|
||||||
|
else:
|
||||||
|
error("unknown property field: " & $name)
|
||||||
|
let regPropDot = newDotExpr(ident "myQObject", ident "registerProperty")
|
||||||
|
let readArg = if read == nil: newNilLit() else: newLit($read)
|
||||||
|
let writeArg = if write == nil: newNilLit() else: newLit($write)
|
||||||
|
let notifyArg = if notify == nil: newNilLit() else: newLit($notify)
|
||||||
|
let call = newCall(regPropDot, newLit($typeName), metaDot, readArg, writeArg, notifyArg)
|
||||||
|
createBody.add call
|
||||||
|
|
||||||
|
#echo repr createProto
|
||||||
|
result.add createProto
|
||||||
|
when not defined(release):
|
||||||
|
echo repr result
|
Loading…
Reference in New Issue