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