# Copyright 2018 Xored Software, Inc. import macros, tables, typetraits, strutils, sets, sequtils, options, algorithm import godotinternal, internal/godotvariants import godotnim, core/variants type VarDecl = ref object name: NimNode typ: NimNode defaultValue: NimNode isNoGodot: bool hint: Option[string] hintStr: Option[string] usage: NimNode isExported: bool MethodDecl = ref object name: string args: seq[VarDecl] isVirtual: bool returnType: NimNode nimNode: NimNode isNoGodot: bool ObjectDecl = ref object name: string parentName: string fields: seq[VarDecl] methods: seq[MethodDecl] isTool: bool ParseError = object of Exception include "internal/backwardcompat.inc.nim" proc godotToNim[T](val: Variant): (T, ConversionResult) = mixin fromVariant result[1] = fromVariant(result[0], val) proc nimToGodot[T](val: T): Variant = mixin toVariant when compiles(toVariant(val)): result = toVariant(val) else: const err = "Cannot convert Nim value of type " & T.name & " into Variant" {.error: err.} template parseError(node: NimNode, msg: string) = raise newException(ParseError, lineinfo(node) & ": " & msg) proc extractNames(definition: NimNode): tuple[name, parentName: string] = if definition.kind == nnkIdent: result.name = definition.strVal else: if not (definition.kind == nnkInfix and definition[0].strVal == "of"): parseError(definition, "invalid type definition") result.name = definition[1].strVal case definition[2].kind: of nnkIdent: result.parentName = definition[2].strVal else: parseError(definition[2], "parent type expected") when not declared(newRStrLit): proc newRStrLit(s: string): NimNode {.compileTime.} = result = newNimNode(nnkRStrLit) result.strVal = s proc newCStringLit(s: string): NimNode {.compileTime.} = newNimNode(nnkCallStrLit).add(ident("cstring"), newRStrLit(s)) iterator pragmas(node: NimNode): tuple[key: string, value: NimNode, index: int] = assert node.kind in {nnkPragma, nnkEmpty} for index in countdown(node.len - 1, 0): if node[index].kind == nnkExprColonExpr: yield (node[index][0].strVal, node[index][1], index) elif node[index].kind == nnkIdent: yield (node[index].strVal, nil, index) proc removePragmaNode(statement: NimNode, pname: string): NimNode {.compileTime.} = ## Removes the pragma from the node and returns value of the pragma ## Works for routine nodes or nnkPragmaExpr if not (RoutineNodes.contains(statement.kind) or statement.kind == nnkPragmaExpr): return nil result = nil var pragmas = if RoutineNodes.contains(statement.kind): statement.pragma() else: statement[1] for ident, val, i in pragmas(pragmas): if ident.eqIdent(pname): pragmas.del(i) return val proc removePragma(statement: NimNode, pname: string): bool = ## Removes the pragma from the node and returns whether pragma was removed if not (RoutineNodes.contains(statement.kind) or statement.kind == nnkPragmaExpr): return false var pragmas = if RoutineNodes.contains(statement.kind): statement.pragma() else: statement[1] for ident, val, i in pragmas(pragmas): if ident.eqIdent(pname): pragmas.del(i) return true proc removeStrPragma(statement: NimNode, pname: string): Option[string] {.compileTime.} = ## Removes the pragma from the node and returns value of the pragma ## Works for routine nodes or nnkPragmaExpr let node = removePragmaNode(statement, pname) result = if node.isNil: none(string) else: some($node) proc isExported(node: NimNode): bool {.compileTime.} = if node.kind == nnkPragmaExpr: result = isExported(node[0]) elif node.kind == nnkPostfix: result = ($node[0] == "*") proc identDefsToVarDecls(identDefs: NimNode): seq[VarDecl] = assert(identDefs.kind == nnkIdentDefs) result = newSeqOfCap[VarDecl](identDefs.len - 2) var typ = identDefs[identDefs.len - 2] if typ.kind == nnkEmpty: let defaultValue = identDefs[identDefs.len - 1] if defaultValue.kind != nnkEmpty: typ = newCall("type", defaultValue) for i in 0..<(identDefs.len - 2): let nameNode = identDefs[i].copyNimTree() let hint = removeStrPragma(nameNode, "hint") let hintStr = removeStrPragma(nameNode, "hintStr") let usage = removePragmaNode(nameNode, "usage") let isGdExport = removePragma(nameNode, "gdExport") result.add(VarDecl( name: if nameNode.kind == nnkPragmaExpr: nameNode[0].basename() else: nameNode.basename(), typ: typ, defaultValue: identDefs[identDefs.len - 1], hint: hint, hintStr: hintStr, isNoGodot: not isGdExport, usage: usage, isExported: nameNode.isExported() )) proc parseMethod(meth: NimNode): MethodDecl = assert(meth.kind in {nnkProcDef, nnkMethodDef}) let isGdExport = removePragma(meth, "gdExport") let isNoGodot = (meth.kind != nnkMethodDef and not isGdExport) or removePragma(meth, "noGdExport") result = MethodDecl( name: $meth[0].basename, args: newSeq[VarDecl](), returnType: meth[3][0], isVirtual: meth.kind == nnkMethodDef, isNoGodot: isNoGodot, nimNode: meth ) for i in 1..`_: ## ## .. code-block:: nim ## import godot, editor_plugin ## ## gdobj(MyTool of EditorPlugin, tool): ## method enterTree*() = ## print("MyTool initialized!") ## ## Objects can be instantiated by invoking ## `gdnew `_ or by using ## `load `_ or any other way ## that you can find in `Godot API `_. let typeDef = parseType(ast) result = genType(typeDef)