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 }