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 }