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