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