1 module godot.util.generator.api; 2 3 import std.range; 4 import std.algorithm; 5 import std.path; 6 import std.conv; 7 import std.string; 8 import std.stdio; 9 10 import godot.util.generator.util; 11 import godot.util.generator.classes, godot.util.generator.methods, godot.util.generator.enums; 12 13 import godot.util.tools.string; 14 15 import asdf; 16 17 struct Header { 18 int version_major, version_minor, version_patch; 19 string version_status, version_build, version_full_name; 20 } 21 22 struct TypeSize { 23 string name; 24 int size; 25 } 26 27 struct ConfigurationTypeSizes { 28 string build_configuration; 29 TypeSize[] sizes; 30 } 31 32 struct MemberOffset { 33 string member; 34 int offset; 35 } 36 37 struct TypeMemberOffsets { 38 string name; 39 MemberOffset[] members; 40 } 41 42 struct ConfigurationTypeMemberOffsets { 43 string build_configuration; 44 TypeMemberOffsets[] classes; 45 } 46 47 struct NativeStructure { 48 Type name; // definition? maybe? 49 string format; // expression(ish) 50 51 @serdeIgnore: 52 53 // additional types that requires import 54 Type[] used_classes; 55 56 void addUsedClass(in Type c) { 57 auto u = c.unqual(); 58 if (u.isNativeStruct || u.isPrimitive || u.isCoreType || u.godotType == "Object") 59 return; 60 if (u.isEnum) 61 u = u.enumParent; 62 // aww this sucks 63 else if (u.godotType.canFind('.')) 64 u = Type.get(u.godotType[0 .. u.godotType.indexOf('.')]); 65 if (!used_classes.canFind(u)) 66 used_classes ~= u; 67 } 68 69 // parse format string and convert directly to D source, 70 // would be better to create GodotClass from it or even dedicated type 71 string parseMembers() { 72 string buf; 73 auto s = appender(buf); 74 foreach (field; format.split(';')) { 75 // replace scope access symbol 76 auto f = field.replace("::", "."); 77 auto space = f.indexOf(' '); 78 auto type = f[0 .. space]; 79 auto expr = f[space + 1 .. $]; 80 81 // now there is 3 possible variants: normal variable, pointer variable, function pointer 82 // 1) pointer - starts with '*' in member name (second part) 83 // 2) function pointer - starts with '(*' in member name part and additionally have second pair braces for arguments 84 // 3) variables can also have default values 'int start = -1', floats is usually written in form '0.f' which is invalid in D 85 86 // case 2 - function pointer 87 if (expr.startsWith("(*")) { 88 auto rb = expr.indexOf(')'); 89 auto name = expr[2 .. rb]; // strip '(*' 90 addUsedClass(Type.get(type.adjustTypeNameOnField.getArrayType)); // ewww, side effects... 91 s ~= gdnToDType(type) ~ " function("; 92 93 auto last = expr.indexOf(')', rb + 1); 94 foreach (i, arg; expr[rb + 2 .. last].split(',')) { 95 if (i != 0) 96 s ~= ", "; 97 auto adjustedField = adjustTypeNameOnField(arg.strip); 98 auto pair = adjustedField.split(' '); 99 s ~= gdnToDType(pair[0]) ~ " " ~ pair[1]; 100 addUsedClass(Type.get(pair[0].adjustTypeNameOnField.getArrayType)); // ewww, side effects... 101 } 102 103 s ~= ") " ~ name ~ ";\n"; 104 } else // plain variable 105 { 106 auto adjustedField = adjustTypeNameOnField(f); 107 auto pair = adjustedField.split(' '); 108 s ~= gdnToDType(pair[0]) ~ " " ~ pair[1]; 109 addUsedClass(Type.get(pair[0].adjustTypeNameOnField.getArrayType)); // ewww, side effects... 110 if (auto idx = pair.countUntil(["="])) { 111 // TODO: convert default value 112 } 113 s ~= ";\n"; 114 } 115 } 116 return s[]; 117 } 118 } 119 120 struct Singleton { 121 Type name; 122 Type type; 123 } 124 125 alias Constant = int[string]; 126 127 struct ExtensionsApi { 128 Header header; 129 ConfigurationTypeSizes[] builtin_class_sizes; 130 ConfigurationTypeMemberOffsets[] builtin_class_member_offsets; 131 Constant[] global_constants; 132 GodotEnum[] global_enums; 133 GodotMethod[] utility_functions; 134 GodotClass[] builtin_classes; // only basic types such as Vector3, Color, Dictionary, etc... 135 GodotClass[] classes; 136 Singleton[] singletons; 137 NativeStructure[] native_structures; 138 139 void finalizeDeserialization(Asdf data) { 140 foreach (cls; builtin_classes) { 141 cls.isBuiltinClass = true; 142 } 143 144 // update native structs before binding classes 145 foreach (s; native_structures) { 146 Type.get(s.name.godotType).isNativeStruct = true; 147 } 148 149 // mark singletons before writing class bindings 150 foreach (s; singletons) { 151 auto cls = classes.find!(c => c.name.godotType == s.name.godotType); 152 if (!cls.empty) 153 cls.front.singleton = true; 154 } 155 156 // 157 foreach (c; classes) { 158 if (c.name.godotType != "Object") { 159 c.base_class = Type.get(c.base_class.godotType); 160 //c.base_class.original = Type.get(c.base_class.godot).original; 161 //c.base_class.objectClass = Type.get(c.base_class.godot).objectClass; 162 } 163 } 164 } 165 } 166 167 string generateHeader(ref ExtensionsApi api) { 168 Appender!string s; 169 170 s ~= "enum VERSION_MAJOR = " ~ api.header.version_major.text ~ ";\n"; 171 s ~= "enum VERSION_MINOR = " ~ api.header.version_minor.text ~ ";\n"; 172 s ~= "enum VERSION_PATCH = " ~ api.header.version_patch.text ~ ";\n"; 173 s ~= `enum VERSION_STATUS = "%s";`.format(api.header.version_status) ~ '\n'; 174 s ~= `enum VERSION_BUILD = "%s";`.format(api.header.version_build) ~ '\n'; 175 s ~= `enum VERSION_FULLNAME = "%s";`.format(api.header.version_full_name) ~ '\n'; 176 177 return s[]; 178 } 179 180 string generateBuiltins(ref ExtensionsApi ap) { 181 string s; 182 183 s ~= "module godot.builtins;\n\n"; 184 185 s ~= `import std.meta : AliasSeq, staticIndexOf; 186 import std.traits : Unqual; 187 import godot.api.traits; 188 import godot; 189 import godot.abi; 190 import godot.api.bind; 191 import godot.api.reference; 192 import godot.globalenums; 193 import godot.object; 194 import godot.classdb;`; 195 s ~= "\n"; 196 s ~= "// This module contains low level type bindings and only provides raw pointers.\n\n"; 197 198 foreach (cls; ap.builtin_classes) { 199 // skip unneeded types like Nil 200 if (!cls.name.isCoreType()) 201 continue; 202 203 s ~= cls.source(); 204 } 205 return s; 206 } 207 208 string generateGlobals(ref ExtensionsApi api) { 209 return null; 210 } 211 212 string generateSingletons(ref ExtensionsApi api) { 213 return null; 214 } 215 216 void writeBindings(ref ExtensionsApi ap, string dirPath) { 217 import std.file, std.path; 218 219 // write global enums 220 writeGlobalEnums(ap, dirPath); 221 222 foreach (cls; ap.classes) { 223 auto path = dirPath.buildPath(cls.name.moduleName ~ ".d"); 224 225 std.file.write(path, cls.source()); 226 } 227 228 writeStructs(ap, dirPath); 229 } 230 231 void writeGlobalEnums(ref ExtensionsApi ap, string dirPath) { 232 string buf; 233 auto s = appender(buf); 234 s ~= "module godot.globalenums;\n\n"; 235 236 foreach (en; ap.global_enums) { 237 if (en.name.startsWith("Variant.")) 238 continue; 239 if (en.name == "Error") // ignore this, already in core defs 240 continue; 241 242 s ~= en.source(); 243 s ~= "\n"; 244 } 245 246 import std.file, std.path; 247 248 std.file.write(dirPath.buildPath("globalenums.d"), s[]); 249 } 250 251 void writeStructs(ref ExtensionsApi api, string dirPath) { 252 string buf; 253 auto s = appender(buf); 254 s ~= "module godot.structs;\n\n"; 255 s ~= "import godot;\n"; 256 s ~= "import godot.abi;\n\n"; 257 258 foreach (st; api.native_structures) { 259 auto source = st.parseMembers(); 260 261 foreach (imp; st.used_classes) 262 s ~= "import godot." ~ imp.moduleName ~ ";\n"; 263 264 s ~= "struct " ~ st.name.dType ~ "\n{\n"; 265 266 // quick hack to indent fields 267 string[dchar] transTable = ['\n': "\n "]; 268 // add leading indent, but skip last one 269 s ~= " " ~ source[0 .. $ - 1].translate(transTable) ~ '\n'; 270 271 s ~= "}\n\n\n"; 272 } 273 274 import std.file, std.path; 275 276 std.file.write(dirPath.buildPath("structs.d"), s[]); 277 } 278 279 // converts GDExtension native types such as ones in Built-In Structures to D form 280 string gdnToDType(string type) { 281 return type; 282 } 283 284 // strips pointers from name part and place it next to type 285 string adjustTypeNameOnField(string expr) { 286 auto pos = expr.indexOf(' '); 287 if (pos == -1) 288 return expr; 289 290 // simply bubble all * to left on a duplicate 291 char[] str = expr.dup; 292 while (pos + 1 < str.length) { 293 // stop when reach name delimiters 294 if (str[pos + 1].among(';', ':', '(', ')', '=')) 295 break; 296 297 if (str[pos + 1] == '*') { 298 auto temp = str[pos]; 299 str[pos] = '*'; 300 str[pos + 1] = temp; 301 } 302 303 pos++; 304 } 305 306 // rewrite C style array with D style array 307 if (str.endsWith("]")) { 308 pos = str.indexOf(' '); 309 if (pos != -1) { 310 auto leftBr = str.lastIndexOf('['); 311 auto dim = str[leftBr .. $]; 312 // reassemble string moving array dimensions next to type 313 auto temp = str[0 .. pos] ~ dim ~ str[pos .. leftBr]; 314 str = temp; 315 } 316 } 317 318 return cast(string) str; 319 } 320 321 /// strips array size from type 322 string getArrayType(string type) { 323 auto pos = type.indexOf('['); 324 if (pos == -1) 325 return type; 326 return type[0 .. pos]; 327 } 328 329 // make operator name from operator symbol, 330 // e.g. "!=" -> "notEqual", "<" -> "less" 331 string opName(string op) { 332 __gshared static string[string] opNames; 333 334 if (!opNames) 335 opNames = [ 336 "==": "equal", 337 "!=": "not_equal", 338 "<": "less", 339 "<=": "less_equal", 340 ">": "greater", 341 ">=": "greater_equal", 342 "+": "add", 343 "-": "subtract", 344 "*": "multiply", 345 "/": "divide", 346 "unary-": "negate", 347 "unary+": "positive", 348 "%": "module", 349 "<<": "shift_left", 350 ">>": "shift_right", 351 "&": "bit_and", 352 "|": "bit_or", 353 "^": "bit_xor", 354 "~": "bit_negate", 355 "and": "and", 356 "or": "or", 357 "xor": "xor", 358 "not": "not", 359 "and": "and", 360 "in": "in", 361 ]; 362 363 return opNames.get(op, "wtf"); 364 }