1 module godot.util.generator.c;
2 
3 import godot.util.generator.util;
4 
5 import std.string;
6 import std.format;
7 import std.algorithm.iteration, std.algorithm.comparison;
8 
9 import asdf;
10 
11 struct Function {
12     string name;
13     string return_type;
14     string[2][] arguments; // type, name
15 
16 @serdeIgnore:
17 
18 }
19 
20 struct ApiVersion {
21     int major, minor;
22 
23     int opCmp(ApiVersion other) const {
24         return cmp([major, minor], [other.major, other.minor]);
25     }
26 
27 @serdeIgnore:
28 
29     string str() {
30         return format!"_%d_%d"(major, minor);
31     }
32 }
33 
34 struct Api {
35     ApiPart core;
36     ApiPart[] extensions;
37 
38 @serdeIgnore:
39 
40     string source() {
41         string ret = "module godot.abi.api;\n\n";
42         ret ~= "import godot.abi.core;\n\n";
43         foreach (v; extensions)
44             ret ~= "import godot.abi." ~ v.name ~ ";\n";
45 
46         ret ~= "import std.meta : AliasSeq, staticIndexOf;\n";
47         ret ~= "import std.format : format;\nimport std.string : capitalize, toLower;\nimport std.conv : text;\n";
48         ret ~= "import core.stdc.stdint;\nimport core.stdc.stddef : wchar_t;\n\n";
49 
50         ret ~= q{
51 			extern(C) struct godot_gdnative_api_version
52 			{
53 				uint major, minor;
54 			}
55 			mixin template ApiStructHeader()
56 			{
57 				uint type;
58 				godot_gdnative_api_version ver;
59 				const godot_gdnative_api_struct *next;
60 			}
61 			extern(C) struct godot_gdnative_api_struct
62 			{
63 				mixin ApiStructHeader;
64 			}
65 			private string versionError(string name, int major, int minor)
66 			{
67 				string ret = "Bindings for GDNative extension "~name;
68 				if((major == 1 && minor == 0)) ret ~= " do not exist. ";
69 				else ret ~= format!" exist, but not for version %d.%d. "(major, minor);
70 				ret ~= "Please provide a more recent gdnative_api.json to the binding generator to fix this.";
71 				return ret;
72 			}
73 		};
74 
75         ret ~= "enum ApiType : uint {\n";
76         ret ~= "\t" ~ core.type.toLower ~ ",\n";
77         foreach (part; extensions)
78             ret ~= "\t" ~ part.type.toLower ~ ",\n";
79         ret ~= "}\n";
80 
81         ret ~= "private\n{\n";
82         ret ~= core.versionSource;
83         foreach (part; extensions)
84             ret ~= part.versionSource;
85         ret ~= "}\n";
86 
87         ret ~= "struct GDNativeVersion\n{\n";
88         ret ~= core.versionGetterSource;
89         foreach (part; extensions)
90             ret ~= part.versionGetterSource;
91         ret ~= q{
92 			@nogc nothrow
93 			static bool opDispatch(string name)()
94 			{
95 				static assert(name.length > 3 && name[0..3] == "has",
96 					"Usage: `GDNativeVersion.has<Extension>!(<major>, <minor>)`");
97 				static assert(0, versionError(name[3..$], 1, 0));
98 			}
99 		};
100         ret ~= "}\n";
101 
102         ret ~= core.source("core");
103         ApiPart coreNext = core.next;
104         while (coreNext) {
105             ret ~= coreNext.source("core");
106             coreNext = coreNext.next;
107         }
108         foreach (part; extensions) {
109             ApiPart p = part;
110             while (p) {
111                 ret ~= p.source(part.name);
112                 p = p.next;
113             }
114         }
115 
116         ret ~= q{
117 			@nogc nothrow
118 			void godot_gdnative_api_struct_init(in godot_gdnative_core_api_struct* s)
119 			{
120 				import std.traits : EnumMembers;
121 				
122 				@nogc nothrow static void initVersions(ApiType type)(in godot_gdnative_api_struct* main)
123 				{
124 					const(godot_gdnative_api_struct)* part = main;
125 					while(part)
126 					{
127 						foreach(int[2] v; SupportedVersions!type)
128 						{
129 							if(part.ver.major == v[0] && part.ver.minor == v[1]) mixin(format!"has%s_%d_%d"
130 								(type.text.capitalize, v[0], v[1])) = true;
131 						}
132 						part = part.next;
133 					}
134 				}
135 				
136 				if(!s) assert(0, "godot_gdnative_core_api_struct is null");
137 				if(_godot_api) return; // already initialized
138 				_godot_api = s;
139 				initVersions!(ApiType.core)(cast(const(godot_gdnative_api_struct)*)(cast(void*)s));
140 				foreach(ext; s.extensions[0..s.num_extensions])
141 				{
142 					// check the actual extension type at runtime, instead of relying on the order in the JSON
143 					if(ext.type == 0) continue; // core? should never happen
144 					if(ext.type >= EnumMembers!ApiType.length)
145 					{
146 						continue; // unknown extension type
147 					}
148 					ApiTypeSwitch: final switch(cast(ApiType)ext.type)
149 					{
150 						foreach(E; EnumMembers!ApiType)
151 						{
152 							case E:
153 								apiStruct!E = cast(typeof(apiStruct!E))(cast(void*)ext);
154 								initVersions!E(ext);
155 								break ApiTypeSwitch;
156 						}
157 					}
158 				}
159 			}
160 		};
161         return ret;
162     }
163 }
164 
165 class ApiPart {
166     @serdeOptional
167     string name;
168     string type;
169     @serdeKeys("version") ApiVersion ver;
170     Function[] api;
171 
172     void finalizeDeserialization(Asdf asdf) {
173         next = asdf["next"].get(ApiPart.init);
174         if (next)
175             next.topLevel = false;
176     }
177 
178 @serdeIgnore:
179     bool topLevel = true; /// is the "main" struct for an extension
180     ApiPart next;
181 
182     string versionID() {
183         return format!"%s_%d_%d"(type.capitalize, ver.major, ver.minor);
184     }
185 
186     string versionSource() {
187         string ret;
188         string verList = "\talias SupportedVersions(ApiType type : ApiType." ~ type.toLower ~ ") = AliasSeq!(";
189         ApiPart p = this;
190         while (p) {
191             ret ~= "\t__gshared bool has" ~ p.versionID ~ " = false;\n";
192             ret ~= "\tversion(GDNativeRequire" ~ p.versionID ~ ") enum bool requires" ~ p.versionID ~ " = true;\n";
193             if (p.next)
194                 ret ~= "\telse enum bool requires" ~ p.versionID ~ " = requires" ~ p.next.versionID ~ ";\n";
195             else
196                 ret ~= "\telse enum bool requires" ~ p.versionID ~ " = false;\n";
197 
198             verList ~= format!"[%d, %d], "(p.ver.major, p.ver.minor);
199 
200             p = p.next;
201         }
202         verList ~= ");\n";
203         return verList ~ ret;
204     }
205 
206     string versionGetterSource() {
207         string ret;
208 
209         ret ~= "\tenum bool supports" ~ type.capitalize ~ "(int major, int minor) = staticIndexOf!(";
210         ret ~= "[major, minor], SupportedVersions!(ApiType." ~ type.toLower ~ ")) != -1;\n";
211 
212         ApiPart p = this;
213         while (p) {
214             ret ~= "\tstatic if(requires" ~ p.versionID;
215             ret ~= format!") enum bool has%s(int major : %d, int minor : %d) = true;\n\telse "(p.type.capitalize, p
216                     .ver.major, p.ver.minor);
217             ret ~= format!"@property @nogc nothrow pragma(inline, true) static bool has%s(int major : %d, int minor : %d)() {"(
218                 p.type.capitalize, p.ver.major, p.ver.minor);
219             ret ~= " return has" ~ p.versionID ~ "; }\n";
220 
221             p = p.next;
222         }
223         ret ~= format!"\t@property @nogc nothrow static bool has%s(int major, int minor)()"(
224             type.capitalize);
225         ret ~= " if(!supports" ~ type.capitalize ~ "!(major, minor))\n\t{\n";
226         ret ~= "\t\tstatic assert(0, versionError(\"" ~ type.capitalize ~ "\", major, minor));\n\t}\n";
227         return ret;
228     }
229 
230     string source(string name) {
231         bool core = name == "core";
232 
233         string ret;
234 
235         ret ~= "private extern(C) @nogc nothrow\n{\n";
236         foreach (const ref f; api) {
237             ret ~= "\talias da_" ~ f.name ~ " = " ~ f.return_type.escapeCType ~ " function(";
238             foreach (ai, const ref a; f.arguments) {
239                 if (ai != 0)
240                     ret ~= ", ";
241                 ret ~= a[0].escapeCType ~ " " ~ a[1].escapeD;
242             }
243             ret ~= ");\n";
244         }
245         ret ~= "}\n";
246 
247         ret ~= "public extern(C) struct " ~ name.structName(ver) ~ "\n{\n";
248         ret ~= "@nogc nothrow:\n";
249 
250         if (core && ver == ApiVersion(4, 0))
251             ret ~= q{
252 			mixin ApiStructHeader;
253 			uint num_extensions;
254 			const godot_gdnative_api_struct **extensions;
255 		};
256         else
257             ret ~= q{
258 			mixin ApiStructHeader;
259 		};
260 
261         foreach (const ref f; api) {
262             ret ~= "\tda_" ~ f.name ~ " " ~ f.name.escapeD ~ ";\n";
263         }
264 
265         if (next) {
266             ret ~= "const(" ~ name.structName(
267                 next.ver) ~ "*) nextVersion() const { return cast(typeof(return))next; }\n";
268             ret ~= "alias nextVersion this;\n";
269         }
270 
271         ret ~= "}\n";
272 
273         if (topLevel) {
274             ret ~= "__gshared const(" ~ name.structName(
275                 ver) ~ ")* " ~ name.globalVarName ~ " = null;\n";
276 
277             ret ~= "private alias apiStruct(ApiType t : ApiType." ~ type.toLower ~ ") = ";
278             ret ~= name.globalVarName ~ ";\n";
279         }
280 
281         ret ~= "\n";
282 
283         return ret;
284     }
285 }
286 
287 string structName(string name, ApiVersion ver) {
288     return (name == "core") ? ("godot_gdnative_core_api_struct" ~
289             (ver == ApiVersion(4, 0) ? "" : ver.str)) : (
290         "godot_gdnative_ext_" ~ name ~ "_api_struct" ~ ver.str);
291 }
292 
293 string globalVarName(string name, ApiVersion ver = ApiVersion(-1, -1)) {
294     string ret;
295     if (name == "core")
296         ret = "_godot_api";
297     else
298         ret = "_godot_" ~ name ~ "_api";
299     if (ver.major != -1)
300         ret ~= ver.str;
301     return ret;
302 }
303 
304 string escapeCType(string cType) {
305     import std.algorithm, std.string;
306 
307     cType = cType.chompPrefix("signed ");
308 
309     if (cType.canFind("godot_object") && cType.endsWith("*"))
310         return cType[0 .. $ - 1];
311     else
312         return cType;
313 }