1 /** 2 * Copyright: Copyright (c) 2012 Jacob Carlborg. All rights reserved. 3 * Authors: Jacob Carlborg 4 * Version: Initial created: Jan 29, 2012 5 * License: $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost Software License 1.0) 6 */ 7 module dstep.translator.Output; 8 9 import std.array; 10 import std.typecons; 11 import std.variant; 12 13 import clang.Cursor; 14 import clang.SourceLocation; 15 import clang.SourceRange; 16 import clang.Util; 17 18 import dstep.translator.CommentIndex; 19 import dstep.translator.Context; 20 import dstep.translator.IncludeHandler; 21 import dstep.translator.Type; 22 23 struct SourceLeaf 24 { 25 string spelling; 26 SourceRange extent; 27 } 28 29 SourceLeaf makeSourceLeaf( 30 string spelling, 31 SourceRange extent = SourceRange.empty) 32 { 33 SourceLeaf result; 34 result.spelling = spelling; 35 result.extent = extent; 36 return result; 37 } 38 39 struct SourceNode 40 { 41 alias Child = Algebraic!(SourceNode*, SourceLeaf); 42 string prefix; 43 string suffix; 44 string separator; 45 Child[] children; 46 SourceRange extent; 47 } 48 49 SourceNode makeSourceNode( 50 string prefix, 51 string[] children, 52 string separator, 53 string suffix, 54 SourceRange extent = SourceRange.empty) 55 { 56 import std.algorithm; 57 SourceNode node; 58 node.prefix = prefix; 59 node.suffix = suffix; 60 node.separator = separator; 61 node.children = children.map!(x => SourceNode.Child(SourceLeaf(x))).array; 62 node.extent = extent; 63 return node; 64 } 65 66 SourceNode makeSourceNode(SourceLeaf leaf) 67 { 68 SourceNode node; 69 node.prefix = leaf.spelling; 70 node.extent = leaf.extent; 71 return node; 72 } 73 74 SourceNode makeSourceNode( 75 string spelling, 76 SourceRange extent = SourceRange.empty) 77 { 78 SourceNode node; 79 node.prefix = spelling; 80 node.extent = extent; 81 return node; 82 } 83 84 SourceNode suffixWith(SourceNode node, string suffix) 85 { 86 SourceNode result = node; 87 result.suffix = result.suffix ~ suffix; 88 return result; 89 } 90 91 SourceNode prefixWith(SourceNode node, string prefix) 92 { 93 SourceNode result = node; 94 result.prefix = prefix ~ result.prefix; 95 return result; 96 } 97 98 SourceNode wrapWith(SourceNode node, string prefix, string suffix) 99 { 100 SourceNode result = node; 101 result.prefix = prefix ~ result.prefix; 102 result.suffix = result.suffix ~ suffix; 103 return result; 104 } 105 106 SourceNode flatten(in SourceNode node) 107 { 108 void flatten(ref Appender!(char[]) output, in SourceNode node) 109 { 110 output.put(node.prefix); 111 112 foreach (index, child; node.children) 113 { 114 if (child.type() == typeid(SourceLeaf)) 115 output ~= child.get!(SourceLeaf).spelling; 116 else 117 flatten(output, *child.get!(SourceNode*)); 118 119 if (index + 1 != node.children.length) 120 output.put(node.separator ~ " "); 121 } 122 123 output.put(node.suffix); 124 } 125 126 Appender!(char[]) output; 127 flatten(output, node); 128 return output.data.idup.makeSourceNode(); 129 } 130 131 string makeString(in SourceNode node) 132 { 133 import std.range; 134 auto flattened = node.flatten(); 135 assert(flattened.children.empty); 136 assert(flattened.suffix.empty); 137 return flattened.prefix; 138 } 139 140 void adaptiveSourceNode(Output output, in SourceNode node) 141 { 142 auto format = node.prefix ~ "%@" ~ node.separator ~ "%@" ~ node.suffix; 143 144 output.adaptiveLine(node.extent, format) in 145 { 146 foreach (child; node.children) 147 { 148 if (child.type() == typeid(SourceLeaf)) 149 { 150 auto leaf = child.get!(SourceLeaf); 151 output.adaptiveLine(leaf.extent, leaf.spelling); 152 } 153 else 154 adaptiveSourceNode(output, *child.get!(SourceNode*)); 155 } 156 }; 157 } 158 159 unittest 160 { 161 SourceNode node = makeSourceNode( 162 "prefix(", [], ",", ");"); 163 164 assert(makeString(node) == "prefix();"); 165 } 166 167 unittest 168 { 169 SourceNode node = makeSourceNode( 170 "prefix(", ["a", "b"], ",", ");"); 171 172 assert(makeString(node) == "prefix(a, b);"); 173 } 174 175 unittest 176 { 177 SourceNode node = makeSourceNode("prefix(a, b);"); 178 179 assert(makeString(node) == "prefix(a, b);"); 180 } 181 182 class Output 183 { 184 private enum Entity 185 { 186 bottom, 187 separator, 188 comment, 189 singleLine, 190 adaptiveLine, 191 multiLine, 192 subscopeWeak, 193 subscopeStrong, 194 } 195 196 private enum ChunkType 197 { 198 opening, 199 closing, 200 item, 201 } 202 203 private struct Chunk 204 { 205 ChunkType type; 206 string content; 207 string separator; 208 } 209 210 const size_t marginSize = 80; 211 const size_t indentSize = 4; 212 private Appender!(char[]) buffer; 213 private Appender!(char[]) weak; 214 private Entity[] stack; 215 private Entity first = Entity.bottom; 216 private Appender!(Chunk[]) chunks; 217 private CommentIndex commentIndex = null; 218 219 private uint lastestOffset = 0; 220 private uint lastestLine = 0; 221 private uint headerEndOffset = 0; 222 223 this(Output parent) 224 { 225 stack ~= Entity.bottom; 226 227 // formattedWrite will not write anything 228 // to the output range if put was not invoked before. 229 buffer.put(""); 230 weak.put(""); 231 commentIndex = parent.commentIndex; 232 lastestOffset = parent.lastestOffset; 233 lastestLine = parent.lastestLine; 234 } 235 236 this( 237 CommentIndex commentIndex = null, 238 size_t marginSize = 80, 239 size_t indentSize = 4) 240 { 241 this.marginSize = marginSize; 242 this.indentSize = indentSize; 243 244 stack ~= Entity.bottom; 245 246 // formattedWrite will not write anything 247 // to the output range if put was not invoked before. 248 buffer.put(""); 249 weak.put(""); 250 251 this.commentIndex = commentIndex; 252 } 253 254 public void reset() 255 { 256 buffer.clear(); 257 weak.clear(); 258 259 // formattedWrite will not write anything 260 // to the output range if put was not invoked before. 261 buffer.put(""); 262 weak.put(""); 263 264 stack = [ Entity.bottom ]; 265 266 first = Entity.bottom; 267 chunks.clear(); 268 269 lastestOffset = 0; 270 lastestLine = 0; 271 headerEndOffset = 0; 272 } 273 274 public bool empty() 275 { 276 return stack.length == 1 && stack.back == Entity.bottom; 277 } 278 279 public void separator() 280 { 281 if (stack.back == Entity.singleLine || 282 stack.back == Entity.adaptiveLine || 283 stack.back == Entity.comment) 284 stack.back = Entity.separator; 285 } 286 287 public void output(Output output) 288 { 289 if (output.stack.length == 1 && output.stack.back == Entity.singleLine) 290 singleLine(output.data()); 291 else 292 { 293 import std..string : splitLines, KeepTerminator; 294 295 if (output.stack.back != Entity.bottom) 296 flush(); 297 298 if (stack.back != Entity.bottom && 299 output.stack.back != Entity.bottom) 300 buffer.put("\n"); 301 302 if (stack.back != Entity.bottom && 303 output.first != Entity.bottom && 304 (stack.back != Entity.singleLine || 305 output.first != Entity.singleLine) && 306 (stack.back != Entity.singleLine || 307 output.first != Entity.adaptiveLine) && 308 (stack.back != Entity.adaptiveLine || 309 output.first != Entity.adaptiveLine)) 310 buffer.put("\n"); 311 312 foreach (line; output.data().splitLines(KeepTerminator.yes)) 313 { 314 indent(); 315 buffer.put(line); 316 } 317 318 stack.popBack(); 319 stack ~= output.stack; 320 321 if (first == Entity.bottom) 322 first = output.first; 323 324 import std.algorithm.comparison; 325 326 lastestOffset = max(lastestOffset, output.lastestOffset); 327 lastestLine = max(lastestLine, output.lastestLine); 328 } 329 } 330 331 public void append(Char, Args...)(in Char[] fmt, Args args) 332 { 333 import std.format; 334 335 if (stack.back != Entity.singleLine && 336 stack.back != Entity.adaptiveLine) 337 singleLine(fmt, args); 338 else if (weak.data.empty) 339 formattedWrite(buffer, fmt, args); 340 else 341 formattedWrite(weak, fmt, args); 342 } 343 344 private void singleLineImpl(Char, Args...)(in Char[] fmt, Args args) 345 { 346 import std.format; 347 348 indent(); 349 formattedWrite(buffer, fmt, args); 350 stack.back = Entity.singleLine; 351 352 if (first == Entity.bottom) 353 first = Entity.singleLine; 354 } 355 356 public void singleLine(Char, Args...)(in Char[] fmt, Args args) 357 { 358 import std.format; 359 360 if (stack.length > 1 && 361 stack.back == Entity.bottom && 362 stack[$ - 2] == Entity.subscopeWeak) 363 { 364 // We are on the first line of weak sub-scope. 365 // Save the line for later handling. 366 formattedWrite(weak, fmt, args); 367 stack.back = Entity.singleLine; 368 } 369 else 370 { 371 flush(); 372 373 if (stack.back != Entity.singleLine && 374 stack.back != Entity.adaptiveLine && 375 stack.back != Entity.bottom && 376 stack.back != Entity.comment) 377 buffer.put("\n"); 378 379 if (stack.back != Entity.bottom) 380 buffer.put("\n"); 381 382 singleLineImpl(fmt, args); 383 } 384 } 385 386 public void singleLine(Char, Args...)( 387 in SourceRange extent, 388 in Char[] fmt, 389 Args args) 390 { 391 flushLocation(extent); 392 singleLine(fmt, args); 393 } 394 395 private Tuple!(string, string, string) adaptiveLineParts(Char, Args...)( 396 in Char[] fmt, 397 Args args) 398 { 399 import std.algorithm.searching; 400 import std.format : format; 401 402 size_t findPlaceholder(in Char[] fmt) 403 { 404 foreach (i; 0 .. fmt.length) 405 { 406 if (fmt[i] == '@' && i != 0) 407 { 408 size_t j = i; 409 410 while (i != 0 && fmt[i - 1] == '%') 411 --i; 412 413 if ((i - j) % 2 == 1) 414 return i; 415 } 416 } 417 418 return fmt.length; 419 } 420 421 size_t begin = findPlaceholder(fmt); 422 423 if (begin != fmt.length) 424 { 425 string separator, opening, closing; 426 size_t end = findPlaceholder(fmt[begin + 2 .. $]) + begin + 2; 427 428 if (end != fmt.length) 429 separator = fmt[begin + 2 .. end].idup; 430 else 431 end = begin; 432 433 size_t minUnique = count(fmt, '@') + 1; 434 auto sentinel = replicate("@", minUnique); 435 436 auto split = format( 437 fmt[0 .. begin] ~ 438 sentinel ~ 439 fmt[end + 2 .. $], 440 args).findSplit(sentinel); 441 442 return tuple(split[0], separator, split[2]); 443 } 444 else 445 { 446 return tuple(format(fmt, args), "", ""); 447 } 448 } 449 450 private string adaptiveLineImpl(Char, Args...)(in Char[] fmt, Args args) 451 { 452 if (stack.length != 1 && 453 stack[$ - 2] != Entity.adaptiveLine || 454 stack.length == 1) 455 { 456 if (stack.back != Entity.singleLine && 457 stack.back != Entity.adaptiveLine && 458 stack.back != Entity.bottom && 459 stack.back != Entity.comment) 460 buffer.put("\n"); 461 462 if (stack.back != Entity.bottom) 463 buffer.put("\n"); 464 } 465 466 stack.back = Entity.adaptiveLine; 467 stack ~= Entity.bottom; 468 469 if (first == Entity.bottom) 470 first = Entity.adaptiveLine; 471 472 auto parts = adaptiveLineParts(fmt, args); 473 474 chunks.put(Chunk(ChunkType.opening, parts[0], parts[1])); 475 return parts[2]; 476 } 477 478 public Indent adaptiveLine(Char, Args...)(in Char[] fmt, Args args) 479 { 480 return Indent(this, adaptiveLineImpl(fmt, args)); 481 } 482 483 /** 484 * adaptiveLine adds a line to the output that is automatically broken to 485 * multiple lines, if it's too long (80 characters). 486 * 487 * It takes a special format specifier that is replaced with other nested 488 * adaptive lines. The specifier has form `%@<separator>%@`, where 489 * <separator> is a string that separates the items/lines (the spaces and 490 * end-lines are added automatically). 491 */ 492 public Indent adaptiveLine(Char, Args...)( 493 in SourceRange extent, 494 in Char[] fmt, 495 Args args) 496 { 497 SourceLocation start = extent.start; 498 SourceLocation end = extent.end; 499 500 flushLocationBegin(start.line, start.column, start.offset); 501 502 return Indent( 503 this, 504 adaptiveLineImpl(fmt, args), 505 end.line, 506 end.column, 507 end.offset); 508 } 509 510 /// 511 unittest 512 { 513 // Simple item. 514 auto example1 = new Output(); 515 516 example1.adaptiveLine("foo"); 517 518 assert(example1.data() == "foo"); 519 520 // Inline function. 521 auto example2 = new Output(); 522 523 example2.adaptiveLine("void foo(%@,%@);") in { 524 example2.adaptiveLine("int bar"); 525 example2.adaptiveLine("int baz"); 526 }; 527 528 assert(example2.data() == "void foo(int bar, int baz);"); 529 530 // Broken function. 531 auto example3 = new Output(); 532 533 example3.adaptiveLine("void foo(%@,%@);") in { 534 example3.adaptiveLine("int bar0"); 535 example3.adaptiveLine("int bar1"); 536 example3.adaptiveLine("int bar2"); 537 example3.adaptiveLine("int bar3"); 538 example3.adaptiveLine("int bar4"); 539 example3.adaptiveLine("int bar5"); 540 example3.adaptiveLine("int bar6"); 541 example3.adaptiveLine("int bar7"); 542 example3.adaptiveLine("int bar8"); 543 }; 544 545 assert(example3.data() == 546 q"D 547 void foo( 548 int bar0, 549 int bar1, 550 int bar2, 551 int bar3, 552 int bar4, 553 int bar5, 554 int bar6, 555 int bar7, 556 int bar8); 557 D"[0 .. $ - 1]); 558 559 // The adaptive lines can be nested multiple times. Breaking algorithm 560 // will try to break only the most outer levels. The %@<sep>%@ can be 561 // mixed with standard format specifiers. 562 auto example4 = new Output(); 563 564 example4.adaptiveLine("foo%d%s(%@,%@);", 123, "bar") in { 565 example4.adaptiveLine("bar0"); 566 example4.adaptiveLine("bar1"); 567 example4.adaptiveLine("bar2"); 568 example4.adaptiveLine("baz(%@ +%@)") in { 569 example4.adaptiveLine("0"); 570 example4.adaptiveLine("1"); 571 example4.adaptiveLine("2"); 572 example4.adaptiveLine("3"); 573 example4.adaptiveLine("4"); 574 }; 575 example4.adaptiveLine("bar4"); 576 example4.adaptiveLine("bar5"); 577 example4.adaptiveLine("bar6"); 578 example4.adaptiveLine("bar7"); 579 example4.adaptiveLine("bar8"); 580 }; 581 582 assert(example4.data() == 583 q"D 584 foo123bar( 585 bar0, 586 bar1, 587 bar2, 588 baz(0 + 1 + 2 + 3 + 4), 589 bar4, 590 bar5, 591 bar6, 592 bar7, 593 bar8); 594 D"[0 .. $ - 1]); 595 596 } 597 598 public Indent multiLine(Char, Args...)(in Char[] fmt, Args args) 599 { 600 import std.format; 601 602 flush(); 603 604 if (stack.back != Entity.bottom) 605 buffer.put("\n\n"); 606 607 indent(); 608 formattedWrite(buffer, fmt, args); 609 buffer.put("\n"); 610 stack.back = Entity.multiLine; 611 stack ~= Entity.bottom; 612 613 if (first == Entity.bottom) 614 first = Entity.multiLine; 615 616 return Indent(this); 617 } 618 619 private void subscopeStrongImpl(Char, Args...)( 620 in Char[] fmt, 621 Args args) 622 { 623 import std.format; 624 625 flush(); 626 627 if (stack.back == Entity.comment) 628 buffer.put("\n"); 629 else if (stack.back != Entity.bottom) 630 buffer.put("\n\n"); 631 632 indent(); 633 formattedWrite(buffer, fmt, args); 634 buffer.put("\n"); 635 indent(); 636 buffer.put("{\n"); 637 stack.back = Entity.subscopeStrong; 638 stack ~= Entity.bottom; 639 640 if (first == Entity.bottom) 641 first = Entity.subscopeStrong; 642 } 643 644 public Indent subscopeStrong(Char, Args...)( 645 in Char[] fmt, 646 Args args) 647 { 648 subscopeStrongImpl!(Char, Args)(fmt, args); 649 return Indent(this, "}"); 650 } 651 652 public Indent subscopeStrong(Char, Args...)( 653 in SourceRange extent, 654 in Char[] fmt, 655 Args args) 656 { 657 SourceLocation start = extent.start; 658 SourceLocation end = extent.end; 659 660 flushLocationBegin(start.line, start.column, start.offset); 661 subscopeStrongImpl(fmt, args); 662 return Indent(this, "}", end.line, end.column, end.offset); 663 } 664 665 public Indent subscopeWeak(Char, Args...)(in Char[] fmt, Args args) 666 { 667 import std.format; 668 669 flush(); 670 671 if (stack.back != Entity.bottom) 672 buffer.put("\n\n"); 673 674 indent(); 675 formattedWrite(buffer, fmt, args); 676 buffer.put("\n"); 677 stack.back = Entity.subscopeWeak; 678 stack ~= Entity.bottom; 679 680 if (first == Entity.bottom) 681 first = Entity.subscopeWeak; 682 683 return Indent(this, "}"); 684 } 685 686 private void writeComment(in CommentIndex.Comment comment) 687 { 688 import std..string; 689 import std.format; 690 691 auto lines = lineSplitter!(KeepTerminator.yes)(comment.content); 692 693 if (!lines.empty) 694 { 695 size_t indentAmount = comment.indentAmount; 696 697 if (lastestLine != comment.line) 698 indent(); 699 700 buffer.put(lines.front); 701 lines.popFront; 702 703 foreach (line; lines) 704 { 705 indent(); 706 buffer.put(line[indentAmount .. $]); 707 } 708 } 709 } 710 711 private void comment(in CommentIndex.Comment comment) 712 { 713 if (lastestLine == comment.line) 714 { 715 buffer.put(" "); 716 } 717 else if (stack.back != Entity.bottom) 718 { 719 if (lastestLine + 1 < comment.line || 720 (stack.back != Entity.singleLine && 721 stack.back != Entity.adaptiveLine && 722 stack.back != Entity.comment)) 723 buffer.put('\n'); 724 725 buffer.put('\n'); 726 } 727 728 writeComment(comment); 729 730 stack.back = Entity.comment; 731 732 if (first == Entity.bottom) 733 first = Entity.comment; 734 735 lastestLine = comment.extent.end.line; 736 lastestOffset = comment.extent.end.offset; 737 } 738 739 private void flushComments(uint offset) 740 { 741 if (lastestOffset < offset && commentIndex) 742 { 743 auto comments = commentIndex.queryComments(lastestOffset, offset); 744 745 foreach (c; comments) 746 comment(c); 747 748 lastestOffset = offset; 749 } 750 } 751 752 public void finalize() 753 { 754 if (!buffer.data.empty) 755 { 756 buffer.put("\n"); 757 758 if (stack.back == Entity.separator) 759 buffer.put("\n"); 760 } 761 } 762 763 private void flushLocationBegin( 764 uint beginLine, 765 uint beginColumn, 766 uint beginOffset, 767 bool separate = true) 768 { 769 flushComments(beginOffset); 770 771 if (separate && lastestLine + 1 < beginLine) 772 separator(); 773 } 774 775 private void flushLocationEnd( 776 uint endLine, 777 uint endColumn, 778 uint endOffset) 779 { 780 flushComments(endOffset); 781 782 import std.algorithm.comparison; 783 784 lastestLine = max(endLine, lastestLine); 785 lastestOffset = max(endOffset, lastestOffset); 786 } 787 788 public void flushLocation( 789 uint beginLine, 790 uint beginColumn, 791 uint beginOffset, 792 uint endLine, 793 uint endColumn, 794 uint endOffset, 795 bool separate = true) 796 { 797 flushLocationBegin(beginLine, beginColumn, beginOffset, separate); 798 flushLocationEnd(endLine, endColumn, endOffset); 799 } 800 801 public void flushLocation( 802 uint line, 803 uint column, 804 uint offset, 805 bool separate = true) 806 { 807 flushLocation(line, column, offset, line, column, offset, separate); 808 } 809 810 public void flushLocation(in SourceLocation location, bool separate = true) 811 { 812 flushLocation( 813 location.line, 814 location.column, 815 location.offset, 816 separate); 817 } 818 819 public void flushLocation(in SourceRange range, bool separate = true) 820 { 821 SourceLocation begin = range.start; 822 SourceLocation end = range.end; 823 824 flushLocation( 825 begin.line, 826 begin.column, 827 begin.offset, 828 end.line, 829 end.column, 830 end.offset, 831 separate); 832 } 833 834 public void flushLocation(in Cursor cursor, bool separate = true) 835 { 836 flushLocation(cursor.extent, separate); 837 } 838 839 public bool flushHeaderComment() 840 { 841 if (commentIndex && commentIndex.isHeaderCommentPresent) 842 { 843 auto location = commentIndex.queryHeaderCommentExtent.end; 844 headerEndOffset = location.offset + 2; 845 flushLocation(location, false); 846 return true; 847 } 848 else 849 { 850 return false; 851 } 852 } 853 854 public string data(string suffix = "") 855 { 856 if (!suffix.empty) 857 return (buffer.data() ~ suffix).idup; 858 else 859 return buffer.data().idup; 860 } 861 862 public string header() 863 { 864 import std.algorithm.comparison; 865 866 return buffer.data[0 .. min(headerEndOffset, $)].idup; 867 } 868 869 public string content() 870 { 871 import std.algorithm.comparison; 872 873 return buffer.data[min(headerEndOffset, $) .. $].idup; 874 } 875 876 struct Indent 877 { 878 private Output output; 879 private string sentinel; 880 private uint line = 0; 881 private uint column = 0; 882 private uint offset = 0; 883 884 ~this() 885 { 886 if (output.stack.length > 1 && 887 output.stack[$ - 2] == Entity.adaptiveLine) 888 { 889 if (offset != 0) 890 output.flushLocationEnd(line, column, offset); 891 892 output.stack.popBack(); 893 894 auto data = output.chunks.data; 895 896 if (data.back.type == ChunkType.opening) 897 { 898 data.back.type = ChunkType.item; 899 data.back.content ~= sentinel; 900 } 901 else 902 { 903 output.chunks.put(Chunk(ChunkType.closing, sentinel)); 904 } 905 906 if (output.stack.length == 1 || 907 output.stack[$ - 2] != Entity.adaptiveLine) 908 output.resolveAdaptive(); 909 } 910 else 911 { 912 bool flushed = output.flush(false); 913 914 if (offset != 0) 915 output.flushLocationEnd(line, column, offset); 916 917 auto back = output.stack.back; 918 output.stack.popBack(); 919 920 if (!sentinel.empty && !flushed) 921 { 922 if (back != Entity.bottom) 923 output.buffer.put("\n"); 924 925 output.indent(); 926 output.buffer.put(sentinel); 927 } 928 } 929 } 930 931 void opIn (void delegate () nested) 932 { 933 nested(); 934 } 935 } 936 937 private bool flush(bool brace = true) 938 { 939 // Handle a case when there is only line in sub-scope. 940 // When there is only single line, no braces should be put. 941 942 if (!weak.data.empty) 943 { 944 string weak = this.weak.data.idup; 945 this.weak.clear(); 946 947 if (brace) 948 { 949 indent(-1); 950 buffer.put("{\n"); 951 } 952 953 stack.back = Entity.singleLine; 954 singleLineImpl(weak); 955 weak = null; 956 957 return true; 958 } 959 else if (stack.length > 1 && 960 stack[$ - 2] == Entity.subscopeWeak && 961 stack.back == Entity.bottom && 962 brace) 963 { 964 indent(-1); 965 buffer.put("{\n"); 966 } 967 968 return false; 969 } 970 971 private Tuple!(size_t, size_t) resolveAdaptiveWidth(size_t itr) 972 { 973 auto data = chunks.data; 974 string[] separators = [ data[itr].separator ]; 975 size_t jtr = itr + 1; 976 size_t width = data[itr].content.length; 977 978 size_t separator(size_t jtr, size_t width) 979 { 980 if (jtr + 1 < data.length && 981 separators.length != 0 && 982 data[jtr + 1].type != ChunkType.closing && 983 width != 0) 984 return separators.back.length + 1; 985 else 986 return 0; 987 } 988 989 while (separators.length != 0) 990 { 991 final switch (data[jtr].type) 992 { 993 case ChunkType.opening: 994 width += data[jtr].content.length; 995 separators ~= data[jtr].separator; 996 break; 997 998 case ChunkType.closing: 999 separators = separators[0 .. $ - 1]; 1000 width += 1001 data[jtr].content.length + 1002 separator(jtr, width); 1003 break; 1004 1005 case ChunkType.item: 1006 width += 1007 data[jtr].content.length + 1008 separator(jtr, width); 1009 break; 1010 } 1011 1012 ++jtr; 1013 } 1014 1015 return tuple(width, jtr); 1016 } 1017 1018 private void resolveAdaptiveAppend(size_t itr) 1019 { 1020 auto data = chunks.data; 1021 string[] separators = [ data[itr].separator ]; 1022 size_t jtr = itr + 1; 1023 1024 buffer.put(data[itr].content); 1025 1026 while (separators.length != 0) 1027 { 1028 final switch (data[jtr].type) 1029 { 1030 case ChunkType.opening: 1031 buffer.put(data[jtr].content); 1032 separators ~= data[jtr].separator; 1033 break; 1034 1035 case ChunkType.closing: 1036 separators = separators[0 .. $ - 1]; 1037 buffer.put(data[jtr].content); 1038 1039 if (jtr + 1 < data.length && separators.length != 0 && 1040 data[jtr + 1].type != ChunkType.closing) 1041 { 1042 buffer.put(separators.back); 1043 buffer.put(" "); 1044 } 1045 1046 break; 1047 1048 case ChunkType.item: 1049 buffer.put(data[jtr].content); 1050 1051 if (jtr + 1 < data.length && separators.length != 0 && 1052 data[jtr + 1].type != ChunkType.closing) 1053 { 1054 buffer.put(separators.back); 1055 buffer.put(" "); 1056 } 1057 1058 break; 1059 } 1060 1061 ++jtr; 1062 } 1063 } 1064 1065 private void resolveAdaptive() 1066 { 1067 string[] separators; 1068 size_t amount = (stack.length - 1) * indentSize; 1069 size_t itr = 0; 1070 1071 auto data = chunks.data; 1072 auto feed = ""; 1073 1074 while (itr < data.length) 1075 { 1076 if (data[itr].type == ChunkType.opening) 1077 { 1078 auto tuple = resolveAdaptiveWidth(itr); 1079 1080 size_t width = 1081 amount + separators.length * indentSize + tuple[0]; 1082 1083 if (width < marginSize) 1084 { 1085 buffer.put(feed); 1086 indent(separators.length); 1087 resolveAdaptiveAppend(itr); 1088 1089 if (tuple[1] < data.length && 1090 data[tuple[1]].type != ChunkType.closing) 1091 buffer.put(separators.back); 1092 1093 itr = tuple[1]; 1094 } 1095 else 1096 { 1097 buffer.put(feed); 1098 indent(separators.length); 1099 buffer.put(data[itr].content); 1100 separators ~= data[itr].separator; 1101 1102 ++itr; 1103 } 1104 1105 feed = "\n"; 1106 } 1107 else if (data[itr].type == ChunkType.closing) 1108 { 1109 separators = separators[0..$-1]; 1110 buffer.put(data[itr].content); 1111 1112 if (itr + 1 < data.length && 1113 data[itr + 1].type != ChunkType.closing && 1114 separators.length != 0) 1115 buffer.put(separators.back); 1116 1117 ++itr; 1118 1119 feed = "\n"; 1120 } 1121 else 1122 { 1123 buffer.put(feed); 1124 indent(separators.length); 1125 1126 if (!data[itr].content.empty) 1127 { 1128 buffer.put(data[itr].content); 1129 1130 if (itr + 1 < data.length && 1131 data[itr + 1].type != ChunkType.closing) 1132 buffer.put(separators.back); 1133 1134 feed = "\n"; 1135 } 1136 1137 ++itr; 1138 } 1139 } 1140 1141 chunks = appender!(Chunk[])(); 1142 } 1143 1144 private void indent() 1145 { 1146 foreach (x; 0 .. stack.length - 1) 1147 buffer.put(" "); 1148 } 1149 1150 private void indent(int shift) 1151 { 1152 foreach (x; 0 .. stack.length - 1 + shift) 1153 buffer.put(" "); 1154 } 1155 1156 private void indent(size_t shift) 1157 { 1158 foreach (x; 0 .. stack.length - 1 + shift) 1159 buffer.put(" "); 1160 } 1161 } 1162 1163 class StructData 1164 { 1165 string name; 1166 protected Context context; 1167 1168 Output[] instanceVariables; 1169 1170 bool isFwdDeclaration; 1171 1172 this(Context context) 1173 { 1174 this.context = context; 1175 } 1176 1177 @property Output data () 1178 { 1179 import std.format : format; 1180 1181 Output output = new Output(); 1182 1183 if (name.length) 1184 name = ' ' ~ name; 1185 1186 if (isFwdDeclaration) 1187 { 1188 isFwdDeclaration = true; 1189 output.singleLine("%s%s;", type, name); 1190 return output; 1191 } 1192 else 1193 { 1194 output.subscopeStrong("%s%s", type, name) in { 1195 addDeclarations(output, instanceVariables); 1196 }; 1197 1198 return output; 1199 } 1200 } 1201 1202 protected: 1203 1204 @property string type () 1205 { 1206 return "struct"; 1207 } 1208 1209 void addDeclarations (Output output, Output[] declarations) 1210 { 1211 foreach (i, e ; declarations) 1212 output.output(e); 1213 } 1214 } 1215 1216 class ClassData : StructData 1217 { 1218 Output[] members; 1219 1220 string name; 1221 string superclass; 1222 string[] interfaces; 1223 1224 Set!string propertyList; 1225 1226 private Set!string mangledMethods; 1227 1228 this (Context context) 1229 { 1230 super(context); 1231 } 1232 1233 string getMethodName (FunctionCursor func, string name = "", bool translateIdentifier = true) 1234 { 1235 import std.range : empty; 1236 1237 auto mangledName = mangle(func, name); 1238 auto selector = func.spelling; 1239 1240 if (!(mangledName in mangledMethods)) 1241 { 1242 mangledMethods.add(mangledName); 1243 name = name.empty ? selector : name; 1244 return translateSelector(name, false, translateIdentifier); 1245 } 1246 1247 return translateSelector(name, true, translateIdentifier); 1248 } 1249 1250 private string mangle (FunctionCursor func, string name) 1251 { 1252 import std.range : empty; 1253 auto selector = func.spelling; 1254 name = name.empty ? translateSelector(selector) : name; 1255 auto mangledName = name; 1256 1257 foreach (param ; func.parameters) 1258 mangledName ~= translateType(context, param).prefixWith("_").makeString(); 1259 1260 return mangledName; 1261 } 1262 1263 @property override Output data () 1264 { 1265 import std.format; 1266 import std.array; 1267 1268 auto header = appender!string(); 1269 1270 formattedWrite( 1271 header, 1272 "%s %s", 1273 type, 1274 name); 1275 1276 if (superclass.length) 1277 { 1278 header.put(" : "); 1279 header.put(superclass); 1280 } 1281 1282 writeInterfaces(header); 1283 1284 Output output = new Output(); 1285 1286 output.subscopeStrong(header.data) in { 1287 writeMembers(output); 1288 }; 1289 1290 return output; 1291 } 1292 1293 override protected @property string type () 1294 { 1295 return "class"; 1296 } 1297 1298 private: 1299 1300 void writeInterfaces (ref Appender!string header) 1301 { 1302 import std.range : empty; 1303 1304 if (interfaces.length) 1305 { 1306 if (superclass.empty) 1307 header.put(" : "); 1308 1309 foreach (i, s ; interfaces) 1310 { 1311 if (i != 0) 1312 header.put(", "); 1313 1314 header.put(s); 1315 } 1316 } 1317 } 1318 1319 void writeMembers (Output output) 1320 { 1321 addDeclarations(output, members); 1322 } 1323 } 1324 1325 class InterfaceData : ClassData 1326 { 1327 this(Context context) 1328 { 1329 super(context); 1330 } 1331 1332 protected @property override string type () 1333 { 1334 return "interface"; 1335 } 1336 } 1337 1338 class ClassExtensionData : ClassData 1339 { 1340 this(Context context) 1341 { 1342 super(context); 1343 } 1344 1345 protected @property override string type () 1346 { 1347 return "__classext"; 1348 } 1349 }