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