1 
2 //          Copyright Brian Schott 2015.
3 // Distributed under the Boost Software License, Version 1.0.
4 //    (See accompanying file LICENSE.txt or copy at
5 //          http://www.boost.org/LICENSE_1_0.txt)
6 
7 module dfmt.formatter;
8 
9 import dparse.lexer;
10 import dparse.parser;
11 import dparse.rollback_allocator;
12 import dfmt.config;
13 import dfmt.ast_info;
14 import dfmt.indentation;
15 import dfmt.tokens;
16 import dfmt.wrapping;
17 import std.array;
18 
19 void format(OutputRange)(string source_desc, ubyte[] buffer, OutputRange output,
20         Config* formatterConfig)
21 {
22     LexerConfig config;
23     config.stringBehavior = StringBehavior.source;
24     config.whitespaceBehavior = WhitespaceBehavior.skip;
25     LexerConfig parseConfig;
26     parseConfig.stringBehavior = StringBehavior.source;
27     parseConfig.whitespaceBehavior = WhitespaceBehavior.skip;
28     StringCache cache = StringCache(StringCache.defaultBucketCount);
29     ASTInformation astInformation;
30     RollbackAllocator allocator;
31     auto parseTokens = getTokensForParser(buffer, parseConfig, &cache);
32     auto mod = parseModule(parseTokens, source_desc, &allocator);
33     auto visitor = new FormatVisitor(&astInformation);
34     visitor.visit(mod);
35     astInformation.cleanup();
36     auto tokens = byToken(buffer, config, &cache).array();
37     auto depths = generateDepthInfo(tokens);
38     auto tokenFormatter = TokenFormatter!OutputRange(buffer, tokens, depths,
39             output, &astInformation, formatterConfig);
40     tokenFormatter.format();
41 }
42 
43 immutable(short[]) generateDepthInfo(const Token[] tokens) pure nothrow @trusted
44 {
45     import std.exception : assumeUnique;
46 
47     short[] retVal = new short[](tokens.length);
48     short depth = 0;
49     foreach (i, ref t; tokens)
50     {
51         switch (t.type)
52         {
53         case tok!"[":
54             depth++;
55             goto case;
56         case tok!"{":
57         case tok!"(":
58             depth++;
59             break;
60         case tok!"]":
61             depth--;
62             goto case;
63         case tok!"}":
64         case tok!")":
65             depth--;
66             break;
67         default:
68             break;
69         }
70         retVal[i] = depth;
71     }
72     return cast(immutable) retVal;
73 }
74 
75 struct TokenFormatter(OutputRange)
76 {
77     /**
78      * Params:
79      *     rawSource = ?
80      *     tokens = the tokens to format
81      *     depths = ?
82      *     output = the output range that the code will be formatted to
83      *     astInformation = information about the AST used to inform formatting
84      *         decisions.
85      *     config = ?
86      */
87     this(const ubyte[] rawSource, const(Token)[] tokens, immutable short[] depths,
88             OutputRange output, ASTInformation* astInformation, Config* config)
89     {
90         this.rawSource = rawSource;
91         this.tokens = tokens;
92         this.depths = depths;
93         this.output = output;
94         this.astInformation = astInformation;
95         this.config = config;
96 
97         {
98             auto eol = config.end_of_line;
99             if (eol == eol.cr)
100                 this.eolString = "\r";
101             else if (eol == eol.lf)
102                 this.eolString = "\n";
103             else if (eol == eol.crlf)
104                 this.eolString = "\r\n";
105             else if (eol == eol.unspecified)
106                 assert(false, "config.end_of_line was unspecified");
107         }
108     }
109 
110     /// Runs the formatting process
111     void format()
112     {
113         while (index < tokens.length)
114             formatStep();
115     }
116 
117 private:
118 
119     /// Current indentation level
120     int indentLevel;
121 
122     /// Current index into the tokens array
123     size_t index;
124 
125     /// Length of the current line (so far)
126     uint currentLineLength = 0;
127 
128     /// Output to write output to
129     OutputRange output;
130 
131     /// Used for skipping parts of the file with `dfmt off` and `dfmt on` comments
132     const ubyte[] rawSource;
133 
134     /// Tokens being formatted
135     const Token[] tokens;
136 
137     /// Paren depth info
138     immutable short[] depths;
139 
140     /// Information about the AST
141     const ASTInformation* astInformation;
142 
143     /// Token indices where line breaks should be placed
144     size_t[] linebreakHints;
145 
146     /// Current indentation stack for the file
147     IndentStack indents;
148 
149     /// Configuration
150     const Config* config;
151 
152     /// chached end of line string
153     const string eolString;
154 
155     /// Keep track of whether or not an extra newline was just added because of
156     /// an import statement.
157     bool justAddedExtraNewline;
158 
159     /// Current paren depth
160     int parenDepth;
161 
162     /// Current special brace depth. Used for struct initializers and lambdas.
163     int sBraceDepth;
164 
165     /// Current non-indented brace depth. Used for struct initializers and lambdas.
166     int niBraceDepth;
167 
168     /// True if a space should be placed when parenDepth reaches zero
169     bool spaceAfterParens;
170 
171     /// True if we're in an ASM block
172     bool inAsm;
173 
174     /// True if the next "this" should have a space behind it
175     bool thisSpace;
176 
177     void formatStep()
178     {
179         import std.range : assumeSorted;
180 
181         assert(index < tokens.length);
182         if (currentIs(tok!"comment"))
183         {
184             formatComment();
185         }
186         else if (isStringLiteral(current.type)
187                 || isNumberLiteral(current.type) || currentIs(tok!"characterLiteral"))
188         {
189             writeToken();
190             if (index < tokens.length)
191             {
192                 immutable t = tokens[index].type;
193                 if (t == tok!"identifier" || isStringLiteral(t)
194                         || isNumberLiteral(t) || t == tok!"characterLiteral")
195                     write(" ");
196             }
197         }
198         else if (currentIs(tok!"module") || currentIs(tok!"import"))
199         {
200             formatModuleOrImport();
201         }
202         else if (currentIs(tok!"return"))
203         {
204             writeToken();
205             if (!currentIs(tok!";") && !currentIs(tok!")"))
206                 write(" ");
207         }
208         else if (currentIs(tok!"with"))
209         {
210             if (indents.length == 0 || indents.top != tok!"switch")
211                 indents.push(tok!"with");
212             writeToken();
213             write(" ");
214             if (currentIs(tok!"("))
215                 writeParens(false);
216             if (!currentIs(tok!"switch") && !currentIs(tok!"with")
217                     && !currentIs(tok!"{") && !(currentIs(tok!"final") && peekIs(tok!"switch")))
218             {
219                 newline();
220             }
221             else if (!currentIs(tok!"{"))
222                 write(" ");
223         }
224         else if (currentIs(tok!"switch"))
225         {
226             formatSwitch();
227         }
228         else if (currentIs(tok!"extern") && peekIs(tok!"("))
229         {
230             writeToken();
231             write(" ");
232         }
233         else if ((isBlockHeader() || currentIs(tok!"version")
234                 || currentIs(tok!"debug")) && peekIs(tok!"(", false))
235         {
236             if (!assumeSorted(astInformation.constraintLocations).equalRange(current.index).empty)
237                 formatConstraint();
238             else
239                 formatBlockHeader();
240         }
241         else if (currentIs(tok!"do"))
242         {
243             formatBlockHeader();
244         }
245         else if (currentIs(tok!"else"))
246         {
247             formatElse();
248         }
249         else if (currentIs(tok!"asm"))
250         {
251             formatKeyword();
252             while (index < tokens.length && !currentIs(tok!"{"))
253                 formatStep();
254             if (index < tokens.length)
255             {
256                 int depth = 1;
257                 formatStep();
258                 inAsm = true;
259                 while (index < tokens.length && depth > 0)
260                 {
261                     if (currentIs(tok!"{"))
262                         ++depth;
263                     else if (currentIs(tok!"}"))
264                         --depth;
265                     formatStep();
266                 }
267                 inAsm = false;
268             }
269         }
270         else if (currentIs(tok!"this"))
271         {
272             const thisIndex = current.index;
273             formatKeyword();
274             if (config.dfmt_space_before_function_parameters
275                 && (thisSpace || astInformation.constructorDestructorLocations
276                     .canFindIndex(thisIndex)))
277             {
278                 write(" ");
279                 thisSpace = false;
280             }
281         }
282         else if (isKeyword(current.type))
283         {
284             formatKeyword();
285         }
286         else if (isBasicType(current.type))
287         {
288             writeToken();
289             if (currentIs(tok!"identifier") || isKeyword(current.type) || inAsm)
290                 write(" ");
291         }
292         else if (isOperator(current.type))
293         {
294             formatOperator();
295         }
296         else if (currentIs(tok!"identifier"))
297         {
298             writeToken();
299             //dfmt off
300             if (index < tokens.length && ( currentIs(tok!"identifier")
301                     || ( index > 1 && config.dfmt_space_before_function_parameters
302                         && ( isBasicType(peekBack(2).type)
303                             || peekBack2Is(tok!"identifier")
304                             || peekBack2Is(tok!")")
305                             || peekBack2Is(tok!"]") )
306                         && currentIs(tok!("(") )
307                     || isBasicType(current.type) || currentIs(tok!"@") || currentIs(tok!"if")
308                     || isNumberLiteral(tokens[index].type)
309                     || (inAsm  && peekBack2Is(tok!";") && currentIs(tok!"["))
310             )))
311             //dfmt on
312             {
313                 write(" ");
314             }
315         }
316         else if (currentIs(tok!"scriptLine") || currentIs(tok!"specialTokenSequence"))
317         {
318             writeToken();
319             newline();
320         }
321         else
322             writeToken();
323     }
324 
325     void formatConstraint()
326     {
327         import dfmt.editorconfig : OB = OptionalBoolean;
328         with (TemplateConstraintStyle) final switch (config.dfmt_template_constraint_style)
329         {
330         case unspecified:
331             assert(false, "Config was not validated properly");
332         case conditional_newline:
333             immutable l = currentLineLength + betweenParenLength(tokens[index + 1 .. $]);
334             if (l > config.dfmt_soft_max_line_length)
335                 newline();
336             else if (peekBackIs(tok!")"))
337                 write(" ");
338             break;
339         case always_newline:
340             newline();
341             break;
342         case conditional_newline_indent:
343             immutable l = currentLineLength + betweenParenLength(tokens[index + 1 .. $]);
344             if (l > config.dfmt_soft_max_line_length)
345             {
346                 config.dfmt_single_template_constraint_indent == OB.t ?
347                     pushWrapIndent() : pushWrapIndent(tok!"!");
348                 newline();
349             }
350             else if (peekBackIs(tok!")"))
351                 write(" ");
352             break;
353         case always_newline_indent:
354             {
355                 config.dfmt_single_template_constraint_indent == OB.t ?
356                     pushWrapIndent() : pushWrapIndent(tok!"!");
357                 newline();
358             }
359             break;
360         }
361         // if
362         writeToken();
363         // assume that the parens are present, otherwise the parser would not
364         // have told us there was a constraint here
365         write(" ");
366         writeParens(false);
367     }
368 
369     string commentText(size_t i)
370     {
371         import std..string : strip;
372 
373         assert(tokens[i].type == tok!"comment");
374         string commentText = tokens[i].text;
375         if (commentText[0 .. 2] == "//")
376             commentText = commentText[2 .. $];
377         else
378             commentText = commentText[2 .. $ - 2];
379         return commentText.strip();
380     }
381 
382     void skipFormatting()
383     {
384         size_t dfmtOff = index;
385         size_t dfmtOn = index;
386         foreach (i; dfmtOff + 1 .. tokens.length)
387         {
388             dfmtOn = i;
389             if (tokens[i].type != tok!"comment")
390                 continue;
391             immutable string commentText = commentText(i);
392             if (commentText == "dfmt on")
393                 break;
394         }
395         write(cast(string) rawSource[tokens[dfmtOff].index .. tokens[dfmtOn].index]);
396         index = dfmtOn;
397     }
398 
399     void formatComment()
400     {
401         if (commentText(index) == "dfmt off")
402         {
403             skipFormatting();
404             return;
405         }
406 
407         immutable bool currIsSlashSlash = tokens[index].text[0 .. 2] == "//";
408         immutable prevTokenEndLine = index == 0 ? size_t.max : tokenEndLine(tokens[index - 1]);
409         immutable size_t currTokenLine = tokens[index].line;
410         if (index > 0)
411         {
412             immutable t = tokens[index - 1].type;
413             immutable canAddNewline = currTokenLine - prevTokenEndLine < 1;
414             if (peekBackIsOperator() && !isSeparationToken(t))
415                 pushWrapIndent(t);
416             else if (peekBackIs(tok!",") && prevTokenEndLine == currTokenLine
417                     && indents.indentToMostRecent(tok!"enum") == -1)
418                 pushWrapIndent(tok!",");
419             if (peekBackIsOperator() && !peekBackIsOneOf(false, tok!"comment",
420                     tok!"{", tok!"}", tok!":", tok!";", tok!",", tok!"[", tok!"(")
421                     && !canAddNewline && prevTokenEndLine < currTokenLine)
422                 write(" ");
423             else if (prevTokenEndLine == currTokenLine || (t == tok!")" && peekIs(tok!"{")))
424                 write(" ");
425             else if (canAddNewline || (peekIs(tok!"{") && t == tok!"}"))
426                 newline();
427         }
428         writeToken();
429         immutable j = justAddedExtraNewline;
430         if (currIsSlashSlash)
431         {
432             newline();
433             justAddedExtraNewline = j;
434         }
435         else if (index < tokens.length)
436         {
437             if (index < tokens.length && prevTokenEndLine == tokens[index].line)
438             {
439                 if (currentIs(tok!"}"))
440                 {
441                     if (indents.topIs(tok!"{"))
442                         indents.pop();
443                     write(" ");
444                 }
445                 else if (!currentIs(tok!"{"))
446                     write(" ");
447             }
448             else if (!currentIs(tok!"{"))
449             {
450                 if (currentIs(tok!")") && indents.topIs(tok!","))
451                     indents.pop();
452                 else if (peekBack2Is(tok!",") && !indents.topIs(tok!",")
453                         && indents.indentToMostRecent(tok!"enum") == -1)
454                     pushWrapIndent(tok!",");
455                 newline();
456             }
457         }
458         else
459             newline();
460     }
461 
462     void formatModuleOrImport()
463     {
464         immutable t = current.type;
465         writeToken();
466         if (currentIs(tok!"("))
467         {
468             writeParens(false);
469             return;
470         }
471         write(" ");
472         while (index < tokens.length)
473         {
474             if (currentIs(tok!";"))
475             {
476                 writeToken();
477                 if (index >= tokens.length)
478                 {
479                     newline();
480                     break;
481                 }
482                 if (currentIs(tok!"comment") && current.line == peekBack().line)
483                 {
484                     break;
485                 }
486                 else if (currentIs(tok!"{") && config.dfmt_brace_style == BraceStyle.allman)
487                     break;
488                 else if (t == tok!"import" && !currentIs(tok!"import")
489                         && !currentIs(tok!"}")
490                             && !((currentIs(tok!"public")
491                                 || currentIs(tok!"private")
492                                 || currentIs(tok!"static"))
493                             && peekIs(tok!"import")) && !indents.topIsOneOf(tok!"if",
494                             tok!"debug", tok!"version"))
495                 {
496                     simpleNewline();
497                     currentLineLength = 0;
498                     justAddedExtraNewline = true;
499                     newline();
500                 }
501                 else
502                     newline();
503                 break;
504             }
505             else if (currentIs(tok!":"))
506             {
507                 if (config.dfmt_selective_import_space)
508                     write(" ");
509                 writeToken();
510                 write(" ");
511             }
512             else if (currentIs(tok!","))
513             {
514                 // compute length until next ',' or ';'
515                 int lengthOfNextChunk;
516                 for (size_t i = index + 1; i < tokens.length; i++)
517                 {
518                     if (tokens[i].type == tok!"," || tokens[i].type == tok!";")
519                         break;
520                     const len = tokenLength(tokens[i]);
521                     assert(len >= 0);
522                     lengthOfNextChunk += len;
523                 }
524                 assert(lengthOfNextChunk > 0);
525                 writeToken();
526                 if (currentLineLength + 1 + lengthOfNextChunk >= config.dfmt_soft_max_line_length)
527                 {
528                     pushWrapIndent(tok!",");
529                     newline();
530                 }
531                 else
532                     write(" ");
533             }
534             else
535                 formatStep();
536         }
537     }
538 
539     void formatLeftParenOrBracket()
540     in
541     {
542         assert(currentIs(tok!"(") || currentIs(tok!"["));
543     }
544     body
545     {
546         immutable p = current.type;
547         regenLineBreakHintsIfNecessary(index);
548         writeToken();
549         if (p == tok!"(")
550         {
551             if (isBlockHeaderToken(tokens[index - 2].type))
552                 indents.push(tok!")");
553             else
554                 indents.push(p);
555             spaceAfterParens = true;
556             parenDepth++;
557         }
558         immutable bool arrayInitializerStart = p == tok!"[" && linebreakHints.length != 0
559             && astInformation.arrayStartLocations.canFindIndex(tokens[index - 1].index);
560         if (arrayInitializerStart)
561         {
562             // Use the close bracket as the indent token to distinguish
563             // the array initialiazer from an array index in the newline
564             // handling code
565             pushWrapIndent(tok!"]");
566             newline();
567             immutable size_t j = expressionEndIndex(index);
568             linebreakHints = chooseLineBreakTokens(index, tokens[index .. j],
569                     depths[index .. j], config, currentLineLength, indentLevel);
570         }
571         else if (!currentIs(tok!")") && !currentIs(tok!"]")
572                 && (linebreakHints.canFindIndex(index - 1) || (linebreakHints.length == 0
573                     && currentLineLength > config.max_line_length)))
574         {
575             newline();
576         }
577     }
578 
579     void formatRightParen()
580     in
581     {
582         assert(currentIs(tok!")"));
583     }
584     body
585     {
586         parenDepth--;
587         indents.popWrapIndents();
588         while (indents.topIsOneOf(tok!"!", tok!")"))
589             indents.pop();
590         if (indents.topIs(tok!"("))
591             indents.pop();
592 
593         if (parenDepth == 0 && (peekIs(tok!"is") || peekIs(tok!"in")
594                 || peekIs(tok!"out") || peekIs(tok!"body")))
595             writeToken();
596         else if (peekIsLiteralOrIdent() || peekIsBasicType())
597         {
598             writeToken();
599             if (spaceAfterParens || parenDepth > 0)
600                 write(" ");
601         }
602         else if ((peekIsKeyword() || peekIs(tok!"@")) && spaceAfterParens
603                 && !peekIs(tok!"in") && !peekIs(tok!"is") && !peekIs(tok!"if"))
604         {
605             writeToken();
606             write(" ");
607         }
608         else
609             writeToken();
610     }
611 
612     void formatAt()
613     {
614         writeToken();
615         if (currentIs(tok!"identifier"))
616             writeToken();
617         if (currentIs(tok!"("))
618         {
619             writeParens(false);
620             if (tokens[index].type == tok!"{")
621                 return;
622             if (index < tokens.length && tokens[index - 1].line < tokens[index].line)
623                 newline();
624             else
625                 write(" ");
626         }
627         else if (index < tokens.length && (currentIs(tok!"@")
628                 || isBasicType(tokens[index].type)
629                 || currentIs(tok!"identifier") || currentIs(tok!"if"))
630                 && !currentIsIndentedTemplateConstraint())
631             write(" ");
632     }
633 
634     void formatColon()
635     {
636         import dfmt.editorconfig : OptionalBoolean;
637 
638         immutable bool isCase = astInformation.caseEndLocations.canFindIndex(current.index);
639         immutable bool isAttribute = astInformation.attributeDeclarationLines.canFindIndex(
640                 current.line);
641         if (isCase || isAttribute)
642         {
643             writeToken();
644             if (!currentIs(tok!"{"))
645             {
646                 if (isCase && !indents.topIs(tok!"case")
647                         && config.dfmt_align_switch_statements == OptionalBoolean.f)
648                     indents.push(tok!"case");
649                 else if (isAttribute && !indents.topIs(tok!"@")
650                         && config.dfmt_outdent_attributes == OptionalBoolean.f)
651                     indents.push(tok!"@");
652                 newline();
653             }
654         }
655         else if (peekBackIs(tok!"identifier") && (peekBack2Is(tok!"{", true)
656                 || peekBack2Is(tok!"}", true) || peekBack2Is(tok!";", true)
657                 || peekBack2Is(tok!":", true)) && !(isBlockHeader(1) && !peekIs(tok!"if")))
658         {
659             writeToken();
660             if (!currentIs(tok!"{"))
661                 newline();
662         }
663         else
664         {
665             regenLineBreakHintsIfNecessary(index);
666             if (peekIs(tok!".."))
667                 writeToken();
668             else if (isBlockHeader(1) && !peekIs(tok!"if"))
669             {
670                 writeToken();
671                 if (config.dfmt_compact_labeled_statements)
672                     write(" ");
673                 else
674                     newline();
675             }
676             else if (linebreakHints.canFindIndex(index))
677             {
678                 pushWrapIndent();
679                 newline();
680                 writeToken();
681                 write(" ");
682             }
683             else
684             {
685                 write(" : ");
686                 index++;
687             }
688         }
689     }
690 
691     void formatSemicolon()
692     {
693         if ((parenDepth > 0 && sBraceDepth == 0) || (sBraceDepth > 0 && niBraceDepth > 0))
694         {
695             if (currentLineLength > config.dfmt_soft_max_line_length)
696             {
697                 writeToken();
698                 pushWrapIndent(tok!";");
699                 newline();
700             }
701             else
702             {
703                 if (!(peekIs(tok!";") || peekIs(tok!")") || peekIs(tok!"}")))
704                     write("; ");
705                 else
706                     write(";");
707                 index++;
708             }
709         }
710         else
711         {
712             writeToken();
713             linebreakHints = [];
714             while (indents.topIs(tok!"enum"))
715                 indents.pop();
716             if (indents.topAre(tok!"static", tok!"else"))
717             {
718                 indents.pop();
719                 indents.pop();
720             }
721             if (config.dfmt_brace_style == BraceStyle.allman)
722             {
723                 if (!currentIs(tok!"{"))
724                     newline();
725             }
726             else
727             {
728                 if (currentIs(tok!"{"))
729                     indents.popTempIndents();
730                 indentLevel = indents.indentLevel;
731                 newline();
732             }
733         }
734     }
735 
736     void formatLeftBrace()
737     {
738         import std.algorithm : map, sum, canFind;
739 
740         auto tIndex = tokens[index].index;
741 
742         if (astInformation.structInitStartLocations.canFindIndex(tIndex))
743         {
744             sBraceDepth++;
745             auto e = expressionEndIndex(index);
746             immutable int l = currentLineLength + tokens[index .. e].map!(a => tokenLength(a))
747                 .sum();
748             writeToken();
749             if (l > config.dfmt_soft_max_line_length)
750             {
751                 import std.algorithm.searching : find;
752 
753                 auto indentInfo = astInformation.indentInfoSortedByEndLocation
754                     .find!((a,b) => a.startLocation == b)(tIndex);
755                 assert(indentInfo.length > 0);
756                 cast()indentInfo[0].flags |= BraceIndentInfoFlags.tempIndent;
757                 cast()indentInfo[0].beginIndentLevel = indents.indentLevel;
758 
759                 indents.push(tok!"{");
760                 newline();
761             }
762             else
763                 niBraceDepth++;
764         }
765         else if (astInformation.funLitStartLocations.canFindIndex(tIndex))
766         {
767             sBraceDepth++;
768             if (peekBackIs(tok!")"))
769                 write(" ");
770             auto e = expressionEndIndex(index);
771             immutable int l = currentLineLength + tokens[index .. e].map!(a => tokenLength(a))
772                 .sum();
773             immutable bool multiline = l > config.dfmt_soft_max_line_length
774                 || tokens[index .. e].canFind!(a => a.type == tok!"comment"
775                     || isBlockHeaderToken(a.type))();
776             writeToken();
777             if (multiline)
778             {
779                 indents.push(tok!"{");
780                 newline();
781             }
782             else
783             {
784                 niBraceDepth++;
785                 write(" ");
786             }
787         }
788         else
789         {
790             if (peekBackIsSlashSlash())
791             {
792                 if (peekBack2Is(tok!";"))
793                 {
794                     indents.popTempIndents();
795                     indentLevel = indents.indentLevel - 1;
796                 }
797                 writeToken();
798             }
799             else
800             {
801                 if (indents.topIsTemp && indents.indentToMostRecent(tok!"static") == -1)
802                     indentLevel = indents.indentLevel - 1;
803                 else
804                     indentLevel = indents.indentLevel;
805                 if (config.dfmt_brace_style == BraceStyle.allman
806                         || peekBackIsOneOf(true, tok!"{", tok!"}"))
807                     newline();
808                 else if (!peekBackIsOneOf(true, tok!"{", tok!"}", tok!";"))
809                     write(" ");
810                 writeToken();
811             }
812             indents.push(tok!"{");
813             if (!currentIs(tok!"{"))
814                 newline();
815             linebreakHints = [];
816         }
817     }
818 
819     void formatRightBrace()
820     {
821         void popToBeginIndent(BraceIndentInfo indentInfo)
822         {
823             foreach(i; indentInfo.beginIndentLevel .. indents.indentLevel)
824             {
825                 indents.pop();
826             }
827 
828             indentLevel = indentInfo.beginIndentLevel;
829         }
830 
831         size_t pos;
832         if (astInformation.structInitEndLocations.canFindIndex(tokens[index].index, &pos))
833         {
834             if (sBraceDepth > 0)
835                 sBraceDepth--;
836             if (niBraceDepth > 0)
837                 niBraceDepth--;
838 
839             auto indentInfo = astInformation.indentInfoSortedByEndLocation[pos];
840             if (indentInfo.flags & BraceIndentInfoFlags.tempIndent)
841             {
842                 popToBeginIndent(indentInfo);
843                 simpleNewline();
844                 indent();
845             }
846             writeToken();
847         }
848         else if (astInformation.funLitEndLocations.canFindIndex(tokens[index].index, &pos))
849         {
850             if (niBraceDepth > 0)
851             {
852                 if (!peekBackIsSlashSlash())
853                     write(" ");
854                 niBraceDepth--;
855             }
856             if (sBraceDepth > 0)
857                 sBraceDepth--;
858             writeToken();
859         }
860         else
861         {
862             // Silly hack to format enums better.
863             if ((peekBackIsLiteralOrIdent() || peekBackIsOneOf(true, tok!")",
864                     tok!",")) && !peekBackIsSlashSlash())
865                 newline();
866             write("}");
867             if (index + 1 < tokens.length
868                     && astInformation.doubleNewlineLocations.canFindIndex(tokens[index].index)
869                     && !peekIs(tok!"}") && !peekIs(tok!"else")
870                     && !peekIs(tok!";") && !peekIs(tok!"comment", false))
871             {
872                 simpleNewline();
873                 currentLineLength = 0;
874                 justAddedExtraNewline = true;
875             }
876             if (config.dfmt_brace_style == BraceStyle.otbs
877                     && ((peekIs(tok!"else")
878                             && !indents.topAre(tok!"static", tok!"if")
879                             && !indents.topIs(tok!"foreach") && !indents.topIs(tok!"for")
880                             && !indents.topIs(tok!"while") && !indents.topIs(tok!"do"))
881                         || peekIs(tok!"catch") || peekIs(tok!"finally")))
882             {
883                 write(" ");
884                 index++;
885             }
886             else
887             {
888                 if (!peekIs(tok!",") && !peekIs(tok!")")
889                         && !peekIs(tok!";") && !peekIs(tok!"{"))
890                 {
891                     index++;
892                     if (indents.topIs(tok!"static"))
893                         indents.pop();
894                     newline();
895                 }
896                 else
897                     index++;
898             }
899         }
900     }
901 
902     void formatSwitch()
903     {
904         while (indents.topIs(tok!"with"))
905             indents.pop();
906         indents.push(tok!"switch");
907         writeToken(); // switch
908         write(" ");
909     }
910 
911     void formatBlockHeader()
912     {
913         if (indents.topIs(tok!"!"))
914             indents.pop();
915         immutable bool a = !currentIs(tok!"version") && !currentIs(tok!"debug");
916         immutable bool b = a
917             || astInformation.conditionalWithElseLocations.canFindIndex(current.index);
918         immutable bool c = b
919             || astInformation.conditionalStatementLocations.canFindIndex(current.index);
920         immutable bool shouldPushIndent = (c || peekBackIs(tok!"else"))
921             && !(currentIs(tok!"if") && indents.topIsWrap());
922         if (currentIs(tok!"out") && !peekBackIs(tok!"}"))
923             newline();
924         if (shouldPushIndent)
925         {
926             if (peekBackIs(tok!"static"))
927             {
928                 if (indents.topIs(tok!"else"))
929                     indents.pop();
930                 if (!indents.topIs(tok!"static"))
931                     indents.push(tok!"static");
932             }
933             indents.push(current.type);
934         }
935         writeToken();
936         if (currentIs(tok!"("))
937         {
938             write(" ");
939             writeParens(false);
940         }
941         if (currentIs(tok!"switch") || (currentIs(tok!"final") && peekIs(tok!"switch")))
942             write(" ");
943         else if (currentIs(tok!"comment"))
944             formatStep();
945         else if (!shouldPushIndent)
946         {
947             if (!currentIs(tok!"{") && !currentIs(tok!";"))
948                 write(" ");
949         }
950         else if (!currentIs(tok!"{") && !currentIs(tok!";")
951                 && !currentIs(tok!"in") && !currentIs(tok!"out") && !currentIs(tok!"body"))
952             newline();
953         else if (currentIs(tok!"{") && indents.topAre(tok!"static", tok!"if"))
954         {
955             // Hacks to format braced vs non-braced static if declarations.
956             indents.pop();
957             indents.pop();
958             indents.push(tok!"if");
959             formatLeftBrace();
960         }
961         else if (currentIs(tok!"{") && indents.topAre(tok!"static", tok!"foreach"))
962         {
963             indents.pop();
964             indents.pop();
965             indents.push(tok!"foreach");
966             formatLeftBrace();
967         }
968         else if (currentIs(tok!"{") && indents.topAre(tok!"static", tok!"foreach_reverse"))
969         {
970             indents.pop();
971             indents.pop();
972             indents.push(tok!"foreach_reverse");
973             formatLeftBrace();
974         }
975     }
976 
977     void formatElse()
978     {
979         writeToken();
980         if (currentIs(tok!"if") || currentIs(tok!"version")
981                 || (currentIs(tok!"static") && peekIs(tok!"if")))
982         {
983             if (indents.topIs(tok!"if") || indents.topIs(tok!"version"))
984                 indents.pop();
985             write(" ");
986         }
987         else if (currentIs(tok!":"))
988         {
989             writeToken();
990             newline();
991         }
992         else if (!currentIs(tok!"{") && !currentIs(tok!"comment"))
993         {
994             //indents.dump();
995             while (indents.topIsOneOf(tok!"foreach", tok!"for", tok!"while"))
996                 indents.pop();
997             if (indents.topIsOneOf(tok!"if", tok!"version"))
998                 indents.pop();
999             indents.push(tok!"else");
1000             newline();
1001         }
1002         else if (currentIs(tok!"{") && indents.topAre(tok!"static", tok!"if"))
1003         {
1004             indents.pop();
1005             indents.pop();
1006             indents.push(tok!"else");
1007         }
1008     }
1009 
1010     void formatKeyword()
1011     {
1012         import dfmt.editorconfig : OptionalBoolean;
1013 
1014         switch (current.type)
1015         {
1016         case tok!"default":
1017             writeToken();
1018             break;
1019         case tok!"cast":
1020             writeToken();
1021             if (currentIs(tok!"("))
1022                 writeParens(config.dfmt_space_after_cast == OptionalBoolean.t);
1023             break;
1024         case tok!"out":
1025             if (!peekBackIs(tok!"}")
1026                     && astInformation.contractLocations.canFindIndex(current.index))
1027                 newline();
1028             else if (peekBackIsKeyword)
1029                 write(" ");
1030             writeToken();
1031             if (!currentIs(tok!"(") && !currentIs(tok!"{"))
1032                 write(" ");
1033             break;
1034         case tok!"try":
1035         case tok!"finally":
1036             indents.push(current.type);
1037             writeToken();
1038             if (!currentIs(tok!"{"))
1039                 newline();
1040             break;
1041         case tok!"body":
1042             if (!peekBackIs(tok!"}"))
1043                 newline();
1044             writeToken();
1045             break;
1046         case tok!"in":
1047             immutable isContract = astInformation.contractLocations.canFindIndex(current.index);
1048             if (isContract)
1049             {
1050                 indents.popTempIndents();
1051                 newline();
1052             }
1053             else if (!peekBackIsOneOf(false, tok!"(", tok!",", tok!"!"))
1054                 write(" ");
1055             writeToken();
1056             immutable isFunctionLit = astInformation.funLitStartLocations.canFindIndex(
1057                     current.index);
1058             if (isFunctionLit && config.dfmt_brace_style == BraceStyle.allman)
1059                 newline();
1060             else if (!isContract)
1061                 write(" ");
1062             break;
1063         case tok!"is":
1064             if (!peekBackIsOneOf(false, tok!"!", tok!"(", tok!",",
1065                     tok!"}", tok!"=", tok!"&&", tok!"||") && !peekBackIsKeyword())
1066                 write(" ");
1067             writeToken();
1068             if (!currentIs(tok!"(") && !currentIs(tok!"{"))
1069                 write(" ");
1070             break;
1071         case tok!"case":
1072             writeToken();
1073             if (!currentIs(tok!";"))
1074                 write(" ");
1075             break;
1076         case tok!"enum":
1077             if (peekIs(tok!")") || peekIs(tok!"=="))
1078             {
1079                 writeToken();
1080             }
1081             else
1082             {
1083                 if (peekBackIs(tok!"identifier"))
1084                     write(" ");
1085                 indents.push(tok!"enum");
1086                 writeToken();
1087                 if (!currentIs(tok!":") && !currentIs(tok!"{"))
1088                     write(" ");
1089             }
1090             break;
1091         case tok!"static":
1092             {
1093                 if (astInformation.staticConstructorDestructorLocations
1094                     .canFindIndex(current.index))
1095                 {
1096                     thisSpace = true;
1097                 }
1098             }
1099             goto default;
1100         case tok!"shared":
1101             {
1102                 if (astInformation.sharedStaticConstructorDestructorLocations
1103                     .canFindIndex(current.index))
1104                 {
1105                     thisSpace = true;
1106                 }
1107             }
1108             goto default;
1109         default:
1110             if (peekBackIs(tok!"identifier"))
1111                 write(" ");
1112             if (index + 1 < tokens.length)
1113             {
1114                 if (!peekIs(tok!"@") && (peekIsOperator()
1115                         || peekIs(tok!"out") || peekIs(tok!"in")))
1116                     writeToken();
1117                 else
1118                 {
1119                     writeToken();
1120                     if (!currentIsIndentedTemplateConstraint())
1121                         write(" ");
1122                 }
1123             }
1124             else
1125                 writeToken();
1126             break;
1127         }
1128     }
1129 
1130     bool currentIsIndentedTemplateConstraint()
1131     {
1132         return index < tokens.length
1133             && astInformation.constraintLocations.canFindIndex(current.index)
1134             && (config.dfmt_template_constraint_style == TemplateConstraintStyle.always_newline
1135                 || currentLineLength >= config.dfmt_soft_max_line_length);
1136     }
1137 
1138     void formatOperator()
1139     {
1140         import std.algorithm : canFind;
1141 
1142         switch (current.type)
1143         {
1144         case tok!"*":
1145             if (astInformation.spaceAfterLocations.canFindIndex(current.index))
1146             {
1147                 writeToken();
1148                 if (!currentIs(tok!"*") && !currentIs(tok!")")
1149                         && !currentIs(tok!"[") && !currentIs(tok!",") && !currentIs(tok!";"))
1150                 {
1151                     write(" ");
1152                 }
1153                 break;
1154             }
1155             else if (astInformation.unaryLocations.canFindIndex(current.index))
1156             {
1157                 writeToken();
1158                 break;
1159             }
1160             regenLineBreakHintsIfNecessary(index);
1161             goto binary;
1162         case tok!"~":
1163             if (peekIs(tok!"this") && peek2Is(tok!"("))
1164             {
1165                 if (!(index == 0 || peekBackIs(tok!"{", true)
1166                         || peekBackIs(tok!"}", true) || peekBackIs(tok!";", true)))
1167                 {
1168                     write(" ");
1169                 }
1170                 writeToken();
1171                 break;
1172             }
1173             goto case;
1174         case tok!"&":
1175         case tok!"+":
1176         case tok!"-":
1177             if (astInformation.unaryLocations.canFindIndex(current.index))
1178             {
1179                 writeToken();
1180                 break;
1181             }
1182             regenLineBreakHintsIfNecessary(index);
1183             goto binary;
1184         case tok!"[":
1185         case tok!"(":
1186             formatLeftParenOrBracket();
1187             break;
1188         case tok!")":
1189             formatRightParen();
1190             break;
1191         case tok!"@":
1192             formatAt();
1193             break;
1194         case tok!"!":
1195             if (((peekIs(tok!"is") || peekIs(tok!"in"))
1196                     && !peekBackIsOperator()) || peekBackIs(tok!")"))
1197                 write(" ");
1198             goto case;
1199         case tok!"...":
1200         case tok!"++":
1201         case tok!"--":
1202         case tok!"$":
1203             writeToken();
1204             break;
1205         case tok!":":
1206             formatColon();
1207             break;
1208         case tok!"]":
1209             indents.popWrapIndents();
1210             if (indents.topIs(tok!"]"))
1211                 newline();
1212             writeToken();
1213             if (currentIs(tok!"identifier"))
1214                 write(" ");
1215             break;
1216         case tok!";":
1217             formatSemicolon();
1218             break;
1219         case tok!"{":
1220             formatLeftBrace();
1221             break;
1222         case tok!"}":
1223             formatRightBrace();
1224             break;
1225         case tok!".":
1226             regenLineBreakHintsIfNecessary(index);
1227             if (linebreakHints.canFind(index) || (linebreakHints.length == 0
1228                     && currentLineLength + nextTokenLength() > config.max_line_length))
1229             {
1230                 pushWrapIndent();
1231                 newline();
1232             }
1233             writeToken();
1234             break;
1235         case tok!",":
1236             formatComma();
1237             break;
1238         case tok!"&&":
1239         case tok!"||":
1240         case tok!"|":
1241             regenLineBreakHintsIfNecessary(index);
1242             goto case;
1243         case tok!"=":
1244         case tok!">=":
1245         case tok!">>=":
1246         case tok!">>>=":
1247         case tok!"|=":
1248         case tok!"-=":
1249         case tok!"/=":
1250         case tok!"*=":
1251         case tok!"&=":
1252         case tok!"%=":
1253         case tok!"+=":
1254         case tok!"^^":
1255         case tok!"^=":
1256         case tok!"^":
1257         case tok!"~=":
1258         case tok!"<<=":
1259         case tok!"<<":
1260         case tok!"<=":
1261         case tok!"<>=":
1262         case tok!"<>":
1263         case tok!"<":
1264         case tok!"==":
1265         case tok!"=>":
1266         case tok!">>>":
1267         case tok!">>":
1268         case tok!">":
1269         case tok!"!<=":
1270         case tok!"!<>=":
1271         case tok!"!<>":
1272         case tok!"!<":
1273         case tok!"!=":
1274         case tok!"!>=":
1275         case tok!"!>":
1276         case tok!"?":
1277         case tok!"/":
1278         case tok!"..":
1279         case tok!"%":
1280         binary:
1281             immutable bool isWrapToken = linebreakHints.canFind(index);
1282             if (config.dfmt_split_operator_at_line_end)
1283             {
1284                 if (isWrapToken)
1285                 {
1286                     pushWrapIndent();
1287                     write(" ");
1288                     writeToken();
1289                     newline();
1290                 }
1291                 else
1292                 {
1293                     write(" ");
1294                     writeToken();
1295                     if (!currentIs(tok!"comment"))
1296                         write(" ");
1297                 }
1298             }
1299             else
1300             {
1301                 if (isWrapToken)
1302                 {
1303                     pushWrapIndent();
1304                     newline();
1305                     writeToken();
1306                 }
1307                 else
1308                 {
1309                     write(" ");
1310                     writeToken();
1311                 }
1312                 if (!currentIs(tok!"comment"))
1313                     write(" ");
1314             }
1315             break;
1316         default:
1317             writeToken();
1318             break;
1319         }
1320     }
1321 
1322     void formatComma()
1323     {
1324         import std.algorithm : canFind;
1325 
1326         regenLineBreakHintsIfNecessary(index);
1327         if (indents.indentToMostRecent(tok!"enum") != -1
1328                 && !peekIs(tok!"}") && indents.topIs(tok!"{") && parenDepth == 0)
1329         {
1330             writeToken();
1331             newline();
1332         }
1333         else if (!peekIs(tok!"}") && (linebreakHints.canFind(index)
1334                 || (linebreakHints.length == 0 && currentLineLength > config.max_line_length)))
1335         {
1336             pushWrapIndent();
1337             writeToken();
1338             newline();
1339         }
1340         else
1341         {
1342             writeToken();
1343             if (!currentIs(tok!")") && !currentIs(tok!"]")
1344                     && !currentIs(tok!"}") && !currentIs(tok!"comment"))
1345             {
1346                 write(" ");
1347             }
1348         }
1349         regenLineBreakHintsIfNecessary(index - 1);
1350     }
1351 
1352     void regenLineBreakHints(immutable size_t i)
1353     {
1354         immutable size_t j = expressionEndIndex(i);
1355         // Use magical negative value for array literals and wrap indents
1356         immutable inLvl = (indents.topIsWrap() || indents.topIs(tok!"]")) ? -indentLevel
1357             : indentLevel;
1358         linebreakHints = chooseLineBreakTokens(i, tokens[i .. j], depths[i .. j],
1359                 config, currentLineLength, inLvl);
1360     }
1361 
1362     void regenLineBreakHintsIfNecessary(immutable size_t i)
1363     {
1364         if (linebreakHints.length == 0 || linebreakHints[$ - 1] <= i - 1)
1365             regenLineBreakHints(i);
1366     }
1367 
1368     void simpleNewline()
1369     {
1370         import dfmt.editorconfig : EOL;
1371 
1372         output.put(eolString);
1373     }
1374 
1375     void newline()
1376     {
1377         import std.range : assumeSorted;
1378         import std.algorithm : max;
1379         import dfmt.editorconfig : OptionalBoolean;
1380 
1381         if (currentIs(tok!"comment") && index > 0 && current.line == tokenEndLine(tokens[index - 1]))
1382             return;
1383 
1384         immutable bool hasCurrent = index < tokens.length;
1385 
1386         if (niBraceDepth > 0 && !peekBackIsSlashSlash() && hasCurrent && tokens[index].type == tok!"}"
1387                 && !assumeSorted(astInformation.funLitEndLocations).equalRange(
1388                     tokens[index].index).empty)
1389         {
1390             write(" ");
1391             return;
1392         }
1393 
1394         simpleNewline();
1395 
1396         if (!justAddedExtraNewline && index > 0 && hasCurrent
1397                 && tokens[index].line - tokenEndLine(tokens[index - 1]) > 1)
1398         {
1399             simpleNewline();
1400         }
1401 
1402         justAddedExtraNewline = false;
1403         currentLineLength = 0;
1404 
1405         if (hasCurrent)
1406         {
1407             if (currentIs(tok!"else"))
1408             {
1409                 immutable i = indents.indentToMostRecent(tok!"if");
1410                 immutable v = indents.indentToMostRecent(tok!"version");
1411                 immutable mostRecent = max(i, v);
1412                 if (mostRecent != -1)
1413                     indentLevel = mostRecent;
1414             }
1415             else if (currentIs(tok!"identifier") && peekIs(tok!":"))
1416             {
1417                 if (peekBackIs(tok!"}", true) || peekBackIs(tok!";", true))
1418                     indents.popTempIndents();
1419                 immutable l = indents.indentToMostRecent(tok!"switch");
1420                 if (l != -1 && config.dfmt_align_switch_statements == OptionalBoolean.t)
1421                     indentLevel = l;
1422                 else if (config.dfmt_compact_labeled_statements == OptionalBoolean.f
1423                         || !isBlockHeader(2) || peek2Is(tok!"if"))
1424                 {
1425                     immutable l2 = indents.indentToMostRecent(tok!"{");
1426                     indentLevel = l2 != -1 ? l2 : indents.indentLevel - 1;
1427                 }
1428                 else
1429                     indentLevel = indents.indentLevel;
1430             }
1431             else if (currentIs(tok!"case") || currentIs(tok!"default"))
1432             {
1433                 if (peekBackIs(tok!"}", true) || peekBackIs(tok!";", true))
1434                 {
1435                     indents.popTempIndents();
1436                     if (indents.topIs(tok!"case"))
1437                         indents.pop();
1438                 }
1439                 immutable l = indents.indentToMostRecent(tok!"switch");
1440                 if (l != -1)
1441                     indentLevel = config.dfmt_align_switch_statements == OptionalBoolean.t
1442                         ? l : indents.indentLevel;
1443             }
1444             else if (currentIs(tok!")"))
1445             {
1446                 if (indents.topIs(tok!"("))
1447                     indents.pop();
1448                 indentLevel = indents.indentLevel;
1449             }
1450             else if (currentIs(tok!"{"))
1451             {
1452                 indents.popWrapIndents();
1453                 if (peekBackIsSlashSlash() && peekBack2Is(tok!";"))
1454                 {
1455                     indents.popTempIndents();
1456                     indentLevel = indents.indentLevel;
1457                 }
1458             }
1459             else if (currentIs(tok!"}"))
1460             {
1461                 indents.popTempIndents();
1462                 while (indents.topIsOneOf(tok!"case", tok!"@", tok!"static"))
1463                     indents.pop();
1464                 if (indents.topIs(tok!"{"))
1465                 {
1466                     indentLevel = indents.indentToMostRecent(tok!"{");
1467                     indents.pop();
1468                 }
1469                 if (indents.topIsOneOf(tok!"try", tok!"catch"))
1470                 {
1471                     indents.pop();
1472                 }
1473                 else while (sBraceDepth == 0 && indents.topIsTemp()
1474                         && ((!indents.topIsOneOf(tok!"else", tok!"if",
1475                             tok!"static", tok!"version")) || !peekIs(tok!"else")))
1476                 {
1477                     indents.pop();
1478                 }
1479             }
1480             else if (currentIs(tok!"]"))
1481             {
1482                 indents.popWrapIndents();
1483                 if (indents.topIs(tok!"]"))
1484                 {
1485                     indents.pop();
1486                     indentLevel = indents.indentLevel;
1487                 }
1488             }
1489             else if (astInformation.attributeDeclarationLines.canFindIndex(current.line))
1490             {
1491                 if (config.dfmt_outdent_attributes == OptionalBoolean.t)
1492                 {
1493                     immutable l = indents.indentToMostRecent(tok!"{");
1494                     if (l != -1)
1495                         indentLevel = l;
1496                 }
1497                 else
1498                 {
1499                     if (indents.topIs(tok!"@"))
1500                         indents.pop();
1501                     indentLevel = indents.indentLevel;
1502                 }
1503             }
1504             else if (currentIs(tok!"catch") || currentIs(tok!"finally"))
1505             {
1506                 while (indents.topIsOneOf(tok!"catch", tok!"try"))
1507                     indents.pop();
1508                 indentLevel = indents.indentLevel;
1509             }
1510             else
1511             {
1512                 if (indents.topIsTemp() && (peekBackIsOneOf(true, tok!"}",
1513                         tok!";") && indents.top != tok!";"))
1514                     indents.popTempIndents();
1515                 indentLevel = indents.indentLevel;
1516             }
1517             indent();
1518         }
1519     }
1520 
1521     void write(string str)
1522     {
1523         currentLineLength += str.length;
1524         output.put(str);
1525     }
1526 
1527     void writeToken()
1528     {
1529         import std.range:retro;
1530         import std.algorithm.searching:countUntil;
1531 
1532         if (current.text is null)
1533         {
1534             immutable s = str(current.type);
1535             currentLineLength += s.length;
1536             output.put(str(current.type));
1537         }
1538         else
1539         {
1540             // You know what's awesome? Windows can't handle its own line
1541             // endings correctly.
1542             version (Windows)
1543                 output.put(current.text.replace("\r", ""));
1544             else
1545                 output.put(current.text);
1546             switch (current.type)
1547             {
1548             case tok!"stringLiteral":
1549             case tok!"wstringLiteral":
1550             case tok!"dstringLiteral":
1551                 immutable o = current.text.retro().countUntil('\n');
1552                 currentLineLength += o == -1 ? current.text.length : o;
1553                 break;
1554             default:
1555                 currentLineLength += current.text.length;
1556                 break;
1557             }
1558         }
1559         index++;
1560     }
1561 
1562     void writeParens(bool spaceAfter)
1563     in
1564     {
1565         assert(currentIs(tok!"("), str(current.type));
1566     }
1567     body
1568     {
1569         immutable int depth = parenDepth;
1570         immutable int startingNiBraceDepth = niBraceDepth;
1571         immutable int startingSBraceDepth = sBraceDepth;
1572         parenDepth = 0;
1573         do
1574         {
1575             spaceAfterParens = spaceAfter;
1576             if (currentIs(tok!";") && niBraceDepth <= startingNiBraceDepth
1577                     && sBraceDepth <= startingSBraceDepth)
1578             {
1579                 if (currentLineLength >= config.dfmt_soft_max_line_length)
1580                 {
1581                     pushWrapIndent();
1582                     writeToken();
1583                     newline();
1584                 }
1585                 else
1586                 {
1587                     writeToken();
1588                     if (!currentIs(tok!")") && !currentIs(tok!";"))
1589                         write(" ");
1590                 }
1591             }
1592             else
1593                 formatStep();
1594         }
1595         while (index < tokens.length && parenDepth > 0);
1596         if (indents.topIs(tok!"!"))
1597             indents.pop();
1598         parenDepth = depth;
1599         spaceAfterParens = spaceAfter;
1600     }
1601 
1602     void indent()
1603     {
1604         import dfmt.editorconfig : IndentStyle;
1605 
1606         if (config.indent_style == IndentStyle.tab)
1607         {
1608             foreach (i; 0 .. indentLevel)
1609             {
1610                 currentLineLength += config.tab_width;
1611                 output.put("\t");
1612             }
1613         }
1614         else
1615         {
1616             foreach (i; 0 .. indentLevel)
1617                 foreach (j; 0 .. config.indent_size)
1618                 {
1619                     output.put(" ");
1620                     currentLineLength++;
1621                 }
1622         }
1623     }
1624 
1625     void pushWrapIndent(IdType type = tok!"")
1626     {
1627         immutable t = type == tok!"" ? tokens[index].type : type;
1628         if (parenDepth == 0)
1629         {
1630             if (indents.wrapIndents == 0)
1631                 indents.push(t);
1632         }
1633         else if (indents.wrapIndents < 1)
1634             indents.push(t);
1635     }
1636 
1637 const pure @safe @nogc:
1638 
1639     size_t expressionEndIndex(size_t i) nothrow
1640     {
1641         immutable bool braces = i < tokens.length && tokens[i].type == tok!"{";
1642         immutable d = depths[i];
1643         while (true)
1644         {
1645             if (i >= tokens.length)
1646                 break;
1647             if (depths[i] < d)
1648                 break;
1649             if (!braces && (tokens[i].type == tok!";" || tokens[i].type == tok!"{"))
1650                 break;
1651             i++;
1652         }
1653         return i;
1654     }
1655 
1656     bool peekIsKeyword() nothrow
1657     {
1658         return index + 1 < tokens.length && isKeyword(tokens[index + 1].type);
1659     }
1660 
1661     bool peekIsBasicType() nothrow
1662     {
1663         return index + 1 < tokens.length && isBasicType(tokens[index + 1].type);
1664     }
1665 
1666     bool peekIsLabel() nothrow
1667     {
1668         return peekIs(tok!"identifier") && peek2Is(tok!":");
1669     }
1670 
1671     int currentTokenLength()
1672     {
1673         return tokenLength(tokens[index]);
1674     }
1675 
1676     int nextTokenLength()
1677     {
1678         immutable size_t i = index + 1;
1679         if (i >= tokens.length)
1680             return INVALID_TOKEN_LENGTH;
1681         return tokenLength(tokens[i]);
1682     }
1683 
1684     ref current() nothrow
1685     in
1686     {
1687         assert(index < tokens.length);
1688     }
1689     body
1690     {
1691         return tokens[index];
1692     }
1693 
1694     const(Token) peekBack(uint distance = 1) nothrow
1695     {
1696         if (index < distance)
1697 	{
1698 		assert(0, "Trying to peek before the first token");
1699 	}
1700         return tokens[index - distance];
1701     }
1702 
1703     bool peekBackIsLiteralOrIdent() nothrow
1704     {
1705         if (index == 0)
1706             return false;
1707         switch (tokens[index - 1].type)
1708         {
1709         case tok!"doubleLiteral":
1710         case tok!"floatLiteral":
1711         case tok!"idoubleLiteral":
1712         case tok!"ifloatLiteral":
1713         case tok!"intLiteral":
1714         case tok!"longLiteral":
1715         case tok!"realLiteral":
1716         case tok!"irealLiteral":
1717         case tok!"uintLiteral":
1718         case tok!"ulongLiteral":
1719         case tok!"characterLiteral":
1720         case tok!"identifier":
1721         case tok!"stringLiteral":
1722         case tok!"wstringLiteral":
1723         case tok!"dstringLiteral":
1724             return true;
1725         default:
1726             return false;
1727         }
1728     }
1729 
1730     bool peekIsLiteralOrIdent() nothrow
1731     {
1732         if (index + 1 >= tokens.length)
1733             return false;
1734         switch (tokens[index + 1].type)
1735         {
1736         case tok!"doubleLiteral":
1737         case tok!"floatLiteral":
1738         case tok!"idoubleLiteral":
1739         case tok!"ifloatLiteral":
1740         case tok!"intLiteral":
1741         case tok!"longLiteral":
1742         case tok!"realLiteral":
1743         case tok!"irealLiteral":
1744         case tok!"uintLiteral":
1745         case tok!"ulongLiteral":
1746         case tok!"characterLiteral":
1747         case tok!"identifier":
1748         case tok!"stringLiteral":
1749         case tok!"wstringLiteral":
1750         case tok!"dstringLiteral":
1751             return true;
1752         default:
1753             return false;
1754         }
1755     }
1756 
1757     bool peekBackIs(IdType tokenType, bool ignoreComments = false) nothrow
1758     {
1759         return peekImplementation(tokenType, -1, ignoreComments);
1760     }
1761 
1762     bool peekBackIsKeyword(bool ignoreComments = true) nothrow
1763     {
1764         if (index == 0)
1765             return false;
1766         auto i = index - 1;
1767         if (ignoreComments)
1768             while (tokens[i].type == tok!"comment")
1769             {
1770                 if (i == 0)
1771                     return false;
1772                 i--;
1773             }
1774         return isKeyword(tokens[i].type);
1775     }
1776 
1777     bool peekBackIsOperator() nothrow
1778     {
1779         return index == 0 ? false : isOperator(tokens[index - 1].type);
1780     }
1781 
1782     bool peekBackIsOneOf(bool ignoreComments, IdType[] tokenTypes...) nothrow
1783     {
1784         if (index == 0)
1785             return false;
1786         auto i = index - 1;
1787         if (ignoreComments)
1788             while (tokens[i].type == tok!"comment")
1789             {
1790                 if (i == 0)
1791                     return false;
1792                 i--;
1793             }
1794         immutable t = tokens[i].type;
1795         foreach (tt; tokenTypes)
1796             if (tt == t)
1797                 return true;
1798         return false;
1799     }
1800 
1801     bool peekBack2Is(IdType tokenType, bool ignoreComments = false) nothrow
1802     {
1803         return peekImplementation(tokenType, -2, ignoreComments);
1804     }
1805 
1806     bool peekImplementation(IdType tokenType, int n, bool ignoreComments = true) nothrow
1807     {
1808         auto i = index + n;
1809         if (ignoreComments)
1810             while (n != 0 && i < tokens.length && tokens[i].type == tok!"comment")
1811                 i = n > 0 ? i + 1 : i - 1;
1812         return i < tokens.length && tokens[i].type == tokenType;
1813     }
1814 
1815     bool peek2Is(IdType tokenType, bool ignoreComments = true) nothrow
1816     {
1817         return peekImplementation(tokenType, 2, ignoreComments);
1818     }
1819 
1820     bool peekIsOperator() nothrow
1821     {
1822         return index + 1 < tokens.length && isOperator(tokens[index + 1].type);
1823     }
1824 
1825     bool peekIs(IdType tokenType, bool ignoreComments = true) nothrow
1826     {
1827         return peekImplementation(tokenType, 1, ignoreComments);
1828     }
1829 
1830     bool peekBackIsSlashSlash() nothrow
1831     {
1832         return index > 0 && tokens[index - 1].type == tok!"comment"
1833             && tokens[index - 1].text[0 .. 2] == "//";
1834     }
1835 
1836     bool currentIs(IdType tokenType) nothrow
1837     {
1838         return index < tokens.length && tokens[index].type == tokenType;
1839     }
1840 
1841     /// Bugs: not unicode correct
1842     size_t tokenEndLine(const Token t)
1843     {
1844         import std.algorithm : count;
1845 
1846         switch (t.type)
1847         {
1848         case tok!"comment":
1849         case tok!"stringLiteral":
1850         case tok!"wstringLiteral":
1851         case tok!"dstringLiteral":
1852             return t.line + t.text.count('\n');
1853         default:
1854             return t.line;
1855         }
1856     }
1857 
1858     bool isBlockHeaderToken(IdType t)
1859     {
1860         return t == tok!"for" || t == tok!"foreach" || t == tok!"foreach_reverse"
1861             || t == tok!"while" || t == tok!"if" || t == tok!"out"
1862             || t == tok!"do" || t == tok!"catch" || t == tok!"with"
1863             || t == tok!"synchronized" || t == tok!"scope";
1864     }
1865 
1866     bool isBlockHeader(int i = 0) nothrow
1867     {
1868         if (i + index < 0 || i + index >= tokens.length)
1869             return false;
1870         auto t = tokens[i + index].type;
1871         return isBlockHeaderToken(t);
1872     }
1873 
1874     bool isSeparationToken(IdType t) nothrow
1875     {
1876         return t == tok!"," || t == tok!";" || t == tok!":" || t == tok!"("
1877             || t == tok!")" || t == tok!"[" || t == tok!"]" || t == tok!"{" || t == tok!"}";
1878     }
1879 }
1880 
1881 bool canFindIndex(const size_t[] items, size_t index, size_t* pos = null) pure @safe @nogc
1882 {
1883     import std.range : assumeSorted;
1884     if (!pos)
1885     {
1886         return !assumeSorted(items).equalRange(index).empty;
1887     }
1888     else
1889     {
1890         auto trisection_result = assumeSorted(items).trisect(index);
1891         if (trisection_result[1].length == 1)
1892         {
1893             *pos = trisection_result[0].length;
1894             return true;
1895         }
1896         else if (trisection_result[1].length == 0)
1897         {
1898             return false;
1899         }
1900         else
1901         {
1902             assert(0, "the constraint of having unique locations has been violated");
1903         }
1904     }
1905 }