libgo: update to Go 1.11
[official-gcc.git] / libgo / go / go / printer / printer.go
blob9143442a27cb95985d391900ce1b4d710131d89c
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.
5 // Package printer implements printing of AST nodes.
6 package printer
8 import (
9 "fmt"
10 "go/ast"
11 "go/token"
12 "io"
13 "os"
14 "strings"
15 "text/tabwriter"
16 "unicode"
19 const (
20 maxNewlines = 2 // max. number of newlines between source text
21 debug = false // enable for debugging
22 infinity = 1 << 30
25 type whiteSpace byte
27 const (
28 ignore = whiteSpace(0)
29 blank = whiteSpace(' ')
30 vtab = whiteSpace('\v')
31 newline = whiteSpace('\n')
32 formfeed = whiteSpace('\f')
33 indent = whiteSpace('>')
34 unindent = whiteSpace('<')
37 // A pmode value represents the current printer mode.
38 type pmode int
40 const (
41 noExtraBlank pmode = 1 << iota // disables extra blank after /*-style comment
42 noExtraLinebreak // disables extra line break after /*-style comment
45 type commentInfo struct {
46 cindex int // current comment index
47 comment *ast.CommentGroup // = printer.comments[cindex]; or nil
48 commentOffset int // = printer.posFor(printer.comments[cindex].List[0].Pos()).Offset; or infinity
49 commentNewline bool // true if the comment group contains newlines
52 type printer struct {
53 // Configuration (does not change after initialization)
54 Config
55 fset *token.FileSet
57 // Current state
58 output []byte // raw printer result
59 indent int // current indentation
60 level int // level == 0: outside composite literal; level > 0: inside composite literal
61 mode pmode // current printer mode
62 endAlignment bool // if set, terminate alignment immediately
63 impliedSemi bool // if set, a linebreak implies a semicolon
64 lastTok token.Token // last token printed (token.ILLEGAL if it's whitespace)
65 prevOpen token.Token // previous non-brace "open" token (, [, or token.ILLEGAL
66 wsbuf []whiteSpace // delayed white space
68 // Positions
69 // The out position differs from the pos position when the result
70 // formatting differs from the source formatting (in the amount of
71 // white space). If there's a difference and SourcePos is set in
72 // ConfigMode, //line directives are used in the output to restore
73 // original source positions for a reader.
74 pos token.Position // current position in AST (source) space
75 out token.Position // current position in output space
76 last token.Position // value of pos after calling writeString
77 linePtr *int // if set, record out.Line for the next token in *linePtr
79 // The list of all source comments, in order of appearance.
80 comments []*ast.CommentGroup // may be nil
81 useNodeComments bool // if not set, ignore lead and line comments of nodes
83 // Information about p.comments[p.cindex]; set up by nextComment.
84 commentInfo
86 // Cache of already computed node sizes.
87 nodeSizes map[ast.Node]int
89 // Cache of most recently computed line position.
90 cachedPos token.Pos
91 cachedLine int // line corresponding to cachedPos
94 func (p *printer) init(cfg *Config, fset *token.FileSet, nodeSizes map[ast.Node]int) {
95 p.Config = *cfg
96 p.fset = fset
97 p.pos = token.Position{Line: 1, Column: 1}
98 p.out = token.Position{Line: 1, Column: 1}
99 p.wsbuf = make([]whiteSpace, 0, 16) // whitespace sequences are short
100 p.nodeSizes = nodeSizes
101 p.cachedPos = -1
104 func (p *printer) internalError(msg ...interface{}) {
105 if debug {
106 fmt.Print(p.pos.String() + ": ")
107 fmt.Println(msg...)
108 panic("go/printer")
112 // commentsHaveNewline reports whether a list of comments belonging to
113 // an *ast.CommentGroup contains newlines. Because the position information
114 // may only be partially correct, we also have to read the comment text.
115 func (p *printer) commentsHaveNewline(list []*ast.Comment) bool {
116 // len(list) > 0
117 line := p.lineFor(list[0].Pos())
118 for i, c := range list {
119 if i > 0 && p.lineFor(list[i].Pos()) != line {
120 // not all comments on the same line
121 return true
123 if t := c.Text; len(t) >= 2 && (t[1] == '/' || strings.Contains(t, "\n")) {
124 return true
127 _ = line
128 return false
131 func (p *printer) nextComment() {
132 for p.cindex < len(p.comments) {
133 c := p.comments[p.cindex]
134 p.cindex++
135 if list := c.List; len(list) > 0 {
136 p.comment = c
137 p.commentOffset = p.posFor(list[0].Pos()).Offset
138 p.commentNewline = p.commentsHaveNewline(list)
139 return
141 // we should not reach here (correct ASTs don't have empty
142 // ast.CommentGroup nodes), but be conservative and try again
144 // no more comments
145 p.commentOffset = infinity
148 // commentBefore reports whether the current comment group occurs
149 // before the next position in the source code and printing it does
150 // not introduce implicit semicolons.
152 func (p *printer) commentBefore(next token.Position) bool {
153 return p.commentOffset < next.Offset && (!p.impliedSemi || !p.commentNewline)
156 // commentSizeBefore returns the estimated size of the
157 // comments on the same line before the next position.
159 func (p *printer) commentSizeBefore(next token.Position) int {
160 // save/restore current p.commentInfo (p.nextComment() modifies it)
161 defer func(info commentInfo) {
162 p.commentInfo = info
163 }(p.commentInfo)
165 size := 0
166 for p.commentBefore(next) {
167 for _, c := range p.comment.List {
168 size += len(c.Text)
170 p.nextComment()
172 return size
175 // recordLine records the output line number for the next non-whitespace
176 // token in *linePtr. It is used to compute an accurate line number for a
177 // formatted construct, independent of pending (not yet emitted) whitespace
178 // or comments.
180 func (p *printer) recordLine(linePtr *int) {
181 p.linePtr = linePtr
184 // linesFrom returns the number of output lines between the current
185 // output line and the line argument, ignoring any pending (not yet
186 // emitted) whitespace or comments. It is used to compute an accurate
187 // size (in number of lines) for a formatted construct.
189 func (p *printer) linesFrom(line int) int {
190 return p.out.Line - line
193 func (p *printer) posFor(pos token.Pos) token.Position {
194 // not used frequently enough to cache entire token.Position
195 return p.fset.PositionFor(pos, false /* absolute position */)
198 func (p *printer) lineFor(pos token.Pos) int {
199 if pos != p.cachedPos {
200 p.cachedPos = pos
201 p.cachedLine = p.fset.PositionFor(pos, false /* absolute position */).Line
203 return p.cachedLine
206 // writeLineDirective writes a //line directive if necessary.
207 func (p *printer) writeLineDirective(pos token.Position) {
208 if pos.IsValid() && (p.out.Line != pos.Line || p.out.Filename != pos.Filename) {
209 p.output = append(p.output, tabwriter.Escape) // protect '\n' in //line from tabwriter interpretation
210 p.output = append(p.output, fmt.Sprintf("//line %s:%d\n", pos.Filename, pos.Line)...)
211 p.output = append(p.output, tabwriter.Escape)
212 // p.out must match the //line directive
213 p.out.Filename = pos.Filename
214 p.out.Line = pos.Line
218 // writeIndent writes indentation.
219 func (p *printer) writeIndent() {
220 // use "hard" htabs - indentation columns
221 // must not be discarded by the tabwriter
222 n := p.Config.Indent + p.indent // include base indentation
223 for i := 0; i < n; i++ {
224 p.output = append(p.output, '\t')
227 // update positions
228 p.pos.Offset += n
229 p.pos.Column += n
230 p.out.Column += n
233 // writeByte writes ch n times to p.output and updates p.pos.
234 // Only used to write formatting (white space) characters.
235 func (p *printer) writeByte(ch byte, n int) {
236 if p.endAlignment {
237 // Ignore any alignment control character;
238 // and at the end of the line, break with
239 // a formfeed to indicate termination of
240 // existing columns.
241 switch ch {
242 case '\t', '\v':
243 ch = ' '
244 case '\n', '\f':
245 ch = '\f'
246 p.endAlignment = false
250 if p.out.Column == 1 {
251 // no need to write line directives before white space
252 p.writeIndent()
255 for i := 0; i < n; i++ {
256 p.output = append(p.output, ch)
259 // update positions
260 p.pos.Offset += n
261 if ch == '\n' || ch == '\f' {
262 p.pos.Line += n
263 p.out.Line += n
264 p.pos.Column = 1
265 p.out.Column = 1
266 return
268 p.pos.Column += n
269 p.out.Column += n
272 // writeString writes the string s to p.output and updates p.pos, p.out,
273 // and p.last. If isLit is set, s is escaped w/ tabwriter.Escape characters
274 // to protect s from being interpreted by the tabwriter.
276 // Note: writeString is only used to write Go tokens, literals, and
277 // comments, all of which must be written literally. Thus, it is correct
278 // to always set isLit = true. However, setting it explicitly only when
279 // needed (i.e., when we don't know that s contains no tabs or line breaks)
280 // avoids processing extra escape characters and reduces run time of the
281 // printer benchmark by up to 10%.
283 func (p *printer) writeString(pos token.Position, s string, isLit bool) {
284 if p.out.Column == 1 {
285 if p.Config.Mode&SourcePos != 0 {
286 p.writeLineDirective(pos)
288 p.writeIndent()
291 if pos.IsValid() {
292 // update p.pos (if pos is invalid, continue with existing p.pos)
293 // Note: Must do this after handling line beginnings because
294 // writeIndent updates p.pos if there's indentation, but p.pos
295 // is the position of s.
296 p.pos = pos
299 if isLit {
300 // Protect s such that is passes through the tabwriter
301 // unchanged. Note that valid Go programs cannot contain
302 // tabwriter.Escape bytes since they do not appear in legal
303 // UTF-8 sequences.
304 p.output = append(p.output, tabwriter.Escape)
307 if debug {
308 p.output = append(p.output, fmt.Sprintf("/*%s*/", pos)...) // do not update p.pos!
310 p.output = append(p.output, s...)
312 // update positions
313 nlines := 0
314 var li int // index of last newline; valid if nlines > 0
315 for i := 0; i < len(s); i++ {
316 // Raw string literals may contain any character except back quote (`).
317 if ch := s[i]; ch == '\n' || ch == '\f' {
318 // account for line break
319 nlines++
320 li = i
321 // A line break inside a literal will break whatever column
322 // formatting is in place; ignore any further alignment through
323 // the end of the line.
324 p.endAlignment = true
327 p.pos.Offset += len(s)
328 if nlines > 0 {
329 p.pos.Line += nlines
330 p.out.Line += nlines
331 c := len(s) - li
332 p.pos.Column = c
333 p.out.Column = c
334 } else {
335 p.pos.Column += len(s)
336 p.out.Column += len(s)
339 if isLit {
340 p.output = append(p.output, tabwriter.Escape)
343 p.last = p.pos
346 // writeCommentPrefix writes the whitespace before a comment.
347 // If there is any pending whitespace, it consumes as much of
348 // it as is likely to help position the comment nicely.
349 // pos is the comment position, next the position of the item
350 // after all pending comments, prev is the previous comment in
351 // a group of comments (or nil), and tok is the next token.
353 func (p *printer) writeCommentPrefix(pos, next token.Position, prev *ast.Comment, tok token.Token) {
354 if len(p.output) == 0 {
355 // the comment is the first item to be printed - don't write any whitespace
356 return
359 if pos.IsValid() && pos.Filename != p.last.Filename {
360 // comment in a different file - separate with newlines
361 p.writeByte('\f', maxNewlines)
362 return
365 if pos.Line == p.last.Line && (prev == nil || prev.Text[1] != '/') {
366 // comment on the same line as last item:
367 // separate with at least one separator
368 hasSep := false
369 if prev == nil {
370 // first comment of a comment group
371 j := 0
372 for i, ch := range p.wsbuf {
373 switch ch {
374 case blank:
375 // ignore any blanks before a comment
376 p.wsbuf[i] = ignore
377 continue
378 case vtab:
379 // respect existing tabs - important
380 // for proper formatting of commented structs
381 hasSep = true
382 continue
383 case indent:
384 // apply pending indentation
385 continue
387 j = i
388 break
390 p.writeWhitespace(j)
392 // make sure there is at least one separator
393 if !hasSep {
394 sep := byte('\t')
395 if pos.Line == next.Line {
396 // next item is on the same line as the comment
397 // (which must be a /*-style comment): separate
398 // with a blank instead of a tab
399 sep = ' '
401 p.writeByte(sep, 1)
404 } else {
405 // comment on a different line:
406 // separate with at least one line break
407 droppedLinebreak := false
408 j := 0
409 for i, ch := range p.wsbuf {
410 switch ch {
411 case blank, vtab:
412 // ignore any horizontal whitespace before line breaks
413 p.wsbuf[i] = ignore
414 continue
415 case indent:
416 // apply pending indentation
417 continue
418 case unindent:
419 // if this is not the last unindent, apply it
420 // as it is (likely) belonging to the last
421 // construct (e.g., a multi-line expression list)
422 // and is not part of closing a block
423 if i+1 < len(p.wsbuf) && p.wsbuf[i+1] == unindent {
424 continue
426 // if the next token is not a closing }, apply the unindent
427 // if it appears that the comment is aligned with the
428 // token; otherwise assume the unindent is part of a
429 // closing block and stop (this scenario appears with
430 // comments before a case label where the comments
431 // apply to the next case instead of the current one)
432 if tok != token.RBRACE && pos.Column == next.Column {
433 continue
435 case newline, formfeed:
436 p.wsbuf[i] = ignore
437 droppedLinebreak = prev == nil // record only if first comment of a group
439 j = i
440 break
442 p.writeWhitespace(j)
444 // determine number of linebreaks before the comment
445 n := 0
446 if pos.IsValid() && p.last.IsValid() {
447 n = pos.Line - p.last.Line
448 if n < 0 { // should never happen
449 n = 0
453 // at the package scope level only (p.indent == 0),
454 // add an extra newline if we dropped one before:
455 // this preserves a blank line before documentation
456 // comments at the package scope level (issue 2570)
457 if p.indent == 0 && droppedLinebreak {
461 // make sure there is at least one line break
462 // if the previous comment was a line comment
463 if n == 0 && prev != nil && prev.Text[1] == '/' {
464 n = 1
467 if n > 0 {
468 // use formfeeds to break columns before a comment;
469 // this is analogous to using formfeeds to separate
470 // individual lines of /*-style comments
471 p.writeByte('\f', nlimit(n))
476 // Returns true if s contains only white space
477 // (only tabs and blanks can appear in the printer's context).
479 func isBlank(s string) bool {
480 for i := 0; i < len(s); i++ {
481 if s[i] > ' ' {
482 return false
485 return true
488 // commonPrefix returns the common prefix of a and b.
489 func commonPrefix(a, b string) string {
490 i := 0
491 for i < len(a) && i < len(b) && a[i] == b[i] && (a[i] <= ' ' || a[i] == '*') {
494 return a[0:i]
497 // trimRight returns s with trailing whitespace removed.
498 func trimRight(s string) string {
499 return strings.TrimRightFunc(s, unicode.IsSpace)
502 // stripCommonPrefix removes a common prefix from /*-style comment lines (unless no
503 // comment line is indented, all but the first line have some form of space prefix).
504 // The prefix is computed using heuristics such that is likely that the comment
505 // contents are nicely laid out after re-printing each line using the printer's
506 // current indentation.
508 func stripCommonPrefix(lines []string) {
509 if len(lines) <= 1 {
510 return // at most one line - nothing to do
512 // len(lines) > 1
514 // The heuristic in this function tries to handle a few
515 // common patterns of /*-style comments: Comments where
516 // the opening /* and closing */ are aligned and the
517 // rest of the comment text is aligned and indented with
518 // blanks or tabs, cases with a vertical "line of stars"
519 // on the left, and cases where the closing */ is on the
520 // same line as the last comment text.
522 // Compute maximum common white prefix of all but the first,
523 // last, and blank lines, and replace blank lines with empty
524 // lines (the first line starts with /* and has no prefix).
525 // In cases where only the first and last lines are not blank,
526 // such as two-line comments, or comments where all inner lines
527 // are blank, consider the last line for the prefix computation
528 // since otherwise the prefix would be empty.
530 // Note that the first and last line are never empty (they
531 // contain the opening /* and closing */ respectively) and
532 // thus they can be ignored by the blank line check.
533 prefix := ""
534 prefixSet := false
535 if len(lines) > 2 {
536 for i, line := range lines[1 : len(lines)-1] {
537 if isBlank(line) {
538 lines[1+i] = "" // range starts with lines[1]
539 } else {
540 if !prefixSet {
541 prefix = line
542 prefixSet = true
544 prefix = commonPrefix(prefix, line)
549 // If we don't have a prefix yet, consider the last line.
550 if !prefixSet {
551 line := lines[len(lines)-1]
552 prefix = commonPrefix(line, line)
556 * Check for vertical "line of stars" and correct prefix accordingly.
558 lineOfStars := false
559 if i := strings.Index(prefix, "*"); i >= 0 {
560 // Line of stars present.
561 if i > 0 && prefix[i-1] == ' ' {
562 i-- // remove trailing blank from prefix so stars remain aligned
564 prefix = prefix[0:i]
565 lineOfStars = true
566 } else {
567 // No line of stars present.
568 // Determine the white space on the first line after the /*
569 // and before the beginning of the comment text, assume two
570 // blanks instead of the /* unless the first character after
571 // the /* is a tab. If the first comment line is empty but
572 // for the opening /*, assume up to 3 blanks or a tab. This
573 // whitespace may be found as suffix in the common prefix.
574 first := lines[0]
575 if isBlank(first[2:]) {
576 // no comment text on the first line:
577 // reduce prefix by up to 3 blanks or a tab
578 // if present - this keeps comment text indented
579 // relative to the /* and */'s if it was indented
580 // in the first place
581 i := len(prefix)
582 for n := 0; n < 3 && i > 0 && prefix[i-1] == ' '; n++ {
585 if i == len(prefix) && i > 0 && prefix[i-1] == '\t' {
588 prefix = prefix[0:i]
589 } else {
590 // comment text on the first line
591 suffix := make([]byte, len(first))
592 n := 2 // start after opening /*
593 for n < len(first) && first[n] <= ' ' {
594 suffix[n] = first[n]
597 if n > 2 && suffix[2] == '\t' {
598 // assume the '\t' compensates for the /*
599 suffix = suffix[2:n]
600 } else {
601 // otherwise assume two blanks
602 suffix[0], suffix[1] = ' ', ' '
603 suffix = suffix[0:n]
605 // Shorten the computed common prefix by the length of
606 // suffix, if it is found as suffix of the prefix.
607 prefix = strings.TrimSuffix(prefix, string(suffix))
611 // Handle last line: If it only contains a closing */, align it
612 // with the opening /*, otherwise align the text with the other
613 // lines.
614 last := lines[len(lines)-1]
615 closing := "*/"
616 i := strings.Index(last, closing) // i >= 0 (closing is always present)
617 if isBlank(last[0:i]) {
618 // last line only contains closing */
619 if lineOfStars {
620 closing = " */" // add blank to align final star
622 lines[len(lines)-1] = prefix + closing
623 } else {
624 // last line contains more comment text - assume
625 // it is aligned like the other lines and include
626 // in prefix computation
627 prefix = commonPrefix(prefix, last)
630 // Remove the common prefix from all but the first and empty lines.
631 for i, line := range lines {
632 if i > 0 && line != "" {
633 lines[i] = line[len(prefix):]
638 func (p *printer) writeComment(comment *ast.Comment) {
639 text := comment.Text
640 pos := p.posFor(comment.Pos())
642 const linePrefix = "//line "
643 if strings.HasPrefix(text, linePrefix) && (!pos.IsValid() || pos.Column == 1) {
644 // Possibly a //-style line directive.
645 // Suspend indentation temporarily to keep line directive valid.
646 defer func(indent int) { p.indent = indent }(p.indent)
647 p.indent = 0
650 // shortcut common case of //-style comments
651 if text[1] == '/' {
652 p.writeString(pos, trimRight(text), true)
653 return
656 // for /*-style comments, print line by line and let the
657 // write function take care of the proper indentation
658 lines := strings.Split(text, "\n")
660 // The comment started in the first column but is going
661 // to be indented. For an idempotent result, add indentation
662 // to all lines such that they look like they were indented
663 // before - this will make sure the common prefix computation
664 // is the same independent of how many times formatting is
665 // applied (was issue 1835).
666 if pos.IsValid() && pos.Column == 1 && p.indent > 0 {
667 for i, line := range lines[1:] {
668 lines[1+i] = " " + line
672 stripCommonPrefix(lines)
674 // write comment lines, separated by formfeed,
675 // without a line break after the last line
676 for i, line := range lines {
677 if i > 0 {
678 p.writeByte('\f', 1)
679 pos = p.pos
681 if len(line) > 0 {
682 p.writeString(pos, trimRight(line), true)
687 // writeCommentSuffix writes a line break after a comment if indicated
688 // and processes any leftover indentation information. If a line break
689 // is needed, the kind of break (newline vs formfeed) depends on the
690 // pending whitespace. The writeCommentSuffix result indicates if a
691 // newline was written or if a formfeed was dropped from the whitespace
692 // buffer.
694 func (p *printer) writeCommentSuffix(needsLinebreak bool) (wroteNewline, droppedFF bool) {
695 for i, ch := range p.wsbuf {
696 switch ch {
697 case blank, vtab:
698 // ignore trailing whitespace
699 p.wsbuf[i] = ignore
700 case indent, unindent:
701 // don't lose indentation information
702 case newline, formfeed:
703 // if we need a line break, keep exactly one
704 // but remember if we dropped any formfeeds
705 if needsLinebreak {
706 needsLinebreak = false
707 wroteNewline = true
708 } else {
709 if ch == formfeed {
710 droppedFF = true
712 p.wsbuf[i] = ignore
716 p.writeWhitespace(len(p.wsbuf))
718 // make sure we have a line break
719 if needsLinebreak {
720 p.writeByte('\n', 1)
721 wroteNewline = true
724 return
727 // containsLinebreak reports whether the whitespace buffer contains any line breaks.
728 func (p *printer) containsLinebreak() bool {
729 for _, ch := range p.wsbuf {
730 if ch == newline || ch == formfeed {
731 return true
734 return false
737 // intersperseComments consumes all comments that appear before the next token
738 // tok and prints it together with the buffered whitespace (i.e., the whitespace
739 // that needs to be written before the next token). A heuristic is used to mix
740 // the comments and whitespace. The intersperseComments result indicates if a
741 // newline was written or if a formfeed was dropped from the whitespace buffer.
743 func (p *printer) intersperseComments(next token.Position, tok token.Token) (wroteNewline, droppedFF bool) {
744 var last *ast.Comment
745 for p.commentBefore(next) {
746 for _, c := range p.comment.List {
747 p.writeCommentPrefix(p.posFor(c.Pos()), next, last, tok)
748 p.writeComment(c)
749 last = c
751 p.nextComment()
754 if last != nil {
755 // If the last comment is a /*-style comment and the next item
756 // follows on the same line but is not a comma, and not a "closing"
757 // token immediately following its corresponding "opening" token,
758 // add an extra separator unless explicitly disabled. Use a blank
759 // as separator unless we have pending linebreaks, they are not
760 // disabled, and we are outside a composite literal, in which case
761 // we want a linebreak (issue 15137).
762 // TODO(gri) This has become overly complicated. We should be able
763 // to track whether we're inside an expression or statement and
764 // use that information to decide more directly.
765 needsLinebreak := false
766 if p.mode&noExtraBlank == 0 &&
767 last.Text[1] == '*' && p.lineFor(last.Pos()) == next.Line &&
768 tok != token.COMMA &&
769 (tok != token.RPAREN || p.prevOpen == token.LPAREN) &&
770 (tok != token.RBRACK || p.prevOpen == token.LBRACK) {
771 if p.containsLinebreak() && p.mode&noExtraLinebreak == 0 && p.level == 0 {
772 needsLinebreak = true
773 } else {
774 p.writeByte(' ', 1)
777 // Ensure that there is a line break after a //-style comment,
778 // before EOF, and before a closing '}' unless explicitly disabled.
779 if last.Text[1] == '/' ||
780 tok == token.EOF ||
781 tok == token.RBRACE && p.mode&noExtraLinebreak == 0 {
782 needsLinebreak = true
784 return p.writeCommentSuffix(needsLinebreak)
787 // no comment was written - we should never reach here since
788 // intersperseComments should not be called in that case
789 p.internalError("intersperseComments called without pending comments")
790 return
793 // whiteWhitespace writes the first n whitespace entries.
794 func (p *printer) writeWhitespace(n int) {
795 // write entries
796 for i := 0; i < n; i++ {
797 switch ch := p.wsbuf[i]; ch {
798 case ignore:
799 // ignore!
800 case indent:
801 p.indent++
802 case unindent:
803 p.indent--
804 if p.indent < 0 {
805 p.internalError("negative indentation:", p.indent)
806 p.indent = 0
808 case newline, formfeed:
809 // A line break immediately followed by a "correcting"
810 // unindent is swapped with the unindent - this permits
811 // proper label positioning. If a comment is between
812 // the line break and the label, the unindent is not
813 // part of the comment whitespace prefix and the comment
814 // will be positioned correctly indented.
815 if i+1 < n && p.wsbuf[i+1] == unindent {
816 // Use a formfeed to terminate the current section.
817 // Otherwise, a long label name on the next line leading
818 // to a wide column may increase the indentation column
819 // of lines before the label; effectively leading to wrong
820 // indentation.
821 p.wsbuf[i], p.wsbuf[i+1] = unindent, formfeed
822 i-- // do it again
823 continue
825 fallthrough
826 default:
827 p.writeByte(byte(ch), 1)
831 // shift remaining entries down
832 l := copy(p.wsbuf, p.wsbuf[n:])
833 p.wsbuf = p.wsbuf[:l]
836 // ----------------------------------------------------------------------------
837 // Printing interface
839 // nlines limits n to maxNewlines.
840 func nlimit(n int) int {
841 if n > maxNewlines {
842 n = maxNewlines
844 return n
847 func mayCombine(prev token.Token, next byte) (b bool) {
848 switch prev {
849 case token.INT:
850 b = next == '.' // 1.
851 case token.ADD:
852 b = next == '+' // ++
853 case token.SUB:
854 b = next == '-' // --
855 case token.QUO:
856 b = next == '*' // /*
857 case token.LSS:
858 b = next == '-' || next == '<' // <- or <<
859 case token.AND:
860 b = next == '&' || next == '^' // && or &^
862 return
865 // print prints a list of "items" (roughly corresponding to syntactic
866 // tokens, but also including whitespace and formatting information).
867 // It is the only print function that should be called directly from
868 // any of the AST printing functions in nodes.go.
870 // Whitespace is accumulated until a non-whitespace token appears. Any
871 // comments that need to appear before that token are printed first,
872 // taking into account the amount and structure of any pending white-
873 // space for best comment placement. Then, any leftover whitespace is
874 // printed, followed by the actual token.
876 func (p *printer) print(args ...interface{}) {
877 for _, arg := range args {
878 // information about the current arg
879 var data string
880 var isLit bool
881 var impliedSemi bool // value for p.impliedSemi after this arg
883 // record previous opening token, if any
884 switch p.lastTok {
885 case token.ILLEGAL:
886 // ignore (white space)
887 case token.LPAREN, token.LBRACK:
888 p.prevOpen = p.lastTok
889 default:
890 // other tokens followed any opening token
891 p.prevOpen = token.ILLEGAL
894 switch x := arg.(type) {
895 case pmode:
896 // toggle printer mode
897 p.mode ^= x
898 continue
900 case whiteSpace:
901 if x == ignore {
902 // don't add ignore's to the buffer; they
903 // may screw up "correcting" unindents (see
904 // LabeledStmt)
905 continue
907 i := len(p.wsbuf)
908 if i == cap(p.wsbuf) {
909 // Whitespace sequences are very short so this should
910 // never happen. Handle gracefully (but possibly with
911 // bad comment placement) if it does happen.
912 p.writeWhitespace(i)
913 i = 0
915 p.wsbuf = p.wsbuf[0 : i+1]
916 p.wsbuf[i] = x
917 if x == newline || x == formfeed {
918 // newlines affect the current state (p.impliedSemi)
919 // and not the state after printing arg (impliedSemi)
920 // because comments can be interspersed before the arg
921 // in this case
922 p.impliedSemi = false
924 p.lastTok = token.ILLEGAL
925 continue
927 case *ast.Ident:
928 data = x.Name
929 impliedSemi = true
930 p.lastTok = token.IDENT
932 case *ast.BasicLit:
933 data = x.Value
934 isLit = true
935 impliedSemi = true
936 p.lastTok = x.Kind
938 case token.Token:
939 s := x.String()
940 if mayCombine(p.lastTok, s[0]) {
941 // the previous and the current token must be
942 // separated by a blank otherwise they combine
943 // into a different incorrect token sequence
944 // (except for token.INT followed by a '.' this
945 // should never happen because it is taken care
946 // of via binary expression formatting)
947 if len(p.wsbuf) != 0 {
948 p.internalError("whitespace buffer not empty")
950 p.wsbuf = p.wsbuf[0:1]
951 p.wsbuf[0] = ' '
953 data = s
954 // some keywords followed by a newline imply a semicolon
955 switch x {
956 case token.BREAK, token.CONTINUE, token.FALLTHROUGH, token.RETURN,
957 token.INC, token.DEC, token.RPAREN, token.RBRACK, token.RBRACE:
958 impliedSemi = true
960 p.lastTok = x
962 case token.Pos:
963 if x.IsValid() {
964 p.pos = p.posFor(x) // accurate position of next item
966 continue
968 case string:
969 // incorrect AST - print error message
970 data = x
971 isLit = true
972 impliedSemi = true
973 p.lastTok = token.STRING
975 default:
976 fmt.Fprintf(os.Stderr, "print: unsupported argument %v (%T)\n", arg, arg)
977 panic("go/printer type")
979 // data != ""
981 next := p.pos // estimated/accurate position of next item
982 wroteNewline, droppedFF := p.flush(next, p.lastTok)
984 // intersperse extra newlines if present in the source and
985 // if they don't cause extra semicolons (don't do this in
986 // flush as it will cause extra newlines at the end of a file)
987 if !p.impliedSemi {
988 n := nlimit(next.Line - p.pos.Line)
989 // don't exceed maxNewlines if we already wrote one
990 if wroteNewline && n == maxNewlines {
991 n = maxNewlines - 1
993 if n > 0 {
994 ch := byte('\n')
995 if droppedFF {
996 ch = '\f' // use formfeed since we dropped one before
998 p.writeByte(ch, n)
999 impliedSemi = false
1003 // the next token starts now - record its line number if requested
1004 if p.linePtr != nil {
1005 *p.linePtr = p.out.Line
1006 p.linePtr = nil
1009 p.writeString(next, data, isLit)
1010 p.impliedSemi = impliedSemi
1014 // flush prints any pending comments and whitespace occurring textually
1015 // before the position of the next token tok. The flush result indicates
1016 // if a newline was written or if a formfeed was dropped from the whitespace
1017 // buffer.
1019 func (p *printer) flush(next token.Position, tok token.Token) (wroteNewline, droppedFF bool) {
1020 if p.commentBefore(next) {
1021 // if there are comments before the next item, intersperse them
1022 wroteNewline, droppedFF = p.intersperseComments(next, tok)
1023 } else {
1024 // otherwise, write any leftover whitespace
1025 p.writeWhitespace(len(p.wsbuf))
1027 return
1030 // getNode returns the ast.CommentGroup associated with n, if any.
1031 func getDoc(n ast.Node) *ast.CommentGroup {
1032 switch n := n.(type) {
1033 case *ast.Field:
1034 return n.Doc
1035 case *ast.ImportSpec:
1036 return n.Doc
1037 case *ast.ValueSpec:
1038 return n.Doc
1039 case *ast.TypeSpec:
1040 return n.Doc
1041 case *ast.GenDecl:
1042 return n.Doc
1043 case *ast.FuncDecl:
1044 return n.Doc
1045 case *ast.File:
1046 return n.Doc
1048 return nil
1051 func getLastComment(n ast.Node) *ast.CommentGroup {
1052 switch n := n.(type) {
1053 case *ast.Field:
1054 return n.Comment
1055 case *ast.ImportSpec:
1056 return n.Comment
1057 case *ast.ValueSpec:
1058 return n.Comment
1059 case *ast.TypeSpec:
1060 return n.Comment
1061 case *ast.GenDecl:
1062 if len(n.Specs) > 0 {
1063 return getLastComment(n.Specs[len(n.Specs)-1])
1065 case *ast.File:
1066 if len(n.Comments) > 0 {
1067 return n.Comments[len(n.Comments)-1]
1070 return nil
1073 func (p *printer) printNode(node interface{}) error {
1074 // unpack *CommentedNode, if any
1075 var comments []*ast.CommentGroup
1076 if cnode, ok := node.(*CommentedNode); ok {
1077 node = cnode.Node
1078 comments = cnode.Comments
1081 if comments != nil {
1082 // commented node - restrict comment list to relevant range
1083 n, ok := node.(ast.Node)
1084 if !ok {
1085 goto unsupported
1087 beg := n.Pos()
1088 end := n.End()
1089 // if the node has associated documentation,
1090 // include that commentgroup in the range
1091 // (the comment list is sorted in the order
1092 // of the comment appearance in the source code)
1093 if doc := getDoc(n); doc != nil {
1094 beg = doc.Pos()
1096 if com := getLastComment(n); com != nil {
1097 if e := com.End(); e > end {
1098 end = e
1101 // token.Pos values are global offsets, we can
1102 // compare them directly
1103 i := 0
1104 for i < len(comments) && comments[i].End() < beg {
1107 j := i
1108 for j < len(comments) && comments[j].Pos() < end {
1111 if i < j {
1112 p.comments = comments[i:j]
1114 } else if n, ok := node.(*ast.File); ok {
1115 // use ast.File comments, if any
1116 p.comments = n.Comments
1119 // if there are no comments, use node comments
1120 p.useNodeComments = p.comments == nil
1122 // get comments ready for use
1123 p.nextComment()
1125 // format node
1126 switch n := node.(type) {
1127 case ast.Expr:
1128 p.expr(n)
1129 case ast.Stmt:
1130 // A labeled statement will un-indent to position the label.
1131 // Set p.indent to 1 so we don't get indent "underflow".
1132 if _, ok := n.(*ast.LabeledStmt); ok {
1133 p.indent = 1
1135 p.stmt(n, false)
1136 case ast.Decl:
1137 p.decl(n)
1138 case ast.Spec:
1139 p.spec(n, 1, false)
1140 case []ast.Stmt:
1141 // A labeled statement will un-indent to position the label.
1142 // Set p.indent to 1 so we don't get indent "underflow".
1143 for _, s := range n {
1144 if _, ok := s.(*ast.LabeledStmt); ok {
1145 p.indent = 1
1148 p.stmtList(n, 0, false)
1149 case []ast.Decl:
1150 p.declList(n)
1151 case *ast.File:
1152 p.file(n)
1153 default:
1154 goto unsupported
1157 return nil
1159 unsupported:
1160 return fmt.Errorf("go/printer: unsupported node type %T", node)
1163 // ----------------------------------------------------------------------------
1164 // Trimmer
1166 // A trimmer is an io.Writer filter for stripping tabwriter.Escape
1167 // characters, trailing blanks and tabs, and for converting formfeed
1168 // and vtab characters into newlines and htabs (in case no tabwriter
1169 // is used). Text bracketed by tabwriter.Escape characters is passed
1170 // through unchanged.
1172 type trimmer struct {
1173 output io.Writer
1174 state int
1175 space []byte
1178 // trimmer is implemented as a state machine.
1179 // It can be in one of the following states:
1180 const (
1181 inSpace = iota // inside space
1182 inEscape // inside text bracketed by tabwriter.Escapes
1183 inText // inside text
1186 func (p *trimmer) resetSpace() {
1187 p.state = inSpace
1188 p.space = p.space[0:0]
1191 // Design note: It is tempting to eliminate extra blanks occurring in
1192 // whitespace in this function as it could simplify some
1193 // of the blanks logic in the node printing functions.
1194 // However, this would mess up any formatting done by
1195 // the tabwriter.
1197 var aNewline = []byte("\n")
1199 func (p *trimmer) Write(data []byte) (n int, err error) {
1200 // invariants:
1201 // p.state == inSpace:
1202 // p.space is unwritten
1203 // p.state == inEscape, inText:
1204 // data[m:n] is unwritten
1205 m := 0
1206 var b byte
1207 for n, b = range data {
1208 if b == '\v' {
1209 b = '\t' // convert to htab
1211 switch p.state {
1212 case inSpace:
1213 switch b {
1214 case '\t', ' ':
1215 p.space = append(p.space, b)
1216 case '\n', '\f':
1217 p.resetSpace() // discard trailing space
1218 _, err = p.output.Write(aNewline)
1219 case tabwriter.Escape:
1220 _, err = p.output.Write(p.space)
1221 p.state = inEscape
1222 m = n + 1 // +1: skip tabwriter.Escape
1223 default:
1224 _, err = p.output.Write(p.space)
1225 p.state = inText
1226 m = n
1228 case inEscape:
1229 if b == tabwriter.Escape {
1230 _, err = p.output.Write(data[m:n])
1231 p.resetSpace()
1233 case inText:
1234 switch b {
1235 case '\t', ' ':
1236 _, err = p.output.Write(data[m:n])
1237 p.resetSpace()
1238 p.space = append(p.space, b)
1239 case '\n', '\f':
1240 _, err = p.output.Write(data[m:n])
1241 p.resetSpace()
1242 if err == nil {
1243 _, err = p.output.Write(aNewline)
1245 case tabwriter.Escape:
1246 _, err = p.output.Write(data[m:n])
1247 p.state = inEscape
1248 m = n + 1 // +1: skip tabwriter.Escape
1250 default:
1251 panic("unreachable")
1253 if err != nil {
1254 return
1257 n = len(data)
1259 switch p.state {
1260 case inEscape, inText:
1261 _, err = p.output.Write(data[m:n])
1262 p.resetSpace()
1265 return
1268 // ----------------------------------------------------------------------------
1269 // Public interface
1271 // A Mode value is a set of flags (or 0). They control printing.
1272 type Mode uint
1274 const (
1275 RawFormat Mode = 1 << iota // do not use a tabwriter; if set, UseSpaces is ignored
1276 TabIndent // use tabs for indentation independent of UseSpaces
1277 UseSpaces // use spaces instead of tabs for alignment
1278 SourcePos // emit //line directives to preserve original source positions
1281 // A Config node controls the output of Fprint.
1282 type Config struct {
1283 Mode Mode // default: 0
1284 Tabwidth int // default: 8
1285 Indent int // default: 0 (all code is indented at least by this much)
1288 // fprint implements Fprint and takes a nodesSizes map for setting up the printer state.
1289 func (cfg *Config) fprint(output io.Writer, fset *token.FileSet, node interface{}, nodeSizes map[ast.Node]int) (err error) {
1290 // print node
1291 var p printer
1292 p.init(cfg, fset, nodeSizes)
1293 if err = p.printNode(node); err != nil {
1294 return
1296 // print outstanding comments
1297 p.impliedSemi = false // EOF acts like a newline
1298 p.flush(token.Position{Offset: infinity, Line: infinity}, token.EOF)
1300 // redirect output through a trimmer to eliminate trailing whitespace
1301 // (Input to a tabwriter must be untrimmed since trailing tabs provide
1302 // formatting information. The tabwriter could provide trimming
1303 // functionality but no tabwriter is used when RawFormat is set.)
1304 output = &trimmer{output: output}
1306 // redirect output through a tabwriter if necessary
1307 if cfg.Mode&RawFormat == 0 {
1308 minwidth := cfg.Tabwidth
1310 padchar := byte('\t')
1311 if cfg.Mode&UseSpaces != 0 {
1312 padchar = ' '
1315 twmode := tabwriter.DiscardEmptyColumns
1316 if cfg.Mode&TabIndent != 0 {
1317 minwidth = 0
1318 twmode |= tabwriter.TabIndent
1321 output = tabwriter.NewWriter(output, minwidth, cfg.Tabwidth, 1, padchar, twmode)
1324 // write printer result via tabwriter/trimmer to output
1325 if _, err = output.Write(p.output); err != nil {
1326 return
1329 // flush tabwriter, if any
1330 if tw, _ := output.(*tabwriter.Writer); tw != nil {
1331 err = tw.Flush()
1334 return
1337 // A CommentedNode bundles an AST node and corresponding comments.
1338 // It may be provided as argument to any of the Fprint functions.
1340 type CommentedNode struct {
1341 Node interface{} // *ast.File, or ast.Expr, ast.Decl, ast.Spec, or ast.Stmt
1342 Comments []*ast.CommentGroup
1345 // Fprint "pretty-prints" an AST node to output for a given configuration cfg.
1346 // Position information is interpreted relative to the file set fset.
1347 // The node type must be *ast.File, *CommentedNode, []ast.Decl, []ast.Stmt,
1348 // or assignment-compatible to ast.Expr, ast.Decl, ast.Spec, or ast.Stmt.
1350 func (cfg *Config) Fprint(output io.Writer, fset *token.FileSet, node interface{}) error {
1351 return cfg.fprint(output, fset, node, make(map[ast.Node]int))
1354 // Fprint "pretty-prints" an AST node to output.
1355 // It calls Config.Fprint with default settings.
1356 // Note that gofmt uses tabs for indentation but spaces for alignment;
1357 // use format.Node (package go/format) for output that matches gofmt.
1359 func Fprint(output io.Writer, fset *token.FileSet, node interface{}) error {
1360 return (&Config{Tabwidth: 8}).Fprint(output, fset, node)