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