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