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