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.
20 maxNewlines
= 2 // max. number of newlines between source text
21 debug
= false // enable for debugging
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.
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
53 // Configuration (does not change after initialization)
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
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.
86 // Cache of already computed node sizes.
87 nodeSizes
map[ast
.Node
]int
89 // Cache of most recently computed line position.
91 cachedLine
int // line corresponding to cachedPos
94 func (p
*printer
) init(cfg
*Config
, fset
*token
.FileSet
, nodeSizes
map[ast
.Node
]int) {
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
104 func (p
*printer
) internalError(msg
...interface{}) {
106 fmt
.Print(p
.pos
.String() + ": ")
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 {
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
123 if t
:= c
.Text
; len(t
) >= 2 && (t
[1] == '/' || strings
.Contains(t
, "\n")) {
131 func (p
*printer
) nextComment() {
132 for p
.cindex
< len(p
.comments
) {
133 c
:= p
.comments
[p
.cindex
]
135 if list
:= c
.List
; len(list
) > 0 {
137 p
.commentOffset
= p
.posFor(list
[0].Pos()).Offset
138 p
.commentNewline
= p
.commentsHaveNewline(list
)
141 // we should not reach here (correct ASTs don't have empty
142 // ast.CommentGroup nodes), but be conservative and try again
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
) {
166 for p
.commentBefore(next
) {
167 for _
, c
:= range p
.comment
.List
{
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
180 func (p
*printer
) recordLine(linePtr
*int) {
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
{
201 p
.cachedLine
= p
.fset
.PositionFor(pos
, false /* absolute position */).Line
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')
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) {
237 // Ignore any alignment control character;
238 // and at the end of the line, break with
239 // a formfeed to indicate termination of
246 p
.endAlignment
= false
250 if p
.out
.Column
== 1 {
251 // no need to write line directives before white space
255 for i
:= 0; i
< n
; i
++ {
256 p
.output
= append(p
.output
, ch
)
261 if ch
== '\n' || ch
== '\f' {
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
)
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.
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
304 p
.output
= append(p
.output
, tabwriter
.Escape
)
308 p
.output
= append(p
.output
, fmt
.Sprintf("/*%s*/", pos
)...) // do not update p.pos!
310 p
.output
= append(p
.output
, s
...)
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
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
)
335 p
.pos
.Column
+= len(s
)
336 p
.out
.Column
+= len(s
)
340 p
.output
= append(p
.output
, tabwriter
.Escape
)
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
359 if pos
.IsValid() && pos
.Filename
!= p
.last
.Filename
{
360 // comment in a different file - separate with newlines
361 p
.writeByte('\f', maxNewlines
)
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
370 // first comment of a comment group
372 for i
, ch
:= range p
.wsbuf
{
375 // ignore any blanks before a comment
379 // respect existing tabs - important
380 // for proper formatting of commented structs
384 // apply pending indentation
392 // make sure there is at least one separator
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
405 // comment on a different line:
406 // separate with at least one line break
407 droppedLinebreak
:= false
409 for i
, ch
:= range p
.wsbuf
{
412 // ignore any horizontal whitespace before line breaks
416 // apply pending indentation
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
{
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
{
435 case newline
, formfeed
:
437 droppedLinebreak
= prev
== nil // record only if first comment of a group
444 // determine number of linebreaks before the comment
446 if pos
.IsValid() && p
.last
.IsValid() {
447 n
= pos
.Line
- p
.last
.Line
448 if n
< 0 { // should never happen
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] == '/' {
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
++ {
488 // commonPrefix returns the common prefix of a and b.
489 func commonPrefix(a
, b
string) string {
491 for i
< len(a
) && i
< len(b
) && a
[i
] == b
[i
] && (a
[i
] <= ' ' || a
[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) {
510 return // at most one line - nothing to do
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.
536 for i
, line
:= range lines
[1 : len(lines
)-1] {
538 lines
[1+i
] = "" // range starts with lines[1]
544 prefix
= commonPrefix(prefix
, line
)
549 // If we don't have a prefix yet, consider the last line.
551 line
:= lines
[len(lines
)-1]
552 prefix
= commonPrefix(line
, line
)
556 * Check for vertical "line of stars" and correct prefix accordingly.
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
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.
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
582 for n
:= 0; n
< 3 && i
> 0 && prefix
[i
-1] == ' '; n
++ {
585 if i
== len(prefix
) && i
> 0 && prefix
[i
-1] == '\t' {
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
] <= ' ' {
597 if n
> 2 && suffix
[2] == '\t' {
598 // assume the '\t' compensates for the /*
601 // otherwise assume two blanks
602 suffix
[0], suffix
[1] = ' ', ' '
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
614 last
:= lines
[len(lines
)-1]
616 i
:= strings
.Index(last
, closing
) // i >= 0 (closing is always present)
617 if isBlank(last
[0:i
]) {
618 // last line only contains closing */
620 closing
= " */" // add blank to align final star
622 lines
[len(lines
)-1] = prefix
+ closing
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
) {
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
)
650 // shortcut common case of //-style comments
652 p
.writeString(pos
, trimRight(text
), true)
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
{
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
694 func (p
*printer
) writeCommentSuffix(needsLinebreak
bool) (wroteNewline
, droppedFF
bool) {
695 for i
, ch
:= range p
.wsbuf
{
698 // ignore trailing whitespace
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
706 needsLinebreak
= false
716 p
.writeWhitespace(len(p
.wsbuf
))
718 // make sure we have a line break
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
{
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
)
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
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] == '/' ||
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")
793 // whiteWhitespace writes the first n whitespace entries.
794 func (p
*printer
) writeWhitespace(n
int) {
796 for i
:= 0; i
< n
; i
++ {
797 switch ch
:= p
.wsbuf
[i
]; ch
{
805 p
.internalError("negative indentation:", p
.indent
)
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
821 p
.wsbuf
[i
], p
.wsbuf
[i
+1] = unindent
, formfeed
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 {
847 func mayCombine(prev token
.Token
, next
byte) (b
bool) {
850 b
= next
== '.' // 1.
852 b
= next
== '+' // ++
854 b
= next
== '-' // --
856 b
= next
== '*' // /*
858 b
= next
== '-' || next
== '<' // <- or <<
860 b
= next
== '&' || next
== '^' // && or &^
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
881 var impliedSemi
bool // value for p.impliedSemi after this arg
883 // record previous opening token, if any
886 // ignore (white space)
887 case token
.LPAREN
, token
.LBRACK
:
888 p
.prevOpen
= p
.lastTok
890 // other tokens followed any opening token
891 p
.prevOpen
= token
.ILLEGAL
894 switch x
:= arg
.(type) {
896 // toggle printer mode
902 // don't add ignore's to the buffer; they
903 // may screw up "correcting" unindents (see
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.
915 p
.wsbuf
= p
.wsbuf
[0 : i
+1]
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
922 p
.impliedSemi
= false
924 p
.lastTok
= token
.ILLEGAL
930 p
.lastTok
= token
.IDENT
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]
954 // some keywords followed by a newline imply a semicolon
956 case token
.BREAK
, token
.CONTINUE
, token
.FALLTHROUGH
, token
.RETURN
,
957 token
.INC
, token
.DEC
, token
.RPAREN
, token
.RBRACK
, token
.RBRACE
:
964 p
.pos
= p
.posFor(x
) // accurate position of next item
969 // incorrect AST - print error message
973 p
.lastTok
= token
.STRING
976 fmt
.Fprintf(os
.Stderr
, "print: unsupported argument %v (%T)\n", arg
, arg
)
977 panic("go/printer type")
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)
988 n
:= nlimit(next
.Line
- p
.pos
.Line
)
989 // don't exceed maxNewlines if we already wrote one
990 if wroteNewline
&& n
== maxNewlines
{
996 ch
= '\f' // use formfeed since we dropped one before
1003 // the next token starts now - record its line number if requested
1004 if p
.linePtr
!= nil {
1005 *p
.linePtr
= p
.out
.Line
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
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
)
1024 // otherwise, write any leftover whitespace
1025 p
.writeWhitespace(len(p
.wsbuf
))
1030 // getNode returns the ast.CommentGroup associated with n, if any.
1031 func getDoc(n ast
.Node
) *ast
.CommentGroup
{
1032 switch n
:= n
.(type) {
1035 case *ast
.ImportSpec
:
1037 case *ast
.ValueSpec
:
1051 func getLastComment(n ast
.Node
) *ast
.CommentGroup
{
1052 switch n
:= n
.(type) {
1055 case *ast
.ImportSpec
:
1057 case *ast
.ValueSpec
:
1062 if len(n
.Specs
) > 0 {
1063 return getLastComment(n
.Specs
[len(n
.Specs
)-1])
1066 if len(n
.Comments
) > 0 {
1067 return n
.Comments
[len(n
.Comments
)-1]
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
{
1078 comments
= cnode
.Comments
1081 if comments
!= nil {
1082 // commented node - restrict comment list to relevant range
1083 n
, ok
:= node
.(ast
.Node
)
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 {
1096 if com
:= getLastComment(n
); com
!= nil {
1097 if e
:= com
.End(); e
> end
{
1101 // token.Pos values are global offsets, we can
1102 // compare them directly
1104 for i
< len(comments
) && comments
[i
].End() < beg
{
1108 for j
< len(comments
) && comments
[j
].Pos() < end
{
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
1126 switch n
:= node
.(type) {
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
{
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
{
1148 p
.stmtList(n
, 0, false)
1160 return fmt
.Errorf("go/printer: unsupported node type %T", node
)
1163 // ----------------------------------------------------------------------------
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 {
1178 // trimmer is implemented as a state machine.
1179 // It can be in one of the following states:
1181 inSpace
= iota // inside space
1182 inEscape
// inside text bracketed by tabwriter.Escapes
1183 inText
// inside text
1186 func (p
*trimmer
) resetSpace() {
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
1197 var aNewline
= []byte("\n")
1199 func (p
*trimmer
) Write(data
[]byte) (n
int, err error
) {
1201 // p.state == inSpace:
1202 // p.space is unwritten
1203 // p.state == inEscape, inText:
1204 // data[m:n] is unwritten
1207 for n
, b
= range data
{
1209 b
= '\t' // convert to htab
1215 p
.space
= append(p
.space
, b
)
1217 p
.resetSpace() // discard trailing space
1218 _
, err
= p
.output
.Write(aNewline
)
1219 case tabwriter
.Escape
:
1220 _
, err
= p
.output
.Write(p
.space
)
1222 m
= n
+ 1 // +1: skip tabwriter.Escape
1224 _
, err
= p
.output
.Write(p
.space
)
1229 if b
== tabwriter
.Escape
{
1230 _
, err
= p
.output
.Write(data
[m
:n
])
1236 _
, err
= p
.output
.Write(data
[m
:n
])
1238 p
.space
= append(p
.space
, b
)
1240 _
, err
= p
.output
.Write(data
[m
:n
])
1243 _
, err
= p
.output
.Write(aNewline
)
1245 case tabwriter
.Escape
:
1246 _
, err
= p
.output
.Write(data
[m
:n
])
1248 m
= n
+ 1 // +1: skip tabwriter.Escape
1251 panic("unreachable")
1260 case inEscape
, inText
:
1261 _
, err
= p
.output
.Write(data
[m
:n
])
1268 // ----------------------------------------------------------------------------
1271 // A Mode value is a set of flags (or 0). They control printing.
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
) {
1292 p
.init(cfg
, fset
, nodeSizes
)
1293 if err
= p
.printNode(node
); err
!= nil {
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 {
1315 twmode
:= tabwriter
.DiscardEmptyColumns
1316 if cfg
.Mode
&TabIndent
!= 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 {
1329 // flush tabwriter, if any
1330 if tw
, _
:= output
.(*tabwriter
.Writer
); tw
!= nil {
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
)