1 module godot.util.generator.api;
2 
3 import std.range;
4 import std.algorithm;
5 import std.path;
6 import std.conv;
7 import std.string;
8 import std.stdio;
9 
10 import godot.util.generator.util;
11 import godot.util.generator.classes, godot.util.generator.methods, godot.util.generator.enums;
12 
13 import godot.util.tools.string;
14 
15 import asdf;
16 
17 struct Header {
18     int version_major, version_minor, version_patch;
19     string version_status, version_build, version_full_name;
20 }
21 
22 struct TypeSize {
23     string name;
24     int size;
25 }
26 
27 struct ConfigurationTypeSizes {
28     string build_configuration;
29     TypeSize[] sizes;
30 }
31 
32 struct MemberOffset {
33     string member;
34     int offset;
35 }
36 
37 struct TypeMemberOffsets {
38     string name;
39     MemberOffset[] members;
40 }
41 
42 struct ConfigurationTypeMemberOffsets {
43     string build_configuration;
44     TypeMemberOffsets[] classes;
45 }
46 
47 struct NativeStructure {
48     Type name; // definition? maybe?
49     string format; // expression(ish)
50 
51 @serdeIgnore:
52 
53     // additional types that requires import
54     Type[] used_classes;
55 
56     void addUsedClass(in Type c) {
57         auto u = c.unqual();
58         if (u.isNativeStruct || u.isPrimitive || u.isCoreType || u.godotType == "Object")
59             return;
60         if (u.isEnum)
61             u = u.enumParent;
62         // aww this sucks
63         else if (u.godotType.canFind('.'))
64             u = Type.get(u.godotType[0 .. u.godotType.indexOf('.')]);
65         if (!used_classes.canFind(u))
66             used_classes ~= u;
67     }
68 
69     // parse format string and convert directly to D source, 
70     // would be better to create GodotClass from it or even dedicated type
71     string parseMembers() {
72         string buf;
73         auto s = appender(buf);
74         foreach (field; format.split(';')) {
75             // replace scope access symbol
76             auto f = field.replace("::", ".");
77             auto space = f.indexOf(' ');
78             auto type = f[0 .. space];
79             auto expr = f[space + 1 .. $];
80 
81             // now there is 3 possible variants: normal variable, pointer variable, function pointer
82             // 1) pointer - starts with '*' in member name (second part)
83             // 2) function pointer - starts with '(*' in member  name part and additionally have second pair braces for arguments
84             // 3) variables can also have default values 'int start = -1', floats is usually written in form '0.f' which is invalid in D
85 
86             // case 2 - function pointer
87             if (expr.startsWith("(*")) {
88                 auto rb = expr.indexOf(')');
89                 auto name = expr[2 .. rb]; // strip '(*'
90                 addUsedClass(Type.get(type.adjustTypeNameOnField.getArrayType)); // ewww, side effects...
91                 s ~= gdnToDType(type) ~ " function(";
92 
93                 auto last = expr.indexOf(')', rb + 1);
94                 foreach (i, arg; expr[rb + 2 .. last].split(',')) {
95                     if (i != 0)
96                         s ~= ", ";
97                     auto adjustedField = adjustTypeNameOnField(arg.strip);
98                     auto pair = adjustedField.split(' ');
99                     s ~= gdnToDType(pair[0]) ~ " " ~ pair[1];
100                     addUsedClass(Type.get(pair[0].adjustTypeNameOnField.getArrayType)); // ewww, side effects...
101                 }
102 
103                 s ~= ") " ~ name ~ ";\n";
104             } else // plain variable
105             {
106                 auto adjustedField = adjustTypeNameOnField(f);
107                 auto pair = adjustedField.split(' ');
108                 s ~= gdnToDType(pair[0]) ~ " " ~ pair[1];
109                 addUsedClass(Type.get(pair[0].adjustTypeNameOnField.getArrayType)); // ewww, side effects...
110                 if (auto idx = pair.countUntil(["="])) {
111                     // TODO: convert default value
112                 }
113                 s ~= ";\n";
114             }
115         }
116         return s[];
117     }
118 }
119 
120 struct Singleton {
121     Type name;
122     Type type;
123 }
124 
125 alias Constant = int[string];
126 
127 struct ExtensionsApi {
128     Header header;
129     ConfigurationTypeSizes[] builtin_class_sizes;
130     ConfigurationTypeMemberOffsets[] builtin_class_member_offsets;
131     Constant[] global_constants;
132     GodotEnum[] global_enums;
133     GodotMethod[] utility_functions;
134     GodotClass[] builtin_classes; // only basic types such as Vector3, Color, Dictionary, etc...
135     GodotClass[] classes;
136     Singleton[] singletons;
137     NativeStructure[] native_structures;
138 
139     void finalizeDeserialization(Asdf data) {
140         foreach (cls; builtin_classes) {
141             cls.isBuiltinClass = true;
142         }
143 
144         // update native structs before binding classes
145         foreach (s; native_structures) {
146             Type.get(s.name.godotType).isNativeStruct = true;
147         }
148 
149         // mark singletons before writing class bindings
150         foreach (s; singletons) {
151             auto cls = classes.find!(c => c.name.godotType == s.name.godotType);
152             if (!cls.empty)
153                 cls.front.singleton = true;
154         }
155 
156         // 
157         foreach (c; classes) {
158             if (c.name.godotType != "Object") {
159                 c.base_class = Type.get(c.base_class.godotType);
160                 //c.base_class.original = Type.get(c.base_class.godot).original;
161                 //c.base_class.objectClass = Type.get(c.base_class.godot).objectClass;
162             }
163         }
164     }
165 }
166 
167 string generateHeader(ref ExtensionsApi api) {
168     Appender!string s;
169 
170     s ~= "enum VERSION_MAJOR = " ~ api.header.version_major.text ~ ";\n";
171     s ~= "enum VERSION_MINOR = " ~ api.header.version_minor.text ~ ";\n";
172     s ~= "enum VERSION_PATCH = " ~ api.header.version_patch.text ~ ";\n";
173     s ~= `enum VERSION_STATUS = "%s";`.format(api.header.version_status) ~ '\n';
174     s ~= `enum VERSION_BUILD = "%s";`.format(api.header.version_build) ~ '\n';
175     s ~= `enum VERSION_FULLNAME = "%s";`.format(api.header.version_full_name) ~ '\n';
176 
177     return s[];
178 }
179 
180 string generateBuiltins(ref ExtensionsApi ap) {
181     string s;
182 
183     s ~= "module godot.builtins;\n\n";
184 
185     s ~= `import std.meta : AliasSeq, staticIndexOf;
186 import std.traits : Unqual;
187 import godot.api.traits;
188 import godot;
189 import godot.abi;
190 import godot.api.bind;
191 import godot.api.reference;
192 import godot.globalenums;
193 import godot.object;
194 import godot.classdb;`;
195     s ~= "\n";
196     s ~= "// This module contains low level type bindings and only provides raw pointers.\n\n";
197 
198     foreach (cls; ap.builtin_classes) {
199         // skip unneeded types like Nil
200         if (!cls.name.isCoreType())
201             continue;
202 
203         s ~= cls.source();
204     }
205     return s;
206 }
207 
208 string generateGlobals(ref ExtensionsApi api) {
209     return null;
210 }
211 
212 string generateSingletons(ref ExtensionsApi api) {
213     return null;
214 }
215 
216 void writeBindings(ref ExtensionsApi ap, string dirPath) {
217     import std.file, std.path;
218 
219     // write global enums
220     writeGlobalEnums(ap, dirPath);
221 
222     foreach (cls; ap.classes) {
223         auto path = dirPath.buildPath(cls.name.moduleName ~ ".d");
224 
225         std.file.write(path, cls.source());
226     }
227 
228     writeStructs(ap, dirPath);
229 }
230 
231 void writeGlobalEnums(ref ExtensionsApi ap, string dirPath) {
232     string buf;
233     auto s = appender(buf);
234     s ~= "module godot.globalenums;\n\n";
235 
236     foreach (en; ap.global_enums) {
237         if (en.name.startsWith("Variant."))
238             continue;
239         if (en.name == "Error") // ignore this, already in core defs
240             continue;
241 
242         s ~= en.source();
243         s ~= "\n";
244     }
245 
246     import std.file, std.path;
247 
248     std.file.write(dirPath.buildPath("globalenums.d"), s[]);
249 }
250 
251 void writeStructs(ref ExtensionsApi api, string dirPath) {
252     string buf;
253     auto s = appender(buf);
254     s ~= "module godot.structs;\n\n";
255     s ~= "import godot;\n";
256     s ~= "import godot.abi;\n\n";
257 
258     foreach (st; api.native_structures) {
259         auto source = st.parseMembers();
260 
261         foreach (imp; st.used_classes)
262             s ~= "import godot." ~ imp.moduleName ~ ";\n";
263 
264         s ~= "struct " ~ st.name.dType ~ "\n{\n";
265 
266         // quick hack to indent fields
267         string[dchar] transTable = ['\n': "\n    "];
268         // add leading indent, but skip last one
269         s ~= "    " ~ source[0 .. $ - 1].translate(transTable) ~ '\n';
270 
271         s ~= "}\n\n\n";
272     }
273 
274     import std.file, std.path;
275 
276     std.file.write(dirPath.buildPath("structs.d"), s[]);
277 }
278 
279 // converts GDExtension native types such as ones in Built-In Structures to D form
280 string gdnToDType(string type) {
281     return type;
282 }
283 
284 // strips pointers from name part and place it next to type
285 string adjustTypeNameOnField(string expr) {
286     auto pos = expr.indexOf(' ');
287     if (pos == -1)
288         return expr;
289 
290     // simply bubble all * to left on a duplicate
291     char[] str = expr.dup;
292     while (pos + 1 < str.length) {
293         // stop when reach name delimiters
294         if (str[pos + 1].among(';', ':', '(', ')', '='))
295             break;
296 
297         if (str[pos + 1] == '*') {
298             auto temp = str[pos];
299             str[pos] = '*';
300             str[pos + 1] = temp;
301         }
302 
303         pos++;
304     }
305 
306     // rewrite C style array with D style array
307     if (str.endsWith("]")) {
308         pos = str.indexOf(' ');
309         if (pos != -1) {
310             auto leftBr = str.lastIndexOf('[');
311             auto dim = str[leftBr .. $];
312             // reassemble string moving array dimensions next to type
313             auto temp = str[0 .. pos] ~ dim ~ str[pos .. leftBr];
314             str = temp;
315         }
316     }
317 
318     return cast(string) str;
319 }
320 
321 /// strips array size from type
322 string getArrayType(string type) {
323     auto pos = type.indexOf('[');
324     if (pos == -1)
325         return type;
326     return type[0 .. pos];
327 }
328 
329 // make operator name from operator symbol,
330 // e.g. "!=" -> "notEqual", "<" -> "less"
331 string opName(string op) {
332     __gshared static string[string] opNames;
333 
334     if (!opNames)
335         opNames = [
336             "==": "equal",
337             "!=": "not_equal",
338             "<": "less",
339             "<=": "less_equal",
340             ">": "greater",
341             ">=": "greater_equal",
342             "+": "add",
343             "-": "subtract",
344             "*": "multiply",
345             "/": "divide",
346             "unary-": "negate",
347             "unary+": "positive",
348             "%": "module",
349             "<<": "shift_left",
350             ">>": "shift_right",
351             "&": "bit_and",
352             "|": "bit_or",
353             "^": "bit_xor",
354             "~": "bit_negate",
355             "and": "and",
356             "or": "or",
357             "xor": "xor",
358             "not": "not",
359             "and": "and",
360             "in": "in",
361         ];
362 
363     return opNames.get(op, "wtf");
364 }