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 		ApiPart coreNext = core.next;
103 		while(coreNext)
104 		{
105 			ret ~= coreNext.source("core");
106 			coreNext = coreNext.next;
107 		}
108 		foreach(part; extensions)
109 		{
110 			ApiPart p = part;
111 			while(p)
112 			{
113 				ret ~= p.source(part.name);
114 				p = p.next;
115 			}
116 		}
117 
118 		ret ~= q{
119 			@nogc nothrow
120 			void godot_gdnative_api_struct_init(in godot_gdnative_core_api_struct* s)
121 			{
122 				import std.traits : EnumMembers;
123 				
124 				@nogc nothrow static void initVersions(ApiType type)(in godot_gdnative_api_struct* main)
125 				{
126 					const(godot_gdnative_api_struct)* part = main;
127 					while(part)
128 					{
129 						foreach(int[2] v; SupportedVersions!type)
130 						{
131 							if(part.ver.major == v[0] && part.ver.minor == v[1]) mixin(format!"has%s_%d_%d"
132 								(type.text.capitalize, v[0], v[1])) = true;
133 						}
134 						part = part.next;
135 					}
136 				}
137 				
138 				if(!s) assert(0, "godot_gdnative_core_api_struct is null");
139 				if(_godot_api) return; // already initialized
140 				_godot_api = s;
141 				initVersions!(ApiType.core)(cast(const(godot_gdnative_api_struct)*)(cast(void*)s));
142 				foreach(ext; s.extensions[0..s.num_extensions])
143 				{
144 					// check the actual extension type at runtime, instead of relying on the order in the JSON
145 					if(ext.type == 0) continue; // core? should never happen
146 					if(ext.type >= EnumMembers!ApiType.length)
147 					{
148 						continue; // unknown extension type
149 					}
150 					ApiTypeSwitch: final switch(cast(ApiType)ext.type)
151 					{
152 						foreach(E; EnumMembers!ApiType)
153 						{
154 							case E:
155 								apiStruct!E = cast(typeof(apiStruct!E))(cast(void*)ext);
156 								initVersions!E(ext);
157 								break ApiTypeSwitch;
158 						}
159 					}
160 				}
161 			}
162 		};
163 		return ret;
164 	}
165 }
166 
167 class ApiPart
168 {
169 	string name;
170 	string type;
171 	@serializationKeys("version") ApiVersion ver;
172 	Function[] api;
173 	
174 	void finalizeDeserialization(Asdf asdf)
175 	{
176 		next = asdf["next"].get(ApiPart.init);
177 		if(next) next.topLevel = false;
178 	}
179 	
180 	@serializationIgnore:
181 	bool topLevel = true; /// is the "main" struct for an extension
182 	ApiPart next;
183 	
184 	string versionID()
185 	{
186 		return format!"%s_%d_%d"(type.capitalize, ver.major, ver.minor);
187 	}
188 	
189 	string versionSource()
190 	{
191 		string ret;
192 		string verList = "\talias SupportedVersions(ApiType type : ApiType." ~ type.toLower ~ ") = AliasSeq!(";
193 		ApiPart p = this;
194 		while(p)
195 		{
196 			ret ~= "\t__gshared bool has"~p.versionID~" = false;\n";
197 			ret ~= "\tversion(GDNativeRequire"~p.versionID~") enum bool requires"~p.versionID~" = true;\n";
198 			if(p.next) ret ~= "\telse enum bool requires"~p.versionID~" = requires"~p.next.versionID~";\n";
199 			else 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 	{
211 		string ret;
212 		
213 		ret ~= "\tenum bool supports"~type.capitalize~"(int major, int minor) = staticIndexOf!(";
214 		ret ~= "[major, minor], SupportedVersions!(ApiType."~type.toLower~")) != -1;\n";
215 		
216 		ApiPart p = this;
217 		while(p)
218 		{
219 			ret ~= "\tstatic if(requires"~p.versionID;
220 			ret ~= format!") enum bool has%s(int major : %d, int minor : %d) = true;\n\telse "
221 				(p.type.capitalize, p.ver.major, p.ver.minor);
222 			ret ~= format!"@property @nogc nothrow pragma(inline, true) static bool has%s(int major : %d, int minor : %d)() {"
223 				(p.type.capitalize, p.ver.major, p.ver.minor);
224 			ret ~= " return has"~p.versionID~"; }\n";
225 			
226 			p = p.next;
227 		}
228 		ret ~= format!"\t@property @nogc nothrow static bool has%s(int major, int minor)()"(type.capitalize);
229 		ret ~= " if(!supports"~type.capitalize~"!(major, minor))\n\t{\n";
230 		ret ~= "\t\tstatic assert(0, versionError(\""~type.capitalize~"\", major, minor));\n\t}\n";
231 		return ret;
232 	}
233 	
234 	string source(string name)
235 	{
236 		bool core = name == "core";
237 		
238 		string ret;
239 		
240 		ret ~= "private extern(C) @nogc nothrow\n{\n";
241 		foreach(const ref f; api)
242 		{
243 			ret ~= "\talias da_" ~ f.name ~ " = " ~ f.return_type.escapeCType ~ " function(";
244 			foreach(ai, const ref a; f.arguments)
245 			{
246 				if(ai != 0) ret ~= ", ";
247 				ret ~= a[0].escapeCType ~ " " ~ a[1].escapeD;
248 			}
249 			ret ~= ");\n";
250 		}
251 		ret ~= "}\n";
252 		
253 		ret ~= "public extern(C) struct "~name.structName(ver)~"\n{\n";
254 		ret ~= "@nogc nothrow:\n";
255 		
256 		if(core && ver == ApiVersion(1,0)) ret ~= q{
257 			mixin ApiStructHeader;
258 			uint num_extensions;
259 			const godot_gdnative_api_struct **extensions;
260 		};
261 		else ret ~= q{
262 			mixin ApiStructHeader;
263 		};
264 		
265 		foreach(const ref f; api)
266 		{
267 			ret ~= "\tda_" ~ f.name ~ " " ~ f.name.escapeD ~ ";\n";
268 		}
269 		
270 		if(next)
271 		{
272 			ret ~= "const("~name.structName(next.ver)~"*) nextVersion() const { return cast(typeof(return))next; }\n";
273 			ret ~= "alias nextVersion this;\n";
274 		}
275 		
276 		ret ~= "}\n";
277 		
278 		if(topLevel)
279 		{
280 			ret ~= "__gshared const("~name.structName(ver)~")* "~name.globalVarName~" = null;\n";
281 
282 			ret ~= "private alias apiStruct(ApiType t : ApiType." ~ type.toLower ~ ") = ";
283 			ret ~= name.globalVarName ~ ";\n";
284 		}
285 		
286 		ret ~= "\n";
287 		
288 		return ret;
289 	}
290 }
291 
292 string structName(string name, ApiVersion ver)
293 {
294 	return (name=="core")?( "godot_gdnative_core_api_struct" ~
295 		(ver==ApiVersion(1,0) ? "" : ver.str) ) :
296 		("godot_gdnative_ext_"~name~"_api_struct"~ver.str);
297 }
298 string globalVarName(string name, ApiVersion ver = ApiVersion(-1,-1))
299 {
300 	string ret;
301 	if(name=="core") ret = "_godot_api";
302 	else ret = "_godot_"~name~"_api";
303 	if(ver.major != -1) ret ~= ver.str;
304 	return ret;
305 }
306 
307 string escapeCType(string cType)
308 {
309 	import std.algorithm, std..string;
310 	
311 	cType = cType.chompPrefix("signed ");
312 	
313 	if(cType.canFind("godot_object") && cType.endsWith("*"))
314 		return cType[0..$-1];
315 	else return cType;
316 }
317