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