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