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 (current.text == "body" && peekBackIsFunctionDeclarationEnding()) 287 { 288 formatKeyword(); 289 } 290 else if (isBasicType(current.type)) 291 { 292 writeToken(); 293 if (currentIs(tok!"identifier") || isKeyword(current.type) || inAsm) 294 write(" "); 295 } 296 else if (isOperator(current.type)) 297 { 298 formatOperator(); 299 } 300 else if (currentIs(tok!"identifier")) 301 { 302 writeToken(); 303 //dfmt off 304 if (index < tokens.length && ( currentIs(tok!"identifier") 305 || ( index > 1 && config.dfmt_space_before_function_parameters 306 && ( isBasicType(peekBack(2).type) 307 || peekBack2Is(tok!"identifier") 308 || peekBack2Is(tok!")") 309 || peekBack2Is(tok!"]") ) 310 && currentIs(tok!("(") ) 311 || isBasicType(current.type) || currentIs(tok!"@") || currentIs(tok!"if") 312 || isNumberLiteral(tokens[index].type) 313 || (inAsm && peekBack2Is(tok!";") && currentIs(tok!"[")) 314 ))) 315 //dfmt on 316 { 317 write(" "); 318 } 319 } 320 else if (currentIs(tok!"scriptLine") || currentIs(tok!"specialTokenSequence")) 321 { 322 writeToken(); 323 newline(); 324 } 325 else 326 writeToken(); 327 } 328 329 void formatConstraint() 330 { 331 import dfmt.editorconfig : OB = OptionalBoolean; 332 with (TemplateConstraintStyle) final switch (config.dfmt_template_constraint_style) 333 { 334 case unspecified: 335 assert(false, "Config was not validated properly"); 336 case conditional_newline: 337 immutable l = currentLineLength + betweenParenLength(tokens[index + 1 .. $]); 338 if (l > config.dfmt_soft_max_line_length) 339 newline(); 340 else if (peekBackIs(tok!")")) 341 write(" "); 342 break; 343 case always_newline: 344 newline(); 345 break; 346 case conditional_newline_indent: 347 immutable l = currentLineLength + betweenParenLength(tokens[index + 1 .. $]); 348 if (l > config.dfmt_soft_max_line_length) 349 { 350 config.dfmt_single_template_constraint_indent == OB.t ? 351 pushWrapIndent() : pushWrapIndent(tok!"!"); 352 newline(); 353 } 354 else if (peekBackIs(tok!")")) 355 write(" "); 356 break; 357 case always_newline_indent: 358 { 359 config.dfmt_single_template_constraint_indent == OB.t ? 360 pushWrapIndent() : pushWrapIndent(tok!"!"); 361 newline(); 362 } 363 break; 364 } 365 // if 366 writeToken(); 367 // assume that the parens are present, otherwise the parser would not 368 // have told us there was a constraint here 369 write(" "); 370 writeParens(false); 371 } 372 373 string commentText(size_t i) 374 { 375 import std..string : strip; 376 377 assert(tokens[i].type == tok!"comment"); 378 string commentText = tokens[i].text; 379 if (commentText[0 .. 2] == "//") 380 commentText = commentText[2 .. $]; 381 else 382 { 383 if (commentText.length > 3) 384 commentText = commentText[2 .. $ - 2]; 385 else 386 commentText = commentText[2 .. $]; 387 } 388 return commentText.strip(); 389 } 390 391 void skipFormatting() 392 { 393 size_t dfmtOff = index; 394 size_t dfmtOn = index; 395 foreach (i; dfmtOff + 1 .. tokens.length) 396 { 397 dfmtOn = i; 398 if (tokens[i].type != tok!"comment") 399 continue; 400 immutable string commentText = commentText(i); 401 if (commentText == "dfmt on") 402 break; 403 } 404 write(cast(string) rawSource[tokens[dfmtOff].index .. tokens[dfmtOn].index]); 405 index = dfmtOn; 406 } 407 408 void formatComment() 409 { 410 if (commentText(index) == "dfmt off") 411 { 412 skipFormatting(); 413 return; 414 } 415 416 immutable bool currIsSlashSlash = tokens[index].text[0 .. 2] == "//"; 417 immutable prevTokenEndLine = index == 0 ? size_t.max : tokenEndLine(tokens[index - 1]); 418 immutable size_t currTokenLine = tokens[index].line; 419 if (index > 0) 420 { 421 immutable t = tokens[index - 1].type; 422 immutable canAddNewline = currTokenLine - prevTokenEndLine < 1; 423 if (peekBackIsOperator() && !isSeparationToken(t)) 424 pushWrapIndent(t); 425 else if (peekBackIs(tok!",") && prevTokenEndLine == currTokenLine 426 && indents.indentToMostRecent(tok!"enum") == -1) 427 pushWrapIndent(tok!","); 428 if (peekBackIsOperator() && !peekBackIsOneOf(false, tok!"comment", 429 tok!"{", tok!"}", tok!":", tok!";", tok!",", tok!"[", tok!"(") 430 && !canAddNewline && prevTokenEndLine < currTokenLine) 431 write(" "); 432 else if (prevTokenEndLine == currTokenLine || (t == tok!")" && peekIs(tok!"{"))) 433 write(" "); 434 else if (canAddNewline || (peekIs(tok!"{") && t == tok!"}")) 435 newline(); 436 } 437 writeToken(); 438 immutable j = justAddedExtraNewline; 439 if (currIsSlashSlash) 440 { 441 newline(); 442 justAddedExtraNewline = j; 443 } 444 else if (index < tokens.length) 445 { 446 if (index < tokens.length && prevTokenEndLine == tokens[index].line) 447 { 448 if (currentIs(tok!"}")) 449 { 450 if (indents.topIs(tok!"{")) 451 indents.pop(); 452 write(" "); 453 } 454 else if (!currentIs(tok!"{")) 455 write(" "); 456 } 457 else if (!currentIs(tok!"{")) 458 { 459 if (currentIs(tok!")") && indents.topIs(tok!",")) 460 indents.pop(); 461 else if (peekBack2Is(tok!",") && !indents.topIs(tok!",") 462 && indents.indentToMostRecent(tok!"enum") == -1) 463 pushWrapIndent(tok!","); 464 newline(); 465 } 466 } 467 else 468 newline(); 469 } 470 471 void formatModuleOrImport() 472 { 473 immutable t = current.type; 474 writeToken(); 475 if (currentIs(tok!"(")) 476 { 477 writeParens(false); 478 return; 479 } 480 write(" "); 481 while (index < tokens.length) 482 { 483 if (currentIs(tok!";")) 484 { 485 writeToken(); 486 if (index >= tokens.length) 487 { 488 newline(); 489 break; 490 } 491 if (currentIs(tok!"comment") && current.line == peekBack().line) 492 { 493 break; 494 } 495 else if (currentIs(tok!"{") && config.dfmt_brace_style == BraceStyle.allman) 496 break; 497 else if (t == tok!"import" && !currentIs(tok!"import") 498 && !currentIs(tok!"}") 499 && !((currentIs(tok!"public") 500 || currentIs(tok!"private") 501 || currentIs(tok!"static")) 502 && peekIs(tok!"import")) && !indents.topIsOneOf(tok!"if", 503 tok!"debug", tok!"version")) 504 { 505 simpleNewline(); 506 currentLineLength = 0; 507 justAddedExtraNewline = true; 508 newline(); 509 } 510 else 511 newline(); 512 break; 513 } 514 else if (currentIs(tok!":")) 515 { 516 if (config.dfmt_selective_import_space) 517 write(" "); 518 writeToken(); 519 write(" "); 520 } 521 else if (currentIs(tok!",")) 522 { 523 // compute length until next ',' or ';' 524 int lengthOfNextChunk; 525 for (size_t i = index + 1; i < tokens.length; i++) 526 { 527 if (tokens[i].type == tok!"," || tokens[i].type == tok!";") 528 break; 529 const len = tokenLength(tokens[i]); 530 assert(len >= 0); 531 lengthOfNextChunk += len; 532 } 533 assert(lengthOfNextChunk > 0); 534 writeToken(); 535 if (currentLineLength + 1 + lengthOfNextChunk >= config.dfmt_soft_max_line_length) 536 { 537 pushWrapIndent(tok!","); 538 newline(); 539 } 540 else 541 write(" "); 542 } 543 else 544 formatStep(); 545 } 546 } 547 548 void formatLeftParenOrBracket() 549 in 550 { 551 assert(currentIs(tok!"(") || currentIs(tok!"[")); 552 } 553 body 554 { 555 immutable p = current.type; 556 regenLineBreakHintsIfNecessary(index); 557 writeToken(); 558 if (p == tok!"(") 559 { 560 // If the file starts with an open paren, just give up. This isn't 561 // valid D code. 562 if (index < 2) 563 return; 564 if (isBlockHeaderToken(tokens[index - 2].type)) 565 indents.push(tok!")"); 566 else 567 indents.push(p); 568 spaceAfterParens = true; 569 parenDepth++; 570 } 571 // No heuristics apply if we can't look before the opening paren/bracket 572 if (index < 1) 573 return; 574 immutable bool arrayInitializerStart = p == tok!"[" && linebreakHints.length != 0 575 && astInformation.arrayStartLocations.canFindIndex(tokens[index - 1].index); 576 if (arrayInitializerStart) 577 { 578 // Use the close bracket as the indent token to distinguish 579 // the array initialiazer from an array index in the newline 580 // handling code 581 pushWrapIndent(tok!"]"); 582 newline(); 583 immutable size_t j = expressionEndIndex(index); 584 linebreakHints = chooseLineBreakTokens(index, tokens[index .. j], 585 depths[index .. j], config, currentLineLength, indentLevel); 586 } 587 else if (!currentIs(tok!")") && !currentIs(tok!"]") 588 && (linebreakHints.canFindIndex(index - 1) || (linebreakHints.length == 0 589 && currentLineLength > config.max_line_length))) 590 { 591 newline(); 592 } 593 } 594 595 void formatRightParen() 596 in 597 { 598 assert(currentIs(tok!")")); 599 } 600 body 601 { 602 parenDepth--; 603 indents.popWrapIndents(); 604 while (indents.topIsOneOf(tok!"!", tok!")")) 605 indents.pop(); 606 if (indents.topIs(tok!"(")) 607 indents.pop(); 608 609 if (parenDepth == 0 && (peekIs(tok!"is") || peekIs(tok!"in") 610 || peekIs(tok!"out") || peekIs(tok!"do") || peekIsBody)) 611 { 612 writeToken(); 613 } 614 else if (peekIsLiteralOrIdent() || peekIsBasicType()) 615 { 616 writeToken(); 617 if (spaceAfterParens || parenDepth > 0) 618 write(" "); 619 } 620 else if ((peekIsKeyword() || peekIs(tok!"@")) && spaceAfterParens 621 && !peekIs(tok!"in") && !peekIs(tok!"is") && !peekIs(tok!"if")) 622 { 623 writeToken(); 624 write(" "); 625 } 626 else 627 writeToken(); 628 } 629 630 void formatAt() 631 { 632 immutable size_t atIndex = tokens[index].index; 633 writeToken(); 634 if (currentIs(tok!"identifier")) 635 writeToken(); 636 if (currentIs(tok!"(")) 637 { 638 writeParens(false); 639 if (tokens[index].type == tok!"{") 640 return; 641 if (index < tokens.length && tokens[index - 1].line < tokens[index].line 642 && astInformation.atAttributeStartLocations.canFindIndex(atIndex)) 643 newline(); 644 else 645 write(" "); 646 } 647 else if (index < tokens.length && (currentIs(tok!"@") 648 || isBasicType(tokens[index].type) 649 || currentIs(tok!"identifier") || currentIs(tok!"if")) 650 && !currentIsIndentedTemplateConstraint()) 651 write(" "); 652 } 653 654 void formatColon() 655 { 656 import dfmt.editorconfig : OptionalBoolean; 657 658 immutable bool isCase = astInformation.caseEndLocations.canFindIndex(current.index); 659 immutable bool isAttribute = astInformation.attributeDeclarationLines.canFindIndex( 660 current.line); 661 if (isCase || isAttribute) 662 { 663 writeToken(); 664 if (!currentIs(tok!"{")) 665 { 666 if (isCase && !indents.topIs(tok!"case") 667 && config.dfmt_align_switch_statements == OptionalBoolean.f) 668 indents.push(tok!"case"); 669 else if (isAttribute && !indents.topIs(tok!"@") 670 && config.dfmt_outdent_attributes == OptionalBoolean.f) 671 indents.push(tok!"@"); 672 newline(); 673 } 674 } 675 else if (peekBackIs(tok!"identifier") && (peekBack2Is(tok!"{", true) 676 || peekBack2Is(tok!"}", true) || peekBack2Is(tok!";", true) 677 || peekBack2Is(tok!":", true)) && !(isBlockHeader(1) && !peekIs(tok!"if"))) 678 { 679 writeToken(); 680 if (!currentIs(tok!"{")) 681 newline(); 682 } 683 else 684 { 685 regenLineBreakHintsIfNecessary(index); 686 if (peekIs(tok!"..")) 687 writeToken(); 688 else if (isBlockHeader(1) && !peekIs(tok!"if")) 689 { 690 writeToken(); 691 if (config.dfmt_compact_labeled_statements) 692 write(" "); 693 else 694 newline(); 695 } 696 else if (linebreakHints.canFindIndex(index)) 697 { 698 pushWrapIndent(); 699 newline(); 700 writeToken(); 701 write(" "); 702 } 703 else 704 { 705 write(" : "); 706 index++; 707 } 708 } 709 } 710 711 void formatSemicolon() 712 { 713 if ((parenDepth > 0 && sBraceDepth == 0) || (sBraceDepth > 0 && niBraceDepth > 0)) 714 { 715 if (currentLineLength > config.dfmt_soft_max_line_length) 716 { 717 writeToken(); 718 pushWrapIndent(tok!";"); 719 newline(); 720 } 721 else 722 { 723 if (!(peekIs(tok!";") || peekIs(tok!")") || peekIs(tok!"}"))) 724 write("; "); 725 else 726 write(";"); 727 index++; 728 } 729 } 730 else 731 { 732 writeToken(); 733 linebreakHints = []; 734 while (indents.topIs(tok!"enum")) 735 indents.pop(); 736 if (indents.topAre(tok!"static", tok!"else")) 737 { 738 indents.pop(); 739 indents.pop(); 740 } 741 if (config.dfmt_brace_style == BraceStyle.allman) 742 { 743 if (!currentIs(tok!"{")) 744 newline(); 745 } 746 else 747 { 748 if (currentIs(tok!"{")) 749 indents.popTempIndents(); 750 indentLevel = indents.indentLevel; 751 newline(); 752 } 753 } 754 } 755 756 void formatLeftBrace() 757 { 758 import std.algorithm : map, sum, canFind; 759 760 auto tIndex = tokens[index].index; 761 762 if (astInformation.structInitStartLocations.canFindIndex(tIndex)) 763 { 764 sBraceDepth++; 765 auto e = expressionEndIndex(index); 766 immutable int l = currentLineLength + tokens[index .. e].map!(a => tokenLength(a)) 767 .sum(); 768 writeToken(); 769 if (l > config.dfmt_soft_max_line_length) 770 { 771 import std.algorithm.searching : find; 772 773 auto indentInfo = astInformation.indentInfoSortedByEndLocation 774 .find!((a,b) => a.startLocation == b)(tIndex); 775 assert(indentInfo.length > 0); 776 cast()indentInfo[0].flags |= BraceIndentInfoFlags.tempIndent; 777 cast()indentInfo[0].beginIndentLevel = indents.indentLevel; 778 779 indents.push(tok!"{"); 780 newline(); 781 } 782 else 783 niBraceDepth++; 784 } 785 else if (astInformation.funLitStartLocations.canFindIndex(tIndex)) 786 { 787 sBraceDepth++; 788 if (peekBackIs(tok!")")) 789 write(" "); 790 auto e = expressionEndIndex(index); 791 immutable int l = currentLineLength + tokens[index .. e].map!(a => tokenLength(a)) 792 .sum(); 793 immutable bool multiline = l > config.dfmt_soft_max_line_length 794 || tokens[index .. e].canFind!(a => a.type == tok!"comment" 795 || isBlockHeaderToken(a.type))(); 796 writeToken(); 797 if (multiline) 798 { 799 indents.push(tok!"{"); 800 newline(); 801 } 802 else 803 { 804 niBraceDepth++; 805 write(" "); 806 } 807 } 808 else 809 { 810 if (peekBackIsSlashSlash()) 811 { 812 if (peekBack2Is(tok!";")) 813 { 814 indents.popTempIndents(); 815 indentLevel = indents.indentLevel - 1; 816 } 817 writeToken(); 818 } 819 else 820 { 821 if (indents.topIsTemp && indents.indentToMostRecent(tok!"static") == -1) 822 indentLevel = indents.indentLevel - 1; 823 else 824 indentLevel = indents.indentLevel; 825 if (config.dfmt_brace_style == BraceStyle.allman 826 || peekBackIsOneOf(true, tok!"{", tok!"}")) 827 newline(); 828 else if (!peekBackIsOneOf(true, tok!"{", tok!"}", tok!";")) 829 write(" "); 830 writeToken(); 831 } 832 indents.push(tok!"{"); 833 if (!currentIs(tok!"{")) 834 newline(); 835 linebreakHints = []; 836 } 837 } 838 839 void formatRightBrace() 840 { 841 void popToBeginIndent(BraceIndentInfo indentInfo) 842 { 843 foreach(i; indentInfo.beginIndentLevel .. indents.indentLevel) 844 { 845 indents.pop(); 846 } 847 848 indentLevel = indentInfo.beginIndentLevel; 849 } 850 851 size_t pos; 852 if (astInformation.structInitEndLocations.canFindIndex(tokens[index].index, &pos)) 853 { 854 if (sBraceDepth > 0) 855 sBraceDepth--; 856 if (niBraceDepth > 0) 857 niBraceDepth--; 858 859 auto indentInfo = astInformation.indentInfoSortedByEndLocation[pos]; 860 if (indentInfo.flags & BraceIndentInfoFlags.tempIndent) 861 { 862 popToBeginIndent(indentInfo); 863 simpleNewline(); 864 indent(); 865 } 866 writeToken(); 867 } 868 else if (astInformation.funLitEndLocations.canFindIndex(tokens[index].index, &pos)) 869 { 870 if (niBraceDepth > 0) 871 { 872 if (!peekBackIsSlashSlash()) 873 write(" "); 874 niBraceDepth--; 875 } 876 if (sBraceDepth > 0) 877 sBraceDepth--; 878 writeToken(); 879 } 880 else 881 { 882 // Silly hack to format enums better. 883 if ((peekBackIsLiteralOrIdent() || peekBackIsOneOf(true, tok!")", 884 tok!",")) && !peekBackIsSlashSlash()) 885 newline(); 886 write("}"); 887 if (index + 1 < tokens.length 888 && astInformation.doubleNewlineLocations.canFindIndex(tokens[index].index) 889 && !peekIs(tok!"}") && !peekIs(tok!"else") 890 && !peekIs(tok!";") && !peekIs(tok!"comment", false)) 891 { 892 simpleNewline(); 893 currentLineLength = 0; 894 justAddedExtraNewline = true; 895 } 896 if (config.dfmt_brace_style == BraceStyle.otbs 897 && ((peekIs(tok!"else") 898 && !indents.topAre(tok!"static", tok!"if") 899 && !indents.topIs(tok!"foreach") && !indents.topIs(tok!"for") 900 && !indents.topIs(tok!"while") && !indents.topIs(tok!"do")) 901 || peekIs(tok!"catch") || peekIs(tok!"finally"))) 902 { 903 write(" "); 904 index++; 905 } 906 else 907 { 908 if (!peekIs(tok!",") && !peekIs(tok!")") 909 && !peekIs(tok!";") && !peekIs(tok!"{")) 910 { 911 index++; 912 if (indents.topIs(tok!"static")) 913 indents.pop(); 914 newline(); 915 } 916 else 917 index++; 918 } 919 } 920 } 921 922 void formatSwitch() 923 { 924 while (indents.topIs(tok!"with")) 925 indents.pop(); 926 indents.push(tok!"switch"); 927 writeToken(); // switch 928 write(" "); 929 } 930 931 void formatBlockHeader() 932 { 933 if (indents.topIs(tok!"!")) 934 indents.pop(); 935 immutable bool a = !currentIs(tok!"version") && !currentIs(tok!"debug"); 936 immutable bool b = a 937 || astInformation.conditionalWithElseLocations.canFindIndex(current.index); 938 immutable bool c = b 939 || astInformation.conditionalStatementLocations.canFindIndex(current.index); 940 immutable bool shouldPushIndent = (c || peekBackIs(tok!"else")) 941 && !(currentIs(tok!"if") && indents.topIsWrap()); 942 if (currentIs(tok!"out") && !peekBackIs(tok!"}")) 943 newline(); 944 if (shouldPushIndent) 945 { 946 if (peekBackIs(tok!"static")) 947 { 948 if (indents.topIs(tok!"else")) 949 indents.pop(); 950 if (!indents.topIs(tok!"static")) 951 indents.push(tok!"static"); 952 } 953 indents.push(current.type); 954 } 955 writeToken(); 956 if (currentIs(tok!"(")) 957 { 958 write(" "); 959 writeParens(false); 960 } 961 if (currentIs(tok!"switch") || (currentIs(tok!"final") && peekIs(tok!"switch"))) 962 write(" "); 963 else if (currentIs(tok!"comment")) 964 formatStep(); 965 else if (!shouldPushIndent) 966 { 967 if (!currentIs(tok!"{") && !currentIs(tok!";")) 968 write(" "); 969 } 970 else if (!currentIs(tok!"{") && !currentIs(tok!";") && !currentIs(tok!"in") && 971 !currentIs(tok!"out") && !currentIs(tok!"do") && current.text != "body") 972 { 973 newline(); 974 } 975 else if (currentIs(tok!"{") && indents.topAre(tok!"static", tok!"if")) 976 { 977 // Hacks to format braced vs non-braced static if declarations. 978 indents.pop(); 979 indents.pop(); 980 indents.push(tok!"if"); 981 formatLeftBrace(); 982 } 983 else if (currentIs(tok!"{") && indents.topAre(tok!"static", tok!"foreach")) 984 { 985 indents.pop(); 986 indents.pop(); 987 indents.push(tok!"foreach"); 988 formatLeftBrace(); 989 } 990 else if (currentIs(tok!"{") && indents.topAre(tok!"static", tok!"foreach_reverse")) 991 { 992 indents.pop(); 993 indents.pop(); 994 indents.push(tok!"foreach_reverse"); 995 formatLeftBrace(); 996 } 997 } 998 999 void formatElse() 1000 { 1001 writeToken(); 1002 if (currentIs(tok!"if") || currentIs(tok!"version") 1003 || (currentIs(tok!"static") && peekIs(tok!"if"))) 1004 { 1005 if (indents.topIs(tok!"if") || indents.topIs(tok!"version")) 1006 indents.pop(); 1007 write(" "); 1008 } 1009 else if (currentIs(tok!":")) 1010 { 1011 writeToken(); 1012 newline(); 1013 } 1014 else if (!currentIs(tok!"{") && !currentIs(tok!"comment")) 1015 { 1016 //indents.dump(); 1017 while (indents.topIsOneOf(tok!"foreach", tok!"for", tok!"while")) 1018 indents.pop(); 1019 if (indents.topIsOneOf(tok!"if", tok!"version")) 1020 indents.pop(); 1021 indents.push(tok!"else"); 1022 newline(); 1023 } 1024 else if (currentIs(tok!"{") && indents.topAre(tok!"static", tok!"if")) 1025 { 1026 indents.pop(); 1027 indents.pop(); 1028 indents.push(tok!"else"); 1029 } 1030 } 1031 1032 void formatKeyword() 1033 { 1034 import dfmt.editorconfig : OptionalBoolean; 1035 1036 switch (current.type) 1037 { 1038 case tok!"default": 1039 writeToken(); 1040 break; 1041 case tok!"cast": 1042 writeToken(); 1043 if (currentIs(tok!"(")) 1044 writeParens(config.dfmt_space_after_cast == OptionalBoolean.t); 1045 break; 1046 case tok!"out": 1047 if (!peekBackIs(tok!"}") 1048 && astInformation.contractLocations.canFindIndex(current.index)) 1049 newline(); 1050 else if (peekBackIsKeyword) 1051 write(" "); 1052 writeToken(); 1053 if (!currentIs(tok!"(") && !currentIs(tok!"{") && !currentIs(tok!"comment")) 1054 write(" "); 1055 break; 1056 case tok!"try": 1057 case tok!"finally": 1058 indents.push(current.type); 1059 writeToken(); 1060 if (!currentIs(tok!"{")) 1061 newline(); 1062 break; 1063 case tok!"identifier": 1064 if (current.text == "body") 1065 goto case tok!"do"; 1066 else 1067 goto default; 1068 case tok!"do": 1069 if (!peekBackIs(tok!"}")) 1070 newline(); 1071 writeToken(); 1072 break; 1073 case tok!"in": 1074 immutable isContract = astInformation.contractLocations.canFindIndex(current.index); 1075 if (isContract) 1076 { 1077 indents.popTempIndents(); 1078 newline(); 1079 } 1080 else if (!peekBackIsOneOf(false, tok!"(", tok!",", tok!"!")) 1081 write(" "); 1082 writeToken(); 1083 immutable isFunctionLit = astInformation.funLitStartLocations.canFindIndex( 1084 current.index); 1085 if (isFunctionLit && config.dfmt_brace_style == BraceStyle.allman) 1086 newline(); 1087 else if (!isContract) 1088 write(" "); 1089 break; 1090 case tok!"is": 1091 if (!peekBackIsOneOf(false, tok!"!", tok!"(", tok!",", 1092 tok!"}", tok!"=", tok!"&&", tok!"||") && !peekBackIsKeyword()) 1093 write(" "); 1094 writeToken(); 1095 if (!currentIs(tok!"(") && !currentIs(tok!"{") && !currentIs(tok!"comment")) 1096 write(" "); 1097 break; 1098 case tok!"case": 1099 writeToken(); 1100 if (!currentIs(tok!";")) 1101 write(" "); 1102 break; 1103 case tok!"enum": 1104 if (peekIs(tok!")") || peekIs(tok!"==")) 1105 { 1106 writeToken(); 1107 } 1108 else 1109 { 1110 if (peekBackIs(tok!"identifier")) 1111 write(" "); 1112 indents.push(tok!"enum"); 1113 writeToken(); 1114 if (!currentIs(tok!":") && !currentIs(tok!"{")) 1115 write(" "); 1116 } 1117 break; 1118 case tok!"static": 1119 { 1120 if (astInformation.staticConstructorDestructorLocations 1121 .canFindIndex(current.index)) 1122 { 1123 thisSpace = true; 1124 } 1125 } 1126 goto default; 1127 case tok!"shared": 1128 { 1129 if (astInformation.sharedStaticConstructorDestructorLocations 1130 .canFindIndex(current.index)) 1131 { 1132 thisSpace = true; 1133 } 1134 } 1135 goto default; 1136 default: 1137 if (peekBackIs(tok!"identifier")) 1138 write(" "); 1139 if (index + 1 < tokens.length) 1140 { 1141 if (!peekIs(tok!"@") && (peekIsOperator() 1142 || peekIs(tok!"out") || peekIs(tok!"in"))) 1143 writeToken(); 1144 else 1145 { 1146 writeToken(); 1147 if (!currentIsIndentedTemplateConstraint()) 1148 write(" "); 1149 } 1150 } 1151 else 1152 writeToken(); 1153 break; 1154 } 1155 } 1156 1157 bool currentIsIndentedTemplateConstraint() 1158 { 1159 return index < tokens.length 1160 && astInformation.constraintLocations.canFindIndex(current.index) 1161 && (config.dfmt_template_constraint_style == TemplateConstraintStyle.always_newline 1162 || currentLineLength >= config.dfmt_soft_max_line_length); 1163 } 1164 1165 void formatOperator() 1166 { 1167 import std.algorithm : canFind; 1168 1169 switch (current.type) 1170 { 1171 case tok!"*": 1172 if (astInformation.spaceAfterLocations.canFindIndex(current.index)) 1173 { 1174 writeToken(); 1175 if (!currentIs(tok!"*") && !currentIs(tok!")") 1176 && !currentIs(tok!"[") && !currentIs(tok!",") && !currentIs(tok!";")) 1177 { 1178 write(" "); 1179 } 1180 break; 1181 } 1182 else if (astInformation.unaryLocations.canFindIndex(current.index)) 1183 { 1184 writeToken(); 1185 break; 1186 } 1187 regenLineBreakHintsIfNecessary(index); 1188 goto binary; 1189 case tok!"~": 1190 if (peekIs(tok!"this") && peek2Is(tok!"(")) 1191 { 1192 if (!(index == 0 || peekBackIs(tok!"{", true) 1193 || peekBackIs(tok!"}", true) || peekBackIs(tok!";", true))) 1194 { 1195 write(" "); 1196 } 1197 writeToken(); 1198 break; 1199 } 1200 goto case; 1201 case tok!"&": 1202 case tok!"+": 1203 case tok!"-": 1204 if (astInformation.unaryLocations.canFindIndex(current.index)) 1205 { 1206 writeToken(); 1207 break; 1208 } 1209 regenLineBreakHintsIfNecessary(index); 1210 goto binary; 1211 case tok!"[": 1212 case tok!"(": 1213 formatLeftParenOrBracket(); 1214 break; 1215 case tok!")": 1216 formatRightParen(); 1217 break; 1218 case tok!"@": 1219 formatAt(); 1220 break; 1221 case tok!"!": 1222 if (((peekIs(tok!"is") || peekIs(tok!"in")) 1223 && !peekBackIsOperator()) || peekBackIs(tok!")")) 1224 write(" "); 1225 goto case; 1226 case tok!"...": 1227 case tok!"++": 1228 case tok!"--": 1229 case tok!"$": 1230 writeToken(); 1231 break; 1232 case tok!":": 1233 formatColon(); 1234 break; 1235 case tok!"]": 1236 indents.popWrapIndents(); 1237 if (indents.topIs(tok!"]")) 1238 newline(); 1239 writeToken(); 1240 if (currentIs(tok!"identifier")) 1241 write(" "); 1242 break; 1243 case tok!";": 1244 formatSemicolon(); 1245 break; 1246 case tok!"{": 1247 formatLeftBrace(); 1248 break; 1249 case tok!"}": 1250 formatRightBrace(); 1251 break; 1252 case tok!".": 1253 regenLineBreakHintsIfNecessary(index); 1254 immutable bool ufcsWrap = astInformation.ufcsHintLocations.canFindIndex(current.index); 1255 if (ufcsWrap || linebreakHints.canFind(index) || (linebreakHints.length == 0 1256 && currentLineLength + nextTokenLength() > config.max_line_length)) 1257 { 1258 pushWrapIndent(); 1259 newline(); 1260 if (ufcsWrap) 1261 regenLineBreakHints(index); 1262 } 1263 writeToken(); 1264 break; 1265 case tok!",": 1266 formatComma(); 1267 break; 1268 case tok!"&&": 1269 case tok!"||": 1270 case tok!"|": 1271 regenLineBreakHintsIfNecessary(index); 1272 goto case; 1273 case tok!"=": 1274 case tok!">=": 1275 case tok!">>=": 1276 case tok!">>>=": 1277 case tok!"|=": 1278 case tok!"-=": 1279 case tok!"/=": 1280 case tok!"*=": 1281 case tok!"&=": 1282 case tok!"%=": 1283 case tok!"+=": 1284 case tok!"^^": 1285 case tok!"^=": 1286 case tok!"^": 1287 case tok!"~=": 1288 case tok!"<<=": 1289 case tok!"<<": 1290 case tok!"<=": 1291 case tok!"<>=": 1292 case tok!"<>": 1293 case tok!"<": 1294 case tok!"==": 1295 case tok!"=>": 1296 case tok!">>>": 1297 case tok!">>": 1298 case tok!">": 1299 case tok!"!<=": 1300 case tok!"!<>=": 1301 case tok!"!<>": 1302 case tok!"!<": 1303 case tok!"!=": 1304 case tok!"!>=": 1305 case tok!"!>": 1306 case tok!"?": 1307 case tok!"/": 1308 case tok!"..": 1309 case tok!"%": 1310 binary: 1311 immutable bool isWrapToken = linebreakHints.canFind(index); 1312 if (config.dfmt_split_operator_at_line_end) 1313 { 1314 if (isWrapToken) 1315 { 1316 pushWrapIndent(); 1317 write(" "); 1318 writeToken(); 1319 newline(); 1320 } 1321 else 1322 { 1323 write(" "); 1324 writeToken(); 1325 if (!currentIs(tok!"comment")) 1326 write(" "); 1327 } 1328 } 1329 else 1330 { 1331 if (isWrapToken) 1332 { 1333 pushWrapIndent(); 1334 newline(); 1335 writeToken(); 1336 } 1337 else 1338 { 1339 write(" "); 1340 writeToken(); 1341 } 1342 if (!currentIs(tok!"comment")) 1343 write(" "); 1344 } 1345 break; 1346 default: 1347 writeToken(); 1348 break; 1349 } 1350 } 1351 1352 void formatComma() 1353 { 1354 import std.algorithm : canFind; 1355 1356 regenLineBreakHintsIfNecessary(index); 1357 if (indents.indentToMostRecent(tok!"enum") != -1 1358 && !peekIs(tok!"}") && indents.topIs(tok!"{") && parenDepth == 0) 1359 { 1360 writeToken(); 1361 newline(); 1362 } 1363 else if (!peekIs(tok!"}") && (linebreakHints.canFind(index) 1364 || (linebreakHints.length == 0 && currentLineLength > config.max_line_length))) 1365 { 1366 pushWrapIndent(); 1367 writeToken(); 1368 newline(); 1369 } 1370 else 1371 { 1372 writeToken(); 1373 if (!currentIs(tok!")") && !currentIs(tok!"]") 1374 && !currentIs(tok!"}") && !currentIs(tok!"comment")) 1375 { 1376 write(" "); 1377 } 1378 } 1379 regenLineBreakHintsIfNecessary(index - 1); 1380 } 1381 1382 void regenLineBreakHints(immutable size_t i) 1383 { 1384 import std.range : assumeSorted; 1385 import std.algorithm.comparison : min; 1386 import std.algorithm.searching : countUntil; 1387 1388 // The end of the tokens considered by the line break algorithm is 1389 // either the expression end index or the next mandatory line break, 1390 // whichever is first. 1391 auto r = assumeSorted(astInformation.ufcsHintLocations).upperBound(tokens[i].index); 1392 immutable ufcsBreakLocation = r.empty 1393 ? size_t.max 1394 : tokens[i .. $].countUntil!(t => t.index == r.front) + i; 1395 immutable size_t j = min(expressionEndIndex(i), ufcsBreakLocation); 1396 // Use magical negative value for array literals and wrap indents 1397 immutable inLvl = (indents.topIsWrap() || indents.topIs(tok!"]")) ? -indentLevel 1398 : indentLevel; 1399 linebreakHints = chooseLineBreakTokens(i, tokens[i .. j], depths[i .. j], 1400 config, currentLineLength, inLvl); 1401 } 1402 1403 void regenLineBreakHintsIfNecessary(immutable size_t i) 1404 { 1405 if (linebreakHints.length == 0 || linebreakHints[$ - 1] <= i - 1) 1406 regenLineBreakHints(i); 1407 } 1408 1409 void simpleNewline() 1410 { 1411 import dfmt.editorconfig : EOL; 1412 1413 output.put(eolString); 1414 } 1415 1416 void newline() 1417 { 1418 import std.range : assumeSorted; 1419 import std.algorithm : max; 1420 import dfmt.editorconfig : OptionalBoolean; 1421 1422 if (currentIs(tok!"comment") && index > 0 && current.line == tokenEndLine(tokens[index - 1])) 1423 return; 1424 1425 immutable bool hasCurrent = index < tokens.length; 1426 1427 if (niBraceDepth > 0 && !peekBackIsSlashSlash() && hasCurrent && tokens[index].type == tok!"}" 1428 && !assumeSorted(astInformation.funLitEndLocations).equalRange( 1429 tokens[index].index).empty) 1430 { 1431 write(" "); 1432 return; 1433 } 1434 1435 simpleNewline(); 1436 1437 if (!justAddedExtraNewline && index > 0 && hasCurrent 1438 && tokens[index].line - tokenEndLine(tokens[index - 1]) > 1) 1439 { 1440 simpleNewline(); 1441 } 1442 1443 justAddedExtraNewline = false; 1444 currentLineLength = 0; 1445 1446 if (hasCurrent) 1447 { 1448 if (currentIs(tok!"else")) 1449 { 1450 immutable i = indents.indentToMostRecent(tok!"if"); 1451 immutable v = indents.indentToMostRecent(tok!"version"); 1452 immutable mostRecent = max(i, v); 1453 if (mostRecent != -1) 1454 indentLevel = mostRecent; 1455 } 1456 else if (currentIs(tok!"identifier") && peekIs(tok!":")) 1457 { 1458 if (peekBackIs(tok!"}", true) || peekBackIs(tok!";", true)) 1459 indents.popTempIndents(); 1460 immutable l = indents.indentToMostRecent(tok!"switch"); 1461 if (l != -1 && config.dfmt_align_switch_statements == OptionalBoolean.t) 1462 indentLevel = l; 1463 else if (config.dfmt_compact_labeled_statements == OptionalBoolean.f 1464 || !isBlockHeader(2) || peek2Is(tok!"if")) 1465 { 1466 immutable l2 = indents.indentToMostRecent(tok!"{"); 1467 indentLevel = l2 != -1 ? l2 : indents.indentLevel - 1; 1468 } 1469 else 1470 indentLevel = indents.indentLevel; 1471 } 1472 else if (currentIs(tok!"case") || currentIs(tok!"default")) 1473 { 1474 if (peekBackIs(tok!"}", true) || peekBackIs(tok!";", true)) 1475 { 1476 indents.popTempIndents(); 1477 if (indents.topIs(tok!"case")) 1478 indents.pop(); 1479 } 1480 immutable l = indents.indentToMostRecent(tok!"switch"); 1481 if (l != -1) 1482 indentLevel = config.dfmt_align_switch_statements == OptionalBoolean.t 1483 ? l : indents.indentLevel; 1484 } 1485 else if (currentIs(tok!")")) 1486 { 1487 if (indents.topIs(tok!"(")) 1488 indents.pop(); 1489 indentLevel = indents.indentLevel; 1490 } 1491 else if (currentIs(tok!"{")) 1492 { 1493 indents.popWrapIndents(); 1494 if (peekBackIsSlashSlash() && peekBack2Is(tok!";")) 1495 { 1496 indents.popTempIndents(); 1497 indentLevel = indents.indentLevel; 1498 } 1499 } 1500 else if (currentIs(tok!"}")) 1501 { 1502 indents.popTempIndents(); 1503 while (indents.topIsOneOf(tok!"case", tok!"@", tok!"static")) 1504 indents.pop(); 1505 if (indents.topIs(tok!"{")) 1506 { 1507 indentLevel = indents.indentToMostRecent(tok!"{"); 1508 indents.pop(); 1509 } 1510 if (indents.topIsOneOf(tok!"try", tok!"catch")) 1511 { 1512 indents.pop(); 1513 } 1514 else while (sBraceDepth == 0 && indents.topIsTemp() 1515 && ((!indents.topIsOneOf(tok!"else", tok!"if", 1516 tok!"static", tok!"version")) || !peekIs(tok!"else"))) 1517 { 1518 indents.pop(); 1519 } 1520 } 1521 else if (currentIs(tok!"]")) 1522 { 1523 indents.popWrapIndents(); 1524 if (indents.topIs(tok!"]")) 1525 { 1526 indents.pop(); 1527 indentLevel = indents.indentLevel; 1528 } 1529 } 1530 else if (astInformation.attributeDeclarationLines.canFindIndex(current.line)) 1531 { 1532 if (config.dfmt_outdent_attributes == OptionalBoolean.t) 1533 { 1534 immutable l = indents.indentToMostRecent(tok!"{"); 1535 if (l != -1) 1536 indentLevel = l; 1537 } 1538 else 1539 { 1540 if (indents.topIs(tok!"@")) 1541 indents.pop(); 1542 indentLevel = indents.indentLevel; 1543 } 1544 } 1545 else if (currentIs(tok!"catch") || currentIs(tok!"finally")) 1546 { 1547 while (indents.topIsOneOf(tok!"catch", tok!"try")) 1548 indents.pop(); 1549 indentLevel = indents.indentLevel; 1550 } 1551 else 1552 { 1553 if (indents.topIsTemp() && (peekBackIsOneOf(true, tok!"}", 1554 tok!";") && indents.top != tok!";")) 1555 indents.popTempIndents(); 1556 indentLevel = indents.indentLevel; 1557 } 1558 indent(); 1559 } 1560 } 1561 1562 void write(string str) 1563 { 1564 currentLineLength += str.length; 1565 output.put(str); 1566 } 1567 1568 void writeToken() 1569 { 1570 import std.range:retro; 1571 import std.algorithm.searching:countUntil; 1572 1573 if (current.text is null) 1574 { 1575 immutable s = str(current.type); 1576 currentLineLength += s.length; 1577 output.put(str(current.type)); 1578 } 1579 else 1580 { 1581 // You know what's awesome? Windows can't handle its own line 1582 // endings correctly. 1583 version (Windows) 1584 output.put(current.text.replace("\r", "")); 1585 else 1586 output.put(current.text); 1587 switch (current.type) 1588 { 1589 case tok!"stringLiteral": 1590 case tok!"wstringLiteral": 1591 case tok!"dstringLiteral": 1592 immutable o = current.text.retro().countUntil('\n'); 1593 currentLineLength += o == -1 ? current.text.length : o; 1594 break; 1595 default: 1596 currentLineLength += current.text.length; 1597 break; 1598 } 1599 } 1600 index++; 1601 } 1602 1603 void writeParens(bool spaceAfter) 1604 in 1605 { 1606 assert(currentIs(tok!"("), str(current.type)); 1607 } 1608 body 1609 { 1610 immutable int depth = parenDepth; 1611 immutable int startingNiBraceDepth = niBraceDepth; 1612 immutable int startingSBraceDepth = sBraceDepth; 1613 parenDepth = 0; 1614 do 1615 { 1616 spaceAfterParens = spaceAfter; 1617 if (currentIs(tok!";") && niBraceDepth <= startingNiBraceDepth 1618 && sBraceDepth <= startingSBraceDepth) 1619 { 1620 if (currentLineLength >= config.dfmt_soft_max_line_length) 1621 { 1622 pushWrapIndent(); 1623 writeToken(); 1624 newline(); 1625 } 1626 else 1627 { 1628 writeToken(); 1629 if (!currentIs(tok!")") && !currentIs(tok!";")) 1630 write(" "); 1631 } 1632 } 1633 else 1634 formatStep(); 1635 } 1636 while (index < tokens.length && parenDepth > 0); 1637 if (indents.topIs(tok!"!")) 1638 indents.pop(); 1639 parenDepth = depth; 1640 spaceAfterParens = spaceAfter; 1641 } 1642 1643 void indent() 1644 { 1645 import dfmt.editorconfig : IndentStyle; 1646 1647 if (config.indent_style == IndentStyle.tab) 1648 { 1649 foreach (i; 0 .. indentLevel) 1650 { 1651 currentLineLength += config.tab_width; 1652 output.put("\t"); 1653 } 1654 } 1655 else 1656 { 1657 foreach (i; 0 .. indentLevel) 1658 foreach (j; 0 .. config.indent_size) 1659 { 1660 output.put(" "); 1661 currentLineLength++; 1662 } 1663 } 1664 } 1665 1666 void pushWrapIndent(IdType type = tok!"") 1667 { 1668 immutable t = type == tok!"" ? tokens[index].type : type; 1669 if (parenDepth == 0) 1670 { 1671 if (indents.wrapIndents == 0) 1672 indents.push(t); 1673 } 1674 else if (indents.wrapIndents < 1) 1675 indents.push(t); 1676 } 1677 1678 const pure @safe @nogc: 1679 1680 size_t expressionEndIndex(size_t i) nothrow 1681 { 1682 immutable bool braces = i < tokens.length && tokens[i].type == tok!"{"; 1683 immutable d = depths[i]; 1684 while (true) 1685 { 1686 if (i >= tokens.length) 1687 break; 1688 if (depths[i] < d) 1689 break; 1690 if (!braces && (tokens[i].type == tok!";" || tokens[i].type == tok!"{")) 1691 break; 1692 i++; 1693 } 1694 return i; 1695 } 1696 1697 bool peekIsKeyword() nothrow 1698 { 1699 return index + 1 < tokens.length && isKeyword(tokens[index + 1].type); 1700 } 1701 1702 bool peekIsBasicType() nothrow 1703 { 1704 return index + 1 < tokens.length && isBasicType(tokens[index + 1].type); 1705 } 1706 1707 bool peekIsLabel() nothrow 1708 { 1709 return peekIs(tok!"identifier") && peek2Is(tok!":"); 1710 } 1711 1712 int currentTokenLength() 1713 { 1714 return tokenLength(tokens[index]); 1715 } 1716 1717 int nextTokenLength() 1718 { 1719 immutable size_t i = index + 1; 1720 if (i >= tokens.length) 1721 return INVALID_TOKEN_LENGTH; 1722 return tokenLength(tokens[i]); 1723 } 1724 1725 ref current() nothrow 1726 in 1727 { 1728 assert(index < tokens.length); 1729 } 1730 body 1731 { 1732 return tokens[index]; 1733 } 1734 1735 const(Token) peekBack(uint distance = 1) nothrow 1736 { 1737 assert(index >= distance, "Trying to peek before the first token"); 1738 return tokens[index - distance]; 1739 } 1740 1741 bool peekBackIsLiteralOrIdent() nothrow 1742 { 1743 if (index == 0) 1744 return false; 1745 switch (tokens[index - 1].type) 1746 { 1747 case tok!"doubleLiteral": 1748 case tok!"floatLiteral": 1749 case tok!"idoubleLiteral": 1750 case tok!"ifloatLiteral": 1751 case tok!"intLiteral": 1752 case tok!"longLiteral": 1753 case tok!"realLiteral": 1754 case tok!"irealLiteral": 1755 case tok!"uintLiteral": 1756 case tok!"ulongLiteral": 1757 case tok!"characterLiteral": 1758 case tok!"identifier": 1759 case tok!"stringLiteral": 1760 case tok!"wstringLiteral": 1761 case tok!"dstringLiteral": 1762 return true; 1763 default: 1764 return false; 1765 } 1766 } 1767 1768 bool peekIsLiteralOrIdent() nothrow 1769 { 1770 if (index + 1 >= tokens.length) 1771 return false; 1772 switch (tokens[index + 1].type) 1773 { 1774 case tok!"doubleLiteral": 1775 case tok!"floatLiteral": 1776 case tok!"idoubleLiteral": 1777 case tok!"ifloatLiteral": 1778 case tok!"intLiteral": 1779 case tok!"longLiteral": 1780 case tok!"realLiteral": 1781 case tok!"irealLiteral": 1782 case tok!"uintLiteral": 1783 case tok!"ulongLiteral": 1784 case tok!"characterLiteral": 1785 case tok!"identifier": 1786 case tok!"stringLiteral": 1787 case tok!"wstringLiteral": 1788 case tok!"dstringLiteral": 1789 return true; 1790 default: 1791 return false; 1792 } 1793 } 1794 1795 bool peekBackIs(IdType tokenType, bool ignoreComments = false) nothrow 1796 { 1797 return peekImplementation(tokenType, -1, ignoreComments); 1798 } 1799 1800 bool peekBackIsKeyword(bool ignoreComments = true) nothrow 1801 { 1802 if (index == 0) 1803 return false; 1804 auto i = index - 1; 1805 if (ignoreComments) 1806 while (tokens[i].type == tok!"comment") 1807 { 1808 if (i == 0) 1809 return false; 1810 i--; 1811 } 1812 return isKeyword(tokens[i].type); 1813 } 1814 1815 bool peekBackIsOperator() nothrow 1816 { 1817 return index == 0 ? false : isOperator(tokens[index - 1].type); 1818 } 1819 1820 bool peekBackIsOneOf(bool ignoreComments, IdType[] tokenTypes...) nothrow 1821 { 1822 if (index == 0) 1823 return false; 1824 auto i = index - 1; 1825 if (ignoreComments) 1826 while (tokens[i].type == tok!"comment") 1827 { 1828 if (i == 0) 1829 return false; 1830 i--; 1831 } 1832 immutable t = tokens[i].type; 1833 foreach (tt; tokenTypes) 1834 if (tt == t) 1835 return true; 1836 return false; 1837 } 1838 1839 bool peekBack2Is(IdType tokenType, bool ignoreComments = false) nothrow 1840 { 1841 return peekImplementation(tokenType, -2, ignoreComments); 1842 } 1843 1844 bool peekImplementation(IdType tokenType, int n, bool ignoreComments = true) nothrow 1845 { 1846 auto i = index + n; 1847 if (ignoreComments) 1848 while (n != 0 && i < tokens.length && tokens[i].type == tok!"comment") 1849 i = n > 0 ? i + 1 : i - 1; 1850 return i < tokens.length && tokens[i].type == tokenType; 1851 } 1852 1853 bool peek2Is(IdType tokenType, bool ignoreComments = true) nothrow 1854 { 1855 return peekImplementation(tokenType, 2, ignoreComments); 1856 } 1857 1858 bool peekIsOperator() nothrow 1859 { 1860 return index + 1 < tokens.length && isOperator(tokens[index + 1].type); 1861 } 1862 1863 bool peekIs(IdType tokenType, bool ignoreComments = true) nothrow 1864 { 1865 return peekImplementation(tokenType, 1, ignoreComments); 1866 } 1867 1868 bool peekIsBody() nothrow 1869 { 1870 return index + 1 < tokens.length && tokens[index + 1].text == "body"; 1871 } 1872 1873 bool peekBackIsFunctionDeclarationEnding() nothrow 1874 { 1875 return peekBackIsOneOf(false, tok!")", tok!"const", tok!"immutable", 1876 tok!"inout", tok!"shared", tok!"@", tok!"pure", tok!"nothrow", 1877 tok!"return", tok!"scope"); 1878 } 1879 1880 bool peekBackIsSlashSlash() nothrow 1881 { 1882 return index > 0 && tokens[index - 1].type == tok!"comment" 1883 && tokens[index - 1].text[0 .. 2] == "//"; 1884 } 1885 1886 bool currentIs(IdType tokenType) nothrow 1887 { 1888 return index < tokens.length && tokens[index].type == tokenType; 1889 } 1890 1891 /// Bugs: not unicode correct 1892 size_t tokenEndLine(const Token t) 1893 { 1894 import std.algorithm : count; 1895 1896 switch (t.type) 1897 { 1898 case tok!"comment": 1899 case tok!"stringLiteral": 1900 case tok!"wstringLiteral": 1901 case tok!"dstringLiteral": 1902 return t.line + t.text.count('\n'); 1903 default: 1904 return t.line; 1905 } 1906 } 1907 1908 bool isBlockHeaderToken(IdType t) 1909 { 1910 return t == tok!"for" || t == tok!"foreach" || t == tok!"foreach_reverse" 1911 || t == tok!"while" || t == tok!"if" || t == tok!"in"|| t == tok!"out" 1912 || t == tok!"do" || t == tok!"catch" || t == tok!"with" 1913 || t == tok!"synchronized" || t == tok!"scope"; 1914 } 1915 1916 bool isBlockHeader(int i = 0) nothrow 1917 { 1918 if (i + index < 0 || i + index >= tokens.length) 1919 return false; 1920 auto t = tokens[i + index].type; 1921 bool isExpressionContract; 1922 1923 if (i + index + 3 < tokens.length) 1924 { 1925 isExpressionContract = (t == tok!"in" && peekImplementation(tok!"(", i + 1, true)) 1926 || (t == tok!"out" && (peekImplementation(tok!"(", i + 1, true) 1927 && (peekImplementation(tok!";", i + 2, true) 1928 || (peekImplementation(tok!"identifier", i + 2, true) 1929 && peekImplementation(tok!";", i + 3, true))))); 1930 } 1931 1932 return isBlockHeaderToken(t) && !isExpressionContract; 1933 } 1934 1935 bool isSeparationToken(IdType t) nothrow 1936 { 1937 return t == tok!"," || t == tok!";" || t == tok!":" || t == tok!"(" 1938 || t == tok!")" || t == tok!"[" || t == tok!"]" || t == tok!"{" || t == tok!"}"; 1939 } 1940 } 1941 1942 bool canFindIndex(const size_t[] items, size_t index, size_t* pos = null) pure @safe @nogc 1943 { 1944 import std.range : assumeSorted; 1945 if (!pos) 1946 { 1947 return !assumeSorted(items).equalRange(index).empty; 1948 } 1949 else 1950 { 1951 auto trisection_result = assumeSorted(items).trisect(index); 1952 if (trisection_result[1].length == 1) 1953 { 1954 *pos = trisection_result[0].length; 1955 return true; 1956 } 1957 else if (trisection_result[1].length == 0) 1958 { 1959 return false; 1960 } 1961 else 1962 { 1963 assert(0, "the constraint of having unique locations has been violated"); 1964 } 1965 } 1966 }