1 module godot.util.generator.util; 2 3 import godot.util.generator.classes; 4 5 import std.range; 6 import std.algorithm.searching, std.algorithm.iteration; 7 import std.path; 8 import std.conv : text; 9 import std.string; 10 11 import asdf; 12 13 //import asdf.source.asdf.asdf; 14 15 struct TypeStruct { 16 @serdeKeys("name", "type") string name; 17 @serdeOptional string meta; 18 19 SerdeException deserializeFromAsdf(Asdf data) { 20 // here we try read 3 options, 'name' variant is for native_structs 21 name = data["type"].get!string(null); 22 meta = data["meta"].get!string(null); 23 if (name is null) { 24 string val; 25 if (auto exc = deserializeScopedString(data, val)) 26 return exc; 27 name = val; 28 } 29 30 return null; 31 } 32 } 33 34 //@serdeProxy!string 35 @serdeProxy!TypeStruct 36 class Type { 37 static Type[string] typesByGodotName; 38 static Type[string] typesByDName; 39 40 static Type[] enums; 41 42 GodotClass objectClass; 43 GodotClass original; // original GodotClass associated with this Type 44 string dType; 45 string godotType; 46 bool isNativeStruct; 47 48 @property string dRef() const { 49 return isRef ? ("Ref!" ~ dType) : dType; 50 } 51 52 Type enumParent; 53 54 //alias dType this; 55 56 string moduleName() const { 57 if (isPrimitive || isCoreType) 58 return null; 59 if (isNativeStruct) 60 return "structs"; // module godot.structs 61 return godotType.chompPrefix("_").toLower; 62 } 63 64 /// Backing opaque type to use instead of raw GDNativeTypePtr 65 string opaqueType() const { 66 switch (godotType) { 67 case "TypedArray": 68 case "Array": 69 return "godot_array"; 70 case "Variant": 71 return "godot_variant"; 72 case "String": 73 case "StringName": 74 return "godot_string"; 75 case "NodePath": 76 return "godot_node_path"; 77 case "Dictionary": 78 return "godot_dictionary"; 79 case "PackedByteArray": 80 case "PackedInt32Array": 81 case "PackedInt64Array": 82 case "PackedFloat32Array": 83 case "PackedFloat64Array": 84 case "PackedStringArray": 85 case "PackedVector2Array": 86 case "PackedVector3Array": 87 case "PackedColorArray": 88 //case "Nil": 89 return "GDNativeTypePtr"; 90 default: 91 break; 92 } 93 94 if (!isRef || canBeCopied) 95 return this.dType; 96 97 return "godot_object"; 98 } 99 100 bool isEnum() const { 101 return godotType.startsWith("enum::"); 102 } 103 104 bool isBitfield() const { 105 return godotType.startsWith("bitfield::"); 106 } 107 108 bool isTypedArray() const { 109 return godotType.startsWith("typedarray::"); 110 } 111 112 bool isPointerType() const { 113 return dType.indexOf("*") != -1; 114 } 115 116 bool isPrimitive() const { 117 if (isEnum || isBitfield) 118 return true; 119 return only("int", "bool", "real", "float", "void", "double", "real_t", 120 "uint8_t", "int8_t", "uint16_t", "int16_t", "uint32_t", "int32_t", "uint64_t", "int64_t", // well... 121 "uint8", "int8", "uint16", "int16", "uint32", "int32", "uint64", "int64" // hope they will merge it or smth 122 123 124 125 ).canFind(unqual.godotType); 126 } 127 128 // types that have simple value semantics and doesn't require special wrappers 129 bool canBeCopied() const { 130 return only("Vector2", "Vector2i", "Vector3", "Vector3i", "Vector4", "Vector4i", 131 "Transform2D", "Transform3D", "Projection", "Rect2", "Rect2i", 132 "Color", "Plane", "AABB", "Quaternion", "Basis", "RID" 133 ).canFind(unqual.godotType); 134 } 135 136 bool isCoreType() const { 137 if (auto arraytype = arrayType()) 138 return arraytype.isCoreType(); 139 // basically all types from extension_api.json from builtin_classes 140 auto coreTypes = only("AABB", 141 "Array", 142 "Basis", 143 "Callable", 144 "Color", 145 "Dictionary", 146 "GodotError", 147 "NodePath", 148 "StringName", 149 "Plane", 150 "PackedByteArray", 151 "PackedInt32Array", 152 "PackedInt64Array", 153 "PackedFloat32Array", 154 "PackedFloat64Array", 155 "PackedStringArray", 156 "PackedVector2Array", 157 "PackedVector3Array", 158 "PackedColorArray", 159 "Quaternion", 160 "Rect2", 161 "Rect2i", 162 "RID", 163 "String", 164 "Transform3D", 165 "Transform2D", 166 "TypedArray", 167 "Projection", 168 "Variant", 169 "Vector2", 170 "Vector2i", 171 "Vector3", 172 "Vector3i", 173 "Vector4", 174 "Vector4i", 175 "Nil", // why godot, why? 176 "ObjectID"); 177 return coreTypes.canFind(godotType); 178 } 179 180 /// Get variant type name for method calls 181 string toNativeVariantType() const { 182 import godot.util.tools.string; 183 184 // useless but ok 185 if (!(isCoreType || isPrimitive)) 186 return "GDNATIVE_VARIANT_TYPE_OBJECT"; 187 188 return "GDNATIVE_VARIANT_TYPE_" ~ camelToSnake(godotType).toUpper; 189 } 190 191 /// returns TypedArray type 192 Type arrayType() const { 193 if (!isTypedArray) 194 return null; 195 return Type.get(godotType["typedarray::".length .. $]); 196 } 197 198 bool isRef() const { 199 return objectClass && objectClass.is_reference; 200 } 201 202 /// type should be taken as template arg by methods to allow implicit conversion in ptrcall 203 bool acceptImplicit() const { 204 auto accept = only("Variant"); 205 return accept.canFind(godotType); 206 } 207 208 /// prefix for function parameter of this type 209 string dCallParamPrefix() const { 210 if (isRef) 211 return ""; 212 else if (objectClass) 213 return ""; 214 else if (godotType.indexOf("const") != -1) 215 return ""; 216 else 217 return "in "; 218 } 219 /// how to pass parameters of this type into ptrcall void** arg 220 string ptrCallArgPrefix() const { 221 if (isPrimitive || isCoreType) 222 return "&"; 223 return ""; 224 //return "cast(godot_object)"; // for both base classes and D classes (through alias this) 225 } 226 227 /// returns value name from string literal, default values in api.json uses integers instead of qualified enum values 228 string getEnumValueStr(string value) const { 229 import std.conv : to; 230 import godot.util.tools.string; 231 232 if (!isEnum()) 233 return null; 234 235 import godot.util.generator.enums; 236 237 // global enums doesn't have parent 238 auto parent = Type.get(godot.util.generator.enums.enumParent(godotType)); 239 if (!parent) 240 parent = Type.get("CoreConstants"); 241 242 // just the enum name, without parent class name 243 string innerName = splitEnumName(godotType)[1]; 244 245 // FIXME why it's always parent.original? 246 auto searchInParent = parent.godotType == "CoreConstants" ? parent.original 247 : parent.original; 248 249 // HACK: core types not available here 250 // if (searchInParent is null && parent.isCoreType) { 251 // FIXME higher if throws access violation when searching later 252 if (searchInParent is null) { 253 return "cast(" ~ dType ~ ")" ~ value; 254 } 255 auto found = searchInParent.enums.find!(s => s.name == innerName); 256 if (!found.empty) { 257 foreach (pair; found.front.values) 258 if (pair.value == to!int(value)) 259 return dType ~ "." ~ snakeToCamel(pair.name); 260 } 261 262 return "cast(" ~ dType ~ ")" ~ value; 263 } 264 265 /// strip constness, also strips indirections (despite the name) 266 Type unqual() const { 267 char[] unqualified = cast(char[]) godotType.replace("const ", "").dup; 268 while (unqualified[$ - 1] == '*') { 269 unqualified[$ - 1] = '\0'; 270 unqualified = unqualified[0 .. $ - 1]; 271 } 272 unqualified = unqualified.stripRight; // strip whitespace leftovers 273 return Type.get(cast(string) unqualified); 274 } 275 276 // same as unqual() but only strips constness, useful for return types and template params 277 Type stripConst() const { 278 char[] unqualified = cast(char[]) godotType.replace("const ", "").dup; 279 unqualified = unqualified.stripRight; // strip whitespace leftovers 280 return Type.get(cast(string) unqualified); 281 } 282 283 this(string godotName) { 284 godotType = godotName; 285 dType = godotName.escapeType; 286 } 287 288 this(TypeStruct t) { 289 // here t.name usually specifies old type like int, with meta describing actual length like int64 290 if (t.meta) 291 this(t.meta); 292 else 293 this(t.name); 294 } 295 296 static Type get(string godotName) { 297 if (!godotName.length) 298 return null; // no type (used in base_class) 299 if (Type* ptr = godotName in typesByGodotName) 300 return *ptr; 301 Type ret = new Type(godotName); 302 303 static import godot.util.generator.enums; 304 305 if (ret.isEnum) { 306 ret.enumParent = get(godot.util.generator.enums.enumParent(godotName)); 307 enums ~= ret; 308 } 309 310 typesByGodotName[godotName] = ret; 311 typesByDName[ret.dType] = ret; 312 313 return ret; 314 } 315 316 /* 317 SerdeException deserializeFromAsdf(Asdf data) 318 { 319 string val; 320 if (auto exc = deserializeScopedString(data, val)) 321 return exc; 322 323 godotType = val; 324 dType = godotType.escapeType; 325 326 return null; 327 } 328 */ 329 /* 330 static Type deserialize(ref Asdf asdf) 331 { 332 string gn = asdf.get!string(null); 333 Type ret = get(gn); 334 return ret; 335 } 336 */ 337 } 338 339 /// the default value to use for an argument if none is provided 340 string emptyDefault(in Type type) { 341 import std.string; 342 import std.conv : text; 343 344 bool isPointer = type.dType.indexOf("*") != -1; 345 346 switch (type.dType) { 347 case "String": 348 return `gs!""`; 349 case "Dictionary": 350 return type.dType ~ ".make()"; 351 case "Array": 352 return type.dType ~ ".make()"; 353 default: // all default-blittable types 354 { 355 if (isPointer) 356 return "(" ~ type.dType ~ ").init"; 357 else 358 return type.dType ~ ".init"; // D's default initializer 359 ///return "null"; 360 } 361 } 362 } 363 364 /++ 365 PoolVector2Array 366 PoolColorArray 367 Array 368 Vector2 369 float 370 Color 371 bool 372 Object 373 PoolVector3Array 374 Vector3 375 Transform2D 376 RID 377 int 378 Transform 379 Rect2 380 String 381 Variant 382 PoolStringArray 383 +/ 384 string escapeDefault(in Type type, string arg) { 385 import std.string; 386 import std.conv : text; 387 388 if (!arg || arg.length == 0) 389 return emptyDefault(type); 390 391 // parse the defaults in api.json 392 switch (type.dType) { 393 case "Color": // "1,1,1,1" 394 if (arg.startsWith("Color(")) 395 return arg; 396 return "Color(" ~ arg ~ ")"; 397 case "bool": // True, False 398 return arg.toLower; 399 case "Array": // "[]", "Null" - just use the empty one 400 case "Dictionary": 401 case "PackedByteArray": 402 case "PackedInt32Array": 403 case "PackedInt64Array": 404 case "PackedFloat32Array": 405 case "PackedFloat64Array": 406 case "PackedVector2Array": 407 case "PackedVector3Array": 408 case "PackedStringArray": 409 case "PackedColorArray": 410 return emptyDefault(type); 411 case "Transform3D": // "1, 0, 0, 0, 1, 0, 0, 0, 1 - 0, 0, 0" TODO: parse this 412 if (arg.startsWith("Transform3D(")) 413 return arg; 414 return "Transform3D(" ~ arg ~ ")"; 415 case "Transform2D": 416 if (arg.startsWith("Transform2D(")) 417 return arg; 418 return "Transform2D(" ~ arg ~ ")"; 419 case "Projection": 420 if (arg.startsWith("Projection(")) 421 return arg; 422 return "Projection(" ~ arg ~ ")"; 423 case "RID": // always empty? 424 return emptyDefault(type); // D's default initializer 425 case "Vector2": // "(0, 0)" 426 case "Vector2i": // "(0, 0)" 427 case "Vector3": 428 case "Vector3i": 429 case "Vector4": 430 case "Vector4i": 431 case "Rect2": // "(0, 0, 0, 0)" 432 case "AABB": 433 if (arg.startsWith(type.godotType)) // prevent junk like 'Vector2Vector2(0, 0)' 434 arg = arg[type.godotType.length .. $]; 435 return type.dType ~ arg; 436 case "Variant": 437 if (arg == "Null") 438 return "Variant.nil"; 439 else 440 return arg; 441 case "String": 442 if (arg.canFind('"')) 443 return "gs!" ~ arg; 444 return "gs!\"" ~ arg ~ "\""; 445 case "StringName": 446 if (arg[0] == '&') 447 arg = arg[1 .. $]; 448 if (arg.canFind('"')) 449 return "gn!" ~ arg; 450 return "gn!\"" ~ arg ~ "\""; 451 default: // all Object types 452 { 453 if (arg == "Null" || arg == "null") 454 return emptyDefault(type); 455 if (arg == "[Object:null]") 456 return emptyDefault(type); 457 if (type.isEnum) 458 return type.getEnumValueStr(arg); 459 return arg; 460 } 461 } 462 } 463 464 string escapeType(string t) { 465 import godot.util.generator.enums : qualifyEnumName; 466 467 t = t.chompPrefix("_"); 468 469 if (t == "Object") 470 return "GodotObject"; 471 if (t == "Error") 472 return "GodotError"; 473 if (t == "Callable") 474 return "GodotCallable"; 475 if (t == "Signal") 476 return "Signal"; 477 if (t == "float") 478 return "double"; 479 if (t == "int") 480 return "long"; 481 if (t == "Nil") 482 return "GDNativeTypePtr"; 483 if (t.startsWith("enum::")) 484 return t.qualifyEnumName; 485 if (t.startsWith("bitfield::")) 486 return t["bitfield::".length .. $]; 487 if (t.startsWith("typedarray::")) 488 return t.qualifyTypedArray; 489 return t; 490 } 491 492 string escapeD(string s) { 493 import std.meta; 494 import std.uni, std.utf; 495 496 /// TODO: there must be a better way of doing this... 497 /// maybe one of the D parser libraries has a list of keywords and basic types 498 499 if (s.toUTF32[0].isNumber) 500 s = "_" ~ s; // can't start with a number 501 502 alias keywords = AliasSeq!( 503 "class", 504 "interface", 505 "struct", 506 "enum", 507 "bool", 508 "ubyte", 509 "byte", 510 "ushort", 511 "short", 512 "uint", 513 "int", 514 "ulong", 515 "long", 516 "cent", // really? 517 "ucent", 518 "float", 519 "double", 520 "real", 521 "char", 522 "wchar", 523 "dchar", 524 "function", 525 "delegate", 526 "override", 527 "default", 528 "case", 529 "switch", 530 "export", 531 "import", 532 "template", 533 "new", 534 "delete", 535 "return", 536 "with", 537 "align", 538 "in", 539 "out", 540 "ref", 541 "scope", 542 "auto", 543 "init", 544 "version", 545 "body", // for now at least... 546 547 548 549 ); 550 switch (s) { 551 case "Object": 552 return "GodotObject"; 553 case "Error": 554 return "GodotError"; 555 case "Signal": 556 return "GodotSignal"; 557 case "Callable": 558 return "GodotCallable"; 559 foreach (kw; keywords) { 560 case kw: 561 } 562 return "_" ~ s; 563 default: 564 return s; 565 } 566 } 567 568 string qualifyTypedArray(string type) { 569 return "TypedArray!(" ~ type["typedarray::".length .. $] ~ ")"; 570 }