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