1 // Copyright © 2017-2021 Rémi Thebault 2 /++ 3 + Wayland scanner for D. 4 + Generation of common protocol code. 5 +/ 6 module wayland.scanner.common; 7 8 import wayland.scanner; 9 10 import arsd.dom; 11 12 import std.algorithm; 13 import std.array; 14 import std.conv; 15 import std.exception; 16 import std.format; 17 import std.range; 18 import std.stdio; 19 import std.typecons; 20 import std.uni; 21 22 interface Factory 23 { 24 Protocol makeProtocol(Element el); 25 Interface makeInterface(Element el, Protocol protocol); 26 Message makeMessage(Element el, Interface iface); 27 Arg makeArg(Element el); 28 } 29 30 Factory fact; 31 32 33 void writeMultilineParenthesis(SourceFile sf, in string before, 34 in string[] args, in string after) 35 { 36 immutable indent = ' '.repeat(before.length+1).array(); 37 sf.write("%s(", before); 38 foreach (i, a; args) 39 { 40 if (i != 0) 41 { 42 sf.writeln(","); 43 sf.write(indent); 44 } 45 sf.write(a); 46 } 47 sf.writeln(")%s", after); 48 } 49 50 void writeDelegateAlias(SourceFile sf, string name, string ret, string[] rtArgs) 51 { 52 immutable fstLine = format("alias %s = %s delegate(", name, ret); 53 immutable indent = ' '.repeat(fstLine.length).array(); 54 sf.write(fstLine); 55 foreach(i, rta; rtArgs) 56 { 57 if (i != 0) 58 { 59 sf.writeln(","); 60 sf.write(indent); 61 } 62 sf.write(rta); 63 } 64 sf.writeln(");"); 65 } 66 67 void writeFnSigRaw(SourceFile sf, string qualifiers, string ret, string name, 68 string[] rtArgs, string[] ctArgs=[], string constraint="") 69 { 70 immutable ctSig = ctArgs.length ? format("!(%(%s, %))", ctArgs) : ""; 71 immutable qual = qualifiers.length ? format("%s ", qualifiers) : ""; 72 immutable fstLine = format("%s%s %s%s(", qual, ret, name, ctSig); 73 immutable indent = ' '.repeat(fstLine.length).array(); 74 sf.write(fstLine); 75 foreach (i, rta; rtArgs) 76 { 77 if (i != 0) 78 { 79 sf.writeln(","); 80 sf.write(indent); 81 } 82 sf.write(rta); 83 } 84 sf.write(")"); 85 if (constraint.length) 86 { 87 sf.write("if (%s)", constraint); 88 } 89 } 90 91 void writeFnSig(SourceFile sf, string ret, string name, string[] rtArgs, 92 string[] ctArgs=[], string constraint="") 93 { 94 writeFnSigRaw(sf, "", ret, name, rtArgs, ctArgs, constraint); 95 sf.writeln(); 96 } 97 98 void writeFnExpr(SourceFile sf, string stmt, string[] exprs) 99 { 100 sf.writeln("%s(", stmt); 101 sf.indentedBlock!({ 102 immutable ic = sf.indentChars; 103 int width = ic; 104 foreach (i, e; exprs) 105 { 106 if (i != 0) 107 { 108 sf.write(","); 109 width += 1; 110 } 111 width += e.length; 112 if (width > wrapWidth) 113 { 114 sf.writeln(); 115 width = ic; 116 } 117 else if (i != 0) 118 { 119 sf.write(" "); 120 width += 1; 121 } 122 sf.write(e); 123 } 124 }); 125 sf.writeln(); 126 sf.writeln(");"); 127 } 128 129 void writeFnPointer(SourceFile sf, string name, string ret, string[] args) 130 { 131 writeMultilineParenthesis( 132 sf, format("%s function", ret), args, format(" %s;", name) 133 ); 134 } 135 136 void writeFnBody(SourceFile sf, string[] preStmts, string mainStmt, 137 string[] exprs, string[] postStmt) 138 { 139 sf.bracedBlock!({ 140 foreach (ps; preStmts) 141 { 142 sf.writeln(ps); 143 } 144 writeFnExpr(sf, mainStmt, exprs); 145 foreach (ps; postStmt) 146 { 147 sf.writeln(ps); 148 } 149 }); 150 } 151 152 enum ArgType 153 { 154 Int, UInt, Fixed, String, Object, NewId, Array, Fd 155 } 156 157 @property bool isNullable(in ArgType at) pure 158 { 159 switch (at) 160 { 161 case ArgType.String: 162 case ArgType.Object: 163 case ArgType.NewId: 164 case ArgType.Array: 165 return true; 166 default: 167 return false; 168 } 169 } 170 171 172 class Description 173 { 174 string summary; 175 string text; 176 177 this (Element parentEl) 178 { 179 foreach (el; parentEl.getElementsByTagName("description")) 180 { 181 summary = el.getAttribute("summary"); 182 text = el.getElText(); 183 break; 184 } 185 } 186 187 @property bool empty() const 188 { 189 return summary.empty && text.empty; 190 } 191 192 void writeCode(SourceFile sf) 193 { 194 if (empty) return; 195 if (text.empty) 196 { 197 enforce(!summary.canFind('\n'), "Summary should be a 1 liner."); 198 sf.writeln("/// %s", summary); 199 } 200 else if (summary.empty) 201 { 202 sf.writeDoc("%s", text); 203 } 204 else 205 { 206 sf.writeDoc("%s\n\n%s", summary, text); 207 } 208 } 209 } 210 211 212 class EnumEntry 213 { 214 string ifaceName; 215 string enumName; 216 string name; 217 string value; 218 string summary; 219 220 this (Element el, string ifaceName, string enumName) 221 { 222 assert(el.tagName == "entry"); 223 this.ifaceName = ifaceName; 224 this.enumName = enumName; 225 name = el.getAttribute("name"); 226 value = el.getAttribute("value"); 227 summary = el.getAttribute("summary"); 228 229 enforce(!value.empty, "enum entries without value aren't supported"); 230 } 231 232 void writeCode(SourceFile sf) 233 { 234 if (summary.length) 235 { 236 sf.writeln("/// %s", summary); 237 } 238 sf.writeln( 239 "%s = %s,", validDName(camelName(name)), value 240 ); 241 } 242 } 243 244 245 class Enum 246 { 247 string name; 248 string ifaceName; 249 Description description; 250 EnumEntry[] entries; 251 252 this (Element el, string ifaceName) 253 { 254 assert(el.tagName == "enum"); 255 name = el.getAttribute("name"); 256 this.ifaceName = ifaceName; 257 description = new Description(el); 258 foreach (entryEl; el.getElementsByTagName("entry")) 259 { 260 entries ~= new EnumEntry(entryEl, ifaceName, name); 261 } 262 } 263 264 @property dName() const 265 { 266 return titleCamelName(name); 267 } 268 269 bool entriesHaveDoc() const 270 { 271 return entries.any!(e => !e.summary.empty); 272 } 273 274 void writeCode(SourceFile sf) 275 { 276 description.writeCode(sf); 277 sf.writeln("enum %s : uint", dName); 278 sf.bracedBlock!({ 279 foreach(entry; entries) 280 { 281 entry.writeCode(sf); 282 } 283 }); 284 } 285 } 286 287 288 289 abstract class Arg 290 { 291 string name; 292 string summary; 293 string iface; 294 ArgType type; 295 string enumName; 296 bool nullable; 297 298 this (Element el) 299 { 300 assert(el.tagName == "arg"); 301 name = el.getAttribute("name"); 302 summary = el.getAttribute("summary"); 303 iface = el.getAttribute("interface"); 304 enumName = el.getAttribute("enum"); 305 switch (el.getAttribute("type")) 306 { 307 case "int": 308 type = ArgType.Int; 309 break; 310 case "uint": 311 type = ArgType.UInt; 312 break; 313 case "fixed": 314 type = ArgType.Fixed; 315 break; 316 case "string": 317 type = ArgType.String; 318 break; 319 case "object": 320 type = ArgType.Object; 321 break; 322 case "new_id": 323 type = ArgType.NewId; 324 break; 325 case "array": 326 type = ArgType.Array; 327 break; 328 case "fd": 329 type = ArgType.Fd; 330 break; 331 default: 332 throw new Exception("unknown type: "~el.getAttribute("type")); 333 } 334 immutable allowNull = el.getAttribute("allow-null"); 335 enforce (!allowNull.length || (allowNull == "true" || allowNull == "false")); 336 nullable = allowNull == "true"; 337 enforce(!nullable || isNullable(type)); 338 } 339 340 abstract @property string dType() const 341 { 342 final switch(type) { 343 case ArgType.Int: 344 return "int"; 345 case ArgType.UInt: 346 if (enumName.empty) return "uint"; 347 else return qualfiedTypeName(enumName); 348 case ArgType.Fixed: 349 return "WlFixed"; 350 case ArgType.String: 351 return "string"; 352 case ArgType.NewId: 353 assert(false, "must be implemented in subclasses"); 354 case ArgType.Array: 355 return "wl_array*"; // ?? let's check this later 356 case ArgType.Fd: 357 return "int"; 358 case ArgType.Object: 359 assert(false, "must be implemented in subclasses"); 360 } 361 } 362 363 abstract @property string cType() const 364 { 365 final switch(type) { 366 case ArgType.Int: 367 return "int"; 368 case ArgType.UInt: 369 return "uint"; 370 case ArgType.Fixed: 371 return "wl_fixed_t"; 372 case ArgType.String: 373 return "const(char)*"; 374 case ArgType.Object: 375 assert(false, "unimplemented"); 376 case ArgType.NewId: 377 assert(false, "must be implemented in subclasses"); 378 case ArgType.Array: 379 return "wl_array*"; 380 case ArgType.Fd: 381 return "int"; 382 } 383 } 384 385 386 @property string paramName() const 387 { 388 return validDName(camelName(name)); 389 } 390 391 @property string cCastExpr() const 392 { 393 final switch(type) { 394 case ArgType.Int: 395 return paramName; 396 case ArgType.UInt: 397 return paramName; 398 case ArgType.Fixed: 399 return format("%s.raw", paramName); 400 case ArgType.String: 401 return format("toStringz(%s)", paramName); 402 case ArgType.NewId: 403 return paramName; 404 case ArgType.Array: 405 return paramName; 406 case ArgType.Fd: 407 return paramName; 408 case ArgType.Object: 409 assert(false, "must be implemented in subclasses"); 410 } 411 } 412 413 string dCastExpr(string parentIface) const 414 { 415 final switch(type) { 416 case ArgType.Int: 417 return paramName; 418 case ArgType.UInt: 419 if (enumName.empty) return paramName; 420 else 421 { 422 immutable en = (parentIface.empty || enumName.canFind('.')) ? 423 enumName : format("%s.%s", parentIface, enumName); 424 return format("cast(%s)%s", qualfiedTypeName(en), paramName); 425 } 426 case ArgType.Fixed: 427 return format("WlFixed(%s)", paramName); 428 case ArgType.String: 429 return format("fromStringz(%s).idup", paramName); 430 case ArgType.Object: 431 assert(false, "unimplemented"); 432 case ArgType.NewId: 433 return paramName; 434 case ArgType.Array: 435 return paramName; 436 case ArgType.Fd: 437 return paramName; 438 } 439 } 440 } 441 442 abstract class Message 443 { 444 enum Type 445 { 446 event, 447 request, 448 } 449 450 enum ReqType 451 { 452 void_, 453 newObj, 454 dynObj, 455 } 456 457 string name; 458 Interface iface; 459 int since = 1; 460 bool isDtor; 461 Description description; 462 Arg[] args; 463 464 string[] argIfaceTypes; 465 size_t ifaceTypeIndex; 466 Type type; 467 468 ReqType reqType; 469 Arg reqRet; 470 471 this (Element el, Interface iface) 472 { 473 assert(el.tagName == "request" || el.tagName == "event"); 474 this.iface = iface; 475 name = el.getAttribute("name"); 476 if (el.hasAttribute("since")) 477 { 478 since = el.getAttribute("since").to!int; 479 } 480 isDtor = (el.getAttribute("type") == "destructor"); 481 description = new Description(el); 482 foreach (argEl; el.getElementsByTagName("arg")) 483 { 484 args ~= fact.makeArg(argEl); 485 } 486 487 argIfaceTypes = args 488 .filter!(a => a.type == ArgType.NewId || a.type == ArgType.Object) 489 .map!(a => a.iface) 490 .filter!(iface => iface.length != 0) 491 .array(); 492 493 type = el.tagName == "event" ? Type.event : Type.request; 494 495 if (type == Type.request) 496 { 497 auto crr = reqReturn; 498 reqRet = crr[0]; 499 reqType = crr[1]; 500 } 501 } 502 503 @property string ifaceName() 504 { 505 return iface.name; 506 } 507 508 @property Tuple!(Arg, ReqType) reqReturn() 509 { 510 Arg ret; 511 ReqType rt; 512 foreach (arg; args) 513 { 514 if (arg.type == ArgType.NewId) 515 { 516 enforce(!ret, "more than 1 new-id for a request!"); 517 ret = arg; 518 rt = arg.iface.length ? ReqType.newObj : ReqType.dynObj; 519 } 520 } 521 if (!ret) 522 { 523 rt = ReqType.void_; 524 } 525 return tuple(ret, rt); 526 } 527 528 @property bool ifaceTypesAllNull() const 529 { 530 return argIfaceTypes.empty; 531 } 532 533 @property size_t nullIfaceTypeLength() const 534 { 535 return argIfaceTypes.empty ? args.length : 0; 536 } 537 538 @property string dMethodName() 539 { 540 return validDName(camelName(name)); 541 } 542 543 @property string opCodeName() 544 { 545 return format("%sOpCode", camelName(name)); 546 } 547 548 @property string onOpCodeName() 549 { 550 return format("on%sOpCode", titleCamelName(name)); 551 } 552 553 @property string dHandlerName() 554 { 555 return "on" ~ titleCamelName(name); 556 } 557 558 @property string dEvDgType() 559 { 560 return "On" ~ titleCamelName(name) ~ "EventDg"; 561 } 562 563 @property string cEvName() 564 { 565 return validDName(name); 566 } 567 568 @property string signature() const 569 { 570 string sig; 571 if (since > 1) 572 { 573 sig ~= since.to!string; 574 } 575 foreach(arg; args) 576 { 577 if (arg.nullable) sig ~= '?'; 578 final switch(arg.type) 579 { 580 case ArgType.Int: 581 sig ~= 'i'; 582 break; 583 case ArgType.NewId: 584 if (arg.iface.empty) 585 sig ~= "su"; 586 sig ~= "n"; 587 break; 588 case ArgType.UInt: 589 sig ~= "u"; 590 break; 591 case ArgType.Fixed: 592 sig ~= "f"; 593 break; 594 case ArgType.String: 595 sig ~= "s"; 596 break; 597 case ArgType.Object: 598 sig ~= "o"; 599 break; 600 case ArgType.Array: 601 sig ~= "a"; 602 break; 603 case ArgType.Fd: 604 sig ~= "h"; 605 break; 606 } 607 } 608 return sig; 609 } 610 611 void writePrivIfaceMsg(SourceFile sf) 612 { 613 sf.writeln( 614 "wl_message(\"%s\", \"%s\", &msgTypes[%d]),", 615 name, signature, ifaceTypeIndex 616 ); 617 } 618 } 619 620 621 abstract class Interface 622 { 623 Protocol protocol; 624 string name; 625 string ver; 626 Description description; 627 Message[] requests; 628 Message[] events; 629 Enum[] enums; 630 631 // indicate that is created by server rather than by client request. 632 // protocal eventually set to false after all interfaces are parsed 633 bool isGlobal = true; 634 635 636 private static Interface[string] ifaceMap; 637 638 static Interface get(string name) 639 { 640 auto i = name in ifaceMap; 641 if (i) return *i; 642 else return null; 643 } 644 645 this (Element el, Protocol protocol) 646 { 647 assert(el.tagName == "interface"); 648 this.protocol = protocol; 649 name = el.getAttribute("name"); 650 ver = el.getAttribute("version"); 651 description = new Description(el); 652 foreach (rqEl; el.getElementsByTagName("request")) 653 { 654 requests ~= fact.makeMessage(rqEl, this); 655 } 656 foreach (evEl; el.getElementsByTagName("event")) 657 { 658 events ~= fact.makeMessage(evEl, this); 659 } 660 foreach (enEl; el.getElementsByTagName("enum")) 661 { 662 enums ~= new Enum(enEl, name); 663 } 664 ifaceMap[name] = this; 665 } 666 667 abstract void writeCode(SourceFile sf); 668 669 @property string dName() const 670 { 671 return ifaceDName(name); 672 } 673 674 @property bool writeEvents() 675 { 676 return events.length && name != "wl_display"; 677 } 678 679 @property size_t nullIfaceTypeLength() 680 { 681 size_t max =0; 682 foreach (tl; chain(requests, events) 683 .map!(msg => msg.nullIfaceTypeLength)) 684 { 685 if (tl > max) max = tl; 686 } 687 return max; 688 } 689 690 void writeVersion(SourceFile sf) 691 { 692 sf.writeln("/// Version of %s.%s", protocol.name, name); 693 sf.writeln("enum ver = %s;", ver); 694 } 695 696 void writePrivIfaceMsgs(SourceFile sf, Message[] msgs, in string suffix) 697 { 698 if (msgs.empty) return; 699 700 sf.writeln("auto %s_%s = [", name, suffix); 701 sf.indentedBlock!({ 702 foreach(msg; msgs) 703 { 704 msg.writePrivIfaceMsg(sf); 705 } 706 }); 707 sf.writeln("];"); 708 } 709 710 void writePrivIfacePopulate(SourceFile sf) 711 { 712 writePrivIfaceMsgs(sf, requests, "requests"); 713 writePrivIfaceMsgs(sf, events, "events"); 714 immutable memb = format("ifaces[%s]", indexSymbol(name)); 715 sf.writeln(`%s.name = "%s";`, memb, name); 716 sf.writeln("%s.version_ = %s;", memb, ver); 717 718 if (requests.length) 719 { 720 sf.writeln("%s.method_count = %d;", memb, requests.length); 721 sf.writeln("%s.methods = %s_requests.ptr;", memb, name); 722 } 723 if (events.length) 724 { 725 sf.writeln("%s.event_count = %d;", memb, events.length); 726 sf.writeln("%s.events = %s_events.ptr;", memb, name); 727 } 728 } 729 } 730 731 732 abstract class Protocol 733 { 734 string name; 735 string copyright; 736 Interface[] ifaces; 737 738 this(Element el) 739 { 740 enforce(el.tagName == "protocol"); 741 name = el.getAttribute("name"); 742 foreach (cr; el.getElementsByTagName("copyright")) 743 { 744 copyright = cr.getElText(); 745 break; 746 } 747 Interface[string] ifaceMap; 748 foreach (ifEl; el.getElementsByTagName("interface")) 749 { 750 auto iface = fact.makeInterface(ifEl, this); 751 ifaceMap[iface.name] = iface; 752 ifaces ~= iface; 753 } 754 755 foreach(iface; ifaces) 756 { 757 foreach(req; iface.requests) 758 { 759 foreach(arg; req.args) 760 { 761 if (arg.type == ArgType.NewId) 762 { 763 auto ifp = arg.iface in ifaceMap; 764 if (ifp) ifp.isGlobal = false; 765 } 766 } 767 } 768 } 769 } 770 771 abstract void writeCode(SourceFile sf, in Options opt); 772 773 abstract void writePrivIfaces(SourceFile sf); 774 775 abstract void writeNativeIfacesAssignment(SourceFile sf); 776 777 bool isLocalIface(string name) 778 { 779 return ifaces.map!(iface => iface.name).canFind(name); 780 } 781 782 void writeHeader(SourceFile sf, in Options opt) 783 { 784 import std.path : baseName; 785 sf.writeDoc( 786 "Module generated by wayland:scanner-%s for %s protocol\n" ~ 787 " xml protocol: %s\n" ~ 788 " generated code: %s", 789 scannerVersion, name, 790 (opt.inFile.length ? baseName(opt.inFile) : "stdin"), 791 opt.code.to!string 792 ); 793 sf.writeln("module %s;", opt.moduleName); 794 sf.writeComment( 795 "Protocol copyright:\n\n%s", copyright 796 ); 797 sf.writeComment( 798 "Bindings copyright:\n\n%s", bindingsCopyright 799 ); 800 } 801 802 void writeNativeIfaces(SourceFile sf) 803 { 804 sf.writeln(); 805 sf.writeln("immutable wl_interface[] wl_ifaces;"); 806 sf.writeln(); 807 foreach (i, iface; ifaces) 808 { 809 sf.writeln("enum %s = %d;", indexSymbol(iface.name), i); 810 } 811 sf.writeln(); 812 sf.writeln("shared static this()"); 813 sf.bracedBlock!({ 814 sf.writeln("auto ifaces = new wl_interface[%d];", ifaces.length); 815 sf.writeln(); 816 writePrivMsgTypes(sf); 817 sf.writeln(); 818 foreach (iface; ifaces) 819 { 820 iface.writePrivIfacePopulate(sf); 821 sf.writeln(); 822 } 823 sf.writeln("import std.exception : assumeUnique;"); 824 sf.writeln("wl_ifaces = assumeUnique(ifaces);"); 825 sf.writeln(); 826 writeNativeIfacesAssignment(sf); 827 }); 828 } 829 830 void writePrivMsgTypes(SourceFile sf) 831 { 832 size_t max =0; 833 foreach (l; ifaces.map!(iface => iface.nullIfaceTypeLength)) 834 { 835 if (l > max) max = l; 836 } 837 838 immutable nullLength = max; 839 size_t typeIndex = 0; 840 841 sf.writeln("auto msgTypes = ["); 842 sf.indentedBlock!({ 843 foreach(i; 0..nullLength) 844 { 845 sf.writeln("null,"); 846 } 847 foreach (iface; ifaces) 848 { 849 foreach(msg; chain(iface.requests, iface.events)) 850 { 851 if (msg.ifaceTypesAllNull) 852 { 853 msg.ifaceTypeIndex = 0; 854 continue; 855 } 856 msg.ifaceTypeIndex = nullLength + typeIndex; 857 typeIndex += msg.args.length; 858 foreach (arg; msg.args) 859 { 860 if (!arg.iface.empty && 861 (arg.type == ArgType.NewId || 862 arg.type == ArgType.Object)) 863 { 864 if (isLocalIface(arg.iface)) 865 sf.writeln("&ifaces[%s],", indexSymbol(arg.iface)); 866 else 867 sf.writeln("cast(wl_interface*)%s.iface.native,", ifaceDName(arg.iface)); 868 } 869 else 870 { 871 sf.writeln("null,"); 872 } 873 } 874 875 } 876 } 877 }); 878 sf.writeln("];"); 879 } 880 } 881 882 883 // for private wl_interface array 884 string indexSymbol(in string name) pure 885 { 886 return camelName(name, "index"); 887 } 888 889 string ifaceDName(in string name) pure 890 { 891 return titleCamelName(name); 892 } 893 894 string validDName(in string name) pure 895 { 896 switch (name) 897 { 898 case "class": return "class_"; 899 case "default": return "default_"; 900 case "interface": return "iface"; 901 case "version": return "version_"; 902 default: 903 { 904 if (name[0] >= '0' && name[0] <= '9') return "_" ~ name; 905 else return name; 906 } 907 } 908 } 909 910 string getElText(Element el) 911 { 912 string fulltxt; 913 foreach (child; el.children) 914 { 915 if (child.nodeType == NodeType.Text) 916 { 917 fulltxt ~= child.nodeValue; 918 } 919 } 920 921 string[] lines; 922 string offset; 923 bool offsetdone = false; 924 foreach (l; fulltxt.split('\n')) 925 { 926 immutable bool allwhite = l.all!isWhite; 927 if (!offsetdone && allwhite) continue; 928 929 if (!offsetdone && !allwhite) 930 { 931 offsetdone = true; 932 offset = l 933 .until!(c => !c.isWhite) 934 .to!string; 935 } 936 937 if (l.startsWith(offset)) 938 { 939 l = l[offset.length .. $]; 940 } 941 942 lines ~= l; 943 } 944 945 foreach_reverse(l; lines) { 946 if (l.all!isWhite) { 947 lines = lines[0..$-1]; 948 } 949 else break; 950 } 951 952 return lines.join("\n"); 953 } 954 955 956 class SourceFile 957 { 958 File _output; 959 int _indentLev = 0; 960 bool _indentNext = true; 961 962 invariant() 963 { 964 assert(_indentLev >= 0); 965 } 966 967 this(File output) 968 { 969 _output = output; 970 } 971 972 @property File output() 973 { 974 return _output; 975 } 976 977 @property int indentLev() const 978 { 979 return _indentLev; 980 } 981 982 @property int indentChars() const 983 { 984 return _indentLev * charsPerIndent; 985 } 986 987 void indent() 988 { 989 _indentLev += 1; 990 } 991 992 void unindent() 993 { 994 _indentLev -= 1; 995 } 996 997 void write(Args...)(string codeFmt, Args args) 998 { 999 immutable code = format(codeFmt, args); 1000 immutable iStr = indentStr(_indentLev); 1001 if (_indentNext) _output.write(iStr); 1002 _output.write(code.replace("\n", "\n"~iStr)); 1003 _indentNext = false; 1004 } 1005 1006 void writeln() 1007 { 1008 _output.writeln(); 1009 _indentNext = true; 1010 } 1011 1012 /++ 1013 + writes indented code and adds a final '\n' 1014 +/ 1015 void writeln(Args...)(string codeFmt, Args args) 1016 { 1017 immutable code = format(codeFmt, args); 1018 immutable iStr = indentStr(_indentLev); 1019 if (_indentNext) _output.write(iStr); 1020 _output.writeln(code.replace("\n", "\n"~iStr)); 1021 _indentNext = true; 1022 } 1023 1024 void writeComment(Args...)(string textFmt, Args args) 1025 { 1026 immutable text = format(textFmt, args); 1027 immutable indStr = indentStr(_indentLev); 1028 _output.writeln(indStr, "/+"); 1029 foreach (l; text.split("\n")) { 1030 if (l.empty) _output.writeln(indStr, " +"); 1031 else _output.writeln(indStr, " + ", l); 1032 } 1033 _output.writeln(indStr, " +/"); 1034 } 1035 1036 void writeDoc(Args...)(string textFmt, Args args) 1037 { 1038 immutable text = format(textFmt, args); 1039 immutable indStr = indentStr(_indentLev); 1040 _output.writeln(indStr, "/++"); 1041 foreach (l; text.split("\n")) { 1042 if (l.empty) { 1043 _output.writeln(indStr, " +"); 1044 } 1045 else { 1046 _output.write(indStr, " + "); 1047 foreach (dchar c; l) { 1048 switch (c) { 1049 case '(': _output.write("$(LPAREN)"); break; 1050 case ')': _output.write("$(RPAREN)"); break; 1051 case '<': _output.write("<"); break; 1052 case '>': _output.write(">"); break; 1053 case '&': _output.write("&"); break; 1054 default: _output.write(c); break; 1055 } 1056 } 1057 _output.writeln(); 1058 } 1059 } 1060 _output.writeln(indStr, " +/"); 1061 } 1062 } 1063 1064 1065 void bracedBlock(alias writeF)(SourceFile sf) 1066 { 1067 sf.writeln("{"); 1068 sf.indent(); 1069 writeF(); 1070 sf.unindent(); 1071 sf.writeln("}"); 1072 } 1073 1074 void indentedBlock(alias writeF)(SourceFile sf) 1075 { 1076 sf.indent(); 1077 writeF(); 1078 sf.unindent(); 1079 } 1080 1081 1082 /// Build a camel name from components 1083 string buildCamelName(in string comp, in bool tit) pure 1084 { 1085 string name; 1086 bool cap = tit; 1087 foreach(char c; comp.toLower()) 1088 { 1089 if (c != '_') 1090 { 1091 name ~= cap ? c.toUpper() : c; 1092 cap = false; 1093 } 1094 else 1095 { 1096 cap = true; 1097 } 1098 } 1099 return name; 1100 } 1101 1102 string camelName(in string comp) pure 1103 { 1104 return buildCamelName(comp, false); 1105 } 1106 1107 string titleCamelName(in string comp) pure 1108 { 1109 return buildCamelName(comp, true); 1110 } 1111 1112 string camelName(in string[] comps...) pure 1113 { 1114 string name = buildCamelName(comps[0], false); 1115 foreach (c; comps[1 .. $]) 1116 { 1117 name ~= buildCamelName(c, true); 1118 } 1119 return name; 1120 } 1121 1122 string titleCamelName(in string[] comps...) pure 1123 { 1124 string name = buildCamelName(comps[0], true); 1125 foreach (c; comps[1 .. $]) 1126 { 1127 name ~= buildCamelName(c, true); 1128 } 1129 return name; 1130 } 1131 1132 enum charsPerIndent = 4; 1133 enum wrapWidth = 80; 1134 1135 string indentStr(int indent) pure 1136 { 1137 return ' '.repeat(indent * charsPerIndent).array(); 1138 } 1139 1140 string qualfiedTypeName(in string name) pure 1141 { 1142 return name 1143 .splitter(".") 1144 .map!(part => titleCamelName(part)) 1145 .join("."); 1146 } 1147 1148 string splitLinesForWidth(string input, in string suffix, in string indent, 1149 in size_t width=wrapWidth) pure 1150 { 1151 string res; 1152 size_t w; 1153 foreach(i, word; input.split(" ")) 1154 { 1155 if (i != 0) 1156 { 1157 string spacer = " "; 1158 if (w + word.length + suffix.length >= width) 1159 { 1160 spacer = suffix ~ "\n" ~ indent; 1161 w = indent.length; 1162 } 1163 res ~= spacer; 1164 } 1165 res ~= word; 1166 w += word.length; 1167 } 1168 return res; 1169 }