1 module bamboo.codegen; 2 3 import std.algorithm; 4 import std.array; 5 import std.conv; 6 import std.math; 7 import std.string; 8 import std.uni; 9 10 import bamboo.hashgen; 11 import bamboo.types; 12 import bamboo.util; 13 14 /// Aliases for dclass int type names. 15 enum intTypes = ` 16 alias int8 = byte; 17 alias int16 = short; 18 alias int32 = int; 19 alias int64 = long; 20 21 alias uint8 = ubyte; 22 alias uint16 = ushort; 23 alias uint32 = uint; 24 alias uint64 = long; 25 `; 26 27 /// Aliases for dclass float type names. 28 enum floatTypes = ` 29 alias float32 = float; 30 alias float64 = double; 31 `; 32 33 // NB: string and char are properly aliased in D. 34 35 /// Alias for the dclass `blob` type. 36 enum blobTypes = ` 37 alias blob = immutable ubyte[]; 38 `; 39 40 /// Handy mixin for all dclass primitives. 41 enum Primitives = ` 42 mixin(intTypes); 43 mixin(floatTypes); 44 mixin(blobTypes); 45 `; 46 47 /// Provides metadata for D introspection on dclass types. 48 struct TypeId 49 { 50 int id; 51 string name; 52 int firstFieldId; 53 } 54 55 /// Provides metadata for D introspection on dclass fields. 56 struct FieldId 57 { 58 int id; 59 } 60 61 /// Provides metadata for D introspection on dclass fields. 62 struct FieldType(T) 63 { 64 T typeInit = T.init; 65 } 66 67 /// Generates D source code from a given module. It will optionally declare a D module. 68 /// This utility function also imports codegen helpers and privately aliases dclass primitives. 69 /// This function is suitable for creating a self-contained file. 70 /// See_Also: $(D generateModule). 71 string generateFile(Module file, string moduleName = "", 72 string distributedObjectModule = "libastrond", string baseType = "", 73 bool generateStubs = true) 74 { 75 string format; 76 77 if (moduleName.length == 0) 78 { 79 moduleName = file.symbol; 80 } 81 82 format ~= "// This code was generated by a tool.\n"; 83 if (moduleName.length > 0) 84 { 85 format ~= "module "; 86 format ~= moduleName; 87 format ~= ";"; 88 } 89 format ~= "import std.exception;"; 90 format ~= "import bamboo.codegen;"; 91 format ~= "import " ~ distributedObjectModule ~ ";"; 92 93 foreach (imprt; file.importDeclarations) 94 { 95 if (imprt.packageName == moduleName) 96 { 97 continue; 98 } 99 format ~= "import " ~ imprt.packageName ~ " : "; 100 foreach (type; imprt.symbols[0 .. $ - 1]) 101 { 102 format ~= type ~ ", "; 103 } 104 format ~= imprt.symbols[$ - 1] ~ ";"; 105 } 106 107 format ~= "private { mixin(Primitives); }"; 108 format ~= generateModule(file, baseType, generateStubs); 109 110 return format; 111 } 112 113 /// Ditto. 114 string generateFile(string dcFile, string moduleName = "", 115 string distributedObjectModule = "libastrond", string baseType = "", 116 bool generateStubs = true) 117 { 118 import bamboo.astgen : parseModule; 119 120 Module file = parseModule(dcFile); 121 return generateFile(file, moduleName, distributedObjectModule, baseType, generateStubs); 122 } 123 124 /// Generates D source code from a given module. This function is 125 /// suitable for mixing into a larger D source file. 126 /// See_Also: $(D generateFile). 127 string generateModule(Module file, string baseType = "", bool generateStubs = true) 128 { 129 string format; 130 131 foreach (type; file.typesById) 132 { 133 if (auto cls = cast(ClassDeclaration) type) 134 { 135 format ~= generateClass(cls, baseType, generateStubs); 136 } 137 else if (auto strct = cast(StructDeclaration) type) 138 { 139 format ~= generateStruct(strct); 140 } 141 } 142 143 return format; 144 } 145 146 /// Ditto. 147 string generateModule(string dcFile, string baseType = "", bool generateStubs = true) 148 { 149 import bamboo.astgen : parseModule; 150 151 Module file = parseModule(dcFile); 152 return generateModule(file, baseType, generateStubs); 153 } 154 155 private: 156 157 string generateClass(ClassDeclaration cls, string baseType, bool generateStubs) 158 { 159 string format; 160 format ~= "@TypeId(" ~ cls.id.to!string ~ ", `" ~ cls.symbol ~ "`, " 161 ~ cls.fields[0].id.to!string ~ ") "; 162 163 if (!generateStubs) 164 { 165 format ~= "abstract "; 166 } 167 168 format ~= "class "; 169 format ~= cls.symbol; 170 171 if (cls.hasSuperclass) 172 { 173 format ~= " : "; 174 format ~= cls.parents[0].symbol; 175 } 176 else if (baseType.length > 0) 177 { 178 format ~= " : "; 179 format ~= baseType; 180 } 181 182 format ~= "{"; 183 184 if (cls.hasConstructor) 185 { 186 assert(0, "Constructors are not supported."); 187 } 188 189 foreach (field; cls.fields) 190 { 191 format ~= generateField(field, generateStubs); 192 } 193 194 format ~= "}"; 195 return format; 196 } 197 198 string generateStruct(StructDeclaration strct) 199 { 200 string format; 201 format ~= "@TypeId(" ~ strct.id.to!string ~ ", `" ~ strct.symbol ~ "`, " 202 ~ strct.parameters[0].id.to!string ~ ")"; 203 format ~= "struct "; 204 format ~= strct.symbol; 205 format ~= " {"; 206 foreach (field; strct.parameters) 207 { 208 format ~= generateParameterField(field, true); 209 } 210 format ~= "}"; 211 return format; 212 } 213 214 string generateField(FieldDeclaration field, bool generateStubs) 215 { 216 string format; 217 if (auto molecular = cast(MolecularField) field) 218 { 219 format ~= generateMolecular(molecular); 220 } 221 else if (auto atomic = cast(AtomicField) field) 222 { 223 format ~= generateAtomic(atomic, generateStubs); 224 } 225 else if (auto parameter = cast(ParameterField) field) 226 { 227 format ~= generateParameterField(parameter, generateStubs); 228 } 229 else 230 { 231 assert(0); 232 } 233 return format; 234 } 235 236 string generateMolecular(MolecularField field) 237 { 238 string format; 239 format ~= "@FieldId(" ~ field.id.to!string ~ ")"; 240 format ~= "void "; 241 format ~= field.symbol; 242 243 format ~= "("; 244 245 foreach (reference; field.references) 246 { 247 format ~= generateParameterListFor(reference); 248 } 249 250 format ~= ") {"; 251 foreach (reference; field.references) 252 { 253 format ~= generateCallFor(reference) ~ ";"; 254 } 255 256 format ~= "}"; 257 258 return format; 259 } 260 261 /** 262 * This function generates parameter names from a field's name. 263 * 264 * Notes: 265 * Some fields follow the pattern of, e.g., `setXYZ(float32, float32, float32)`. 266 * Using the name generator, ugly parameter names will be generated when 267 * enough semantic information is already available to properly generate 268 * parameter names. 269 * 270 * This method would take `setXYZ(float32, float32, float32)` and transform 271 * it to `setXYZ(float32 x, float32, y, float32 z)`, for example. 272 */ 273 void autogenParameterNames(AtomicField field) 274 { 275 if (!field.symbol.startsWith("set")) 276 { 277 return; 278 } 279 280 foreach (parameter; field.parameters) 281 { 282 if (parameter.symbol.length > 0) 283 { 284 return; 285 } 286 } 287 288 string name = field.symbol[3 .. $]; 289 if (name.filter!(a => isUpper(cast(dchar) a))().array.length != field.parameters.length) 290 { 291 return; 292 } 293 294 int start; 295 int index = 1; 296 int param; 297 298 string[] names; 299 300 void appendName() 301 { 302 if (index - start == 1) 303 { 304 names ~= [cast(char)(name[start].toLower)]; 305 } 306 else 307 { 308 names ~= cast(char)(name[start].toLower) ~ name[start + 1 .. index]; 309 } 310 311 } 312 313 foreach (value; name[1 .. $]) 314 { 315 if (isUpper(cast(dchar) value)) 316 { 317 appendName(); 318 start = index; 319 } 320 index++; 321 } 322 if (names.length != field.parameters.length) 323 { 324 appendName(); 325 } 326 327 foreach (i, para; field.parameters) 328 { 329 para.symbol = names[i]; 330 } 331 } 332 333 string generateAtomic(AtomicField field, bool stub) 334 { 335 string format; 336 337 bool isComplex = field.parameters.length > 1; 338 bool isProperty = field.name.startsWith("set"); 339 string name; 340 341 autogenParameterNames(field); 342 343 if (isProperty) 344 { 345 name = cast(char)(field.name[3].toLower) ~ field.name[4 .. $]; 346 if (name[1 .. $].count!(x => isUpper(cast(dchar) x)) == name.length - 1) 347 { 348 name = field.name[3 .. $]; 349 } 350 if (stub) 351 { 352 if (isComplex) 353 { 354 format ~= "struct " ~ name ~ "_t {"; 355 foreach (parameter; field.parameters) 356 { 357 format ~= generateDefinition(parameter) ~ ";"; 358 } 359 format ~= "}"; 360 format ~= "private " ~ name ~ "_t _" ~ name ~ "; "; 361 } 362 else if (field.parameters.length == 1) 363 { 364 auto parameter = field.parameters[0]; 365 format ~= "private " ~ generateDefinition(parameter).split(' ')[0] ~ " _" 366 ~ name ~ ";"; 367 } 368 else 369 { 370 assert(0, name ~ " is a setter with no value!"); 371 } 372 } 373 } 374 else 375 { 376 name = field.name; 377 } 378 format ~= "@FieldId(" ~ field.id.to!string ~ ") "; 379 string fieldType; 380 381 if (isProperty) 382 { 383 if (isComplex && stub) 384 { 385 fieldType = name ~ "_t"; 386 } 387 else if (!isComplex) 388 { 389 fieldType = generateDefinition(field.parameters[0]).split(' ')[0]; 390 } 391 if (stub) 392 { 393 format ~= "@FieldType!(" ~ fieldType ~ ") "; 394 } 395 } 396 397 foreach (keyword; field.keywords) 398 { 399 format ~= "@" ~ keyword ~ " "; 400 } 401 402 if (!stub) 403 { 404 format ~= " abstract "; 405 } 406 format ~= " void "; 407 408 if (isComplex) 409 { 410 format ~= field.symbol; 411 } 412 else 413 { 414 format ~= name; 415 } 416 417 format ~= "("; 418 format ~= generateParameterListFor(field); 419 format ~= ") "; 420 421 if (!isComplex && isProperty) 422 { 423 format ~= "@property "; 424 } 425 426 string contracts; 427 428 foreach (parameter; field.parameters) 429 { 430 contracts ~= generateContractFor(parameter); 431 } 432 if (contracts.length > 0) 433 { 434 format ~= " in {"; 435 format ~= contracts; 436 format ~= "}"; 437 if (stub) 438 { 439 format ~= "body"; 440 } 441 } 442 443 if (stub) 444 { 445 format ~= "{"; 446 if (isProperty) 447 { 448 if (isComplex) 449 { 450 foreach (parameter; field.parameters) 451 { 452 format ~= "_" ~ name ~ "." ~ parameter.symbol; 453 format ~= "=" ~ parameter.symbol ~ ";"; 454 } 455 } 456 else if (field.parameters.length == 1) 457 { 458 auto parameter = field.parameters[0]; 459 format ~= "_" ~ name; 460 format ~= "=" ~ parameter.symbol ~ ";"; 461 } 462 } 463 else 464 { 465 format ~= `assert(0, "Override body not defined for ` ~ field.name ~ `!");`; 466 } 467 468 format ~= "}"; 469 } 470 else 471 { 472 format ~= ";"; 473 } 474 475 if (isProperty) 476 { 477 format ~= "@FieldId(" ~ field.id.to!string ~ ") "; 478 479 if (!isComplex) 480 { 481 if (!stub) 482 { 483 format ~= "abstract " ~ fieldType ~ " " ~ name ~ "() inout @property;"; 484 } 485 else 486 { 487 format ~= fieldType ~ " " ~ name ~ "() inout @property { "; 488 format ~= "return _" ~ name ~ ";"; 489 format ~= "}"; 490 } 491 } 492 else 493 { 494 if (stub) 495 { 496 format ~= "auto " ~ name ~ "() inout @property {"; 497 format ~= "return _" ~ name ~ ";"; 498 format ~= "}"; 499 } 500 } 501 } 502 503 return format; 504 } 505 506 string generateParameterField(ParameterField field, bool stub) 507 { 508 string format; 509 string def = generateDefinition(field.parameter); 510 string type = def.split(' ')[0]; 511 string name = def.split(' ')[1]; 512 513 format ~= "@FieldId(" ~ field.id.to!string ~ ") "; 514 format ~= "@FieldType!(" ~ generateDefinition(field.parameter).split(' ')[0] ~ ") "; 515 516 foreach (keyword; field.keywords) 517 { 518 format ~= " @" ~ keyword ~ " "; 519 } 520 521 if (!stub) 522 { 523 format ~= "abstract void " ~ name ~ "(" ~ type ~ " value) @property;"; 524 format ~= "abstract " ~ def ~ "();"; 525 } 526 else 527 { 528 format ~= "void " ~ name ~ "(" ~ type ~ " value) @property {"; 529 format ~= "_" ~ name ~ " = value;"; 530 format ~= "}"; 531 532 format ~= "@FieldId(" ~ field.id.to!string ~ ") "; 533 534 format ~= type ~ " " ~ name ~ "() inout @property {"; 535 format ~= "return _" ~ name ~ ";"; 536 format ~= "}"; 537 format ~= type ~ " _" ~ name ~ ";"; 538 } 539 540 return format; 541 } 542 543 string generateCallFor(FieldDeclaration field) 544 { 545 if (auto molecular = cast(MolecularField) field) 546 { 547 return generateMolecularCall(molecular); 548 } 549 else if (auto atomic = cast(AtomicField) field) 550 { 551 return generateAtomicCall(atomic); 552 } 553 else if (auto parameter = cast(ParameterField) field) 554 { 555 return generateParameterFieldCall(parameter); 556 } 557 else 558 { 559 assert(0); 560 } 561 562 } 563 564 string[] flattenArgs(MolecularField field) 565 { 566 string[] args; 567 foreach (reference; field.references) 568 { 569 if (auto molecular = cast(MolecularField) field) 570 { 571 args ~= flattenArgs(molecular); 572 } 573 else if (auto atomic = cast(AtomicField) field) 574 { 575 foreach (arg; atomic.parameters) 576 { 577 args ~= arg.symbol; 578 } 579 } 580 else if (auto paraField = cast(ParameterField) field) 581 { 582 args ~= paraField.parameter.symbol; 583 } 584 } 585 return args; 586 } 587 588 string generateMolecularCall(MolecularField field) 589 { 590 string[] args = flattenArgs(field); 591 string format; 592 format ~= field.symbol; 593 format ~= "("; 594 foreach (arg; args) 595 { 596 format ~= arg ~ ","; 597 } 598 format ~= ")"; 599 600 return format; 601 } 602 603 string generateAtomicCall(AtomicField field) 604 { 605 string format; 606 if (field.parameters.length > 1) 607 { 608 format ~= field.symbol; 609 } 610 else 611 { 612 format ~= "this." ~ cast(char)(field.name[3].toLower) ~ field.name[4 .. $]; 613 } 614 format ~= "("; 615 foreach (arg; field.parameters) 616 { 617 format ~= arg.symbol ~ ","; 618 } 619 format ~= ")"; 620 621 return format; 622 } 623 624 string generateParameterFieldCall(ParameterField field) 625 { 626 return "this." ~ field.name ~ "=" ~ field.name; 627 } 628 629 string generateParameterListFor(FieldDeclaration field) 630 { 631 if (auto molecular = cast(MolecularField) field) 632 { 633 string format; 634 foreach (reference; molecular.references) 635 { 636 format ~= generateParameterListFor(reference); 637 } 638 return format; 639 } 640 else if (auto atomic = cast(AtomicField) field) 641 { 642 return generateParameterList(atomic.parameters); 643 } 644 else if (auto parameter = cast(ParameterField) field) 645 { 646 return generateParameterList([parameter.parameter]); 647 } 648 return ""; 649 } 650 651 string generateParameterList(Parameter[] parameters) 652 { 653 string format; 654 655 foreach (parameter; parameters) 656 { 657 format ~= generateDefinition(parameter) ~ ","; 658 } 659 660 return format; 661 } 662 663 string generateDefinition(Parameter parameter) 664 { 665 switch (parameter.syntaxKind) 666 { 667 case SyntaxKind.StructParameter: 668 return generateDefinition(cast(StructParameter) parameter); 669 case SyntaxKind.ArrayParameter: 670 return generateDefinition(cast(ArrayParameter) parameter); 671 case SyntaxKind.SizedParameter: 672 return generateDefinition(cast(SizedParameter) parameter); 673 case SyntaxKind.NumericParameter: 674 return generateDefinition(cast(NumericParameter) parameter); 675 default: 676 assert(0); 677 } 678 } 679 680 string generateDefinition(StructParameter parameter) 681 { 682 enum string format = `${type} ${name}`; 683 684 string type = mapType(parameter.type.symbol); 685 string name = generateName(parameter.symbol, type); 686 687 parameter.symbol = name; 688 689 return mixin(interp!format); 690 } 691 692 string generateDefinition(ArrayParameter array) 693 { 694 enum string format = `${type}[${maxLength}] ${name}`; 695 696 string type = mapType(array.elementType.symbol); 697 string name = generateName(array.symbol, type); 698 string maxLength = ""; 699 700 version (RespectFixedLength) 701 { 702 if (array.hasRange && array.size.isFixedLength) 703 { 704 maxLength = array.size.maxLength.to!string; 705 } 706 } 707 708 array.symbol = name; 709 710 return mixin(interp!format); 711 } 712 713 string generateDefinition(SizedParameter array) 714 { 715 enum string format = `${type} ${name}`; 716 717 string type; 718 string maxLength = ""; 719 string defaultVal = ""; 720 721 version (RespectFixedLength) 722 { 723 if (array.hasRange && array.size.isFixedLength) 724 { 725 maxLength = array.size.maxLength.to!string; 726 } 727 728 switch (array.parameterType) with (Type) 729 { 730 case string_: 731 case varstring: 732 type = "immutable char[${maxLength}]"; 733 break; 734 case blob: 735 case varblob: 736 type = "immutable ubyte[${maxLength}]"; 737 break; 738 default: 739 assert(0); 740 } 741 type = mixin(interp!type); 742 } 743 else 744 { 745 switch (array.parameterType) with (Type) 746 { 747 case string_: 748 case varstring: 749 type = "string"; 750 break; 751 case blob: 752 case varblob: 753 type = "blob"; 754 break; 755 default: 756 assert(0); 757 } 758 } 759 760 type = mapType(type); 761 762 string name = generateName(array.symbol, type); 763 764 if (array.defaultVal.length > 0) 765 { 766 defaultVal = mixin(interp!" = \"${array.defaultVal}\""); 767 } 768 769 array.symbol = name; 770 771 return mixin(interp!format); 772 } 773 774 string generateDefinition(NumericParameter numeric) 775 { 776 enum string format = `${type} ${name}`; 777 778 string type = mapType(numeric.type.to!string()); 779 string name = generateName(numeric.symbol, type); 780 781 numeric.symbol = name; 782 783 return mixin(interp!format); 784 } 785 786 string mapType(string type) 787 { 788 // dfmt off 789 enum types = [ 790 "int8": "byte", 791 "int16": "short", 792 "int32": "int", 793 "int64": "long", 794 "uint8": "ubyte", 795 "uint16": "ushort", 796 "uint32": "uint", 797 "uint64": "ulong", 798 "float32": "float", 799 "float64": "double", 800 "char_": "char", 801 "varstring": "string", 802 "blob": "ubyte[]", 803 "varblob": "ubyte[]", 804 ]; 805 // dfmt on 806 807 if (auto ret = type in types) 808 { 809 return *ret; 810 } 811 return type; 812 } 813 814 string generateContractFor(Parameter parameter) 815 { 816 if (auto array = cast(SizedParameter) parameter) 817 { 818 return generateContract(array); 819 } 820 else if (auto array = cast(ArrayParameter) parameter) 821 { 822 return generateContract(array); 823 } 824 else if (auto numeric = cast(NumericParameter) parameter) 825 { 826 return generateContract(numeric); 827 } 828 return ""; 829 } 830 831 string generateContract(SizedParameter array) 832 { 833 enum string format = ` 834 assert(${parameter}.length <= ${maxLength}, "${parameter} is oversized!"); 835 assert(${parameter}.length >= ${minLength}, "${parameter} is undersized!"); 836 `; 837 838 if (!array.hasRange) 839 { 840 return ""; 841 } 842 843 string parameter = array.symbol; 844 string maxLength = array.size.maxSize.to!string; 845 string minLength = array.size.minSize.to!string; 846 847 return mixin(interp!format); 848 } 849 850 string generateContract(ArrayParameter array) 851 { 852 enum string format = ` 853 assert(${parameter}.length <= ${maxLength}, "${parameter} is oversized!"); 854 assert(${parameter}.length >= ${minLength}, "${parameter} is undersized!"); 855 `; 856 857 if (!array.hasRange) 858 { 859 return ""; 860 } 861 862 string parameter = array.symbol; 863 string maxLength = array.size.maxLength.to!string; 864 string minLength = array.size.minLength.to!string; 865 866 return mixin(interp!format); 867 } 868 869 string generateContract(NumericParameter numeric) 870 { 871 string formatStr(string parameter, string op, string value, string msg) 872 { 873 return `assert(` ~ parameter ~ ` ` ~ op ~ ` ` ~ value ~ `, "` ~ parameter 874 ~ ` is too ` ~ msg ~ `!");`; 875 } 876 877 string format; 878 879 if (!numeric.hasRange) 880 { 881 return ""; 882 } 883 884 string parameter = numeric.symbol; 885 string max = numeric.range.max.to!string; 886 string min; 887 888 format ~= formatStr(parameter, "<=", max, "large"); 889 890 if (!isNaN(numeric.range.min)) 891 { 892 min = numeric.range.min.to!string; 893 format ~= formatStr(parameter, ">=", min, "small"); 894 } 895 896 return format; 897 } 898 899 private: 900 901 static int g_nameCounter; 902 903 string generateName(string name, string type) 904 { 905 if (name.length == 0) 906 { 907 HashGenerator gen; 908 gen.addInt(g_nameCounter++); 909 gen.addString(type); 910 name = mixin(interp!"_${type.toLower}${gen.hash}"); 911 } 912 return name; 913 }