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