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