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