1 module jaster.serialise.generator.csharp; 2 3 private 4 { 5 import std.file, std.path, std.format, std.meta, std.traits, std.algorithm; 6 import jaster.serialise; 7 8 struct FileInfo 9 { 10 string fileName; 11 string contents; 12 } 13 14 FileInfo[] PREMADE_FILES; 15 const FRAMEWORK_PATH = "languages/c#/framework/Framework/"; 16 static this() 17 { 18 import std.path : baseName; 19 20 static foreach(file; 21 [ 22 "Archive.cs", 23 "ArchiveBinary.cs", 24 "ArchiveObject.cs", 25 "ArchiveValue.cs", 26 "BinaryStream.cs", 27 "CRC32.cs", 28 "Serialiser.cs" 29 ] 30 ) 31 {{ 32 const Data = import(FRAMEWORK_PATH ~ file); 33 PREMADE_FILES ~= FileInfo(file.baseName, Data); 34 }} 35 } 36 } 37 38 class CSharpGenerator 39 { 40 private static 41 { 42 enum LockType 43 { 44 Basic, 45 Struct, 46 Array, 47 Enum 48 } 49 50 struct LockInfo 51 { 52 LockType type; 53 Object info; 54 } 55 56 bool[LockInfo] _genLocks; 57 } 58 59 public static 60 { 61 void genFilesForTypes(T...)(string outputDir, string namespace) 62 if(allSatisfy!(isType, T)) 63 { 64 CSharpGenerator._genLocks.clear(); 65 66 TypeField[] info; 67 foreach(type; T) 68 info ~= TypeIntrospect.getInfoFor!type; 69 70 outputDir = buildNormalizedPath(outputDir); 71 if(!exists(outputDir)) 72 mkdirRecurse(outputDir); 73 74 TypeField[] allInfo; // Some TypeFields are only created *during* the generation process. 75 CSharpGenerator.copyPremadeFiles(outputDir, namespace); 76 foreach(inf; info) 77 CSharpGenerator.genFileFromInfo(outputDir, namespace, inf, allInfo); 78 79 CSharpGenerator.genRegisterFile(outputDir, namespace, allInfo); 80 } 81 } 82 83 private static 84 { 85 string getBasicTypeName(BasicSerialiseInfo info) 86 { 87 return getBasicTypeName(info.type); 88 } 89 90 string getBasicTypeName(TypeInfo info) 91 { 92 assert(info != typeid(null) 93 && info != typeid(ubyte[]) 94 && info != typeid(ArchiveValue[]), 95 "Not supported (yet, if ever)" 96 ); 97 98 return (info == typeid(string)) 99 ? "string" 100 : (info == typeid(byte)) 101 ? "sbyte" 102 : (info == typeid(ubyte)) 103 ? "byte" 104 : info.toString(); 105 } 106 107 string getStructClassName(StructSerialiseInfo info) 108 { 109 auto split = info.structInfo.toString().splitter("."); 110 111 string last; 112 foreach(thing; split) 113 last = thing; 114 115 return last; 116 } 117 118 string getArrayTypeName(ArraySerialiseInfo info) 119 { 120 return info.type.castSwitch!( 121 (BasicSerialiseInfo i) => "List<"~CSharpGenerator.getBasicTypeName(i)~">", 122 (StructSerialiseInfo i) => "List<"~CSharpGenerator.getStructClassName(i)~">", 123 (ArraySerialiseInfo i) => "List<"~CSharpGenerator.getArrayTypeName(i)~">", 124 (EnumSerialiseInfo i) => "List<"~CSharpGenerator.getEnumClassName(i)~">", 125 ); 126 } 127 128 string getArrayClassName(ArraySerialiseInfo info) 129 { 130 import std.array : replace; 131 132 return CSharpGenerator.getArrayTypeName(info).replace("<", "_").replace(">", "_"); 133 } 134 135 string getEnumClassName(EnumSerialiseInfo info) 136 { 137 auto split = info.enumType.toString().splitter("."); 138 139 string last; 140 foreach(thing; split) 141 last = thing; 142 143 return last; 144 } 145 146 string getClassName(TypeSerialiseInfo info) 147 { 148 return info.castSwitch!( 149 (BasicSerialiseInfo i) => CSharpGenerator.getBasicTypeName(i), 150 (StructSerialiseInfo i) => CSharpGenerator.getStructClassName(i), 151 (ArraySerialiseInfo i) => CSharpGenerator.getArrayTypeName(i), 152 (EnumSerialiseInfo i) => CSharpGenerator.getEnumClassName(i), 153 (Object _) {assert(false, info.toPrettyString());} 154 ); 155 } 156 157 void ensureGen(string outputDir, string namespace, TypeField info, ref TypeField[] allInfo) 158 { 159 if(!CSharpGenerator.isLocked(info.info)) 160 CSharpGenerator.genFileFromInfo(outputDir, namespace, info, allInfo); 161 } 162 163 LockInfo getLockInfo(TypeSerialiseInfo info) 164 { 165 return info.castSwitch!( 166 (BasicSerialiseInfo i) => LockInfo(LockType.Basic, i.type), 167 (StructSerialiseInfo i) => LockInfo(LockType.Struct, i.structInfo), 168 (ArraySerialiseInfo i) => LockInfo(LockType.Array, i.type), 169 (EnumSerialiseInfo i) => LockInfo(LockType.Enum, i.type), 170 (Object _) {assert(false, info.toPrettyString());} 171 ); 172 } 173 174 void lock(TypeSerialiseInfo info) 175 { 176 CSharpGenerator._genLocks[CSharpGenerator.getLockInfo(info)] = true; 177 } 178 179 bool isLocked(TypeSerialiseInfo info) 180 { 181 return (CSharpGenerator.getLockInfo(info) in CSharpGenerator._genLocks) !is null; 182 } 183 184 void copyPremadeFiles(string outputDir, string namespace) 185 { 186 import std.array : replace; 187 188 foreach(file; PREMADE_FILES) 189 { 190 auto path = buildPath(outputDir, file.fileName); 191 write(path, file.contents.replace("namespace Framework", "namespace "~namespace)); 192 } 193 } 194 195 string genChildInfoString(TypeField type) 196 { 197 import std.algorithm : joiner; 198 import std.array : array; 199 import std.format : format; 200 201 string[] flags; 202 if(type.info.flags & TypeFlags.IsAttribute) 203 flags ~= "TypeFlags.IsAttribute"; 204 if(type.info.flags & TypeFlags.IsMainValue) 205 flags ~= "TypeFlags.IsMainValue"; 206 if(type.info.allSettings & Serialiser.Settings.ArrayAsObject) 207 flags ~= "TypeFlags.ArrayAsObject"; 208 if(type.info.allSettings & Serialiser.Settings.EnumAsValue) 209 flags ~= "TypeFlags.EnumAsValue"; 210 211 return format("new TypeChildInfo(){ Name = \"%s\", Flags = %s }", type.name, (flags is null) ? "TypeFlags.None" : flags.joiner(" | ").array); 212 } 213 214 void genRegisterFile(string outputDir, string namespace, TypeField[] info) 215 { 216 auto code = new CodeBuilder(); 217 218 code.put("using System;"); 219 code.put("using System.Collections.Generic;"); 220 code.putf("namespace %s", namespace); 221 code.putScope((_) 222 { 223 code.put("public static class Jasterialise"); 224 code.putScope((_) 225 { 226 code.put("public static void RegisterSerialisers()"); 227 code.putScope((_) 228 { 229 foreach(inf; info) 230 { 231 auto className = CSharpGenerator.getClassName(inf.info); 232 auto serialName = (inf.as!ArraySerialiseInfo) ? CSharpGenerator.getArrayClassName(inf.as!ArraySerialiseInfo) 233 : CSharpGenerator.getClassName(inf.info); 234 code.putf("Serialiser.Register<%s>(new %sSerialiser());", 235 className, 236 serialName 237 ); 238 } 239 }); 240 }); 241 }); 242 243 import std.conv : to; 244 write(buildPath(outputDir, "Jasterialise.cs"), code.data.to!string); 245 } 246 247 void genFileFromInfo(string outputDir, string namespace, TypeField info, ref TypeField[] allInfo) 248 { 249 auto code = new CodeBuilder(); 250 string name = (info.as!ArraySerialiseInfo) ? CSharpGenerator.getArrayClassName(info.as!ArraySerialiseInfo) 251 : CSharpGenerator.getClassName(info.info); 252 253 allInfo ~= info; 254 CSharpGenerator.lock(info.info); 255 256 code.put("using System;"); 257 code.put("using System.Collections.Generic;"); 258 code.put("using System.Linq;"); 259 code.putf("namespace %s", namespace); 260 code.putScope((_) 261 { 262 // "info.info" is a "TypeSerialiseInfo", which is a base class for all the switches below. 263 info.info.castSwitch!( 264 (BasicSerialiseInfo _) => CSharpGenerator.genBasicCode(code, info), 265 (ArraySerialiseInfo _) => CSharpGenerator.genArrayCode(outputDir, namespace, code, info, allInfo), 266 (StructSerialiseInfo _) => CSharpGenerator.genStructCode(outputDir, namespace, code, info, allInfo), 267 (EnumSerialiseInfo _) => CSharpGenerator.genEnumCode(outputDir, namespace, code, info, allInfo), 268 (Object _) {assert(false, info.info.toPrettyString());} 269 ); 270 }); 271 272 import std.conv; 273 write(buildPath(outputDir, name~".cs"), code.data.to!string); 274 } 275 276 void genEnumCode(string outputDir, string namespace, CodeBuilder code, TypeField typeField, ref TypeField[] allInfo) 277 { 278 auto info = typeField.as!EnumSerialiseInfo; 279 assert(info !is null); 280 281 auto className = CSharpGenerator.getEnumClassName(info); 282 auto typeName = CSharpGenerator.getBasicTypeName(info.type); 283 284 auto basicInfo = new BasicSerialiseInfo(); 285 basicInfo.type = info.type; 286 CSharpGenerator.ensureGen(outputDir, namespace, TypeField(typeName, basicInfo), allInfo); 287 288 basicInfo.type = typeid(string); 289 CSharpGenerator.ensureGen(outputDir, namespace, TypeField("string", basicInfo), allInfo); 290 291 code.putf("public enum %s : %s", className, typeName); 292 code.putScope((_) 293 { 294 import std.format; 295 code.putf("%s", info.values.map!(v => format("%s = %s", v.name, v.value)).joiner(",\n\t\t")); 296 }); 297 298 code.putf("public class %sSerialiser : ITypeSerialiser", className); 299 code.putScope((_) 300 { 301 code.put("public void Serialise(ArchiveObject parent, Object obj, TypeChildInfo info)"); 302 code.putScope((_) 303 { 304 code.putf("var value = (%s)obj;", className); 305 code.putf("var typeInfo = %s;", CSharpGenerator.genChildInfoString(typeField)); 306 code.put("if(info.Flags.HasFlag(TypeFlags.EnumAsValue))"); 307 code.putf("\tSerialiser.Serialise(parent, (%s)value, typeInfo);", typeName); 308 code.put("else"); 309 code.put("\tSerialiser.Serialise(parent, value.ToString(), typeInfo);"); 310 }); 311 312 code.put("public Object Deserialise(ArchiveObject obj, TypeChildInfo info)"); 313 code.putScope((_) 314 { 315 code.putf("var typeInfo = %s;", CSharpGenerator.genChildInfoString(typeField)); 316 code.put("if(info.Flags.HasFlag(TypeFlags.EnumAsValue))"); 317 code.putf("\treturn (%s)Serialiser.Deserialise<%s>(obj, typeInfo);", className, typeName); 318 code.put("else"); 319 code.putf("\treturn (%s)Enum.Parse(typeof(%s), Serialiser.Deserialise<string>(obj, typeInfo));", className, className); 320 }); 321 }); 322 } 323 324 void genBasicCode(CodeBuilder code, TypeField typeField) 325 { 326 auto info = typeField.as!BasicSerialiseInfo; 327 assert(info !is null); 328 329 auto typeName = CSharpGenerator.getBasicTypeName(info); 330 code.putf("public class %sSerialiser : ITypeSerialiser", typeName); 331 code.putScope((_) 332 { 333 code.put("public void Serialise(ArchiveObject parent, Object obj, TypeChildInfo info)"); 334 code.putScope((_) 335 { 336 code.put("if(info.Flags.HasFlag(TypeFlags.IsMainValue))"); 337 code.putf("\tparent.AddValueAs<%s>((%s)obj);", typeName, typeName); 338 code.put("else if(info.Flags.HasFlag(TypeFlags.IsAttribute))"); 339 code.putf("\tparent.SetAttributeAs<%s>(info.Name ?? \"NAME ME\", (%s)obj);", typeName, typeName); 340 code.put("else"); 341 code.putScope((_) 342 { 343 code.putf("var arc = new ArchiveObject(info.Name ?? \"NAME ME\");"); 344 code.putf("arc.AddValueAs<%s>((%s)obj);", typeName, typeName); 345 code.put("parent.AddChild(arc);"); 346 }); 347 }); 348 349 code.put("public Object Deserialise(ArchiveObject obj, TypeChildInfo info)"); 350 code.putScope((_) 351 { 352 code.put("if(info.Flags.HasFlag(TypeFlags.IsMainValue))"); 353 code.putf("\treturn obj.ExpectValueAs<%s>(0);", typeName); 354 code.put("else if(info.Flags.HasFlag(TypeFlags.IsAttribute))"); 355 code.putf("\treturn obj.ExpectAttributeAs<%s>(info.Name ?? \"NAME ME\");", typeName); 356 code.put("else"); 357 code.putf("\treturn obj.ExpectChild(info.Name ?? \"NAME ME\").ExpectValueAs<%s>(0);", typeName); 358 }); 359 }); 360 } 361 362 void genArrayCode(string outputDir, string namespace, CodeBuilder code, TypeField typeField, ref TypeField[] allInfo) 363 { 364 ArraySerialiseInfo info = typeField.as!ArraySerialiseInfo; 365 code.putf("public class %sSerialiser : ITypeSerialiser", CSharpGenerator.getArrayClassName(info)); 366 code.putScope((_) 367 { 368 code.put("public void Serialise(ArchiveObject parent, Object obj, TypeChildInfo info)"); 369 code.putScope((_) 370 { 371 CSharpGenerator.ensureGen(outputDir, namespace, TypeField(CSharpGenerator.getClassName(info.type), info.type), allInfo); 372 code.put("ArchiveObject arc;"); 373 if(cast(BasicSerialiseInfo)info.type) 374 { 375 code.put("if(!info.Flags.HasFlag(TypeFlags.IsMainValue))"); 376 code.putScope((_) 377 { 378 code.put("arc = new ArchiveObject(info.Name ?? \"NAME ME\");"); 379 code.put("parent.AddChild(arc);"); 380 }); 381 code.put("else"); 382 code.put("\tarc = parent;"); 383 384 code.putf("foreach(var val in (%s)obj)", CSharpGenerator.getArrayTypeName(info)); 385 code.putScope((_) 386 { 387 code.put("arc.AddValueAs(val);"); 388 }); 389 } 390 else if(cast(StructSerialiseInfo)info.type) 391 { 392 code.put("if(info.Flags.HasFlag(TypeFlags.ArrayAsObject))"); 393 code.putScope((_) 394 { 395 code.put("arc = new ArchiveObject(info.Name ?? \"NAME ME\");"); 396 code.put("parent.AddChild(arc);"); 397 }); 398 code.put("else"); 399 code.put("\tarc = parent;"); 400 401 code.putf("foreach(var val in (%s)obj)", CSharpGenerator.getArrayTypeName(info)); 402 code.putScope((_) 403 { 404 code.put("Serialiser.Serialise(arc, val, new TypeChildInfo());"); 405 }); 406 } 407 else if(cast(ArraySerialiseInfo)info.type) 408 assert(false, "Arrays of arrays are not supported"); 409 else 410 assert(false, info.toPrettyString()); 411 }); 412 413 code.put("public Object Deserialise(ArchiveObject obj, TypeChildInfo info)"); 414 code.putScope((_) 415 { 416 code.put("ArchiveObject arc;"); 417 code.putf("var value = new %s();", CSharpGenerator.getArrayTypeName(info)); 418 if(cast(BasicSerialiseInfo)info.type) 419 { 420 auto valueType = cast(BasicSerialiseInfo)info.type; 421 code.put("if(!info.Flags.HasFlag(TypeFlags.IsMainValue))"); 422 code.put("\tarc = obj.ExpectChild(info.Name ?? \"NAME ME\");"); 423 code.put("else"); 424 code.put("\tarc = obj;"); 425 426 code.put("foreach(var val in arc.Values)"); 427 code.putScope((_) 428 { 429 code.putf("value.Add(val.Get<%s>());", CSharpGenerator.getBasicTypeName(valueType)); 430 }); 431 } 432 else if(cast(StructSerialiseInfo)info.type) 433 { 434 auto valueType = cast(StructSerialiseInfo)info.type; 435 code.put("if(info.Flags.HasFlag(TypeFlags.ArrayAsObject))"); 436 code.put("\tarc = obj.ExpectChild(info.Name ?? \"NAME ME\");"); 437 code.put("else"); 438 code.put("\tarc = obj;"); 439 440 code.putf("foreach(var val in arc.Children.Where(c => c.Name == \"%s\"))", CSharpGenerator.getStructClassName(valueType)); 441 code.putScope((_) 442 { 443 code.putf("value.Add(Serialiser.Deserialise<%s>(val, new TypeChildInfo()));", CSharpGenerator.getStructClassName(valueType)); 444 }); 445 } 446 else if(cast(ArraySerialiseInfo)info.type) 447 assert(false, "Arrays of arrays are not supported"); 448 else 449 assert(false); 450 code.put("return value;"); 451 }); 452 }); 453 } 454 455 void genStructCode(string outputDir, string namespace, CodeBuilder code, TypeField typeField, ref TypeField[] allInfo) 456 { 457 CSharpGenerator.genStructClass(outputDir, namespace, code, typeField, allInfo); 458 CSharpGenerator.genStructSerialiser(code, typeField); 459 } 460 461 void genStructClass(string outputDir, string namespace, CodeBuilder code, TypeField typeField, ref TypeField[] allInfo) 462 { 463 auto info = typeField.as!StructSerialiseInfo; 464 465 code.putf("public class %s", CSharpGenerator.getStructClassName(info)); 466 code.putScope((_) 467 { 468 code.putf("public %s()", CSharpGenerator.getStructClassName(info)); 469 code.putScope((_) 470 { 471 foreach(name, field; info.fields) 472 { 473 CSharpGenerator.ensureGen(outputDir, namespace, field, allInfo); 474 if(field.as!ArraySerialiseInfo) 475 code.putf("this.%s = new %s();", name, CSharpGenerator.getArrayTypeName(field.as!ArraySerialiseInfo)); 476 } 477 }); 478 479 foreach(name, field; info.fields) 480 { 481 CSharpGenerator.ensureGen(outputDir, namespace, field, allInfo); 482 code.putf("public %s %s;", CSharpGenerator.getClassName(field.info), name); 483 } 484 }); 485 } 486 487 void genStructSerialiser(CodeBuilder code, TypeField typeField) 488 { 489 auto info = typeField.as!StructSerialiseInfo; 490 491 code.putf("public class %sSerialiser : ITypeSerialiser", CSharpGenerator.getStructClassName(info)); 492 code.putScope((_) 493 { 494 CSharpGenerator.genStructSerialiseFunc(code, info, typeField.name); 495 CSharpGenerator.genStructDeserialiseFunc(code, info, typeField.name); 496 }); 497 } 498 499 void genStructSerialiseFunc(CodeBuilder code, StructSerialiseInfo info, string name) 500 { 501 code.put("public void Serialise(ArchiveObject parent, Object obj, TypeChildInfo info)"); 502 code.putScope((_) 503 { 504 code.putf("var retObj = new ArchiveObject(info.Name ?? \"%s\");", name); 505 code.putf("var value = (%s)obj;", CSharpGenerator.getStructClassName(info)); 506 foreach(fieldName, fieldType; info.fields) 507 { 508 // Static arrays aren't so 'static' in C# 509 if(fieldType.as!ArraySerialiseInfo) 510 { 511 auto array = fieldType.as!ArraySerialiseInfo; 512 if(!array.staticArraySize.isNull) 513 { 514 code.putf("if(value.%s.Count != %s)", fieldName, array.staticArraySize); 515 code.putf("\tthrow new Exception($\"The field '%s' must have exactly %s values, not {value.%s.Count}\");", 516 fieldName, 517 array.staticArraySize, 518 fieldName 519 ); 520 } 521 } 522 523 code.putf("Serialiser.Serialise(retObj, value.%s, %s);", fieldName, CSharpGenerator.genChildInfoString(fieldType)); 524 } 525 code.put("parent.AddChild(retObj);"); 526 }); 527 } 528 529 void genStructDeserialiseFunc(CodeBuilder code, StructSerialiseInfo info, string name) 530 { 531 code.put("public Object Deserialise(ArchiveObject obj, TypeChildInfo info)"); 532 code.putScope((_) 533 { 534 code.putf("if(obj.Name != (info.Name ?? \"%s\"))", name); 535 code.putf("\tobj = obj.ExpectChild(info.Name ?? \"%s\");", name); 536 code.putf("var value = new %s();", CSharpGenerator.getStructClassName(info)); 537 foreach(fieldName, fieldType; info.fields) 538 { 539 string typeName = CSharpGenerator.getClassName(fieldType.info); 540 code.putf("value.%s = Serialiser.Deserialise<%s>(obj, %s);", fieldName, typeName, CSharpGenerator.genChildInfoString(fieldType)); 541 542 // Static arrays aren't so 'static' in C# 543 if(fieldType.as!ArraySerialiseInfo) 544 { 545 auto array = fieldType.as!ArraySerialiseInfo; 546 if(!array.staticArraySize.isNull) 547 { 548 code.putf("if(value.%s.Count != %s)", fieldName, array.staticArraySize); 549 code.putf("\tthrow new Exception($\"The field '%s' expects to have exactly %s values, not {value.%s.Count}\");", 550 fieldName, 551 array.staticArraySize, 552 fieldName 553 ); 554 } 555 } 556 } 557 code.put("return value;"); 558 }); 559 } 560 } 561 } 562 unittest 563 { 564 enum E 565 { 566 A = 1, 567 B = 1, 568 C = 69 569 } 570 571 @Name("sprite") 572 struct SpriteData 573 { 574 @MainValue 575 string name; 576 577 int[2] position; 578 int[2] size; 579 } 580 581 @Name("spriteSheet") 582 struct SpriteSheetData 583 { 584 @MainValue 585 string name; 586 587 int[2] position; 588 int[2] size; 589 int[2] frameSize; 590 } 591 592 @Name("Sprite:atlas") 593 struct AtlasData 594 { 595 E someE; 596 string name; 597 string textureRef; 598 SpriteData[] sprites; 599 SpriteSheetData[] spriteSheets; 600 } 601 602 // import std.stdio; 603 // writeln(TypeIntrospect.getInfoFor!AtlasData.info.toPrettyString); 604 605 CSharpGenerator.genFilesForTypes!AtlasData(buildPath(getcwd(), "test/"), "Test"); 606 }