github.com/bir3/gocompiler@v0.9.2202/src/go/printer/printer.go (about) 1 // Copyright 2009 The Go Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 // Package printer implements printing of AST nodes. 6 package printer 7 8 import ( 9 "fmt" 10 "github.com/bir3/gocompiler/src/go/ast" 11 "github.com/bir3/gocompiler/src/go/build/constraint" 12 "github.com/bir3/gocompiler/src/go/token" 13 "io" 14 "os" 15 "strings" 16 "sync" 17 "text/tabwriter" 18 "unicode" 19 ) 20 21 const ( 22 maxNewlines = 2 // max. number of newlines between source text 23 debug = false // enable for debugging 24 infinity = 1 << 30 25 ) 26 27 type whiteSpace byte 28 29 const ( 30 ignore = whiteSpace(0) 31 blank = whiteSpace(' ') 32 vtab = whiteSpace('\v') 33 newline = whiteSpace('\n') 34 formfeed = whiteSpace('\f') 35 indent = whiteSpace('>') 36 unindent = whiteSpace('<') 37 ) 38 39 // A pmode value represents the current printer mode. 40 type pmode int 41 42 const ( 43 noExtraBlank pmode = 1 << iota // disables extra blank after /*-style comment 44 noExtraLinebreak // disables extra line break after /*-style comment 45 ) 46 47 type commentInfo struct { 48 cindex int // current comment index 49 comment *ast.CommentGroup // = printer.comments[cindex]; or nil 50 commentOffset int // = printer.posFor(printer.comments[cindex].List[0].Pos()).Offset; or infinity 51 commentNewline bool // true if the comment group contains newlines 52 } 53 54 type printer struct { 55 // Configuration (does not change after initialization) 56 Config 57 fset *token.FileSet 58 59 // Current state 60 output []byte // raw printer result 61 indent int // current indentation 62 level int // level == 0: outside composite literal; level > 0: inside composite literal 63 mode pmode // current printer mode 64 endAlignment bool // if set, terminate alignment immediately 65 impliedSemi bool // if set, a linebreak implies a semicolon 66 lastTok token.Token // last token printed (token.ILLEGAL if it's whitespace) 67 prevOpen token.Token // previous non-brace "open" token (, [, or token.ILLEGAL 68 wsbuf []whiteSpace // delayed white space 69 goBuild []int // start index of all //go:build comments in output 70 plusBuild []int // start index of all // +build comments in output 71 72 // Positions 73 // The out position differs from the pos position when the result 74 // formatting differs from the source formatting (in the amount of 75 // white space). If there's a difference and SourcePos is set in 76 // ConfigMode, //line directives are used in the output to restore 77 // original source positions for a reader. 78 pos token.Position // current position in AST (source) space 79 out token.Position // current position in output space 80 last token.Position // value of pos after calling writeString 81 linePtr *int // if set, record out.Line for the next token in *linePtr 82 sourcePosErr error // if non-nil, the first error emitting a //line directive 83 84 // The list of all source comments, in order of appearance. 85 comments []*ast.CommentGroup // may be nil 86 useNodeComments bool // if not set, ignore lead and line comments of nodes 87 88 // Information about p.comments[p.cindex]; set up by nextComment. 89 commentInfo 90 91 // Cache of already computed node sizes. 92 nodeSizes map[ast.Node]int 93 94 // Cache of most recently computed line position. 95 cachedPos token.Pos 96 cachedLine int // line corresponding to cachedPos 97 } 98 99 func (p *printer) internalError(msg ...any) { 100 if debug { 101 fmt.Print(p.pos.String() + ": ") 102 fmt.Println(msg...) 103 panic("go/printer") 104 } 105 } 106 107 // commentsHaveNewline reports whether a list of comments belonging to 108 // an *ast.CommentGroup contains newlines. Because the position information 109 // may only be partially correct, we also have to read the comment text. 110 func (p *printer) commentsHaveNewline(list []*ast.Comment) bool { 111 // len(list) > 0 112 line := p.lineFor(list[0].Pos()) 113 for i, c := range list { 114 if i > 0 && p.lineFor(list[i].Pos()) != line { 115 // not all comments on the same line 116 return true 117 } 118 if t := c.Text; len(t) >= 2 && (t[1] == '/' || strings.Contains(t, "\n")) { 119 return true 120 } 121 } 122 _ = line 123 return false 124 } 125 126 func (p *printer) nextComment() { 127 for p.cindex < len(p.comments) { 128 c := p.comments[p.cindex] 129 p.cindex++ 130 if list := c.List; len(list) > 0 { 131 p.comment = c 132 p.commentOffset = p.posFor(list[0].Pos()).Offset 133 p.commentNewline = p.commentsHaveNewline(list) 134 return 135 } 136 // we should not reach here (correct ASTs don't have empty 137 // ast.CommentGroup nodes), but be conservative and try again 138 } 139 // no more comments 140 p.commentOffset = infinity 141 } 142 143 // commentBefore reports whether the current comment group occurs 144 // before the next position in the source code and printing it does 145 // not introduce implicit semicolons. 146 func (p *printer) commentBefore(next token.Position) bool { 147 return p.commentOffset < next.Offset && (!p.impliedSemi || !p.commentNewline) 148 } 149 150 // commentSizeBefore returns the estimated size of the 151 // comments on the same line before the next position. 152 func (p *printer) commentSizeBefore(next token.Position) int { 153 // save/restore current p.commentInfo (p.nextComment() modifies it) 154 defer func(info commentInfo) { 155 p.commentInfo = info 156 }(p.commentInfo) 157 158 size := 0 159 for p.commentBefore(next) { 160 for _, c := range p.comment.List { 161 size += len(c.Text) 162 } 163 p.nextComment() 164 } 165 return size 166 } 167 168 // recordLine records the output line number for the next non-whitespace 169 // token in *linePtr. It is used to compute an accurate line number for a 170 // formatted construct, independent of pending (not yet emitted) whitespace 171 // or comments. 172 func (p *printer) recordLine(linePtr *int) { 173 p.linePtr = linePtr 174 } 175 176 // linesFrom returns the number of output lines between the current 177 // output line and the line argument, ignoring any pending (not yet 178 // emitted) whitespace or comments. It is used to compute an accurate 179 // size (in number of lines) for a formatted construct. 180 func (p *printer) linesFrom(line int) int { 181 return p.out.Line - line 182 } 183 184 func (p *printer) posFor(pos token.Pos) token.Position { 185 // not used frequently enough to cache entire token.Position 186 return p.fset.PositionFor(pos, false /* absolute position */) 187 } 188 189 func (p *printer) lineFor(pos token.Pos) int { 190 if pos != p.cachedPos { 191 p.cachedPos = pos 192 p.cachedLine = p.fset.PositionFor(pos, false /* absolute position */).Line 193 } 194 return p.cachedLine 195 } 196 197 // writeLineDirective writes a //line directive if necessary. 198 func (p *printer) writeLineDirective(pos token.Position) { 199 if pos.IsValid() && (p.out.Line != pos.Line || p.out.Filename != pos.Filename) { 200 if strings.ContainsAny(pos.Filename, "\r\n") { 201 if p.sourcePosErr == nil { 202 p.sourcePosErr = fmt.Errorf("go/printer: source filename contains unexpected newline character: %q", pos.Filename) 203 } 204 return 205 } 206 207 p.output = append(p.output, tabwriter.Escape) // protect '\n' in //line from tabwriter interpretation 208 p.output = append(p.output, fmt.Sprintf("//line %s:%d\n", pos.Filename, pos.Line)...) 209 p.output = append(p.output, tabwriter.Escape) 210 // p.out must match the //line directive 211 p.out.Filename = pos.Filename 212 p.out.Line = pos.Line 213 } 214 } 215 216 // writeIndent writes indentation. 217 func (p *printer) writeIndent() { 218 // use "hard" htabs - indentation columns 219 // must not be discarded by the tabwriter 220 n := p.Config.Indent + p.indent // include base indentation 221 for i := 0; i < n; i++ { 222 p.output = append(p.output, '\t') 223 } 224 225 // update positions 226 p.pos.Offset += n 227 p.pos.Column += n 228 p.out.Column += n 229 } 230 231 // writeByte writes ch n times to p.output and updates p.pos. 232 // Only used to write formatting (white space) characters. 233 func (p *printer) writeByte(ch byte, n int) { 234 if p.endAlignment { 235 // Ignore any alignment control character; 236 // and at the end of the line, break with 237 // a formfeed to indicate termination of 238 // existing columns. 239 switch ch { 240 case '\t', '\v': 241 ch = ' ' 242 case '\n', '\f': 243 ch = '\f' 244 p.endAlignment = false 245 } 246 } 247 248 if p.out.Column == 1 { 249 // no need to write line directives before white space 250 p.writeIndent() 251 } 252 253 for i := 0; i < n; i++ { 254 p.output = append(p.output, ch) 255 } 256 257 // update positions 258 p.pos.Offset += n 259 if ch == '\n' || ch == '\f' { 260 p.pos.Line += n 261 p.out.Line += n 262 p.pos.Column = 1 263 p.out.Column = 1 264 return 265 } 266 p.pos.Column += n 267 p.out.Column += n 268 } 269 270 // writeString writes the string s to p.output and updates p.pos, p.out, 271 // and p.last. If isLit is set, s is escaped w/ tabwriter.Escape characters 272 // to protect s from being interpreted by the tabwriter. 273 // 274 // Note: writeString is only used to write Go tokens, literals, and 275 // comments, all of which must be written literally. Thus, it is correct 276 // to always set isLit = true. However, setting it explicitly only when 277 // needed (i.e., when we don't know that s contains no tabs or line breaks) 278 // avoids processing extra escape characters and reduces run time of the 279 // printer benchmark by up to 10%. 280 func (p *printer) writeString(pos token.Position, s string, isLit bool) { 281 if p.out.Column == 1 { 282 if p.Config.Mode&SourcePos != 0 { 283 p.writeLineDirective(pos) 284 } 285 p.writeIndent() 286 } 287 288 if pos.IsValid() { 289 // update p.pos (if pos is invalid, continue with existing p.pos) 290 // Note: Must do this after handling line beginnings because 291 // writeIndent updates p.pos if there's indentation, but p.pos 292 // is the position of s. 293 p.pos = pos 294 } 295 296 if isLit { 297 // Protect s such that is passes through the tabwriter 298 // unchanged. Note that valid Go programs cannot contain 299 // tabwriter.Escape bytes since they do not appear in legal 300 // UTF-8 sequences. 301 p.output = append(p.output, tabwriter.Escape) 302 } 303 304 if debug { 305 p.output = append(p.output, fmt.Sprintf("/*%s*/", pos)...) // do not update p.pos! 306 } 307 p.output = append(p.output, s...) 308 309 // update positions 310 nlines := 0 311 var li int // index of last newline; valid if nlines > 0 312 for i := 0; i < len(s); i++ { 313 // Raw string literals may contain any character except back quote (`). 314 if ch := s[i]; ch == '\n' || ch == '\f' { 315 // account for line break 316 nlines++ 317 li = i 318 // A line break inside a literal will break whatever column 319 // formatting is in place; ignore any further alignment through 320 // the end of the line. 321 p.endAlignment = true 322 } 323 } 324 p.pos.Offset += len(s) 325 if nlines > 0 { 326 p.pos.Line += nlines 327 p.out.Line += nlines 328 c := len(s) - li 329 p.pos.Column = c 330 p.out.Column = c 331 } else { 332 p.pos.Column += len(s) 333 p.out.Column += len(s) 334 } 335 336 if isLit { 337 p.output = append(p.output, tabwriter.Escape) 338 } 339 340 p.last = p.pos 341 } 342 343 // writeCommentPrefix writes the whitespace before a comment. 344 // If there is any pending whitespace, it consumes as much of 345 // it as is likely to help position the comment nicely. 346 // pos is the comment position, next the position of the item 347 // after all pending comments, prev is the previous comment in 348 // a group of comments (or nil), and tok is the next token. 349 func (p *printer) writeCommentPrefix(pos, next token.Position, prev *ast.Comment, tok token.Token) { 350 if len(p.output) == 0 { 351 // the comment is the first item to be printed - don't write any whitespace 352 return 353 } 354 355 if pos.IsValid() && pos.Filename != p.last.Filename { 356 // comment in a different file - separate with newlines 357 p.writeByte('\f', maxNewlines) 358 return 359 } 360 361 if pos.Line == p.last.Line && (prev == nil || prev.Text[1] != '/') { 362 // comment on the same line as last item: 363 // separate with at least one separator 364 hasSep := false 365 if prev == nil { 366 // first comment of a comment group 367 j := 0 368 for i, ch := range p.wsbuf { 369 switch ch { 370 case blank: 371 // ignore any blanks before a comment 372 p.wsbuf[i] = ignore 373 continue 374 case vtab: 375 // respect existing tabs - important 376 // for proper formatting of commented structs 377 hasSep = true 378 continue 379 case indent: 380 // apply pending indentation 381 continue 382 } 383 j = i 384 break 385 } 386 p.writeWhitespace(j) 387 } 388 // make sure there is at least one separator 389 if !hasSep { 390 sep := byte('\t') 391 if pos.Line == next.Line { 392 // next item is on the same line as the comment 393 // (which must be a /*-style comment): separate 394 // with a blank instead of a tab 395 sep = ' ' 396 } 397 p.writeByte(sep, 1) 398 } 399 400 } else { 401 // comment on a different line: 402 // separate with at least one line break 403 droppedLinebreak := false 404 j := 0 405 for i, ch := range p.wsbuf { 406 switch ch { 407 case blank, vtab: 408 // ignore any horizontal whitespace before line breaks 409 p.wsbuf[i] = ignore 410 continue 411 case indent: 412 // apply pending indentation 413 continue 414 case unindent: 415 // if this is not the last unindent, apply it 416 // as it is (likely) belonging to the last 417 // construct (e.g., a multi-line expression list) 418 // and is not part of closing a block 419 if i+1 < len(p.wsbuf) && p.wsbuf[i+1] == unindent { 420 continue 421 } 422 // if the next token is not a closing }, apply the unindent 423 // if it appears that the comment is aligned with the 424 // token; otherwise assume the unindent is part of a 425 // closing block and stop (this scenario appears with 426 // comments before a case label where the comments 427 // apply to the next case instead of the current one) 428 if tok != token.RBRACE && pos.Column == next.Column { 429 continue 430 } 431 case newline, formfeed: 432 p.wsbuf[i] = ignore 433 droppedLinebreak = prev == nil // record only if first comment of a group 434 } 435 j = i 436 break 437 } 438 p.writeWhitespace(j) 439 440 // determine number of linebreaks before the comment 441 n := 0 442 if pos.IsValid() && p.last.IsValid() { 443 n = pos.Line - p.last.Line 444 if n < 0 { // should never happen 445 n = 0 446 } 447 } 448 449 // at the package scope level only (p.indent == 0), 450 // add an extra newline if we dropped one before: 451 // this preserves a blank line before documentation 452 // comments at the package scope level (issue 2570) 453 if p.indent == 0 && droppedLinebreak { 454 n++ 455 } 456 457 // make sure there is at least one line break 458 // if the previous comment was a line comment 459 if n == 0 && prev != nil && prev.Text[1] == '/' { 460 n = 1 461 } 462 463 if n > 0 { 464 // use formfeeds to break columns before a comment; 465 // this is analogous to using formfeeds to separate 466 // individual lines of /*-style comments 467 p.writeByte('\f', nlimit(n)) 468 } 469 } 470 } 471 472 // Returns true if s contains only white space 473 // (only tabs and blanks can appear in the printer's context). 474 func isBlank(s string) bool { 475 for i := 0; i < len(s); i++ { 476 if s[i] > ' ' { 477 return false 478 } 479 } 480 return true 481 } 482 483 // commonPrefix returns the common prefix of a and b. 484 func commonPrefix(a, b string) string { 485 i := 0 486 for i < len(a) && i < len(b) && a[i] == b[i] && (a[i] <= ' ' || a[i] == '*') { 487 i++ 488 } 489 return a[0:i] 490 } 491 492 // trimRight returns s with trailing whitespace removed. 493 func trimRight(s string) string { 494 return strings.TrimRightFunc(s, unicode.IsSpace) 495 } 496 497 // stripCommonPrefix removes a common prefix from /*-style comment lines (unless no 498 // comment line is indented, all but the first line have some form of space prefix). 499 // The prefix is computed using heuristics such that is likely that the comment 500 // contents are nicely laid out after re-printing each line using the printer's 501 // current indentation. 502 func stripCommonPrefix(lines []string) { 503 if len(lines) <= 1 { 504 return // at most one line - nothing to do 505 } 506 // len(lines) > 1 507 508 // The heuristic in this function tries to handle a few 509 // common patterns of /*-style comments: Comments where 510 // the opening /* and closing */ are aligned and the 511 // rest of the comment text is aligned and indented with 512 // blanks or tabs, cases with a vertical "line of stars" 513 // on the left, and cases where the closing */ is on the 514 // same line as the last comment text. 515 516 // Compute maximum common white prefix of all but the first, 517 // last, and blank lines, and replace blank lines with empty 518 // lines (the first line starts with /* and has no prefix). 519 // In cases where only the first and last lines are not blank, 520 // such as two-line comments, or comments where all inner lines 521 // are blank, consider the last line for the prefix computation 522 // since otherwise the prefix would be empty. 523 // 524 // Note that the first and last line are never empty (they 525 // contain the opening /* and closing */ respectively) and 526 // thus they can be ignored by the blank line check. 527 prefix := "" 528 prefixSet := false 529 if len(lines) > 2 { 530 for i, line := range lines[1 : len(lines)-1] { 531 if isBlank(line) { 532 lines[1+i] = "" // range starts with lines[1] 533 } else { 534 if !prefixSet { 535 prefix = line 536 prefixSet = true 537 } 538 prefix = commonPrefix(prefix, line) 539 } 540 541 } 542 } 543 // If we don't have a prefix yet, consider the last line. 544 if !prefixSet { 545 line := lines[len(lines)-1] 546 prefix = commonPrefix(line, line) 547 } 548 549 /* 550 * Check for vertical "line of stars" and correct prefix accordingly. 551 */ 552 lineOfStars := false 553 if p, _, ok := strings.Cut(prefix, "*"); ok { 554 // remove trailing blank from prefix so stars remain aligned 555 prefix = strings.TrimSuffix(p, " ") 556 lineOfStars = true 557 } else { 558 // No line of stars present. 559 // Determine the white space on the first line after the /* 560 // and before the beginning of the comment text, assume two 561 // blanks instead of the /* unless the first character after 562 // the /* is a tab. If the first comment line is empty but 563 // for the opening /*, assume up to 3 blanks or a tab. This 564 // whitespace may be found as suffix in the common prefix. 565 first := lines[0] 566 if isBlank(first[2:]) { 567 // no comment text on the first line: 568 // reduce prefix by up to 3 blanks or a tab 569 // if present - this keeps comment text indented 570 // relative to the /* and */'s if it was indented 571 // in the first place 572 i := len(prefix) 573 for n := 0; n < 3 && i > 0 && prefix[i-1] == ' '; n++ { 574 i-- 575 } 576 if i == len(prefix) && i > 0 && prefix[i-1] == '\t' { 577 i-- 578 } 579 prefix = prefix[0:i] 580 } else { 581 // comment text on the first line 582 suffix := make([]byte, len(first)) 583 n := 2 // start after opening /* 584 for n < len(first) && first[n] <= ' ' { 585 suffix[n] = first[n] 586 n++ 587 } 588 if n > 2 && suffix[2] == '\t' { 589 // assume the '\t' compensates for the /* 590 suffix = suffix[2:n] 591 } else { 592 // otherwise assume two blanks 593 suffix[0], suffix[1] = ' ', ' ' 594 suffix = suffix[0:n] 595 } 596 // Shorten the computed common prefix by the length of 597 // suffix, if it is found as suffix of the prefix. 598 prefix = strings.TrimSuffix(prefix, string(suffix)) 599 } 600 } 601 602 // Handle last line: If it only contains a closing */, align it 603 // with the opening /*, otherwise align the text with the other 604 // lines. 605 last := lines[len(lines)-1] 606 closing := "*/" 607 before, _, _ := strings.Cut(last, closing) // closing always present 608 if isBlank(before) { 609 // last line only contains closing */ 610 if lineOfStars { 611 closing = " */" // add blank to align final star 612 } 613 lines[len(lines)-1] = prefix + closing 614 } else { 615 // last line contains more comment text - assume 616 // it is aligned like the other lines and include 617 // in prefix computation 618 prefix = commonPrefix(prefix, last) 619 } 620 621 // Remove the common prefix from all but the first and empty lines. 622 for i, line := range lines { 623 if i > 0 && line != "" { 624 lines[i] = line[len(prefix):] 625 } 626 } 627 } 628 629 func (p *printer) writeComment(comment *ast.Comment) { 630 text := comment.Text 631 pos := p.posFor(comment.Pos()) 632 633 const linePrefix = "//line " 634 if strings.HasPrefix(text, linePrefix) && (!pos.IsValid() || pos.Column == 1) { 635 // Possibly a //-style line directive. 636 // Suspend indentation temporarily to keep line directive valid. 637 defer func(indent int) { p.indent = indent }(p.indent) 638 p.indent = 0 639 } 640 641 // shortcut common case of //-style comments 642 if text[1] == '/' { 643 if constraint.IsGoBuild(text) { 644 p.goBuild = append(p.goBuild, len(p.output)) 645 } else if constraint.IsPlusBuild(text) { 646 p.plusBuild = append(p.plusBuild, len(p.output)) 647 } 648 p.writeString(pos, trimRight(text), true) 649 return 650 } 651 652 // for /*-style comments, print line by line and let the 653 // write function take care of the proper indentation 654 lines := strings.Split(text, "\n") 655 656 // The comment started in the first column but is going 657 // to be indented. For an idempotent result, add indentation 658 // to all lines such that they look like they were indented 659 // before - this will make sure the common prefix computation 660 // is the same independent of how many times formatting is 661 // applied (was issue 1835). 662 if pos.IsValid() && pos.Column == 1 && p.indent > 0 { 663 for i, line := range lines[1:] { 664 lines[1+i] = " " + line 665 } 666 } 667 668 stripCommonPrefix(lines) 669 670 // write comment lines, separated by formfeed, 671 // without a line break after the last line 672 for i, line := range lines { 673 if i > 0 { 674 p.writeByte('\f', 1) 675 pos = p.pos 676 } 677 if len(line) > 0 { 678 p.writeString(pos, trimRight(line), true) 679 } 680 } 681 } 682 683 // writeCommentSuffix writes a line break after a comment if indicated 684 // and processes any leftover indentation information. If a line break 685 // is needed, the kind of break (newline vs formfeed) depends on the 686 // pending whitespace. The writeCommentSuffix result indicates if a 687 // newline was written or if a formfeed was dropped from the whitespace 688 // buffer. 689 func (p *printer) writeCommentSuffix(needsLinebreak bool) (wroteNewline, droppedFF bool) { 690 for i, ch := range p.wsbuf { 691 switch ch { 692 case blank, vtab: 693 // ignore trailing whitespace 694 p.wsbuf[i] = ignore 695 case indent, unindent: 696 // don't lose indentation information 697 case newline, formfeed: 698 // if we need a line break, keep exactly one 699 // but remember if we dropped any formfeeds 700 if needsLinebreak { 701 needsLinebreak = false 702 wroteNewline = true 703 } else { 704 if ch == formfeed { 705 droppedFF = true 706 } 707 p.wsbuf[i] = ignore 708 } 709 } 710 } 711 p.writeWhitespace(len(p.wsbuf)) 712 713 // make sure we have a line break 714 if needsLinebreak { 715 p.writeByte('\n', 1) 716 wroteNewline = true 717 } 718 719 return 720 } 721 722 // containsLinebreak reports whether the whitespace buffer contains any line breaks. 723 func (p *printer) containsLinebreak() bool { 724 for _, ch := range p.wsbuf { 725 if ch == newline || ch == formfeed { 726 return true 727 } 728 } 729 return false 730 } 731 732 // intersperseComments consumes all comments that appear before the next token 733 // tok and prints it together with the buffered whitespace (i.e., the whitespace 734 // that needs to be written before the next token). A heuristic is used to mix 735 // the comments and whitespace. The intersperseComments result indicates if a 736 // newline was written or if a formfeed was dropped from the whitespace buffer. 737 func (p *printer) intersperseComments(next token.Position, tok token.Token) (wroteNewline, droppedFF bool) { 738 var last *ast.Comment 739 for p.commentBefore(next) { 740 list := p.comment.List 741 changed := false 742 if p.lastTok != token.IMPORT && // do not rewrite cgo's import "C" comments 743 p.posFor(p.comment.Pos()).Column == 1 && 744 p.posFor(p.comment.End()+1) == next { 745 // Unindented comment abutting next token position: 746 // a top-level doc comment. 747 list = formatDocComment(list) 748 changed = true 749 750 if len(p.comment.List) > 0 && len(list) == 0 { 751 // The doc comment was removed entirely. 752 // Keep preceding whitespace. 753 p.writeCommentPrefix(p.posFor(p.comment.Pos()), next, last, tok) 754 // Change print state to continue at next. 755 p.pos = next 756 p.last = next 757 // There can't be any more comments. 758 p.nextComment() 759 return p.writeCommentSuffix(false) 760 } 761 } 762 for _, c := range list { 763 p.writeCommentPrefix(p.posFor(c.Pos()), next, last, tok) 764 p.writeComment(c) 765 last = c 766 } 767 // In case list was rewritten, change print state to where 768 // the original list would have ended. 769 if len(p.comment.List) > 0 && changed { 770 last = p.comment.List[len(p.comment.List)-1] 771 p.pos = p.posFor(last.End()) 772 p.last = p.pos 773 } 774 p.nextComment() 775 } 776 777 if last != nil { 778 // If the last comment is a /*-style comment and the next item 779 // follows on the same line but is not a comma, and not a "closing" 780 // token immediately following its corresponding "opening" token, 781 // add an extra separator unless explicitly disabled. Use a blank 782 // as separator unless we have pending linebreaks, they are not 783 // disabled, and we are outside a composite literal, in which case 784 // we want a linebreak (issue 15137). 785 // TODO(gri) This has become overly complicated. We should be able 786 // to track whether we're inside an expression or statement and 787 // use that information to decide more directly. 788 needsLinebreak := false 789 if p.mode&noExtraBlank == 0 && 790 last.Text[1] == '*' && p.lineFor(last.Pos()) == next.Line && 791 tok != token.COMMA && 792 (tok != token.RPAREN || p.prevOpen == token.LPAREN) && 793 (tok != token.RBRACK || p.prevOpen == token.LBRACK) { 794 if p.containsLinebreak() && p.mode&noExtraLinebreak == 0 && p.level == 0 { 795 needsLinebreak = true 796 } else { 797 p.writeByte(' ', 1) 798 } 799 } 800 // Ensure that there is a line break after a //-style comment, 801 // before EOF, and before a closing '}' unless explicitly disabled. 802 if last.Text[1] == '/' || 803 tok == token.EOF || 804 tok == token.RBRACE && p.mode&noExtraLinebreak == 0 { 805 needsLinebreak = true 806 } 807 return p.writeCommentSuffix(needsLinebreak) 808 } 809 810 // no comment was written - we should never reach here since 811 // intersperseComments should not be called in that case 812 p.internalError("intersperseComments called without pending comments") 813 return 814 } 815 816 // writeWhitespace writes the first n whitespace entries. 817 func (p *printer) writeWhitespace(n int) { 818 // write entries 819 for i := 0; i < n; i++ { 820 switch ch := p.wsbuf[i]; ch { 821 case ignore: 822 // ignore! 823 case indent: 824 p.indent++ 825 case unindent: 826 p.indent-- 827 if p.indent < 0 { 828 p.internalError("negative indentation:", p.indent) 829 p.indent = 0 830 } 831 case newline, formfeed: 832 // A line break immediately followed by a "correcting" 833 // unindent is swapped with the unindent - this permits 834 // proper label positioning. If a comment is between 835 // the line break and the label, the unindent is not 836 // part of the comment whitespace prefix and the comment 837 // will be positioned correctly indented. 838 if i+1 < n && p.wsbuf[i+1] == unindent { 839 // Use a formfeed to terminate the current section. 840 // Otherwise, a long label name on the next line leading 841 // to a wide column may increase the indentation column 842 // of lines before the label; effectively leading to wrong 843 // indentation. 844 p.wsbuf[i], p.wsbuf[i+1] = unindent, formfeed 845 i-- // do it again 846 continue 847 } 848 fallthrough 849 default: 850 p.writeByte(byte(ch), 1) 851 } 852 } 853 854 // shift remaining entries down 855 l := copy(p.wsbuf, p.wsbuf[n:]) 856 p.wsbuf = p.wsbuf[:l] 857 } 858 859 // ---------------------------------------------------------------------------- 860 // Printing interface 861 862 // nlimit limits n to maxNewlines. 863 func nlimit(n int) int { 864 return min(n, maxNewlines) 865 } 866 867 func mayCombine(prev token.Token, next byte) (b bool) { 868 switch prev { 869 case token.INT: 870 b = next == '.' // 1. 871 case token.ADD: 872 b = next == '+' // ++ 873 case token.SUB: 874 b = next == '-' // -- 875 case token.QUO: 876 b = next == '*' // /* 877 case token.LSS: 878 b = next == '-' || next == '<' // <- or << 879 case token.AND: 880 b = next == '&' || next == '^' // && or &^ 881 } 882 return 883 } 884 885 func (p *printer) setPos(pos token.Pos) { 886 if pos.IsValid() { 887 p.pos = p.posFor(pos) // accurate position of next item 888 } 889 } 890 891 // print prints a list of "items" (roughly corresponding to syntactic 892 // tokens, but also including whitespace and formatting information). 893 // It is the only print function that should be called directly from 894 // any of the AST printing functions in nodes.go. 895 // 896 // Whitespace is accumulated until a non-whitespace token appears. Any 897 // comments that need to appear before that token are printed first, 898 // taking into account the amount and structure of any pending white- 899 // space for best comment placement. Then, any leftover whitespace is 900 // printed, followed by the actual token. 901 func (p *printer) print(args ...any) { 902 for _, arg := range args { 903 // information about the current arg 904 var data string 905 var isLit bool 906 var impliedSemi bool // value for p.impliedSemi after this arg 907 908 // record previous opening token, if any 909 switch p.lastTok { 910 case token.ILLEGAL: 911 // ignore (white space) 912 case token.LPAREN, token.LBRACK: 913 p.prevOpen = p.lastTok 914 default: 915 // other tokens followed any opening token 916 p.prevOpen = token.ILLEGAL 917 } 918 919 switch x := arg.(type) { 920 case pmode: 921 // toggle printer mode 922 p.mode ^= x 923 continue 924 925 case whiteSpace: 926 if x == ignore { 927 // don't add ignore's to the buffer; they 928 // may screw up "correcting" unindents (see 929 // LabeledStmt) 930 continue 931 } 932 i := len(p.wsbuf) 933 if i == cap(p.wsbuf) { 934 // Whitespace sequences are very short so this should 935 // never happen. Handle gracefully (but possibly with 936 // bad comment placement) if it does happen. 937 p.writeWhitespace(i) 938 i = 0 939 } 940 p.wsbuf = p.wsbuf[0 : i+1] 941 p.wsbuf[i] = x 942 if x == newline || x == formfeed { 943 // newlines affect the current state (p.impliedSemi) 944 // and not the state after printing arg (impliedSemi) 945 // because comments can be interspersed before the arg 946 // in this case 947 p.impliedSemi = false 948 } 949 p.lastTok = token.ILLEGAL 950 continue 951 952 case *ast.Ident: 953 data = x.Name 954 impliedSemi = true 955 p.lastTok = token.IDENT 956 957 case *ast.BasicLit: 958 data = x.Value 959 isLit = true 960 impliedSemi = true 961 p.lastTok = x.Kind 962 963 case token.Token: 964 s := x.String() 965 if mayCombine(p.lastTok, s[0]) { 966 // the previous and the current token must be 967 // separated by a blank otherwise they combine 968 // into a different incorrect token sequence 969 // (except for token.INT followed by a '.' this 970 // should never happen because it is taken care 971 // of via binary expression formatting) 972 if len(p.wsbuf) != 0 { 973 p.internalError("whitespace buffer not empty") 974 } 975 p.wsbuf = p.wsbuf[0:1] 976 p.wsbuf[0] = ' ' 977 } 978 data = s 979 // some keywords followed by a newline imply a semicolon 980 switch x { 981 case token.BREAK, token.CONTINUE, token.FALLTHROUGH, token.RETURN, 982 token.INC, token.DEC, token.RPAREN, token.RBRACK, token.RBRACE: 983 impliedSemi = true 984 } 985 p.lastTok = x 986 987 case string: 988 // incorrect AST - print error message 989 data = x 990 isLit = true 991 impliedSemi = true 992 p.lastTok = token.STRING 993 994 default: 995 fmt.Fprintf(os.Stderr, "print: unsupported argument %v (%T)\n", arg, arg) 996 panic("go/printer type") 997 } 998 // data != "" 999 1000 next := p.pos // estimated/accurate position of next item 1001 wroteNewline, droppedFF := p.flush(next, p.lastTok) 1002 1003 // intersperse extra newlines if present in the source and 1004 // if they don't cause extra semicolons (don't do this in 1005 // flush as it will cause extra newlines at the end of a file) 1006 if !p.impliedSemi { 1007 n := nlimit(next.Line - p.pos.Line) 1008 // don't exceed maxNewlines if we already wrote one 1009 if wroteNewline && n == maxNewlines { 1010 n = maxNewlines - 1 1011 } 1012 if n > 0 { 1013 ch := byte('\n') 1014 if droppedFF { 1015 ch = '\f' // use formfeed since we dropped one before 1016 } 1017 p.writeByte(ch, n) 1018 impliedSemi = false 1019 } 1020 } 1021 1022 // the next token starts now - record its line number if requested 1023 if p.linePtr != nil { 1024 *p.linePtr = p.out.Line 1025 p.linePtr = nil 1026 } 1027 1028 p.writeString(next, data, isLit) 1029 p.impliedSemi = impliedSemi 1030 } 1031 } 1032 1033 // flush prints any pending comments and whitespace occurring textually 1034 // before the position of the next token tok. The flush result indicates 1035 // if a newline was written or if a formfeed was dropped from the whitespace 1036 // buffer. 1037 func (p *printer) flush(next token.Position, tok token.Token) (wroteNewline, droppedFF bool) { 1038 if p.commentBefore(next) { 1039 // if there are comments before the next item, intersperse them 1040 wroteNewline, droppedFF = p.intersperseComments(next, tok) 1041 } else { 1042 // otherwise, write any leftover whitespace 1043 p.writeWhitespace(len(p.wsbuf)) 1044 } 1045 return 1046 } 1047 1048 // getDoc returns the ast.CommentGroup associated with n, if any. 1049 func getDoc(n ast.Node) *ast.CommentGroup { 1050 switch n := n.(type) { 1051 case *ast.Field: 1052 return n.Doc 1053 case *ast.ImportSpec: 1054 return n.Doc 1055 case *ast.ValueSpec: 1056 return n.Doc 1057 case *ast.TypeSpec: 1058 return n.Doc 1059 case *ast.GenDecl: 1060 return n.Doc 1061 case *ast.FuncDecl: 1062 return n.Doc 1063 case *ast.File: 1064 return n.Doc 1065 } 1066 return nil 1067 } 1068 1069 func getLastComment(n ast.Node) *ast.CommentGroup { 1070 switch n := n.(type) { 1071 case *ast.Field: 1072 return n.Comment 1073 case *ast.ImportSpec: 1074 return n.Comment 1075 case *ast.ValueSpec: 1076 return n.Comment 1077 case *ast.TypeSpec: 1078 return n.Comment 1079 case *ast.GenDecl: 1080 if len(n.Specs) > 0 { 1081 return getLastComment(n.Specs[len(n.Specs)-1]) 1082 } 1083 case *ast.File: 1084 if len(n.Comments) > 0 { 1085 return n.Comments[len(n.Comments)-1] 1086 } 1087 } 1088 return nil 1089 } 1090 1091 func (p *printer) printNode(node any) error { 1092 // unpack *CommentedNode, if any 1093 var comments []*ast.CommentGroup 1094 if cnode, ok := node.(*CommentedNode); ok { 1095 node = cnode.Node 1096 comments = cnode.Comments 1097 } 1098 1099 if comments != nil { 1100 // commented node - restrict comment list to relevant range 1101 n, ok := node.(ast.Node) 1102 if !ok { 1103 goto unsupported 1104 } 1105 beg := n.Pos() 1106 end := n.End() 1107 // if the node has associated documentation, 1108 // include that commentgroup in the range 1109 // (the comment list is sorted in the order 1110 // of the comment appearance in the source code) 1111 if doc := getDoc(n); doc != nil { 1112 beg = doc.Pos() 1113 } 1114 if com := getLastComment(n); com != nil { 1115 if e := com.End(); e > end { 1116 end = e 1117 } 1118 } 1119 // token.Pos values are global offsets, we can 1120 // compare them directly 1121 i := 0 1122 for i < len(comments) && comments[i].End() < beg { 1123 i++ 1124 } 1125 j := i 1126 for j < len(comments) && comments[j].Pos() < end { 1127 j++ 1128 } 1129 if i < j { 1130 p.comments = comments[i:j] 1131 } 1132 } else if n, ok := node.(*ast.File); ok { 1133 // use ast.File comments, if any 1134 p.comments = n.Comments 1135 } 1136 1137 // if there are no comments, use node comments 1138 p.useNodeComments = p.comments == nil 1139 1140 // get comments ready for use 1141 p.nextComment() 1142 1143 p.print(pmode(0)) 1144 1145 // format node 1146 switch n := node.(type) { 1147 case ast.Expr: 1148 p.expr(n) 1149 case ast.Stmt: 1150 // A labeled statement will un-indent to position the label. 1151 // Set p.indent to 1 so we don't get indent "underflow". 1152 if _, ok := n.(*ast.LabeledStmt); ok { 1153 p.indent = 1 1154 } 1155 p.stmt(n, false) 1156 case ast.Decl: 1157 p.decl(n) 1158 case ast.Spec: 1159 p.spec(n, 1, false) 1160 case []ast.Stmt: 1161 // A labeled statement will un-indent to position the label. 1162 // Set p.indent to 1 so we don't get indent "underflow". 1163 for _, s := range n { 1164 if _, ok := s.(*ast.LabeledStmt); ok { 1165 p.indent = 1 1166 } 1167 } 1168 p.stmtList(n, 0, false) 1169 case []ast.Decl: 1170 p.declList(n) 1171 case *ast.File: 1172 p.file(n) 1173 default: 1174 goto unsupported 1175 } 1176 1177 return p.sourcePosErr 1178 1179 unsupported: 1180 return fmt.Errorf("go/printer: unsupported node type %T", node) 1181 } 1182 1183 // ---------------------------------------------------------------------------- 1184 // Trimmer 1185 1186 // A trimmer is an io.Writer filter for stripping tabwriter.Escape 1187 // characters, trailing blanks and tabs, and for converting formfeed 1188 // and vtab characters into newlines and htabs (in case no tabwriter 1189 // is used). Text bracketed by tabwriter.Escape characters is passed 1190 // through unchanged. 1191 type trimmer struct { 1192 output io.Writer 1193 state int 1194 space []byte 1195 } 1196 1197 // trimmer is implemented as a state machine. 1198 // It can be in one of the following states: 1199 const ( 1200 inSpace = iota // inside space 1201 inEscape // inside text bracketed by tabwriter.Escapes 1202 inText // inside text 1203 ) 1204 1205 func (p *trimmer) resetSpace() { 1206 p.state = inSpace 1207 p.space = p.space[0:0] 1208 } 1209 1210 // Design note: It is tempting to eliminate extra blanks occurring in 1211 // whitespace in this function as it could simplify some 1212 // of the blanks logic in the node printing functions. 1213 // However, this would mess up any formatting done by 1214 // the tabwriter. 1215 1216 var aNewline = []byte("\n") 1217 1218 func (p *trimmer) Write(data []byte) (n int, err error) { 1219 // invariants: 1220 // p.state == inSpace: 1221 // p.space is unwritten 1222 // p.state == inEscape, inText: 1223 // data[m:n] is unwritten 1224 m := 0 1225 var b byte 1226 for n, b = range data { 1227 if b == '\v' { 1228 b = '\t' // convert to htab 1229 } 1230 switch p.state { 1231 case inSpace: 1232 switch b { 1233 case '\t', ' ': 1234 p.space = append(p.space, b) 1235 case '\n', '\f': 1236 p.resetSpace() // discard trailing space 1237 _, err = p.output.Write(aNewline) 1238 case tabwriter.Escape: 1239 _, err = p.output.Write(p.space) 1240 p.state = inEscape 1241 m = n + 1 // +1: skip tabwriter.Escape 1242 default: 1243 _, err = p.output.Write(p.space) 1244 p.state = inText 1245 m = n 1246 } 1247 case inEscape: 1248 if b == tabwriter.Escape { 1249 _, err = p.output.Write(data[m:n]) 1250 p.resetSpace() 1251 } 1252 case inText: 1253 switch b { 1254 case '\t', ' ': 1255 _, err = p.output.Write(data[m:n]) 1256 p.resetSpace() 1257 p.space = append(p.space, b) 1258 case '\n', '\f': 1259 _, err = p.output.Write(data[m:n]) 1260 p.resetSpace() 1261 if err == nil { 1262 _, err = p.output.Write(aNewline) 1263 } 1264 case tabwriter.Escape: 1265 _, err = p.output.Write(data[m:n]) 1266 p.state = inEscape 1267 m = n + 1 // +1: skip tabwriter.Escape 1268 } 1269 default: 1270 panic("unreachable") 1271 } 1272 if err != nil { 1273 return 1274 } 1275 } 1276 n = len(data) 1277 1278 switch p.state { 1279 case inEscape, inText: 1280 _, err = p.output.Write(data[m:n]) 1281 p.resetSpace() 1282 } 1283 1284 return 1285 } 1286 1287 // ---------------------------------------------------------------------------- 1288 // Public interface 1289 1290 // A Mode value is a set of flags (or 0). They control printing. 1291 type Mode uint 1292 1293 const ( 1294 RawFormat Mode = 1 << iota // do not use a tabwriter; if set, UseSpaces is ignored 1295 TabIndent // use tabs for indentation independent of UseSpaces 1296 UseSpaces // use spaces instead of tabs for alignment 1297 SourcePos // emit //line directives to preserve original source positions 1298 ) 1299 1300 // The mode below is not included in printer's public API because 1301 // editing code text is deemed out of scope. Because this mode is 1302 // unexported, it's also possible to modify or remove it based on 1303 // the evolving needs of go/format and cmd/gofmt without breaking 1304 // users. See discussion in CL 240683. 1305 const ( 1306 // normalizeNumbers means to canonicalize number 1307 // literal prefixes and exponents while printing. 1308 // 1309 // This value is known in and used by go/format and cmd/gofmt. 1310 // It is currently more convenient and performant for those 1311 // packages to apply number normalization during printing, 1312 // rather than by modifying the AST in advance. 1313 normalizeNumbers Mode = 1 << 30 1314 ) 1315 1316 // A Config node controls the output of Fprint. 1317 type Config struct { 1318 Mode Mode // default: 0 1319 Tabwidth int // default: 8 1320 Indent int // default: 0 (all code is indented at least by this much) 1321 } 1322 1323 var printerPool = sync.Pool{ 1324 New: func() any { 1325 return &printer{ 1326 // Whitespace sequences are short. 1327 wsbuf: make([]whiteSpace, 0, 16), 1328 // We start the printer with a 16K output buffer, which is currently 1329 // larger than about 80% of Go files in the standard library. 1330 output: make([]byte, 0, 16<<10), 1331 } 1332 }, 1333 } 1334 1335 func newPrinter(cfg *Config, fset *token.FileSet, nodeSizes map[ast.Node]int) *printer { 1336 p := printerPool.Get().(*printer) 1337 *p = printer{ 1338 Config: *cfg, 1339 fset: fset, 1340 pos: token.Position{Line: 1, Column: 1}, 1341 out: token.Position{Line: 1, Column: 1}, 1342 wsbuf: p.wsbuf[:0], 1343 nodeSizes: nodeSizes, 1344 cachedPos: -1, 1345 output: p.output[:0], 1346 } 1347 return p 1348 } 1349 1350 func (p *printer) free() { 1351 // Hard limit on buffer size; see https://golang.org/issue/23199. 1352 if cap(p.output) > 64<<10 { 1353 return 1354 } 1355 1356 printerPool.Put(p) 1357 } 1358 1359 // fprint implements Fprint and takes a nodesSizes map for setting up the printer state. 1360 func (cfg *Config) fprint(output io.Writer, fset *token.FileSet, node any, nodeSizes map[ast.Node]int) (err error) { 1361 // print node 1362 p := newPrinter(cfg, fset, nodeSizes) 1363 defer p.free() 1364 if err = p.printNode(node); err != nil { 1365 return 1366 } 1367 // print outstanding comments 1368 p.impliedSemi = false // EOF acts like a newline 1369 p.flush(token.Position{Offset: infinity, Line: infinity}, token.EOF) 1370 1371 // output is buffered in p.output now. 1372 // fix //go:build and // +build comments if needed. 1373 p.fixGoBuildLines() 1374 1375 // redirect output through a trimmer to eliminate trailing whitespace 1376 // (Input to a tabwriter must be untrimmed since trailing tabs provide 1377 // formatting information. The tabwriter could provide trimming 1378 // functionality but no tabwriter is used when RawFormat is set.) 1379 output = &trimmer{output: output} 1380 1381 // redirect output through a tabwriter if necessary 1382 if cfg.Mode&RawFormat == 0 { 1383 minwidth := cfg.Tabwidth 1384 1385 padchar := byte('\t') 1386 if cfg.Mode&UseSpaces != 0 { 1387 padchar = ' ' 1388 } 1389 1390 twmode := tabwriter.DiscardEmptyColumns 1391 if cfg.Mode&TabIndent != 0 { 1392 minwidth = 0 1393 twmode |= tabwriter.TabIndent 1394 } 1395 1396 output = tabwriter.NewWriter(output, minwidth, cfg.Tabwidth, 1, padchar, twmode) 1397 } 1398 1399 // write printer result via tabwriter/trimmer to output 1400 if _, err = output.Write(p.output); err != nil { 1401 return 1402 } 1403 1404 // flush tabwriter, if any 1405 if tw, _ := output.(*tabwriter.Writer); tw != nil { 1406 err = tw.Flush() 1407 } 1408 1409 return 1410 } 1411 1412 // A CommentedNode bundles an AST node and corresponding comments. 1413 // It may be provided as argument to any of the [Fprint] functions. 1414 type CommentedNode struct { 1415 Node any // *ast.File, or ast.Expr, ast.Decl, ast.Spec, or ast.Stmt 1416 Comments []*ast.CommentGroup 1417 } 1418 1419 // Fprint "pretty-prints" an AST node to output for a given configuration cfg. 1420 // Position information is interpreted relative to the file set fset. 1421 // The node type must be *[ast.File], *[CommentedNode], [][ast.Decl], [][ast.Stmt], 1422 // or assignment-compatible to [ast.Expr], [ast.Decl], [ast.Spec], or [ast.Stmt]. 1423 func (cfg *Config) Fprint(output io.Writer, fset *token.FileSet, node any) error { 1424 return cfg.fprint(output, fset, node, make(map[ast.Node]int)) 1425 } 1426 1427 // Fprint "pretty-prints" an AST node to output. 1428 // It calls [Config.Fprint] with default settings. 1429 // Note that gofmt uses tabs for indentation but spaces for alignment; 1430 // use format.Node (package go/format) for output that matches gofmt. 1431 func Fprint(output io.Writer, fset *token.FileSet, node any) error { 1432 return (&Config{Tabwidth: 8}).Fprint(output, fset, node) 1433 }