Rename apigen/apigen.nim to godotapigen.nim
This commit is contained in:
parent
7185682025
commit
3b8b12833a
6 changed files with 886 additions and 880 deletions
6
.gitignore
vendored
6
.gitignore
vendored
|
|
@ -1,4 +1,8 @@
|
|||
nimcache/
|
||||
.DS_Store
|
||||
|
||||
/godot/apigen/apigen
|
||||
/godot/apigen/apigen.exe
|
||||
/godot/godotapigen
|
||||
/godot/godotapigen.exe
|
||||
|
||||
.DS_Store
|
||||
2
.vscode/settings.json
vendored
2
.vscode/settings.json
vendored
|
|
@ -6,7 +6,7 @@
|
|||
"files.eol": "\n",
|
||||
"files.trimTrailingWhitespace": true,
|
||||
"nim.project": ["godot/godot.nim",
|
||||
"godot/apigen/apigen.nim"],
|
||||
"godot/godotapigen.nim"],
|
||||
"nim.buildOnSave": false,
|
||||
"nim.lintOnSave": true,
|
||||
"nim.licenseString": "# Copyright 2017 Xored Software, Inc.\n\n",
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
import nake
|
||||
import os, ospaths, times
|
||||
import apigen.apigen
|
||||
import godotapigen
|
||||
|
||||
proc cmdParams(): string =
|
||||
let params = commandLineParams()
|
||||
|
|
|
|||
|
|
@ -1,875 +1,3 @@
|
|||
# Copyright 2017 Xored Software, Inc.
|
||||
{.warning: "'apigen.apigen' module is deprecated and will be removed in v0.4.0. Use 'godotapigen' instead.".}
|
||||
|
||||
import streams, json, ospaths, strutils, times, sets, tables
|
||||
import sequtils, algorithm
|
||||
import compiler / [ast, renderer, idents, astalgo]
|
||||
|
||||
proc ident(ident: string): PNode =
|
||||
result = newNode(nkIdent)
|
||||
result.ident = getIdent(ident)
|
||||
|
||||
proc addChain(node: PNode, others: varargs[PNode]): PNode {.discardable.} =
|
||||
for other in others:
|
||||
node.add(other)
|
||||
result = node
|
||||
|
||||
proc newIdentDefs(name, typ: PNode,
|
||||
defaultVal: PNode = nil): PNode =
|
||||
result = newNode(nkIdentDefs)
|
||||
result.add(name)
|
||||
result.add(typ)
|
||||
if defaultVal.isNil:
|
||||
result.add(newNode(nkEmpty))
|
||||
else:
|
||||
result.add(defaultVal)
|
||||
|
||||
proc newProc(name: PNode, params: openArray[PNode], body = newNode(nkEmpty),
|
||||
procType = nkProcDef): PNode =
|
||||
result = newNode(procType)
|
||||
result.add(name)
|
||||
result.add(newNode(nkEmpty))
|
||||
result.add(newNode(nkEmpty))
|
||||
let formalParams = newNode(nkFormalParams)
|
||||
for param in params:
|
||||
formalParams.add(param)
|
||||
result.add(formalParams)
|
||||
result.add(newNode(nkEmpty))
|
||||
result.add(newNode(nkEmpty))
|
||||
result.add(body)
|
||||
|
||||
proc newIfStmt(condition, body: PNode): PNode =
|
||||
result = newNode(nkIfStmt)
|
||||
let branch = newNode(nkElifBranch)
|
||||
result.add(branch)
|
||||
branch.add(condition)
|
||||
branch.add(body)
|
||||
|
||||
proc newCall(theProc: PNode, args: varargs[PNode]): PNode =
|
||||
result = newNode(nkCall)
|
||||
result.add(theProc)
|
||||
for arg in args:
|
||||
result.add(arg)
|
||||
|
||||
proc newCall(theProc: string, args: varargs[PNode]): PNode =
|
||||
newCall(ident(theProc), args)
|
||||
|
||||
proc newStrLit(s: string): PNode =
|
||||
result = newNode(nkStrLit)
|
||||
result.strVal = s
|
||||
|
||||
proc newRStrLit(s: string): PNode =
|
||||
result = newNode(nkRStrLit)
|
||||
result.strVal = s
|
||||
|
||||
proc newCStringLit(s: string): PNode =
|
||||
newNode(nkCallStrLit).addChain(ident("cstring"), newRStrLit(s))
|
||||
|
||||
proc newIntLit(val: BiggestInt): PNode =
|
||||
result = newNode(nkIntLit)
|
||||
result.intVal = val
|
||||
|
||||
proc newInt64Lit(val: BiggestInt): PNode =
|
||||
result = newNode(nkInt64Lit)
|
||||
result.intVal = val
|
||||
|
||||
proc newFloatLit(val: BiggestFloat): PNode =
|
||||
result = newNode(nkFloatLit)
|
||||
result.floatVal = val
|
||||
|
||||
proc newFloat64Lit(val: BiggestFloat): PNode =
|
||||
result = newNode(nkFloat64Lit)
|
||||
result.floatVal = val
|
||||
|
||||
proc newNilLit(): PNode =
|
||||
result = newNode(nkNilLit)
|
||||
|
||||
proc newDotExpr(left, right: PNode): PNode =
|
||||
result = newNode(nkDotExpr)
|
||||
result.add(left)
|
||||
result.add(right)
|
||||
|
||||
proc newCommand(left, right: PNode): PNode =
|
||||
result = newNode(nkCommand)
|
||||
result.add(left)
|
||||
result.add(right)
|
||||
|
||||
proc newInfix(left, op, right: PNode): PNode =
|
||||
result = newNode(nkInfix)
|
||||
result.add(op)
|
||||
result.add(left)
|
||||
result.add(right)
|
||||
|
||||
proc postfix(left: PNode, op: string): PNode =
|
||||
result = newNode(nkPostfix)
|
||||
result.add(ident(op))
|
||||
result.add(left)
|
||||
|
||||
proc newBracketExpr(first, bracket: PNode): PNode =
|
||||
newNode(nkBracketExpr).addChain(first, bracket)
|
||||
|
||||
proc newEmptyNode(): PNode =
|
||||
newNode(nkEmpty)
|
||||
|
||||
type GodotType = ref object
|
||||
name: string
|
||||
godotName: string
|
||||
baseName: string
|
||||
doc: string
|
||||
derivedCount: int
|
||||
isSingleton: bool
|
||||
jsonNode: JsonNode
|
||||
|
||||
proc findCommonRoot(typeRegistry: Table[string, GodotType],
|
||||
types: seq[string]): string =
|
||||
if types.len == 0: return "Object"
|
||||
var chains = newSeq[seq[string]](types.len)
|
||||
for idx, typ in types:
|
||||
chains[idx] = newSeq[string]()
|
||||
chains[idx].add(typ)
|
||||
var curTyp = typeRegistry[typ]
|
||||
while curTyp.baseName != "NimGodotObject":
|
||||
chains[idx].add(curTyp.baseName)
|
||||
curTyp = typeRegistry[curTyp.baseName]
|
||||
chains[idx].reverse()
|
||||
|
||||
var idx = 0
|
||||
while true:
|
||||
var curType: string
|
||||
var allEqual = true
|
||||
for chain in chains:
|
||||
if idx >= chain.len:
|
||||
allEqual = false
|
||||
break
|
||||
if curType.isNil:
|
||||
curType = chain[idx]
|
||||
elif curType != chain[idx]:
|
||||
allEqual = false
|
||||
break
|
||||
if allEqual:
|
||||
result = curType
|
||||
else:
|
||||
break
|
||||
inc idx
|
||||
|
||||
proc incDerivedCount(types: var Table[string, GodotType],
|
||||
className: string) =
|
||||
let typ = types.getOrDefault(className)
|
||||
if not typ.isNil:
|
||||
inc typ.derivedCount
|
||||
types.incDerivedCount(typ.baseName)
|
||||
|
||||
proc toNimType(godotType: string): string =
|
||||
case godotType:
|
||||
of "float": "float64"
|
||||
of "int": "int64"
|
||||
of "String": "string"
|
||||
of "File": "godottypes.File"
|
||||
else: godotType
|
||||
|
||||
proc toNimType(types: Table[string, GodotType], godotType: string): string =
|
||||
let origType =
|
||||
if godotType.contains(','):
|
||||
findCommonRoot(types, godotType.split(','))
|
||||
else:
|
||||
godotType
|
||||
result = toNimType(origType)
|
||||
|
||||
proc toNimStyle(name: string): string =
|
||||
result = newStringOfCap(name.len + 1)
|
||||
var i = 0
|
||||
var makeUpperCase: bool
|
||||
while i < name.len:
|
||||
if name[i] == '_':
|
||||
if result.len != 0:
|
||||
makeUpperCase = true
|
||||
elif name[i] != '/': # remove slashes
|
||||
if makeUpperCase:
|
||||
result.add(name[i].toUpperAscii())
|
||||
makeUpperCase = false
|
||||
else:
|
||||
result.add(name[i])
|
||||
inc i
|
||||
|
||||
while isKeyword(getIdent(result)):
|
||||
result.add(result[result.len - 1])
|
||||
|
||||
if result == "result":
|
||||
result = "resultVal"
|
||||
|
||||
proc newRefTypeNode(typeSection: PNode, typ, base, doc: string,
|
||||
isExported = true) =
|
||||
let typeDef = newNode(nkTypeDef)
|
||||
typeSection.add(typeDef)
|
||||
if isExported:
|
||||
typeDef.add(postfix(ident(typ), "*"))
|
||||
else:
|
||||
typeDef.add(ident(typ))
|
||||
|
||||
typeDef.add(newNode(nkEmpty)) # generic
|
||||
let objTy = newNode(nkObjectTy)
|
||||
let refTy = newNode(nkRefTy)
|
||||
refTy.add(objTy)
|
||||
typeDef.add(refTy)
|
||||
|
||||
objTy.add(newNode(nkEmpty))
|
||||
let inherit = newNode(nkOfInherit)
|
||||
inherit.add(ident(base))
|
||||
objTy.add(inherit)
|
||||
|
||||
if not doc.isNil:
|
||||
let recList = newNode(nkRecList)
|
||||
let docNode = newNode(nkCommentStmt)
|
||||
docNode.comment = doc
|
||||
recList.add(docNode)
|
||||
objTy.add(recList)
|
||||
else:
|
||||
objTy.add(newNode(nkEmpty))
|
||||
|
||||
proc makeConstSection(constObj: JsonNode): PNode =
|
||||
if constObj.len == 0:
|
||||
return newNode(nkEmpty)
|
||||
|
||||
result = newNode(nkConstSection)
|
||||
for field, val in constObj:
|
||||
let def = newNode(nkConstDef)
|
||||
def.add(postfix(ident(field), "*"))
|
||||
def.add(newNode(nkEmpty)) # infer type
|
||||
var valNode: PNode
|
||||
if val.kind == JInt:
|
||||
valNode = newInt64Lit(val.num)
|
||||
# valNode.intVal = val.num
|
||||
elif val.kind == JFloat:
|
||||
valNode = newFloat64Lit(val.fnum)
|
||||
elif val.kind == JBool:
|
||||
valNode = ident($val.bval)
|
||||
elif val.kind == JString:
|
||||
valNode = newNode(nkStrLit)
|
||||
valNode.strVal = val.str
|
||||
else:
|
||||
raise newException(ValueError, "Unexpected constant kind: " & $val.kind)
|
||||
def.add(valNode)
|
||||
result.add(def)
|
||||
|
||||
type MethodArg = tuple[name, typ: string, defaultVal: JsonNode, isVarargs: bool]
|
||||
|
||||
const standardTypes = toSet(
|
||||
["bool", "cint", "int", "uint8", "int8", "uint16", "int16", "uint32", "int32",
|
||||
"uint64",
|
||||
"int64", "float32", "cfloat", "float64", "GodotString", "Vector2", "Rect2",
|
||||
"Vector3", "Transform2D", "Plane", "Quat", "Rect3", "Basis", "Transform",
|
||||
"Color", "RID", "NodePath", "Dictionary", "Variant", "Array",
|
||||
"PoolByteArray", "PoolIntArray", "PoolRealArray", "PoolStringArray",
|
||||
"PoolVector2Array", "PoolVector3Array", "PoolColorArray"])
|
||||
|
||||
const smallIntTypes = toSet(["uint8", "int8", "uint16", "int16", "uint32", "int32",
|
||||
"cint", "int", "Error"])
|
||||
const int64Types = toSet(["uint64", "int64"])
|
||||
const intTypes = union(smallIntTypes, int64Types)
|
||||
const float64Types = toSet(["float64", "cdouble"])
|
||||
const float32Types = toSet(["float32", "cfloat"])
|
||||
const floatTypes = union(float64Types, float32Types)
|
||||
const arrayTypes = toSet(["Array", "PoolByteArray",
|
||||
"PoolIntArray", "PoolRealArray", "PoolStringArray", "PoolVector2Array",
|
||||
"PoolVector3Array", "PoolColorArray"])
|
||||
const wrapperTypes = union(arrayTypes,
|
||||
toSet(["NodePath", "Dictionary", "Variant"]))
|
||||
|
||||
proc getInternalPtr(varName: PNode, typ: string): PNode =
|
||||
assert(typ in wrapperTypes)
|
||||
result = newDotExpr(varName, ident("godot" & typ))
|
||||
|
||||
proc newNilCheck(ident, toAssign: PNode): PNode =
|
||||
newIfStmt(newCall("isNil", ident),
|
||||
newNode(nkAsgn).addChain(ident).addChain(
|
||||
toAssign
|
||||
)
|
||||
)
|
||||
|
||||
proc singletonName(typ: string): PNode =
|
||||
ident("singleton" & typ)
|
||||
|
||||
proc makeDefaultValue(arg: MethodArg): PNode =
|
||||
result = newEmptyNode()
|
||||
if not arg.defaultVal.isNil:
|
||||
if arg.typ == "bool":
|
||||
result = ident(arg.defaultVal.str.toLowerAscii())
|
||||
elif arg.typ in intTypes:
|
||||
result = newInt64Lit(parseBiggestInt(arg.defaultVal.str))
|
||||
elif arg.typ == "string" or arg.typ == "NodePath":
|
||||
result = newStrLit(arg.defaultVal.str)
|
||||
elif arg.typ in floatTypes:
|
||||
result = newFloatLit(parseFloat(arg.defaultVal.str))
|
||||
elif arg.defaultVal.str == "Null":
|
||||
if arg.typ == "Variant":
|
||||
result = newCall("newVariant")
|
||||
else:
|
||||
result = newNilLit()
|
||||
elif arg.typ == "Color":
|
||||
result = newCall("initColor",
|
||||
arg.defaultVal.str.split(',').mapIt(newFloatLit(parseFloat(it))))
|
||||
# elif arg.typ in arrayTypes and (arg.defaultVal.str == "[]" or
|
||||
# arg.defaultVal.str == "[" & arg.typ & "]"):
|
||||
# result = newCall("init" & arg.typ)
|
||||
elif arg.typ == "RID" and arg.defaultVal.str == "[RID]":
|
||||
result = newCall("initRID")
|
||||
elif arg.typ == "Vector2" or arg.typ == "Vector3" or arg.typ == "Rect2":
|
||||
let parts = arg.defaultVal.str.replace("(", "").replace(")", "").
|
||||
replace(" ", "").
|
||||
split(",").mapIt(newFloatLit(parseFloat(it)))
|
||||
if arg.typ.endsWith("r2"):
|
||||
result = newCall("vec2", parts)
|
||||
elif arg.typ.endsWith('3'):
|
||||
result = newCall("vec3", parts)
|
||||
else:
|
||||
result = newCall("initRect2", parts)
|
||||
elif arg.defaultVal.str == "[Object:null]":
|
||||
result = newNilLit()
|
||||
elif arg.typ != "Transform" and arg.typ != "Transform2D" and
|
||||
arg.typ != "Variant" and arg.typ notin arrayTypes: # TODO
|
||||
raise newException(
|
||||
ValueError,
|
||||
"Cannot build default value from $# ($#)" %
|
||||
[$arg.defaultVal, arg.typ])
|
||||
|
||||
proc doGenerateMethod(tree: PNode, methodBindRegistry: var HashSet[string],
|
||||
name: PNode, godotMethodName: string,
|
||||
typ: GodotType, args: seq[MethodArg],
|
||||
returnType: string, isVirtual,
|
||||
isBase, withImplementation: bool) =
|
||||
var body: PNode
|
||||
if not withImplementation:
|
||||
body = newNode(nkEmpty)
|
||||
else:
|
||||
var methodBindName = toNimStyle(
|
||||
typ.name & "_" & godotMethodName & "_method_bind")
|
||||
if methodBindName[0].isUpperAscii():
|
||||
methodBindName[0] = methodBindName[0].toLowerAscii()
|
||||
if methodBindName notin methodBindRegistry:
|
||||
let methodBindDecl = newNode(nkVarSection).addChain(
|
||||
newIdentDefs(
|
||||
newNode(nkPragmaExpr).addChain(
|
||||
ident(methodBindName)).addChain(
|
||||
newNode(nkPragma).addChain(ident("threadvar"))),
|
||||
newNode(nkPtrTy).addChain(ident("GodotMethodBind"))
|
||||
))
|
||||
tree.add(methodBindDecl)
|
||||
methodBindRegistry.incl(methodBindName)
|
||||
|
||||
let nilCheck = newNilCheck(ident(methodBindName),
|
||||
newCall("getMethod", newCStringLit(typ.godotName),
|
||||
newCStringLit(godotMethodName))
|
||||
)
|
||||
let vars = newNode(nkVarSection)
|
||||
var varargsName: string
|
||||
var isVarargs: bool
|
||||
var staticArgsLen: int
|
||||
var argLenNode = newIntLit(0)
|
||||
let argsName = ident("argsToPassToGodot")
|
||||
if args.len > 0:
|
||||
for arg in args:
|
||||
if arg.isVarargs:
|
||||
varargsName = arg.name
|
||||
isVarargs = true
|
||||
vars.add(newIdentDefs(ident("callError"), ident("VariantCallError")))
|
||||
break
|
||||
let argsAlloc = newNode(nkCast).addChain(
|
||||
newNode(nkPtrTy).addChain(
|
||||
newNode(nkBracketExpr).addChain(
|
||||
ident("array"), ident("MAX_ARG_COUNT"),
|
||||
if isVarargs: newNode(nkPtrTy).addChain(ident("GodotVariant"))
|
||||
else: ident("pointer"))))
|
||||
staticArgsLen = if varargsName.isNil: args.len
|
||||
else: args.len - 1
|
||||
if not varargsName.isNil:
|
||||
argLenNode = newCall("cint",
|
||||
newInfix(newIntLit(staticArgsLen),
|
||||
ident("+"),
|
||||
newDotExpr(ident(varargsName), ident("len"))))
|
||||
argsAlloc.add(newCall("godotAlloc", newCall("cint", newInfix(
|
||||
newCall("sizeof", ident("Variant")), ident("*"),
|
||||
newNode(nkPar).addChain(argLenNode))))
|
||||
)
|
||||
else:
|
||||
argLenNode = newCall("cint", newIntLit(staticArgsLen))
|
||||
vars.add(newIdentDefs(
|
||||
ident("argsStatic"),
|
||||
newNode(nkBracketExpr).addChain(
|
||||
ident("array"), newIntLit(staticArgsLen), ident("pointer")),
|
||||
newNode(nkEmpty)))
|
||||
argsAlloc.add(newCommand(ident("addr"), ident("argsStatic")))
|
||||
vars.add(newIdentDefs(argsName, newNode(nkEmpty), argsAlloc))
|
||||
|
||||
let argConversions = newNode(nkStmtList)
|
||||
for idx, arg in args:
|
||||
var argName = ident(arg.name)
|
||||
if arg.isVarargs:
|
||||
argName = newNode(nkBracketExpr).addChain(
|
||||
ident(varargsName),
|
||||
newInfix(ident("idx"), ident("-"), newIntLit(staticArgsLen)))
|
||||
let argIdx = if arg.isVarargs: ident("idx") else: newIntLit(idx)
|
||||
let isStandardType = arg.typ in standardTypes
|
||||
let isWrappedType = arg.typ in wrapperTypes
|
||||
let convArg = if not varargsName.isNil:
|
||||
getInternalPtr(newCall("toGodot", argName), "Variant")
|
||||
elif isWrappedType: getInternalPtr(argName, arg.typ)
|
||||
elif isStandardType: newCall("unsafeAddr", argName)
|
||||
elif arg.typ == "string": newCall("unsafeAddr",
|
||||
ident("argToPassToGodot" & $idx))
|
||||
else: ident("argToPassToGodot" & $idx) # object
|
||||
if not isStandardType and arg.isVarargs:
|
||||
raise newException(ValueError,
|
||||
"Non-standard type $# for varargs params of method $# of type $#" %
|
||||
[arg.typ, godotMethodName, typ.godotName])
|
||||
if not isStandardType and varargsName.isNil:
|
||||
if arg.typ == "string":
|
||||
argConversions.add(newNode(nkVarSection).addChain(
|
||||
newIdentDefs(ident("argToPassToGodot" & $idx), newEmptyNode(),
|
||||
newCall("toGodotString", argName))))
|
||||
else:
|
||||
argConversions.add(newNode(nkLetSection).addChain(
|
||||
newIdentDefs(ident("argToPassToGodot" & $idx), newEmptyNode(),
|
||||
newDotExpr(argName, ident("godotObject")))))
|
||||
let argAsgn = newNode(nkAsgn).addChain(
|
||||
newNode(nkBracketExpr).addChain(
|
||||
newNode(nkBracketExpr).addChain(argsName), argIdx),
|
||||
convArg
|
||||
)
|
||||
if not arg.isVarargs:
|
||||
argConversions.add(
|
||||
argAsgn
|
||||
)
|
||||
else:
|
||||
argConversions.add(newNode(nkVarSection).addChain(
|
||||
newIdentDefs(ident("idx"), newNode(nkEmpty), newIntLit(staticArgsLen))))
|
||||
let argLoop = newNode(nkWhileStmt)
|
||||
argConversions.add(argLoop)
|
||||
argLoop.add(newInfix(ident("idx"), ident("<"), argLenNode)
|
||||
)
|
||||
argLoop.add(newNode(nkStmtList).addChain(
|
||||
argAsgn,
|
||||
newCommand(ident("inc"), ident("idx"))))
|
||||
|
||||
let retName = if isVarargs: "callRet" else: "ptrCallRet"
|
||||
let theCall = newCall(
|
||||
newDotExpr(ident(methodBindName),
|
||||
ident(if isVarargs: "call" else: "ptrCall")),
|
||||
newDotExpr(ident("self"), ident("godotObject")),
|
||||
if args.len > 0: argsName else: newNode(nkNilLit),
|
||||
argLenNode,
|
||||
if not isVarargs: ident(retName) else: ident("callError")
|
||||
)
|
||||
if not isVarargs:
|
||||
theCall.sons.delete(3)
|
||||
let freeCall = newCall("godotFree", argsName)
|
||||
body = newNode(nkStmtList)
|
||||
if typ.isSingleton:
|
||||
let varName = singletonName(typ.name)
|
||||
body.add(newNilCheck(varName, newCall(
|
||||
newBracketExpr(ident("getSingleton"), ident(typ.name)))))
|
||||
body.add(newNode(nkLetSection).addChain(newIdentDefs(
|
||||
ident("self"), newNode(nkEmpty), varName
|
||||
)))
|
||||
body.add(nilCheck)
|
||||
body.add(vars)
|
||||
body.add(argConversions)
|
||||
if not isVarargs:
|
||||
let retPtrDecl = newNode(nkVarSection).addChain(
|
||||
newIdentDefs(ident(retName), ident("pointer"), newEmptyNode())
|
||||
)
|
||||
body.add(retPtrDecl)
|
||||
var isVariantRet: bool
|
||||
var isConversionRet: bool
|
||||
var isObjRet: bool
|
||||
var isStringRet: bool
|
||||
var isWrapperRet: bool
|
||||
let retValIdent = if not isVarargs: ident("ptrCallVal") else: ident(retName)
|
||||
if not returnType.isNil and not isVarargs:
|
||||
var addrToAssign = newCall("addr", retValIdent)
|
||||
if returnType in smallIntTypes:
|
||||
isConversionRet = true
|
||||
body.add(newNode(nkVarSection).addChain(newIdentDefs(
|
||||
retValIdent, ident("int64"),
|
||||
)))
|
||||
elif returnType in float32Types:
|
||||
isConversionRet = true
|
||||
body.add(newNode(nkVarSection).addChain(newIdentDefs(
|
||||
retValIdent, ident("float64"),
|
||||
)))
|
||||
elif returnType in wrapperTypes:
|
||||
isWrapperRet = true
|
||||
body.add(newNode(nkVarSection).addChain(newIdentDefs(
|
||||
retValIdent, ident("Godot" & returnType),
|
||||
)))
|
||||
elif returnType in standardTypes:
|
||||
addrToAssign = newCall("addr", ident("result"))
|
||||
elif returnType == "string":
|
||||
isStringRet = true
|
||||
body.add(newNode(nkVarSection).addChain(newIdentDefs(
|
||||
retValIdent, ident("GodotString"),
|
||||
)))
|
||||
else:
|
||||
isObjRet = true
|
||||
body.add(newNode(nkVarSection).addChain(newIdentDefs(
|
||||
retValIdent, newNode(nkPtrTy).addChain(ident("GodotObject")),
|
||||
)))
|
||||
body.add(newNode(nkAsgn).addChain(ident(retName), addrToAssign))
|
||||
|
||||
if not isVarargs:
|
||||
body.add(theCall)
|
||||
else:
|
||||
if not returnType.isNil:
|
||||
isVariantRet = true
|
||||
let callStmt = newNode(nkLetSection).addChain(
|
||||
newIdentDefs(
|
||||
newNode(nkPragmaExpr).addChain(
|
||||
ident(retName), newNode(nkPragma).addChain(ident("used"))),
|
||||
newNode(nkEmpty), theCall))
|
||||
body.add(callStmt)
|
||||
|
||||
if varargsName.isNil:
|
||||
for idx, arg in args:
|
||||
if arg.typ == "string":
|
||||
body.add(newCall("deinit", ident("argToPassToGodot" & $idx)))
|
||||
|
||||
if args.len > 0 and not varargsName.isNil:
|
||||
body.add(freeCall)
|
||||
if not varargsName.isNil:
|
||||
let errCheck = newIfStmt(
|
||||
newInfix(newDotExpr(ident("callError"), ident("error")), ident("!="),
|
||||
newDotExpr(ident("VariantCallErrorType"), ident("OK"))),
|
||||
newNode(nkRaiseStmt).addChain(
|
||||
newCall("newCallError", ident("callError")))
|
||||
)
|
||||
body.add(errCheck)
|
||||
if isVariantRet:
|
||||
let convErrDef = newNode(nkLetSection).addChain(
|
||||
newIdentDefs(ident("convErr"), newNode(nkEmpty),
|
||||
newCall("fromGodot", ident("result"), newCall("newVariant", retValIdent))))
|
||||
let convCheck = newIfStmt(
|
||||
newInfix(ident("convErr"), ident("!="),
|
||||
newDotExpr(ident("ConversionResult"), ident("OK"))),
|
||||
newNode(nkRaiseStmt).addChain(
|
||||
newCall("newConversionError", ident("convErr")))
|
||||
)
|
||||
body.add(convErrDef)
|
||||
body.add(convCheck)
|
||||
if isStringRet:
|
||||
body.add(newCall("deinit", retValIdent))
|
||||
elif isConversionRet:
|
||||
body.add(newNode(nkAsgn).addChain(
|
||||
ident("result"),
|
||||
newCall(returnType, retValIdent)))
|
||||
elif isWrapperRet:
|
||||
body.add(newNode(nkAsgn).addChain(
|
||||
ident("result"),
|
||||
newCall("new" & returnType, retValIdent)))
|
||||
elif isObjRet:
|
||||
body.add(newNode(nkAsgn).addChain(ident("result"),
|
||||
newCall(newBracketExpr(ident("asNimGodotObject"),
|
||||
newCall("type", ident("result"))), retValIdent)))
|
||||
elif isStringRet:
|
||||
body.add(newNode(nkAsgn).addChain(ident("result"),
|
||||
newCall("$", retValIdent)))
|
||||
|
||||
let procType = if isVirtual: nkMethodDef
|
||||
else: nkProcDef
|
||||
var params = newSeqOfCap[PNode](args.len + 2)
|
||||
if not returnType.isNil:
|
||||
params.add(ident(returnType))
|
||||
else:
|
||||
params.add(newNode(nkEmpty))
|
||||
if not typ.isSingleton:
|
||||
params.add(newIdentDefs(ident("self"), ident(toNimType(typ.name))))
|
||||
|
||||
for arg in args:
|
||||
if arg.isVarargs:
|
||||
params.add(newIdentDefs(ident(arg.name),
|
||||
newNode(nkBracketExpr).addChain(ident("varargs"), ident(arg.typ))))
|
||||
else:
|
||||
let defaultValue = arg.makeDefaultValue()
|
||||
params.add(newIdentDefs(ident(arg.name), ident(arg.typ), defaultValue))
|
||||
let procDecl = newProc(if not withImplementation: postfix(name, "*")
|
||||
else: name,
|
||||
params, body, procType)
|
||||
if not withImplementation:
|
||||
let pragma = newNode(nkPragma)
|
||||
pragma.add(ident("gcsafe"))
|
||||
pragma.add(newNode(nkExprColonExpr).addChain(ident("locks"),
|
||||
newIntLit(0)))
|
||||
if isVirtual and isBase:
|
||||
pragma.add(ident("base"))
|
||||
procDecl.sons[4] = pragma
|
||||
tree.add(procDecl)
|
||||
|
||||
proc generateMethod(tree: PNode, methodBindRegistry: var HashSet[string],
|
||||
name: PNode, godotMethodName: string,
|
||||
typ: GodotType, args: seq[MethodArg],
|
||||
returnType: string,
|
||||
isVirtual, isBase, withImplementation: bool) =
|
||||
doGenerateMethod(
|
||||
tree, methodBindRegistry, name, godotMethodName, typ,
|
||||
args, returnType, isVirtual, isBase, withImplementation)
|
||||
|
||||
proc makeProperty(types: Table[string, GodotType], tree: PNode,
|
||||
methodBindRegistry: var HashSet[string],
|
||||
typ: GodotType,
|
||||
propertyObj: JsonNode, withImplementation: bool) =
|
||||
let getterNameStr = toNimStyle(propertyObj["name"].str)
|
||||
let getterName = ident(toNimStyle(propertyObj["name"].str))
|
||||
let setterName = newNode(nkAccQuoted).addChain(ident(getterNameStr & "="))
|
||||
|
||||
let nimType = toNimType(types, propertyObj["type"].str)
|
||||
generateMethod(tree, methodBindRegistry,
|
||||
getterName, propertyObj["getter"].str, typ,
|
||||
@[], nimType, isVirtual = false,
|
||||
isBase = false, withImplementation)
|
||||
generateMethod(tree, methodBindRegistry,
|
||||
setterName, propertyObj["setter"].str, typ,
|
||||
@[("val", nimType, JsonNode(nil), false)],
|
||||
nil, isVirtual = false, isBase = false, withImplementation)
|
||||
|
||||
proc makeMethod(types: Table[string, GodotType], tree: PNode,
|
||||
methodBindRegistry: var HashSet[string],
|
||||
typ: GodotType, methodObj: JsonNode,
|
||||
withImplementation: bool) =
|
||||
let returnType = if methodObj["return_type"].str != "void":
|
||||
toNimType(types, methodObj["return_type"].str)
|
||||
else: nil
|
||||
|
||||
for prop in typ.jsonNode["properties"]:
|
||||
if prop["getter"].str == methodObj["name"].str or
|
||||
prop["setter"].str == methodObj["name"].str:
|
||||
return
|
||||
|
||||
var args = newSeqOfCap[MethodArg](methodObj["arguments"].len + 1)
|
||||
var origArgs = methodObj["arguments"]
|
||||
var isBase = methodObj["is_virtual"].bval
|
||||
if methodObj["is_virtual"].bval:
|
||||
proc getMethod(typNode: JsonNode, methName: string): JsonNode =
|
||||
for meth in typNode["methods"]:
|
||||
if meth["is_virtual"].bval and
|
||||
meth["name"].str == methName: return meth
|
||||
var curTyp = types.getOrDefault(typ.baseName)
|
||||
while not curTyp.isNil:
|
||||
let meth = curTyp.jsonNode.getMethod(methodObj["name"].str)
|
||||
if not meth.isNil:
|
||||
origArgs = meth["arguments"]
|
||||
isBase = false
|
||||
curTyp = types.getOrDefault(curTyp.baseName)
|
||||
|
||||
for arg in origArgs:
|
||||
let typ = toNimType(types, arg["type"].str)
|
||||
let defaultVal = if arg["has_default_value"].bval: arg["default_value"]
|
||||
else: nil
|
||||
args.add((toNimStyle(arg["name"].str), typ,
|
||||
defaultVal, false))
|
||||
if methodObj["has_varargs"].bval:
|
||||
args.add(("variantArgs", "Variant", JsonNode(nil), true))
|
||||
|
||||
let godotName = methodObj["name"].str
|
||||
var nimName = toNimStyle(godotName)
|
||||
if not godotName.startsWith('_'):
|
||||
for meth in typ.jsonNode["methods"]:
|
||||
if meth["name"].str == '_' & godotName:
|
||||
nimName = nimName & "Impl"
|
||||
|
||||
generateMethod(tree, methodBindRegistry,
|
||||
ident(nimName), methodObj["name"].str,
|
||||
typ, args,
|
||||
returnType, methodObj["is_virtual"].bval, isBase,
|
||||
withImplementation)
|
||||
|
||||
proc addAttributeToDoc(doc: var string, propName, propValue: string) =
|
||||
if doc.len > 0:
|
||||
doc.add("\n")
|
||||
doc.add(propName & ": " & propValue)
|
||||
|
||||
const classAttributes = ["singleton", "instanciable", "is_reference"]
|
||||
|
||||
proc typeNameToModuleName(name: string): string =
|
||||
var wasUpperOrDigit = true
|
||||
result = newStringOfCap(name.len + 10)
|
||||
for idx, c in name:
|
||||
if c.isUpperAscii() or c.isDigit():
|
||||
if not wasUpperOrDigit or idx > 0 and idx < name.len - 1 and
|
||||
name[idx + 1].isLowerAscii():
|
||||
result.add('_')
|
||||
result.add(c.toLowerAscii())
|
||||
wasUpperOrDigit = true
|
||||
else:
|
||||
result.add(c)
|
||||
wasUpperOrDigit = false
|
||||
if result == "object":
|
||||
# keyword
|
||||
result = "objects"
|
||||
if result == "os":
|
||||
# to avoid clash with stdlib
|
||||
result = "gd_os"
|
||||
|
||||
proc newRegisterClassNode(typ: GodotType): PNode =
|
||||
newCall("registerClass",
|
||||
ident(typ.name),
|
||||
newCStringLit(typ.godotName),
|
||||
ident("true") # isNative
|
||||
)
|
||||
|
||||
proc genSingletonDecl(typ: string): PNode =
|
||||
result = newNode(nkVarSection)
|
||||
let name = singletonName(typ)
|
||||
let threadvar = newNode(nkPragma).addChain(ident("threadvar"))
|
||||
result.add(newIdentDefs(newNode(nkPragmaExpr).addChain(name, threadvar),
|
||||
ident(typ)))
|
||||
|
||||
proc shouldExport(typ: GodotType, types: Table[string, GodotType]): bool =
|
||||
var curTyp = typ
|
||||
while not curTyp.isNil and not curTyp.isSingleton:
|
||||
curTyp = types.getOrDefault(curTyp.baseName)
|
||||
result = curTyp.isNil
|
||||
|
||||
proc genTypeFile*(types: var Table[string, GodotType], targetDir: string) =
|
||||
for typ in types.values():
|
||||
types.incDerivedCount(typ.baseName)
|
||||
|
||||
var sortedTypes = toSeq(types.values())
|
||||
sortedTypes.sort do (x, y: GodotType) -> int:
|
||||
cmp(y.derivedCount, x.derivedCount)
|
||||
|
||||
let godotApiTypesTree = newNode(nkStmtList)
|
||||
godotApiTypesTree.add(newNode(nkImportStmt).addChain(ident("godot")))
|
||||
let typeSection = newNode(nkTypeSection)
|
||||
godotApiTypesTree.add(typeSection)
|
||||
|
||||
for typ in sortedTypes:
|
||||
if not typ.shouldExport(types): continue
|
||||
newRefTypeNode(typeSection, typ.name, typ.baseName, typ.doc)
|
||||
|
||||
for typ in sortedTypes:
|
||||
if not typ.shouldExport(types): continue
|
||||
let regNode = newCall("registerClass",
|
||||
ident(typ.name),
|
||||
newCStringLit(typ.godotName),
|
||||
ident("true") # isNative
|
||||
)
|
||||
godotApiTypesTree.add(regNode)
|
||||
|
||||
renderModule(godotApiTypesTree, targetDir / "godottypes.nim")
|
||||
|
||||
proc genApi*(targetDir: string, apiJsonFile: string) =
|
||||
let apiJson = parseJson(newFileStream(apiJsonFile),
|
||||
apiJsonFile.extractFilename())
|
||||
var megaImport = newStringOfCap(64 * 1024)
|
||||
|
||||
var types = initTable[string, GodotType]()
|
||||
for obj in apiJson:
|
||||
let godotClassName = obj["name"].str
|
||||
let className = godotClassName.replace("_", "")
|
||||
var baseClassName = obj["base_class"].str
|
||||
baseClassName = toNimType(baseClassName.replace("_", ""))
|
||||
if baseClassName.isNil or baseClassName.len == 0:
|
||||
baseClassName = "NimGodotObject"
|
||||
var doc = ""
|
||||
for attr in classAttributes:
|
||||
if attr in obj:
|
||||
addAttributeToDoc(doc, attr, $obj[attr])
|
||||
types[className] = GodotType(
|
||||
name: className,
|
||||
godotName: godotClassName,
|
||||
baseName: baseClassName,
|
||||
doc: doc,
|
||||
jsonNode: obj,
|
||||
isSingleton: obj["singleton"].bval
|
||||
)
|
||||
for typ in types.values():
|
||||
let tree = newNode(nkStmtList)
|
||||
let importStmt = newNode(nkImportStmt)
|
||||
importStmt.add(ident("godot"))
|
||||
importStmt.add(ident("godottypes"))
|
||||
importStmt.add(ident("godotinternal"))
|
||||
tree.add(importStmt)
|
||||
let exportStmt = newNode(nkExportStmt)
|
||||
exportStmt.add(ident("godottypes"))
|
||||
tree.add(exportStmt)
|
||||
|
||||
var methodBindRegsitry = initSet[string]()
|
||||
let obj = typ.jsonNode
|
||||
if typ.baseName != "NimGodotObject":
|
||||
let baseModule = typeNameToModuleName(typ.baseName)
|
||||
let baseModuleNode = if getIdent(baseModule).isKeyword:
|
||||
newStrLit(baseModule & ".nim")
|
||||
else:
|
||||
ident(baseModule)
|
||||
importStmt.add(baseModuleNode)
|
||||
exportStmt.add(baseModuleNode)
|
||||
let constSection = makeConstSection(obj["constants"])
|
||||
if constSection.kind != nkEmpty:
|
||||
# renderer doesn't properly render empty stmt after type section
|
||||
tree.add(makeConstSection(obj["constants"]))
|
||||
|
||||
if typ.isSingleton:
|
||||
let typeDecl = newNode(nkTypeSection)
|
||||
newRefTypeNode(typeDecl, typ.name, typ.baseName, typ.doc,
|
||||
isExported = false)
|
||||
tree.add(typeDecl)
|
||||
tree.add(newRegisterClassNode(typ))
|
||||
tree.add(genSingletonDecl(typ.name))
|
||||
|
||||
# first, generate declarations only for ease of
|
||||
# human readability of the file
|
||||
for property in obj["properties"]:
|
||||
makeProperty(types, tree, methodBindRegsitry, typ, property,
|
||||
withImplementation = false)
|
||||
for meth in obj["methods"]:
|
||||
makeMethod(types, tree, methodBindRegsitry, typ,
|
||||
meth, withImplementation = false)
|
||||
|
||||
for property in obj["properties"]:
|
||||
makeProperty(types, tree, methodBindRegsitry, typ,
|
||||
property, withImplementation = true)
|
||||
for meth in obj["methods"]:
|
||||
makeMethod(types, tree, methodBindRegsitry, typ, meth,
|
||||
withImplementation = true)
|
||||
|
||||
let moduleName = typeNameToModuleName(typ.name)
|
||||
renderModule(tree, targetDir / moduleName & ".nim")
|
||||
|
||||
megaImport.add("import \"" & moduleName & "\"\n")
|
||||
|
||||
genTypeFile(types, targetDir)
|
||||
|
||||
writeFile(targetDir / "godotall.nim", megaImport)
|
||||
writeFile(targetDir / splitPath(targetDir)[1] & ".nimble", "")
|
||||
|
||||
when isMainModule:
|
||||
import os
|
||||
|
||||
const usage = slurp("usage.txt")
|
||||
|
||||
proc main() =
|
||||
if paramCount() < 2:
|
||||
echo usage
|
||||
quit(-1)
|
||||
let godotBin = paramStr(1)
|
||||
let targetDir = paramStr(2)
|
||||
|
||||
if not dirExists(targetDir):
|
||||
echo "The specified directory does not exist"
|
||||
quit(-1)
|
||||
if not fileExists(godotBin):
|
||||
echo "Invalid godot binary path"
|
||||
quit(-1)
|
||||
|
||||
let jsonFile = targetDir / "api.json"
|
||||
if fileExists(jsonFile) and
|
||||
godotBin.getLastModificationTime() <= jsonFile.getLastModificationTime():
|
||||
echo "api.json if up to date - doing nothing."
|
||||
return
|
||||
|
||||
discard execShellCmd(
|
||||
"\"" & godotBin & "\" --gdnative-generate-json-api \"" & jsonFile & "\"")
|
||||
if not fileExists(jsonFile):
|
||||
echo "Failed to generate api.json"
|
||||
quit(-1)
|
||||
|
||||
genApi(targetDir, jsonFile)
|
||||
|
||||
main()
|
||||
include "../godotapigen.nim"
|
||||
|
|
@ -1,3 +0,0 @@
|
|||
Tool for wrapping Godot API into Nim
|
||||
|
||||
apigen [godot binary path] [target directory]
|
||||
877
godot/godotapigen.nim
Normal file
877
godot/godotapigen.nim
Normal file
|
|
@ -0,0 +1,877 @@
|
|||
# Copyright 2017 Xored Software, Inc.
|
||||
|
||||
import streams, json, ospaths, strutils, times, sets, tables
|
||||
import sequtils, algorithm
|
||||
import compiler / [ast, renderer, idents, astalgo]
|
||||
|
||||
proc ident(ident: string): PNode =
|
||||
result = newNode(nkIdent)
|
||||
result.ident = getIdent(ident)
|
||||
|
||||
proc addChain(node: PNode, others: varargs[PNode]): PNode {.discardable.} =
|
||||
for other in others:
|
||||
node.add(other)
|
||||
result = node
|
||||
|
||||
proc newIdentDefs(name, typ: PNode,
|
||||
defaultVal: PNode = nil): PNode =
|
||||
result = newNode(nkIdentDefs)
|
||||
result.add(name)
|
||||
result.add(typ)
|
||||
if defaultVal.isNil:
|
||||
result.add(newNode(nkEmpty))
|
||||
else:
|
||||
result.add(defaultVal)
|
||||
|
||||
proc newProc(name: PNode, params: openArray[PNode], body = newNode(nkEmpty),
|
||||
procType = nkProcDef): PNode =
|
||||
result = newNode(procType)
|
||||
result.add(name)
|
||||
result.add(newNode(nkEmpty))
|
||||
result.add(newNode(nkEmpty))
|
||||
let formalParams = newNode(nkFormalParams)
|
||||
for param in params:
|
||||
formalParams.add(param)
|
||||
result.add(formalParams)
|
||||
result.add(newNode(nkEmpty))
|
||||
result.add(newNode(nkEmpty))
|
||||
result.add(body)
|
||||
|
||||
proc newIfStmt(condition, body: PNode): PNode =
|
||||
result = newNode(nkIfStmt)
|
||||
let branch = newNode(nkElifBranch)
|
||||
result.add(branch)
|
||||
branch.add(condition)
|
||||
branch.add(body)
|
||||
|
||||
proc newCall(theProc: PNode, args: varargs[PNode]): PNode =
|
||||
result = newNode(nkCall)
|
||||
result.add(theProc)
|
||||
for arg in args:
|
||||
result.add(arg)
|
||||
|
||||
proc newCall(theProc: string, args: varargs[PNode]): PNode =
|
||||
newCall(ident(theProc), args)
|
||||
|
||||
proc newStrLit(s: string): PNode =
|
||||
result = newNode(nkStrLit)
|
||||
result.strVal = s
|
||||
|
||||
proc newRStrLit(s: string): PNode =
|
||||
result = newNode(nkRStrLit)
|
||||
result.strVal = s
|
||||
|
||||
proc newCStringLit(s: string): PNode =
|
||||
newNode(nkCallStrLit).addChain(ident("cstring"), newRStrLit(s))
|
||||
|
||||
proc newIntLit(val: BiggestInt): PNode =
|
||||
result = newNode(nkIntLit)
|
||||
result.intVal = val
|
||||
|
||||
proc newInt64Lit(val: BiggestInt): PNode =
|
||||
result = newNode(nkInt64Lit)
|
||||
result.intVal = val
|
||||
|
||||
proc newFloatLit(val: BiggestFloat): PNode =
|
||||
result = newNode(nkFloatLit)
|
||||
result.floatVal = val
|
||||
|
||||
proc newFloat64Lit(val: BiggestFloat): PNode =
|
||||
result = newNode(nkFloat64Lit)
|
||||
result.floatVal = val
|
||||
|
||||
proc newNilLit(): PNode =
|
||||
result = newNode(nkNilLit)
|
||||
|
||||
proc newDotExpr(left, right: PNode): PNode =
|
||||
result = newNode(nkDotExpr)
|
||||
result.add(left)
|
||||
result.add(right)
|
||||
|
||||
proc newCommand(left, right: PNode): PNode =
|
||||
result = newNode(nkCommand)
|
||||
result.add(left)
|
||||
result.add(right)
|
||||
|
||||
proc newInfix(left, op, right: PNode): PNode =
|
||||
result = newNode(nkInfix)
|
||||
result.add(op)
|
||||
result.add(left)
|
||||
result.add(right)
|
||||
|
||||
proc postfix(left: PNode, op: string): PNode =
|
||||
result = newNode(nkPostfix)
|
||||
result.add(ident(op))
|
||||
result.add(left)
|
||||
|
||||
proc newBracketExpr(first, bracket: PNode): PNode =
|
||||
newNode(nkBracketExpr).addChain(first, bracket)
|
||||
|
||||
proc newEmptyNode(): PNode =
|
||||
newNode(nkEmpty)
|
||||
|
||||
type GodotType = ref object
|
||||
name: string
|
||||
godotName: string
|
||||
baseName: string
|
||||
doc: string
|
||||
derivedCount: int
|
||||
isSingleton: bool
|
||||
jsonNode: JsonNode
|
||||
|
||||
proc findCommonRoot(typeRegistry: Table[string, GodotType],
|
||||
types: seq[string]): string =
|
||||
if types.len == 0: return "Object"
|
||||
var chains = newSeq[seq[string]](types.len)
|
||||
for idx, typ in types:
|
||||
chains[idx] = newSeq[string]()
|
||||
chains[idx].add(typ)
|
||||
var curTyp = typeRegistry[typ]
|
||||
while curTyp.baseName != "NimGodotObject":
|
||||
chains[idx].add(curTyp.baseName)
|
||||
curTyp = typeRegistry[curTyp.baseName]
|
||||
chains[idx].reverse()
|
||||
|
||||
var idx = 0
|
||||
while true:
|
||||
var curType: string
|
||||
var allEqual = true
|
||||
for chain in chains:
|
||||
if idx >= chain.len:
|
||||
allEqual = false
|
||||
break
|
||||
if curType.isNil:
|
||||
curType = chain[idx]
|
||||
elif curType != chain[idx]:
|
||||
allEqual = false
|
||||
break
|
||||
if allEqual:
|
||||
result = curType
|
||||
else:
|
||||
break
|
||||
inc idx
|
||||
|
||||
proc incDerivedCount(types: var Table[string, GodotType],
|
||||
className: string) =
|
||||
let typ = types.getOrDefault(className)
|
||||
if not typ.isNil:
|
||||
inc typ.derivedCount
|
||||
types.incDerivedCount(typ.baseName)
|
||||
|
||||
proc toNimType(godotType: string): string =
|
||||
case godotType:
|
||||
of "float": "float64"
|
||||
of "int": "int64"
|
||||
of "String": "string"
|
||||
of "File": "godottypes.File"
|
||||
else: godotType
|
||||
|
||||
proc toNimType(types: Table[string, GodotType], godotType: string): string =
|
||||
let origType =
|
||||
if godotType.contains(','):
|
||||
findCommonRoot(types, godotType.split(','))
|
||||
else:
|
||||
godotType
|
||||
result = toNimType(origType)
|
||||
|
||||
proc toNimStyle(name: string): string =
|
||||
result = newStringOfCap(name.len + 1)
|
||||
var i = 0
|
||||
var makeUpperCase: bool
|
||||
while i < name.len:
|
||||
if name[i] == '_':
|
||||
if result.len != 0:
|
||||
makeUpperCase = true
|
||||
elif name[i] != '/': # remove slashes
|
||||
if makeUpperCase:
|
||||
result.add(name[i].toUpperAscii())
|
||||
makeUpperCase = false
|
||||
else:
|
||||
result.add(name[i])
|
||||
inc i
|
||||
|
||||
while isKeyword(getIdent(result)):
|
||||
result.add(result[result.len - 1])
|
||||
|
||||
if result == "result":
|
||||
result = "resultVal"
|
||||
|
||||
proc newRefTypeNode(typeSection: PNode, typ, base, doc: string,
|
||||
isExported = true) =
|
||||
let typeDef = newNode(nkTypeDef)
|
||||
typeSection.add(typeDef)
|
||||
if isExported:
|
||||
typeDef.add(postfix(ident(typ), "*"))
|
||||
else:
|
||||
typeDef.add(ident(typ))
|
||||
|
||||
typeDef.add(newNode(nkEmpty)) # generic
|
||||
let objTy = newNode(nkObjectTy)
|
||||
let refTy = newNode(nkRefTy)
|
||||
refTy.add(objTy)
|
||||
typeDef.add(refTy)
|
||||
|
||||
objTy.add(newNode(nkEmpty))
|
||||
let inherit = newNode(nkOfInherit)
|
||||
inherit.add(ident(base))
|
||||
objTy.add(inherit)
|
||||
|
||||
if not doc.isNil:
|
||||
let recList = newNode(nkRecList)
|
||||
let docNode = newNode(nkCommentStmt)
|
||||
docNode.comment = doc
|
||||
recList.add(docNode)
|
||||
objTy.add(recList)
|
||||
else:
|
||||
objTy.add(newNode(nkEmpty))
|
||||
|
||||
proc makeConstSection(constObj: JsonNode): PNode =
|
||||
if constObj.len == 0:
|
||||
return newNode(nkEmpty)
|
||||
|
||||
result = newNode(nkConstSection)
|
||||
for field, val in constObj:
|
||||
let def = newNode(nkConstDef)
|
||||
def.add(postfix(ident(field), "*"))
|
||||
def.add(newNode(nkEmpty)) # infer type
|
||||
var valNode: PNode
|
||||
if val.kind == JInt:
|
||||
valNode = newInt64Lit(val.num)
|
||||
# valNode.intVal = val.num
|
||||
elif val.kind == JFloat:
|
||||
valNode = newFloat64Lit(val.fnum)
|
||||
elif val.kind == JBool:
|
||||
valNode = ident($val.bval)
|
||||
elif val.kind == JString:
|
||||
valNode = newNode(nkStrLit)
|
||||
valNode.strVal = val.str
|
||||
else:
|
||||
raise newException(ValueError, "Unexpected constant kind: " & $val.kind)
|
||||
def.add(valNode)
|
||||
result.add(def)
|
||||
|
||||
type MethodArg = tuple[name, typ: string, defaultVal: JsonNode, isVarargs: bool]
|
||||
|
||||
const standardTypes = toSet(
|
||||
["bool", "cint", "int", "uint8", "int8", "uint16", "int16", "uint32", "int32",
|
||||
"uint64",
|
||||
"int64", "float32", "cfloat", "float64", "GodotString", "Vector2", "Rect2",
|
||||
"Vector3", "Transform2D", "Plane", "Quat", "Rect3", "Basis", "Transform",
|
||||
"Color", "RID", "NodePath", "Dictionary", "Variant", "Array",
|
||||
"PoolByteArray", "PoolIntArray", "PoolRealArray", "PoolStringArray",
|
||||
"PoolVector2Array", "PoolVector3Array", "PoolColorArray"])
|
||||
|
||||
const smallIntTypes = toSet(["uint8", "int8", "uint16", "int16", "uint32", "int32",
|
||||
"cint", "int", "Error"])
|
||||
const int64Types = toSet(["uint64", "int64"])
|
||||
const intTypes = union(smallIntTypes, int64Types)
|
||||
const float64Types = toSet(["float64", "cdouble"])
|
||||
const float32Types = toSet(["float32", "cfloat"])
|
||||
const floatTypes = union(float64Types, float32Types)
|
||||
const arrayTypes = toSet(["Array", "PoolByteArray",
|
||||
"PoolIntArray", "PoolRealArray", "PoolStringArray", "PoolVector2Array",
|
||||
"PoolVector3Array", "PoolColorArray"])
|
||||
const wrapperTypes = union(arrayTypes,
|
||||
toSet(["NodePath", "Dictionary", "Variant"]))
|
||||
|
||||
proc getInternalPtr(varName: PNode, typ: string): PNode =
|
||||
assert(typ in wrapperTypes)
|
||||
result = newDotExpr(varName, ident("godot" & typ))
|
||||
|
||||
proc newNilCheck(ident, toAssign: PNode): PNode =
|
||||
newIfStmt(newCall("isNil", ident),
|
||||
newNode(nkAsgn).addChain(ident).addChain(
|
||||
toAssign
|
||||
)
|
||||
)
|
||||
|
||||
proc singletonName(typ: string): PNode =
|
||||
ident("singleton" & typ)
|
||||
|
||||
proc makeDefaultValue(arg: MethodArg): PNode =
|
||||
result = newEmptyNode()
|
||||
if not arg.defaultVal.isNil:
|
||||
if arg.typ == "bool":
|
||||
result = ident(arg.defaultVal.str.toLowerAscii())
|
||||
elif arg.typ in intTypes:
|
||||
result = newInt64Lit(parseBiggestInt(arg.defaultVal.str))
|
||||
elif arg.typ == "string" or arg.typ == "NodePath":
|
||||
result = newStrLit(arg.defaultVal.str)
|
||||
elif arg.typ in floatTypes:
|
||||
result = newFloatLit(parseFloat(arg.defaultVal.str))
|
||||
elif arg.defaultVal.str == "Null":
|
||||
if arg.typ == "Variant":
|
||||
result = newCall("newVariant")
|
||||
else:
|
||||
result = newNilLit()
|
||||
elif arg.typ == "Color":
|
||||
result = newCall("initColor",
|
||||
arg.defaultVal.str.split(',').mapIt(newFloatLit(parseFloat(it))))
|
||||
# elif arg.typ in arrayTypes and (arg.defaultVal.str == "[]" or
|
||||
# arg.defaultVal.str == "[" & arg.typ & "]"):
|
||||
# result = newCall("init" & arg.typ)
|
||||
elif arg.typ == "RID" and arg.defaultVal.str == "[RID]":
|
||||
result = newCall("initRID")
|
||||
elif arg.typ == "Vector2" or arg.typ == "Vector3" or arg.typ == "Rect2":
|
||||
let parts = arg.defaultVal.str.replace("(", "").replace(")", "").
|
||||
replace(" ", "").
|
||||
split(",").mapIt(newFloatLit(parseFloat(it)))
|
||||
if arg.typ.endsWith("r2"):
|
||||
result = newCall("vec2", parts)
|
||||
elif arg.typ.endsWith('3'):
|
||||
result = newCall("vec3", parts)
|
||||
else:
|
||||
result = newCall("initRect2", parts)
|
||||
elif arg.defaultVal.str == "[Object:null]":
|
||||
result = newNilLit()
|
||||
elif arg.typ != "Transform" and arg.typ != "Transform2D" and
|
||||
arg.typ != "Variant" and arg.typ notin arrayTypes: # TODO
|
||||
raise newException(
|
||||
ValueError,
|
||||
"Cannot build default value from $# ($#)" %
|
||||
[$arg.defaultVal, arg.typ])
|
||||
|
||||
proc doGenerateMethod(tree: PNode, methodBindRegistry: var HashSet[string],
|
||||
name: PNode, godotMethodName: string,
|
||||
typ: GodotType, args: seq[MethodArg],
|
||||
returnType: string, isVirtual,
|
||||
isBase, withImplementation: bool) =
|
||||
var body: PNode
|
||||
if not withImplementation:
|
||||
body = newNode(nkEmpty)
|
||||
else:
|
||||
var methodBindName = toNimStyle(
|
||||
typ.name & "_" & godotMethodName & "_method_bind")
|
||||
if methodBindName[0].isUpperAscii():
|
||||
methodBindName[0] = methodBindName[0].toLowerAscii()
|
||||
if methodBindName notin methodBindRegistry:
|
||||
let methodBindDecl = newNode(nkVarSection).addChain(
|
||||
newIdentDefs(
|
||||
newNode(nkPragmaExpr).addChain(
|
||||
ident(methodBindName)).addChain(
|
||||
newNode(nkPragma).addChain(ident("threadvar"))),
|
||||
newNode(nkPtrTy).addChain(ident("GodotMethodBind"))
|
||||
))
|
||||
tree.add(methodBindDecl)
|
||||
methodBindRegistry.incl(methodBindName)
|
||||
|
||||
let nilCheck = newNilCheck(ident(methodBindName),
|
||||
newCall("getMethod", newCStringLit(typ.godotName),
|
||||
newCStringLit(godotMethodName))
|
||||
)
|
||||
let vars = newNode(nkVarSection)
|
||||
var varargsName: string
|
||||
var isVarargs: bool
|
||||
var staticArgsLen: int
|
||||
var argLenNode = newIntLit(0)
|
||||
let argsName = ident("argsToPassToGodot")
|
||||
if args.len > 0:
|
||||
for arg in args:
|
||||
if arg.isVarargs:
|
||||
varargsName = arg.name
|
||||
isVarargs = true
|
||||
vars.add(newIdentDefs(ident("callError"), ident("VariantCallError")))
|
||||
break
|
||||
let argsAlloc = newNode(nkCast).addChain(
|
||||
newNode(nkPtrTy).addChain(
|
||||
newNode(nkBracketExpr).addChain(
|
||||
ident("array"), ident("MAX_ARG_COUNT"),
|
||||
if isVarargs: newNode(nkPtrTy).addChain(ident("GodotVariant"))
|
||||
else: ident("pointer"))))
|
||||
staticArgsLen = if varargsName.isNil: args.len
|
||||
else: args.len - 1
|
||||
if not varargsName.isNil:
|
||||
argLenNode = newCall("cint",
|
||||
newInfix(newIntLit(staticArgsLen),
|
||||
ident("+"),
|
||||
newDotExpr(ident(varargsName), ident("len"))))
|
||||
argsAlloc.add(newCall("godotAlloc", newCall("cint", newInfix(
|
||||
newCall("sizeof", ident("Variant")), ident("*"),
|
||||
newNode(nkPar).addChain(argLenNode))))
|
||||
)
|
||||
else:
|
||||
argLenNode = newCall("cint", newIntLit(staticArgsLen))
|
||||
vars.add(newIdentDefs(
|
||||
ident("argsStatic"),
|
||||
newNode(nkBracketExpr).addChain(
|
||||
ident("array"), newIntLit(staticArgsLen), ident("pointer")),
|
||||
newNode(nkEmpty)))
|
||||
argsAlloc.add(newCommand(ident("addr"), ident("argsStatic")))
|
||||
vars.add(newIdentDefs(argsName, newNode(nkEmpty), argsAlloc))
|
||||
|
||||
let argConversions = newNode(nkStmtList)
|
||||
for idx, arg in args:
|
||||
var argName = ident(arg.name)
|
||||
if arg.isVarargs:
|
||||
argName = newNode(nkBracketExpr).addChain(
|
||||
ident(varargsName),
|
||||
newInfix(ident("idx"), ident("-"), newIntLit(staticArgsLen)))
|
||||
let argIdx = if arg.isVarargs: ident("idx") else: newIntLit(idx)
|
||||
let isStandardType = arg.typ in standardTypes
|
||||
let isWrappedType = arg.typ in wrapperTypes
|
||||
let convArg = if not varargsName.isNil:
|
||||
getInternalPtr(newCall("toGodot", argName), "Variant")
|
||||
elif isWrappedType: getInternalPtr(argName, arg.typ)
|
||||
elif isStandardType: newCall("unsafeAddr", argName)
|
||||
elif arg.typ == "string": newCall("unsafeAddr",
|
||||
ident("argToPassToGodot" & $idx))
|
||||
else: ident("argToPassToGodot" & $idx) # object
|
||||
if not isStandardType and arg.isVarargs:
|
||||
raise newException(ValueError,
|
||||
"Non-standard type $# for varargs params of method $# of type $#" %
|
||||
[arg.typ, godotMethodName, typ.godotName])
|
||||
if not isStandardType and varargsName.isNil:
|
||||
if arg.typ == "string":
|
||||
argConversions.add(newNode(nkVarSection).addChain(
|
||||
newIdentDefs(ident("argToPassToGodot" & $idx), newEmptyNode(),
|
||||
newCall("toGodotString", argName))))
|
||||
else:
|
||||
argConversions.add(newNode(nkLetSection).addChain(
|
||||
newIdentDefs(ident("argToPassToGodot" & $idx), newEmptyNode(),
|
||||
newDotExpr(argName, ident("godotObject")))))
|
||||
let argAsgn = newNode(nkAsgn).addChain(
|
||||
newNode(nkBracketExpr).addChain(
|
||||
newNode(nkBracketExpr).addChain(argsName), argIdx),
|
||||
convArg
|
||||
)
|
||||
if not arg.isVarargs:
|
||||
argConversions.add(
|
||||
argAsgn
|
||||
)
|
||||
else:
|
||||
argConversions.add(newNode(nkVarSection).addChain(
|
||||
newIdentDefs(ident("idx"), newNode(nkEmpty), newIntLit(staticArgsLen))))
|
||||
let argLoop = newNode(nkWhileStmt)
|
||||
argConversions.add(argLoop)
|
||||
argLoop.add(newInfix(ident("idx"), ident("<"), argLenNode)
|
||||
)
|
||||
argLoop.add(newNode(nkStmtList).addChain(
|
||||
argAsgn,
|
||||
newCommand(ident("inc"), ident("idx"))))
|
||||
|
||||
let retName = if isVarargs: "callRet" else: "ptrCallRet"
|
||||
let theCall = newCall(
|
||||
newDotExpr(ident(methodBindName),
|
||||
ident(if isVarargs: "call" else: "ptrCall")),
|
||||
newDotExpr(ident("self"), ident("godotObject")),
|
||||
if args.len > 0: argsName else: newNode(nkNilLit),
|
||||
argLenNode,
|
||||
if not isVarargs: ident(retName) else: ident("callError")
|
||||
)
|
||||
if not isVarargs:
|
||||
theCall.sons.delete(3)
|
||||
let freeCall = newCall("godotFree", argsName)
|
||||
body = newNode(nkStmtList)
|
||||
if typ.isSingleton:
|
||||
let varName = singletonName(typ.name)
|
||||
body.add(newNilCheck(varName, newCall(
|
||||
newBracketExpr(ident("getSingleton"), ident(typ.name)))))
|
||||
body.add(newNode(nkLetSection).addChain(newIdentDefs(
|
||||
ident("self"), newNode(nkEmpty), varName
|
||||
)))
|
||||
body.add(nilCheck)
|
||||
body.add(vars)
|
||||
body.add(argConversions)
|
||||
if not isVarargs:
|
||||
let retPtrDecl = newNode(nkVarSection).addChain(
|
||||
newIdentDefs(ident(retName), ident("pointer"), newEmptyNode())
|
||||
)
|
||||
body.add(retPtrDecl)
|
||||
var isVariantRet: bool
|
||||
var isConversionRet: bool
|
||||
var isObjRet: bool
|
||||
var isStringRet: bool
|
||||
var isWrapperRet: bool
|
||||
let retValIdent = if not isVarargs: ident("ptrCallVal") else: ident(retName)
|
||||
if not returnType.isNil and not isVarargs:
|
||||
var addrToAssign = newCall("addr", retValIdent)
|
||||
if returnType in smallIntTypes:
|
||||
isConversionRet = true
|
||||
body.add(newNode(nkVarSection).addChain(newIdentDefs(
|
||||
retValIdent, ident("int64"),
|
||||
)))
|
||||
elif returnType in float32Types:
|
||||
isConversionRet = true
|
||||
body.add(newNode(nkVarSection).addChain(newIdentDefs(
|
||||
retValIdent, ident("float64"),
|
||||
)))
|
||||
elif returnType in wrapperTypes:
|
||||
isWrapperRet = true
|
||||
body.add(newNode(nkVarSection).addChain(newIdentDefs(
|
||||
retValIdent, ident("Godot" & returnType),
|
||||
)))
|
||||
elif returnType in standardTypes:
|
||||
addrToAssign = newCall("addr", ident("result"))
|
||||
elif returnType == "string":
|
||||
isStringRet = true
|
||||
body.add(newNode(nkVarSection).addChain(newIdentDefs(
|
||||
retValIdent, ident("GodotString"),
|
||||
)))
|
||||
else:
|
||||
isObjRet = true
|
||||
body.add(newNode(nkVarSection).addChain(newIdentDefs(
|
||||
retValIdent, newNode(nkPtrTy).addChain(ident("GodotObject")),
|
||||
)))
|
||||
body.add(newNode(nkAsgn).addChain(ident(retName), addrToAssign))
|
||||
|
||||
if not isVarargs:
|
||||
body.add(theCall)
|
||||
else:
|
||||
if not returnType.isNil:
|
||||
isVariantRet = true
|
||||
let callStmt = newNode(nkLetSection).addChain(
|
||||
newIdentDefs(
|
||||
newNode(nkPragmaExpr).addChain(
|
||||
ident(retName), newNode(nkPragma).addChain(ident("used"))),
|
||||
newNode(nkEmpty), theCall))
|
||||
body.add(callStmt)
|
||||
|
||||
if varargsName.isNil:
|
||||
for idx, arg in args:
|
||||
if arg.typ == "string":
|
||||
body.add(newCall("deinit", ident("argToPassToGodot" & $idx)))
|
||||
|
||||
if args.len > 0 and not varargsName.isNil:
|
||||
body.add(freeCall)
|
||||
if not varargsName.isNil:
|
||||
let errCheck = newIfStmt(
|
||||
newInfix(newDotExpr(ident("callError"), ident("error")), ident("!="),
|
||||
newDotExpr(ident("VariantCallErrorType"), ident("OK"))),
|
||||
newNode(nkRaiseStmt).addChain(
|
||||
newCall("newCallError", ident("callError")))
|
||||
)
|
||||
body.add(errCheck)
|
||||
if isVariantRet:
|
||||
let convErrDef = newNode(nkLetSection).addChain(
|
||||
newIdentDefs(ident("convErr"), newNode(nkEmpty),
|
||||
newCall("fromGodot", ident("result"), newCall("newVariant", retValIdent))))
|
||||
let convCheck = newIfStmt(
|
||||
newInfix(ident("convErr"), ident("!="),
|
||||
newDotExpr(ident("ConversionResult"), ident("OK"))),
|
||||
newNode(nkRaiseStmt).addChain(
|
||||
newCall("newConversionError", ident("convErr")))
|
||||
)
|
||||
body.add(convErrDef)
|
||||
body.add(convCheck)
|
||||
if isStringRet:
|
||||
body.add(newCall("deinit", retValIdent))
|
||||
elif isConversionRet:
|
||||
body.add(newNode(nkAsgn).addChain(
|
||||
ident("result"),
|
||||
newCall(returnType, retValIdent)))
|
||||
elif isWrapperRet:
|
||||
body.add(newNode(nkAsgn).addChain(
|
||||
ident("result"),
|
||||
newCall("new" & returnType, retValIdent)))
|
||||
elif isObjRet:
|
||||
body.add(newNode(nkAsgn).addChain(ident("result"),
|
||||
newCall(newBracketExpr(ident("asNimGodotObject"),
|
||||
newCall("type", ident("result"))), retValIdent)))
|
||||
elif isStringRet:
|
||||
body.add(newNode(nkAsgn).addChain(ident("result"),
|
||||
newCall("$", retValIdent)))
|
||||
|
||||
let procType = if isVirtual: nkMethodDef
|
||||
else: nkProcDef
|
||||
var params = newSeqOfCap[PNode](args.len + 2)
|
||||
if not returnType.isNil:
|
||||
params.add(ident(returnType))
|
||||
else:
|
||||
params.add(newNode(nkEmpty))
|
||||
if not typ.isSingleton:
|
||||
params.add(newIdentDefs(ident("self"), ident(toNimType(typ.name))))
|
||||
|
||||
for arg in args:
|
||||
if arg.isVarargs:
|
||||
params.add(newIdentDefs(ident(arg.name),
|
||||
newNode(nkBracketExpr).addChain(ident("varargs"), ident(arg.typ))))
|
||||
else:
|
||||
let defaultValue = arg.makeDefaultValue()
|
||||
params.add(newIdentDefs(ident(arg.name), ident(arg.typ), defaultValue))
|
||||
let procDecl = newProc(if not withImplementation: postfix(name, "*")
|
||||
else: name,
|
||||
params, body, procType)
|
||||
if not withImplementation:
|
||||
let pragma = newNode(nkPragma)
|
||||
pragma.add(ident("gcsafe"))
|
||||
pragma.add(newNode(nkExprColonExpr).addChain(ident("locks"),
|
||||
newIntLit(0)))
|
||||
if isVirtual and isBase:
|
||||
pragma.add(ident("base"))
|
||||
procDecl.sons[4] = pragma
|
||||
tree.add(procDecl)
|
||||
|
||||
proc generateMethod(tree: PNode, methodBindRegistry: var HashSet[string],
|
||||
name: PNode, godotMethodName: string,
|
||||
typ: GodotType, args: seq[MethodArg],
|
||||
returnType: string,
|
||||
isVirtual, isBase, withImplementation: bool) =
|
||||
doGenerateMethod(
|
||||
tree, methodBindRegistry, name, godotMethodName, typ,
|
||||
args, returnType, isVirtual, isBase, withImplementation)
|
||||
|
||||
proc makeProperty(types: Table[string, GodotType], tree: PNode,
|
||||
methodBindRegistry: var HashSet[string],
|
||||
typ: GodotType,
|
||||
propertyObj: JsonNode, withImplementation: bool) =
|
||||
let getterNameStr = toNimStyle(propertyObj["name"].str)
|
||||
let getterName = ident(toNimStyle(propertyObj["name"].str))
|
||||
let setterName = newNode(nkAccQuoted).addChain(ident(getterNameStr & "="))
|
||||
|
||||
let nimType = toNimType(types, propertyObj["type"].str)
|
||||
generateMethod(tree, methodBindRegistry,
|
||||
getterName, propertyObj["getter"].str, typ,
|
||||
@[], nimType, isVirtual = false,
|
||||
isBase = false, withImplementation)
|
||||
generateMethod(tree, methodBindRegistry,
|
||||
setterName, propertyObj["setter"].str, typ,
|
||||
@[("val", nimType, JsonNode(nil), false)],
|
||||
nil, isVirtual = false, isBase = false, withImplementation)
|
||||
|
||||
proc makeMethod(types: Table[string, GodotType], tree: PNode,
|
||||
methodBindRegistry: var HashSet[string],
|
||||
typ: GodotType, methodObj: JsonNode,
|
||||
withImplementation: bool) =
|
||||
let returnType = if methodObj["return_type"].str != "void":
|
||||
toNimType(types, methodObj["return_type"].str)
|
||||
else: nil
|
||||
|
||||
for prop in typ.jsonNode["properties"]:
|
||||
if prop["getter"].str == methodObj["name"].str or
|
||||
prop["setter"].str == methodObj["name"].str:
|
||||
return
|
||||
|
||||
var args = newSeqOfCap[MethodArg](methodObj["arguments"].len + 1)
|
||||
var origArgs = methodObj["arguments"]
|
||||
var isBase = methodObj["is_virtual"].bval
|
||||
if methodObj["is_virtual"].bval:
|
||||
proc getMethod(typNode: JsonNode, methName: string): JsonNode =
|
||||
for meth in typNode["methods"]:
|
||||
if meth["is_virtual"].bval and
|
||||
meth["name"].str == methName: return meth
|
||||
var curTyp = types.getOrDefault(typ.baseName)
|
||||
while not curTyp.isNil:
|
||||
let meth = curTyp.jsonNode.getMethod(methodObj["name"].str)
|
||||
if not meth.isNil:
|
||||
origArgs = meth["arguments"]
|
||||
isBase = false
|
||||
curTyp = types.getOrDefault(curTyp.baseName)
|
||||
|
||||
for arg in origArgs:
|
||||
let typ = toNimType(types, arg["type"].str)
|
||||
let defaultVal = if arg["has_default_value"].bval: arg["default_value"]
|
||||
else: nil
|
||||
args.add((toNimStyle(arg["name"].str), typ,
|
||||
defaultVal, false))
|
||||
if methodObj["has_varargs"].bval:
|
||||
args.add(("variantArgs", "Variant", JsonNode(nil), true))
|
||||
|
||||
let godotName = methodObj["name"].str
|
||||
var nimName = toNimStyle(godotName)
|
||||
if not godotName.startsWith('_'):
|
||||
for meth in typ.jsonNode["methods"]:
|
||||
if meth["name"].str == '_' & godotName:
|
||||
nimName = nimName & "Impl"
|
||||
|
||||
generateMethod(tree, methodBindRegistry,
|
||||
ident(nimName), methodObj["name"].str,
|
||||
typ, args,
|
||||
returnType, methodObj["is_virtual"].bval, isBase,
|
||||
withImplementation)
|
||||
|
||||
proc addAttributeToDoc(doc: var string, propName, propValue: string) =
|
||||
if doc.len > 0:
|
||||
doc.add("\n")
|
||||
doc.add(propName & ": " & propValue)
|
||||
|
||||
const classAttributes = ["singleton", "instanciable", "is_reference"]
|
||||
|
||||
proc typeNameToModuleName(name: string): string =
|
||||
var wasUpperOrDigit = true
|
||||
result = newStringOfCap(name.len + 10)
|
||||
for idx, c in name:
|
||||
if c.isUpperAscii() or c.isDigit():
|
||||
if not wasUpperOrDigit or idx > 0 and idx < name.len - 1 and
|
||||
name[idx + 1].isLowerAscii():
|
||||
result.add('_')
|
||||
result.add(c.toLowerAscii())
|
||||
wasUpperOrDigit = true
|
||||
else:
|
||||
result.add(c)
|
||||
wasUpperOrDigit = false
|
||||
if result == "object":
|
||||
# keyword
|
||||
result = "objects"
|
||||
if result == "os":
|
||||
# to avoid clash with stdlib
|
||||
result = "gd_os"
|
||||
|
||||
proc newRegisterClassNode(typ: GodotType): PNode =
|
||||
newCall("registerClass",
|
||||
ident(typ.name),
|
||||
newCStringLit(typ.godotName),
|
||||
ident("true") # isNative
|
||||
)
|
||||
|
||||
proc genSingletonDecl(typ: string): PNode =
|
||||
result = newNode(nkVarSection)
|
||||
let name = singletonName(typ)
|
||||
let threadvar = newNode(nkPragma).addChain(ident("threadvar"))
|
||||
result.add(newIdentDefs(newNode(nkPragmaExpr).addChain(name, threadvar),
|
||||
ident(typ)))
|
||||
|
||||
proc shouldExport(typ: GodotType, types: Table[string, GodotType]): bool =
|
||||
var curTyp = typ
|
||||
while not curTyp.isNil and not curTyp.isSingleton:
|
||||
curTyp = types.getOrDefault(curTyp.baseName)
|
||||
result = curTyp.isNil
|
||||
|
||||
proc genTypeFile*(types: var Table[string, GodotType], targetDir: string) =
|
||||
for typ in types.values():
|
||||
types.incDerivedCount(typ.baseName)
|
||||
|
||||
var sortedTypes = toSeq(types.values())
|
||||
sortedTypes.sort do (x, y: GodotType) -> int:
|
||||
cmp(y.derivedCount, x.derivedCount)
|
||||
|
||||
let godotApiTypesTree = newNode(nkStmtList)
|
||||
godotApiTypesTree.add(newNode(nkImportStmt).addChain(ident("godot")))
|
||||
let typeSection = newNode(nkTypeSection)
|
||||
godotApiTypesTree.add(typeSection)
|
||||
|
||||
for typ in sortedTypes:
|
||||
if not typ.shouldExport(types): continue
|
||||
newRefTypeNode(typeSection, typ.name, typ.baseName, typ.doc)
|
||||
|
||||
for typ in sortedTypes:
|
||||
if not typ.shouldExport(types): continue
|
||||
let regNode = newCall("registerClass",
|
||||
ident(typ.name),
|
||||
newCStringLit(typ.godotName),
|
||||
ident("true") # isNative
|
||||
)
|
||||
godotApiTypesTree.add(regNode)
|
||||
|
||||
renderModule(godotApiTypesTree, targetDir / "godottypes.nim")
|
||||
|
||||
proc genApi*(targetDir: string, apiJsonFile: string) =
|
||||
let apiJson = parseJson(newFileStream(apiJsonFile),
|
||||
apiJsonFile.extractFilename())
|
||||
var megaImport = newStringOfCap(64 * 1024)
|
||||
|
||||
var types = initTable[string, GodotType]()
|
||||
for obj in apiJson:
|
||||
let godotClassName = obj["name"].str
|
||||
let className = godotClassName.replace("_", "")
|
||||
var baseClassName = obj["base_class"].str
|
||||
baseClassName = toNimType(baseClassName.replace("_", ""))
|
||||
if baseClassName.isNil or baseClassName.len == 0:
|
||||
baseClassName = "NimGodotObject"
|
||||
var doc = ""
|
||||
for attr in classAttributes:
|
||||
if attr in obj:
|
||||
addAttributeToDoc(doc, attr, $obj[attr])
|
||||
types[className] = GodotType(
|
||||
name: className,
|
||||
godotName: godotClassName,
|
||||
baseName: baseClassName,
|
||||
doc: doc,
|
||||
jsonNode: obj,
|
||||
isSingleton: obj["singleton"].bval
|
||||
)
|
||||
for typ in types.values():
|
||||
let tree = newNode(nkStmtList)
|
||||
let importStmt = newNode(nkImportStmt)
|
||||
importStmt.add(ident("godot"))
|
||||
importStmt.add(ident("godottypes"))
|
||||
importStmt.add(ident("godotinternal"))
|
||||
tree.add(importStmt)
|
||||
let exportStmt = newNode(nkExportStmt)
|
||||
exportStmt.add(ident("godottypes"))
|
||||
tree.add(exportStmt)
|
||||
|
||||
var methodBindRegsitry = initSet[string]()
|
||||
let obj = typ.jsonNode
|
||||
if typ.baseName != "NimGodotObject":
|
||||
let baseModule = typeNameToModuleName(typ.baseName)
|
||||
let baseModuleNode = if getIdent(baseModule).isKeyword:
|
||||
newStrLit(baseModule & ".nim")
|
||||
else:
|
||||
ident(baseModule)
|
||||
importStmt.add(baseModuleNode)
|
||||
exportStmt.add(baseModuleNode)
|
||||
let constSection = makeConstSection(obj["constants"])
|
||||
if constSection.kind != nkEmpty:
|
||||
# renderer doesn't properly render empty stmt after type section
|
||||
tree.add(makeConstSection(obj["constants"]))
|
||||
|
||||
if typ.isSingleton:
|
||||
let typeDecl = newNode(nkTypeSection)
|
||||
newRefTypeNode(typeDecl, typ.name, typ.baseName, typ.doc,
|
||||
isExported = false)
|
||||
tree.add(typeDecl)
|
||||
tree.add(newRegisterClassNode(typ))
|
||||
tree.add(genSingletonDecl(typ.name))
|
||||
|
||||
# first, generate declarations only for ease of
|
||||
# human readability of the file
|
||||
for property in obj["properties"]:
|
||||
makeProperty(types, tree, methodBindRegsitry, typ, property,
|
||||
withImplementation = false)
|
||||
for meth in obj["methods"]:
|
||||
makeMethod(types, tree, methodBindRegsitry, typ,
|
||||
meth, withImplementation = false)
|
||||
|
||||
for property in obj["properties"]:
|
||||
makeProperty(types, tree, methodBindRegsitry, typ,
|
||||
property, withImplementation = true)
|
||||
for meth in obj["methods"]:
|
||||
makeMethod(types, tree, methodBindRegsitry, typ, meth,
|
||||
withImplementation = true)
|
||||
|
||||
let moduleName = typeNameToModuleName(typ.name)
|
||||
renderModule(tree, targetDir / moduleName & ".nim")
|
||||
|
||||
megaImport.add("import \"" & moduleName & "\"\n")
|
||||
|
||||
genTypeFile(types, targetDir)
|
||||
|
||||
writeFile(targetDir / "godotall.nim", megaImport)
|
||||
writeFile(targetDir / splitPath(targetDir)[1] & ".nimble", "")
|
||||
|
||||
when isMainModule:
|
||||
import os
|
||||
|
||||
const usage = """Tool for wrapping Godot API into Nim
|
||||
|
||||
godotapigen [godot binary path] [target directory]"""
|
||||
|
||||
proc main() =
|
||||
if paramCount() < 2:
|
||||
echo usage
|
||||
quit(-1)
|
||||
let godotBin = paramStr(1)
|
||||
let targetDir = paramStr(2)
|
||||
|
||||
if not dirExists(targetDir):
|
||||
echo "The specified directory does not exist"
|
||||
quit(-1)
|
||||
if not fileExists(godotBin):
|
||||
echo "Invalid godot binary path"
|
||||
quit(-1)
|
||||
|
||||
let jsonFile = targetDir / "api.json"
|
||||
if fileExists(jsonFile) and
|
||||
godotBin.getLastModificationTime() <= jsonFile.getLastModificationTime():
|
||||
echo "api.json is up to date - doing nothing."
|
||||
return
|
||||
|
||||
discard execShellCmd(
|
||||
"\"" & godotBin & "\" --gdnative-generate-json-api \"" & jsonFile & "\"")
|
||||
if not fileExists(jsonFile):
|
||||
echo "Failed to generate api.json"
|
||||
quit(-1)
|
||||
|
||||
genApi(targetDir, jsonFile)
|
||||
|
||||
main()
|
||||
Loading…
Add table
Add a link
Reference in a new issue