Provide type hints for standard types

This commit is contained in:
Ruslan Mustakov 2017-08-05 12:43:43 +07:00
commit c1c1852203
2 changed files with 211 additions and 118 deletions

View file

@ -11,7 +11,7 @@ type
defaultValue: NimNode
isNoGodot: bool
hint: string
hintTip: string
hintStr: string
usage: string
isExported: bool
@ -122,7 +122,7 @@ proc identDefsToVarDecls(identDefs: NimNode): seq[VarDecl] =
for i in 0..<(identDefs.len - 2):
let nameNode = identDefs[i].copyNimTree()
let hint = removeStrPragma(nameNode, "hint")
let hintTip = removeStrPragma(nameNode, "tip")
let hintStr = removeStrPragma(nameNode, "hintStr")
let usage = removeStrPragma(nameNode, "usage")
let isGdExport = removePragma(nameNode, "gdExport")
if not nameNode.isExported() and isGdExport:
@ -136,7 +136,7 @@ proc identDefsToVarDecls(identDefs: NimNode): seq[VarDecl] =
typ: identDefs[identDefs.len - 2],
defaultValue: identDefs[identDefs.len - 1],
hint: hint,
hintTip: hintTip,
hintStr: hintStr,
isNoGodot: isNoGodot,
usage: usage,
isExported: nameNode.isExported()
@ -279,13 +279,13 @@ macro invokeVarArgs(procIdent, objIdent;
result.add(newNimNode(nnkElse).add(getAst(
printInvokeErr($procIdent.ident, minArgs, maxArgs, numArgsIdent))))
proc typeError(nimType: cstring, value: string, godotType: VariantType,
proc typeError(nimType: string, value: string, godotType: VariantType,
className: cstring, propertyName: cstring): string =
result = "Tried to assign incompatible value " & value & " (" & $godotType &
") to field \"" & $propertyName & "\" (" & $nimType & ") of " &
$className
proc rangeError(nimType: cstring, value: string, className: cstring,
proc rangeError(nimType: string, value: string, className: cstring,
propertyName: cstring): string =
result = "Tried to assign the out-of-range value " & value &
" to field \"" & $propertyName & "\" (" & $nimType & ") of " &
@ -345,7 +345,7 @@ template registerGodotClass(classNameIdent, classNameLit, isRef,
template registerGodotField(classNameLit, classNameIdent, propNameLit,
propNameIdent, propTypeLit, propTypeIdent,
setFuncIdent, getFuncIdent, hintTipLit,
setFuncIdent, getFuncIdent, hintStrLit,
hintIdent, usageIdent, hasDefaultValue,
defaultValueNode) =
proc setFuncIdent(obj: ptr GodotObject, methData: pointer,
@ -377,12 +377,27 @@ template registerGodotField(classNameLit, classNameIdent, propNameLit,
let getFunc = GodotPropertyGetFunc(
getFunc: getFuncIdent
)
mixin godotTypeInfo
static:
var typeInfo: GodotTypeInfo
when compiles(godotTypeInfo(propTypeIdent)):
typeInfo = godotTypeInfo(propTypeIdent)
const hintStr = when hintStrLit != "NIM":
hintStrLit
else:
typeInfo.hintStr
const hint = when astToStr(hintIdent) != "NIM":
GodotPropertyHint.hintIdent
else:
typeInfo.hint
const variantType = typeInfo.variantType
var hintStrGodot = hintStr.toGodotString()
{.push warning[ProveInit]: off.} # false warning, Nim bug
var hintStr = hintTipLit.toGodotString()
let attr = GodotPropertyAttributes(
typ: ord(variantType(propTypeIdent)),
hintString: hintStr,
hint: GodotPropertyHint.hintIdent,
typ: ord(variantType),
hint: hint,
hintString: hintStrGodot,
usage: GodotPropertyUsageFlags.usageIdent
)
{.push warning[ProveInit]: on.}
@ -390,7 +405,7 @@ template registerGodotField(classNameLit, classNameIdent, propNameLit,
attr.defaultValue = (defaultValueNode).toVariant().godotVariant[]
godotScriptRegisterProperty(getNativeLibHandle(), classNameLit, propNameLit,
unsafeAddr attr, setFunc, getFunc)
hintStr.deinit()
hintStrGodot.deinit()
static:
import strutils, sets, sequtils
@ -466,9 +481,9 @@ proc genType(obj: ObjectDecl): NimNode {.compileTime.} =
# 5. Register fields (properties)
for field in obj.fields:
if field.isNoGodot: continue
let hintTip = if field.hintTip.isNil: ""
else: field.hintTip
let hint = if field.hint.isNil: "None"
let hintStr = if field.hintStr.isNil: "NIM"
else: field.hintStr
let hint = if field.hint.isNil: "NIM"
else: field.hint
let usage = if field.usage.isNil: "Default"
else: field.usage
@ -476,13 +491,14 @@ proc genType(obj: ObjectDecl): NimNode {.compileTime.} =
field.defaultValue.kind != nnkEmpty
let hintIdent = ident(hint)
let usageIdent = ident(usage)
let nimType = repr field.typ
result.add(getAst(
registerGodotField(classNameLit, classNameIdent,
newCStringLit(toGodotStyle($field.name.basename())),
ident(field.name), newCStringLit($field.typ),
ident(field.name), nimType,
field.typ, genSym(nskProc, "setFunc"),
genSym(nskProc, "getFunc"),
newStrLitNode(hintTip), hintIdent, usageIdent,
newStrLitNode(hintStr), hintIdent, usageIdent,
ident($hasDefaultValue), field.defaultValue)))
# 6. Register methods
@ -574,12 +590,12 @@ macro gdobj*(definition: untyped, body: typed): typed {.immediate.} =
## var myField: int
## ## Not exported to Godot (i.e. editor will not see this field).
##
## var myString* {.gdExport, hint: Length, tip: "20".}: string
## var myString* {.gdExport, hint: Length, hintStr: "20".}: string
## ## Exported to Godot as ``my_string``.
## ## Editor will limit this string to length 20.
## ## ``hint` is a value of ``GodotPropertyHint`` enum.
## ## ``tip`` depends on the value of ``hint``, its format is described
## ## in ``GodotPropertyHint`` documentation.
## ## ``hintStr`` depends on the value of ``hint``, its format is
## ## described in ``GodotPropertyHint`` documentation.
##
## method ready*() =
## ## Exported methods are exported to Godot by default,

View file

@ -18,9 +18,12 @@ import godotinternal
## You can also allow conversion of any custom type ``MyType`` by implementing:
##
## .. code-block:: nim
## proc variantType*(T: typedesc[MyType]): VariantType
## proc godotTypeInfo*(T: typedesc[MyType]): GodotTypeInfo
## proc toVariant*(self: MyType): Variant
## proc fromVariant*(self: var MyType, val: Variant): ConversionResult
##
## Implementing ``godotTypeInfo`` is optional and is necessary only if you want
## the type to be editable from the editor.
type
NimGodotObject* = ref object of RootObj
@ -66,6 +69,16 @@ type
isNative: bool
isRef: bool
GodotTypeInfo* = object
## Type information provided to Godot editor
variantType*: VariantType
hint*: GodotPropertyHint
hintStr*: string
## Format depends on the type and the hint.
## For example, for Enum ``hint`` this is a comma separated list of
## values.
## See documentation of ``GodotPropertyHint`` for description of formats.
template printWarning*(warning: typed) =
## Prints ``warning`` to Godot log, adding filename and line information.
let (filename, line) = instantiationInfo()
@ -89,7 +102,9 @@ proc print*(message: cstring) {.inline.} =
s.deinit()
var classRegistry {.threadvar.}: TableRef[cstring, ObjectInfo]
var classRegistryStatic {.compileTime.}: TableRef[cstring, ObjectInfo]
var classRegistryStatic* {.compileTime.}: TableRef[cstring, ObjectInfo]
## Compile-time variable used for implementation of several procedures
## and macros
static:
classRegistryStatic = newTable[cstring, ObjectInfo]()
@ -261,13 +276,22 @@ proc newRStrLit(s: string): NimNode {.compileTime.} =
static:
import strutils
macro toGodotName(T: typedesc): cstring =
let nameStr: string = (($T.getType()[1][1].symbol).split(':')[0])
let godotName = case nameStr:
of "File", "Directory", "Thread", "Mutex", "Semaphore":
"_" & nameStr
else:
nameStr
var godotName: string
if T is GodotString or T is string:
godotName = "String"
elif T is SomeFloat:
godotName = "float"
elif T is SomeUnsignedInt or T is SomeSignedInt:
godotName = "int"
if godotName.isNil:
let nameStr: string = (($T.getType()[1][1].symbol).split(':')[0])
godotName = case nameStr:
of "File", "Directory", "Thread", "Mutex", "Semaphore":
"_" & nameStr
else:
nameStr
result = newNimNode(nnkCallStrLit).add(
ident("cstring"), newRStrLit(godotName))
@ -375,14 +399,18 @@ proc godotObject*(nimObj: NimGodotObject): ptr GodotObject {.inline.} =
## you are doing.
nimObj.godotObject
proc variantType*(T: typedesc[NimGodotObject]): VariantType {.inline.} =
VariantType.Object
proc godotTypeInfo*(T: typedesc[NimGodotObject]): GodotTypeInfo {.inline.} =
GodotTypeInfo(
variantType: VariantType.Object,
hint: GodotPropertyHint.None,
hintStr: toGodotName(T)
)
proc toVariant*(self: NimGodotObject): Variant {.inline.} =
newVariant(self.godotObject)
proc fromVariant*[T: NimGodotObject](self: var T,
val: Variant): ConversionResult =
val: Variant): ConversionResult =
if val.getType() == VariantType.Object:
let objPtr = val.asGodotObject()
self = asNimGodotObject[T](objPtr)
@ -393,15 +421,25 @@ proc fromVariant*[T: NimGodotObject](self: var T,
else:
result = ConversionResult.TypeError
proc variantType*(T: typedesc[enum]): VariantType {.inline.} =
VariantType.Int
proc godotTypeInfo*(T: typedesc[enum]): GodotTypeInfo =
result = GodotTypeInfo(
variantType: VariantType.Int,
hint: GodotPropertyHint.Enum,
hintStr: ""
)
for val in T:
if result.hintStr.len > 0:
result.hintStr.add(',')
result.hintStr.add($val)
proc toVariant*[T: enum](self: T): Variant {.inline.} =
newVariant(int64(ord(self)))
proc fromVariant*[T: enum](self: var T,
val: Variant): ConversionResult {.inline.} =
if val.getType() == VariantType.Int:
val: Variant): ConversionResult {.inline.} =
if val.getType() == VariantType.Nil:
discard
elif val.getType() == VariantType.Int:
self = T(val.asInt())
else:
result = ConversionResult.TypeError
@ -410,73 +448,81 @@ proc toVariant*(self: Variant): Variant {.inline.} =
self
proc fromVariant*(self: var Variant,
val: Variant): ConversionResult {.inline.} =
val: Variant): ConversionResult {.inline.} =
self = newVariant(val)
proc variantType*(T: typedesc[SomeGodotOrNum]): VariantType {.inline.} =
when T is SomeSignedInt or T is SomeUnsignedInt:
VariantType.Int
elif T is bool:
VariantType.Bool
elif T is SomeFloat:
VariantType.Real
elif T is string or T is GodotString:
VariantType.String
elif T is Vector2:
VariantType.Vector2
elif T is Rect2:
VariantType.Rect2
elif T is Vector3:
VariantType.Vector3
elif T is Transform2D:
VariantType.Transform2D
elif T is Plane:
VariantType.Plane
elif T is Quat:
VariantType.Quat
elif T is Rect3:
VariantType.Rect3
elif T is Basis:
VariantType.Basis
elif T is Transform:
VariantType.Transform
elif T is Color:
VariantType.Color
elif T is RID:
VariantType.RID
elif T is ptr GodotObject:
VariantType.Object
elif T is NodePath:
VariantType.NodePath
elif T is Dictionary:
VariantType.Dictionary
elif T is Array:
VariantType.Array
elif T is PoolByteArray:
VariantType.PoolByteArray
elif T is PoolIntArray:
VariantType.PoolIntArray
elif T is PoolRealArray:
VariantType.PoolRealArray
elif T is PoolStringArray:
VariantType.PoolStringArray
elif T is PoolVector2Array:
VariantType.PoolVector2Array
elif T is PoolVector3Array:
VariantType.PoolVector3Array
elif T is PoolColorArray:
VariantType.PoolColorArray
else:
VariantType.Nil
proc godotTypeInfo*(T: typedesc[SomeGodotOrNum]): GodotTypeInfo {.inline.} =
result.variantType =
when T is SomeSignedInt or T is SomeUnsignedInt:
VariantType.Int
elif T is bool:
VariantType.Bool
elif T is SomeFloat:
VariantType.Real
elif T is string or T is GodotString:
VariantType.String
elif T is Vector2:
VariantType.Vector2
elif T is Rect2:
VariantType.Rect2
elif T is Vector3:
VariantType.Vector3
elif T is Transform2D:
VariantType.Transform2D
elif T is Plane:
VariantType.Plane
elif T is Quat:
VariantType.Quat
elif T is Rect3:
VariantType.Rect3
elif T is Basis:
VariantType.Basis
elif T is Transform:
VariantType.Transform
elif T is Color:
VariantType.Color
elif T is RID:
VariantType.RID
elif T is ptr GodotObject:
VariantType.Object
elif T is NodePath:
VariantType.NodePath
elif T is Dictionary:
VariantType.Dictionary
elif T is Array:
VariantType.Array
elif T is PoolByteArray:
VariantType.PoolByteArray
elif T is PoolIntArray:
VariantType.PoolIntArray
elif T is PoolRealArray:
VariantType.PoolRealArray
elif T is PoolStringArray:
VariantType.PoolStringArray
elif T is PoolVector2Array:
VariantType.PoolVector2Array
elif T is PoolVector3Array:
VariantType.PoolVector3Array
elif T is PoolColorArray:
VariantType.PoolColorArray
else:
VariantType.Nil
proc godotTypeInfo*(T: typedesc[range]): VariantType {.inline.} =
GodotTypeInfo(
variantType: VariantType.Int,
hint: GodotPropertyHint.Range,
hintStr: $low(T) & "," & $high(T) & ",1"
)
proc toVariant*[T: SomeGodotOrNum](val: T): Variant {.inline.} =
newVariant(val)
proc fromVariant*[T: SomeSignedInt or SomeUnsignedInt](
self: var T, val: Variant): ConversionResult =
if val.getType() != VariantType.Int:
result = ConversionResult.TypeError
else:
if val.getType() == VariantType.Nil:
self = 0
elif val.getType() == VariantType.Int:
var intVal: (when T is SomeSignedInt: int64
else: uint64)
intVal = when T is SomeSignedInt: val.asInt()
@ -485,9 +531,13 @@ proc fromVariant*[T: SomeSignedInt or SomeUnsignedInt](
result = ConversionResult.RangeError
else:
self = T(intVal)
else:
result = ConversionResult.TypeError
proc fromVariant*[T: SomeFloat](self: var T, val: Variant): ConversionResult =
if val.getType() == VariantType.Real:
if val.getType() == VariantType.Nil:
self = 0
elif val.getType() == VariantType.Real:
self = T(val.asReal())
elif val.getType() == VariantType.Int:
self = T(val.asInt())
@ -495,7 +545,10 @@ proc fromVariant*[T: SomeFloat](self: var T, val: Variant): ConversionResult =
result = ConversionResult.TypeError
proc fromVariant*[T: SomeGodot](self: var T, val: Variant): ConversionResult =
if variantType(T) != val.getType():
if val.getType() == VariantType.Nil:
return
const typeInfo = godotTypeInfo(T)
if typeInfo.variantType != val.getType():
return ConversionResult.TypeError
when self is bool:
self = val.asBool()
@ -549,8 +602,8 @@ proc fromVariant*[T: SomeGodot](self: var T, val: Variant): ConversionResult =
# mustn't reach this
result = ConversionError.TypeError
proc variantType*(T: typedesc[string]): VariantType {.inline.} =
VariantType.String
proc godotTypeInfo*(T: typedesc[string]): GodotTypeInfo {.inline.} =
result.variantType = VariantType.String
proc toVariant*(s: string): Variant {.inline.} =
newVariant(s)
@ -563,61 +616,83 @@ proc fromVariant*(s: var string, val: Variant): ConversionResult =
else:
result = ConversionResult.TypeError
proc variantType*(T: typedesc[seq]): VariantType =
VariantType.Array
template arrTypeInfo(T) =
result.variantType = VariantType.Array
mixin godotTypeInfo
type ItemT = type((
block:
var s: T;
when T is seq: s[0] else: s[low(s)]))
when compiles(godotTypeInfo(ItemT)):
let itemTypeInfo = godotTypeInfo(ItemT)
result.hintStr = $ord(itemTypeInfo.variantType)
result.hintStr.add('/')
result.hintStr.add($ord(itemTypeInfo.hint))
result.hintStr.add(':')
if not itemTypeInfo.hintStr.isNil:
result.hintStr.add(itemTypeInfo.hintStr)
proc variantType*(T: typedesc[array]): VariantType =
VariantType.Array
proc godotTypeInfo*(T: typedesc[seq]): GodotTypeInfo =
arrTypeInfo(T)
proc godotTypeInfo*(T: typedesc[array]): GodotTypeInfo =
arrTypeInfo(T)
proc toVariant*[T](s: openarray[T]): Variant =
var arr = newArray()
mixin toGodot
mixin toVariant
for item in s:
arr.add(toVariant(item))
result = newVariant(arr)
proc fromVariant*[T](s: var seq[T], val: Variant): ConversionResult =
if val.getType() != VariantType.Array:
result = ConversionResult.TypeError
else:
if val.getType() == VariantType.Nil:
s = nil
elif val.getType() == VariantType.Array:
let arr = val.asArray()
s = newSeq[T](arr.len)
for idx, item in arr:
mixin fromGodot
mixin fromVariant
let convResult = fromVariant(s[idx], item)
if convResult != ConversionResult.OK:
s = nil
return convResult
else:
result = ConversionResult.TypeError
proc fromVariant*[T: array](s: var T, val: Variant): ConversionResult =
if val.getType() != VariantType.Array:
result = ConversionResult.TypeError
else:
if val.getType() == VariantType.Nil:
discard
elif val.getType() == VariantType.Array:
let arr = val.asArray()
if s.len != arr.len:
return ConversionResult.TypeError
for idx, item in arr:
mixin fromGodot
mixin fromVariant
let convResult = fromVariant(s[idx], item)
if convResult != ConversionResult.OK:
return convResult
else:
result = ConversionResult.TypeError
proc variantType*(T: typedesc[Table|TableRef]): VariantType {.inline.} =
VariantType.Dictionary
proc godotTypeInfo*(T: typedesc[Table|TableRef]): GodotTypeInfo {.inline.} =
result.variantType = VariantType.Dictionary
proc toVariant*[T: Table or TableRef](t: T): Variant =
var dict = newDictionary()
mixin toGodot
mixin toVariant
for k, v in t.pairs():
dict[toVariant(k)] = toVariant(v)
result = newVariant(dict)
proc fromVariant*[T: Table or TableRef](t: var T, val: Variant): ConversionResult =
if val.getType() != VariantType.Dictionary:
result = ConversionResult.TypeError
else:
proc fromVariant*[T: Table or TableRef](t: var T,
val: Variant): ConversionResult =
if val.getType() == VariantType.Nil:
when t is ref:
t = nil
elif val.getType() == VariantType.Dictionary:
let dict = val.asDictionary()
mixin fromGodot
mixin fromVariant
when t is Table:
t = initTable[type(t.keys()), type(t.values())]()
else:
@ -636,6 +711,8 @@ proc fromVariant*[T: Table or TableRef](t: var T, val: Variant): ConversionResul
t = nil
return valResult
t[nimKey] = nimVal
else:
result = ConversionResult.TypeError
{.emit: """/*TYPESECTION*/
void NimMain(void);