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 }