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