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.algorithm;
689         import std.ascii;
690         import std.format;
691         import std..string;
692 
693         auto lines = lineSplitter!(KeepTerminator.yes)(comment.content);
694 
695         if (!lines.empty)
696         {
697             size_t indentAmount = comment.indentAmount;
698 
699             if (lastestLine != comment.line)
700                 indent();
701 
702             buffer.put(lines.front);
703             lines.popFront;
704 
705             foreach (line; lines)
706             {
707                 if (line.all!isWhite)
708                 {
709                     buffer.put("\n");
710                 }
711                 else
712                 {
713                     indent();
714                     buffer.put(line[indentAmount .. $]);
715                 }
716             }
717         }
718     }
719 
720     private void comment(in CommentIndex.Comment comment)
721     {
722         if (lastestLine == comment.line)
723         {
724             buffer.put(" ");
725         }
726         else if (stack.back != Entity.bottom)
727         {
728             if (lastestLine + 1 < comment.line ||
729                 (stack.back != Entity.singleLine &&
730                 stack.back != Entity.adaptiveLine &&
731                 stack.back != Entity.comment))
732                 buffer.put('\n');
733 
734             buffer.put('\n');
735         }
736 
737         writeComment(comment);
738 
739         stack.back = Entity.comment;
740 
741         if (first == Entity.bottom)
742             first = Entity.comment;
743 
744         lastestLine = comment.extent.end.line;
745         lastestOffset = comment.extent.end.offset;
746     }
747 
748     private void flushComments(uint offset)
749     {
750         if (lastestOffset < offset && commentIndex)
751         {
752             auto comments = commentIndex.queryComments(lastestOffset, offset);
753 
754             foreach (c; comments)
755                 comment(c);
756 
757             lastestOffset = offset;
758         }
759     }
760 
761     public void finalize()
762     {
763         if (!buffer.data.empty)
764         {
765             buffer.put("\n");
766 
767             if (stack.back == Entity.separator)
768                 buffer.put("\n");
769         }
770     }
771 
772     private void flushLocationBegin(
773         uint beginLine,
774         uint beginColumn,
775         uint beginOffset,
776         bool separate = true)
777     {
778         flushComments(beginOffset);
779 
780         if (separate && lastestLine + 1 < beginLine)
781             separator();
782     }
783 
784     private void flushLocationEnd(
785         uint endLine,
786         uint endColumn,
787         uint endOffset)
788     {
789         flushComments(endOffset);
790 
791         import std.algorithm.comparison;
792 
793         lastestLine = max(endLine, lastestLine);
794         lastestOffset = max(endOffset, lastestOffset);
795     }
796 
797     public void flushLocation(
798         uint beginLine,
799         uint beginColumn,
800         uint beginOffset,
801         uint endLine,
802         uint endColumn,
803         uint endOffset,
804         bool separate = true)
805     {
806         flushLocationBegin(beginLine, beginColumn, beginOffset, separate);
807         flushLocationEnd(endLine, endColumn, endOffset);
808     }
809 
810     public void flushLocation(
811         uint line,
812         uint column,
813         uint offset,
814         bool separate = true)
815     {
816         flushLocation(line, column, offset, line, column, offset, separate);
817     }
818 
819     public void flushLocation(in SourceLocation location, bool separate = true)
820     {
821         flushLocation(
822             location.line,
823             location.column,
824             location.offset,
825             separate);
826     }
827 
828     public void flushLocation(in SourceRange range, bool separate = true)
829     {
830         SourceLocation begin = range.start;
831         SourceLocation end = range.end;
832 
833         flushLocation(
834             begin.line,
835             begin.column,
836             begin.offset,
837             end.line,
838             end.column,
839             end.offset,
840             separate);
841     }
842 
843     public void flushLocation(in Cursor cursor, bool separate = true)
844     {
845         flushLocation(cursor.extent, separate);
846     }
847 
848     public bool flushHeaderComment()
849     {
850         if (commentIndex && commentIndex.isHeaderCommentPresent)
851         {
852             auto location = commentIndex.queryHeaderCommentExtent.end;
853             headerEndOffset = location.offset + 2;
854             flushLocation(location, false);
855             return true;
856         }
857         else
858         {
859             return false;
860         }
861     }
862 
863     public string data(string suffix = "")
864     {
865         if (!suffix.empty)
866             return (buffer.data() ~ suffix).idup;
867         else
868             return buffer.data().idup;
869     }
870 
871     public string header()
872     {
873         import std.algorithm.comparison;
874 
875         return buffer.data[0 .. min(headerEndOffset, $)].idup;
876     }
877 
878     public string content()
879     {
880         import std.algorithm.comparison;
881 
882         return buffer.data[min(headerEndOffset, $) .. $].idup;
883     }
884 
885     struct Indent
886     {
887         private Output output;
888         private string sentinel;
889         private uint line = 0;
890         private uint column = 0;
891         private uint offset = 0;
892 
893         ~this()
894         {
895             if (output.stack.length > 1 &&
896                 output.stack[$ - 2] == Entity.adaptiveLine)
897             {
898                 if (offset != 0)
899                     output.flushLocationEnd(line, column, offset);
900 
901                 output.stack.popBack();
902 
903                 auto data = output.chunks.data;
904 
905                 if (data.back.type == ChunkType.opening)
906                 {
907                     data.back.type = ChunkType.item;
908                     data.back.content ~= sentinel;
909                 }
910                 else
911                 {
912                     output.chunks.put(Chunk(ChunkType.closing, sentinel));
913                 }
914 
915                 if (output.stack.length == 1 ||
916                     output.stack[$ - 2] != Entity.adaptiveLine)
917                     output.resolveAdaptive();
918             }
919             else
920             {
921                 bool flushed = output.flush(false);
922 
923                 if (offset != 0)
924                     output.flushLocationEnd(line, column, offset);
925 
926                 auto back = output.stack.back;
927                 output.stack.popBack();
928 
929                 if (!sentinel.empty && !flushed)
930                 {
931                     if (back != Entity.bottom)
932                         output.buffer.put("\n");
933 
934                     output.indent();
935                     output.buffer.put(sentinel);
936                 }
937             }
938         }
939 
940         void opBinary(string op : "in")(void delegate () nested)
941         {
942             nested();
943         }
944     }
945 
946     private bool flush(bool brace = true)
947     {
948         // Handle a case when there is only line in sub-scope.
949         // When there is only single line, no braces should be put.
950 
951         if (!weak.data.empty)
952         {
953             string weak = this.weak.data.idup;
954             this.weak.clear();
955 
956             if (brace)
957             {
958                 indent(-1);
959                 buffer.put("{\n");
960             }
961 
962             stack.back = Entity.singleLine;
963             singleLineImpl(weak);
964             weak = null;
965 
966             return true;
967         }
968         else if (stack.length > 1 &&
969             stack[$ - 2] == Entity.subscopeWeak &&
970             stack.back == Entity.bottom &&
971             brace)
972         {
973             indent(-1);
974             buffer.put("{\n");
975         }
976 
977         return false;
978     }
979 
980     private Tuple!(size_t, size_t) resolveAdaptiveWidth(size_t itr)
981     {
982         auto data = chunks.data;
983         string[] separators = [ data[itr].separator ];
984         size_t jtr = itr + 1;
985         size_t width = data[itr].content.length;
986 
987         size_t separator(size_t jtr, size_t width)
988         {
989             if (jtr + 1 < data.length &&
990                 separators.length != 0 &&
991                 data[jtr + 1].type != ChunkType.closing &&
992                 width != 0)
993                 return separators.back.length + 1;
994             else
995                 return 0;
996         }
997 
998         while (separators.length != 0)
999         {
1000             final switch (data[jtr].type)
1001             {
1002                 case ChunkType.opening:
1003                     width += data[jtr].content.length;
1004                     separators ~= data[jtr].separator;
1005                     break;
1006 
1007                 case ChunkType.closing:
1008                     separators = separators[0 .. $ - 1];
1009                     width +=
1010                         data[jtr].content.length +
1011                         separator(jtr, width);
1012                     break;
1013 
1014                 case ChunkType.item:
1015                     width +=
1016                         data[jtr].content.length +
1017                         separator(jtr, width);
1018                     break;
1019             }
1020 
1021             ++jtr;
1022         }
1023 
1024         return tuple(width, jtr);
1025     }
1026 
1027     private void resolveAdaptiveAppend(size_t itr)
1028     {
1029         auto data = chunks.data;
1030         string[] separators = [ data[itr].separator ];
1031         size_t jtr = itr + 1;
1032 
1033         buffer.put(data[itr].content);
1034 
1035         while (separators.length != 0)
1036         {
1037             final switch (data[jtr].type)
1038             {
1039                 case ChunkType.opening:
1040                     buffer.put(data[jtr].content);
1041                     separators ~= data[jtr].separator;
1042                     break;
1043 
1044                 case ChunkType.closing:
1045                     separators = separators[0 .. $ - 1];
1046                     buffer.put(data[jtr].content);
1047 
1048                     if (jtr + 1 < data.length && separators.length != 0 &&
1049                         data[jtr + 1].type != ChunkType.closing)
1050                     {
1051                         buffer.put(separators.back);
1052                         buffer.put(" ");
1053                     }
1054 
1055                     break;
1056 
1057                 case ChunkType.item:
1058                     buffer.put(data[jtr].content);
1059 
1060                     if (jtr + 1 < data.length && separators.length != 0 &&
1061                         data[jtr + 1].type != ChunkType.closing)
1062                     {
1063                         buffer.put(separators.back);
1064                         buffer.put(" ");
1065                     }
1066 
1067                     break;
1068             }
1069 
1070             ++jtr;
1071         }
1072     }
1073 
1074     private void resolveAdaptive()
1075     {
1076         string[] separators;
1077         size_t amount = (stack.length - 1) * indentSize;
1078         size_t itr = 0;
1079 
1080         auto data = chunks.data;
1081         auto feed = "";
1082 
1083         while (itr < data.length)
1084         {
1085             if (data[itr].type == ChunkType.opening)
1086             {
1087                 auto tuple = resolveAdaptiveWidth(itr);
1088 
1089                 size_t width =
1090                     amount + separators.length * indentSize + tuple[0];
1091 
1092                 if (width < marginSize)
1093                 {
1094                     buffer.put(feed);
1095                     indent(separators.length);
1096                     resolveAdaptiveAppend(itr);
1097 
1098                     if (tuple[1] < data.length &&
1099                         data[tuple[1]].type != ChunkType.closing)
1100                         buffer.put(separators.back);
1101 
1102                     itr = tuple[1];
1103                 }
1104                 else
1105                 {
1106                     buffer.put(feed);
1107                     indent(separators.length);
1108                     buffer.put(data[itr].content);
1109                     separators ~= data[itr].separator;
1110 
1111                     ++itr;
1112                 }
1113 
1114                 feed = "\n";
1115             }
1116             else if (data[itr].type == ChunkType.closing)
1117             {
1118                 separators = separators[0..$-1];
1119                 buffer.put(data[itr].content);
1120 
1121                 if (itr + 1 < data.length &&
1122                     data[itr + 1].type != ChunkType.closing &&
1123                     separators.length != 0)
1124                     buffer.put(separators.back);
1125 
1126                 ++itr;
1127 
1128                 feed = "\n";
1129             }
1130             else
1131             {
1132                 buffer.put(feed);
1133                 indent(separators.length);
1134 
1135                 if (!data[itr].content.empty)
1136                 {
1137                     buffer.put(data[itr].content);
1138 
1139                     if (itr + 1 < data.length &&
1140                         data[itr + 1].type != ChunkType.closing)
1141                         buffer.put(separators.back);
1142 
1143                     feed = "\n";
1144                 }
1145 
1146                 ++itr;
1147             }
1148         }
1149 
1150         chunks = appender!(Chunk[])();
1151     }
1152 
1153     private void indent()
1154     {
1155         foreach (x; 0 .. stack.length - 1)
1156             buffer.put("    ");
1157     }
1158 
1159     private void indent(int shift)
1160     {
1161         foreach (x; 0 .. stack.length - 1 + shift)
1162             buffer.put("    ");
1163     }
1164 
1165     private void indent(size_t shift)
1166     {
1167         foreach (x; 0 .. stack.length - 1 + shift)
1168             buffer.put("    ");
1169     }
1170 }
1171 
1172 class StructData
1173 {
1174     string name;
1175     protected Context context;
1176 
1177     Output[] instanceVariables;
1178 
1179     bool isFwdDeclaration;
1180 
1181     this(Context context)
1182     {
1183         this.context = context;
1184     }
1185 
1186     @property Output data ()
1187     {
1188         import std.format : format;
1189 
1190         Output output = new Output();
1191 
1192         if (name.length)
1193             name = ' ' ~ name;
1194 
1195         if (isFwdDeclaration)
1196         {
1197             isFwdDeclaration = true;
1198             output.singleLine("%s%s;", type, name);
1199             return output;
1200         }
1201         else
1202         {
1203             output.subscopeStrong("%s%s", type, name) in {
1204                 addDeclarations(output, instanceVariables);
1205             };
1206 
1207             return output;
1208         }
1209     }
1210 
1211 protected:
1212 
1213     @property string type ()
1214     {
1215         return "struct";
1216     }
1217 
1218     void addDeclarations (Output output, Output[] declarations)
1219     {
1220         foreach (i, e ; declarations)
1221             output.output(e);
1222     }
1223 }
1224 
1225 class ClassData : StructData
1226 {
1227     Output[] members;
1228 
1229     string name;
1230     string superclass;
1231     string[] interfaces;
1232 
1233     Set!string propertyList;
1234 
1235     private Set!string mangledMethods;
1236 
1237     this (Context context)
1238     {
1239         super(context);
1240     }
1241 
1242     string getMethodName (FunctionCursor func, string name = "", bool translateIdentifier = true)
1243     {
1244         import std.range : empty;
1245 
1246         auto mangledName = mangle(func, name);
1247         auto selector = func.spelling;
1248 
1249         if (!(mangledName in mangledMethods))
1250         {
1251             mangledMethods.add(mangledName);
1252             name = name.empty ? selector : name;
1253             return translateSelector(name, false, translateIdentifier);
1254         }
1255 
1256         return translateSelector(name, true, translateIdentifier);
1257     }
1258 
1259     private string mangle (FunctionCursor func, string name)
1260     {
1261         import std.range : empty;
1262         auto selector = func.spelling;
1263         name = name.empty ? translateSelector(selector) : name;
1264         auto mangledName = name;
1265 
1266         foreach (param ; func.parameters)
1267             mangledName ~= translateType(context, param).prefixWith("_").makeString();
1268 
1269         return mangledName;
1270     }
1271 
1272     @property override Output data ()
1273     {
1274         import std.format;
1275         import std.array;
1276 
1277         auto header = appender!string();
1278 
1279         formattedWrite(
1280             header,
1281             "%s %s",
1282             type,
1283             name);
1284 
1285         if (superclass.length)
1286         {
1287             header.put(" : ");
1288             header.put(superclass);
1289         }
1290 
1291         writeInterfaces(header);
1292 
1293         Output output = new Output();
1294 
1295         output.subscopeStrong(header.data) in {
1296             writeMembers(output);
1297         };
1298 
1299         return output;
1300     }
1301 
1302     override protected @property string type ()
1303     {
1304         return "class";
1305     }
1306 
1307 private:
1308 
1309     void writeInterfaces (ref Appender!string header)
1310     {
1311         import std.range : empty;
1312 
1313         if (interfaces.length)
1314         {
1315             if (superclass.empty)
1316                 header.put(" : ");
1317 
1318             foreach (i, s ; interfaces)
1319             {
1320                 if (i != 0)
1321                     header.put(", ");
1322 
1323                 header.put(s);
1324             }
1325         }
1326     }
1327 
1328     void writeMembers (Output output)
1329     {
1330         addDeclarations(output, members);
1331     }
1332 }
1333 
1334 class InterfaceData : ClassData
1335 {
1336     this(Context context)
1337     {
1338         super(context);
1339     }
1340 
1341     protected @property override string type ()
1342     {
1343         return "interface";
1344     }
1345 }
1346 
1347 class ClassExtensionData : ClassData
1348 {
1349     this(Context context)
1350     {
1351         super(context);
1352     }
1353 
1354     protected @property override string type ()
1355     {
1356         return "__classext";
1357     }
1358 }