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 }