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.
21 maxNewlines
= 2 // max. number of newlines between source text
22 debug
= false // enable for debugging
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('<')
38 // A pmode value represents the current printer mode.
42 noExtraBlank pmode
= 1 << iota // disables extra blank after /*-style comment
43 noExtraLinebreak
// disables extra line break after /*-style comment
46 type commentInfo
struct {
47 cindex
int // current comment index
48 comment
*ast
.CommentGroup
// = printer.comments[cindex]; or nil
49 commentOffset
int // = printer.posFor(printer.comments[cindex].List[0].Pos()).Offset; or infinity
50 commentNewline
bool // true if the comment group contains newlines
54 // Configuration (does not change after initialization)
59 output
[]byte // raw printer result
60 indent
int // current indentation
61 level
int // level == 0: outside composite literal; level > 0: inside composite literal
62 mode pmode
// current printer mode
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
.Position(pos
)
198 func (p
*printer
) lineFor(pos token
.Pos
) int {
199 if pos
!= p
.cachedPos
{
201 p
.cachedLine
= p
.fset
.Position(pos
).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) {
236 if p
.out
.Column
== 1 {
237 // no need to write line directives before white space
241 for i
:= 0; i
< n
; i
++ {
242 p
.output
= append(p
.output
, ch
)
247 if ch
== '\n' || ch
== '\f' {
258 // writeString writes the string s to p.output and updates p.pos, p.out,
259 // and p.last. If isLit is set, s is escaped w/ tabwriter.Escape characters
260 // to protect s from being interpreted by the tabwriter.
262 // Note: writeString is only used to write Go tokens, literals, and
263 // comments, all of which must be written literally. Thus, it is correct
264 // to always set isLit = true. However, setting it explicitly only when
265 // needed (i.e., when we don't know that s contains no tabs or line breaks)
266 // avoids processing extra escape characters and reduces run time of the
267 // printer benchmark by up to 10%.
269 func (p
*printer
) writeString(pos token
.Position
, s
string, isLit
bool) {
270 if p
.out
.Column
== 1 {
271 if p
.Config
.Mode
&SourcePos
!= 0 {
272 p
.writeLineDirective(pos
)
278 // update p.pos (if pos is invalid, continue with existing p.pos)
279 // Note: Must do this after handling line beginnings because
280 // writeIndent updates p.pos if there's indentation, but p.pos
281 // is the position of s.
286 // Protect s such that is passes through the tabwriter
287 // unchanged. Note that valid Go programs cannot contain
288 // tabwriter.Escape bytes since they do not appear in legal
290 p
.output
= append(p
.output
, tabwriter
.Escape
)
294 p
.output
= append(p
.output
, fmt
.Sprintf("/*%s*/", pos
)...) // do not update p.pos!
296 p
.output
= append(p
.output
, s
...)
300 var li
int // index of last newline; valid if nlines > 0
301 for i
:= 0; i
< len(s
); i
++ {
302 // Go tokens cannot contain '\f' - no need to look for it
308 p
.pos
.Offset
+= len(s
)
316 p
.pos
.Column
+= len(s
)
317 p
.out
.Column
+= len(s
)
321 p
.output
= append(p
.output
, tabwriter
.Escape
)
327 // writeCommentPrefix writes the whitespace before a comment.
328 // If there is any pending whitespace, it consumes as much of
329 // it as is likely to help position the comment nicely.
330 // pos is the comment position, next the position of the item
331 // after all pending comments, prev is the previous comment in
332 // a group of comments (or nil), and tok is the next token.
334 func (p
*printer
) writeCommentPrefix(pos
, next token
.Position
, prev
*ast
.Comment
, tok token
.Token
) {
335 if len(p
.output
) == 0 {
336 // the comment is the first item to be printed - don't write any whitespace
340 if pos
.IsValid() && pos
.Filename
!= p
.last
.Filename
{
341 // comment in a different file - separate with newlines
342 p
.writeByte('\f', maxNewlines
)
346 if pos
.Line
== p
.last
.Line
&& (prev
== nil || prev
.Text
[1] != '/') {
347 // comment on the same line as last item:
348 // separate with at least one separator
351 // first comment of a comment group
353 for i
, ch
:= range p
.wsbuf
{
356 // ignore any blanks before a comment
360 // respect existing tabs - important
361 // for proper formatting of commented structs
365 // apply pending indentation
373 // make sure there is at least one separator
376 if pos
.Line
== next
.Line
{
377 // next item is on the same line as the comment
378 // (which must be a /*-style comment): separate
379 // with a blank instead of a tab
386 // comment on a different line:
387 // separate with at least one line break
388 droppedLinebreak
:= false
390 for i
, ch
:= range p
.wsbuf
{
393 // ignore any horizontal whitespace before line breaks
397 // apply pending indentation
400 // if this is not the last unindent, apply it
401 // as it is (likely) belonging to the last
402 // construct (e.g., a multi-line expression list)
403 // and is not part of closing a block
404 if i
+1 < len(p
.wsbuf
) && p
.wsbuf
[i
+1] == unindent
{
407 // if the next token is not a closing }, apply the unindent
408 // if it appears that the comment is aligned with the
409 // token; otherwise assume the unindent is part of a
410 // closing block and stop (this scenario appears with
411 // comments before a case label where the comments
412 // apply to the next case instead of the current one)
413 if tok
!= token
.RBRACE
&& pos
.Column
== next
.Column
{
416 case newline
, formfeed
:
418 droppedLinebreak
= prev
== nil // record only if first comment of a group
425 // determine number of linebreaks before the comment
427 if pos
.IsValid() && p
.last
.IsValid() {
428 n
= pos
.Line
- p
.last
.Line
429 if n
< 0 { // should never happen
434 // at the package scope level only (p.indent == 0),
435 // add an extra newline if we dropped one before:
436 // this preserves a blank line before documentation
437 // comments at the package scope level (issue 2570)
438 if p
.indent
== 0 && droppedLinebreak
{
442 // make sure there is at least one line break
443 // if the previous comment was a line comment
444 if n
== 0 && prev
!= nil && prev
.Text
[1] == '/' {
449 // use formfeeds to break columns before a comment;
450 // this is analogous to using formfeeds to separate
451 // individual lines of /*-style comments
452 p
.writeByte('\f', nlimit(n
))
457 // Returns true if s contains only white space
458 // (only tabs and blanks can appear in the printer's context).
460 func isBlank(s
string) bool {
461 for i
:= 0; i
< len(s
); i
++ {
469 // commonPrefix returns the common prefix of a and b.
470 func commonPrefix(a
, b
string) string {
472 for i
< len(a
) && i
< len(b
) && a
[i
] == b
[i
] && (a
[i
] <= ' ' || a
[i
] == '*') {
478 // trimRight returns s with trailing whitespace removed.
479 func trimRight(s
string) string {
480 return strings
.TrimRightFunc(s
, unicode
.IsSpace
)
483 // stripCommonPrefix removes a common prefix from /*-style comment lines (unless no
484 // comment line is indented, all but the first line have some form of space prefix).
485 // The prefix is computed using heuristics such that is likely that the comment
486 // contents are nicely laid out after re-printing each line using the printer's
487 // current indentation.
489 func stripCommonPrefix(lines
[]string) {
491 return // at most one line - nothing to do
495 // The heuristic in this function tries to handle a few
496 // common patterns of /*-style comments: Comments where
497 // the opening /* and closing */ are aligned and the
498 // rest of the comment text is aligned and indented with
499 // blanks or tabs, cases with a vertical "line of stars"
500 // on the left, and cases where the closing */ is on the
501 // same line as the last comment text.
503 // Compute maximum common white prefix of all but the first,
504 // last, and blank lines, and replace blank lines with empty
505 // lines (the first line starts with /* and has no prefix).
506 // In cases where only the first and last lines are not blank,
507 // such as two-line comments, or comments where all inner lines
508 // are blank, consider the last line for the prefix computation
509 // since otherwise the prefix would be empty.
511 // Note that the first and last line are never empty (they
512 // contain the opening /* and closing */ respectively) and
513 // thus they can be ignored by the blank line check.
517 for i
, line
:= range lines
[1 : len(lines
)-1] {
519 lines
[1+i
] = "" // range starts with lines[1]
525 prefix
= commonPrefix(prefix
, line
)
530 // If we don't have a prefix yet, consider the last line.
532 line
:= lines
[len(lines
)-1]
533 prefix
= commonPrefix(line
, line
)
537 * Check for vertical "line of stars" and correct prefix accordingly.
540 if i
:= strings
.Index(prefix
, "*"); i
>= 0 {
541 // Line of stars present.
542 if i
> 0 && prefix
[i
-1] == ' ' {
543 i
-- // remove trailing blank from prefix so stars remain aligned
548 // No line of stars present.
549 // Determine the white space on the first line after the /*
550 // and before the beginning of the comment text, assume two
551 // blanks instead of the /* unless the first character after
552 // the /* is a tab. If the first comment line is empty but
553 // for the opening /*, assume up to 3 blanks or a tab. This
554 // whitespace may be found as suffix in the common prefix.
556 if isBlank(first
[2:]) {
557 // no comment text on the first line:
558 // reduce prefix by up to 3 blanks or a tab
559 // if present - this keeps comment text indented
560 // relative to the /* and */'s if it was indented
561 // in the first place
563 for n
:= 0; n
< 3 && i
> 0 && prefix
[i
-1] == ' '; n
++ {
566 if i
== len(prefix
) && i
> 0 && prefix
[i
-1] == '\t' {
571 // comment text on the first line
572 suffix
:= make([]byte, len(first
))
573 n
:= 2 // start after opening /*
574 for n
< len(first
) && first
[n
] <= ' ' {
578 if n
> 2 && suffix
[2] == '\t' {
579 // assume the '\t' compensates for the /*
582 // otherwise assume two blanks
583 suffix
[0], suffix
[1] = ' ', ' '
586 // Shorten the computed common prefix by the length of
587 // suffix, if it is found as suffix of the prefix.
588 prefix
= strings
.TrimSuffix(prefix
, string(suffix
))
592 // Handle last line: If it only contains a closing */, align it
593 // with the opening /*, otherwise align the text with the other
595 last
:= lines
[len(lines
)-1]
597 i
:= strings
.Index(last
, closing
) // i >= 0 (closing is always present)
598 if isBlank(last
[0:i
]) {
599 // last line only contains closing */
601 closing
= " */" // add blank to align final star
603 lines
[len(lines
)-1] = prefix
+ closing
605 // last line contains more comment text - assume
606 // it is aligned like the other lines and include
607 // in prefix computation
608 prefix
= commonPrefix(prefix
, last
)
611 // Remove the common prefix from all but the first and empty lines.
612 for i
, line
:= range lines
{
613 if i
> 0 && line
!= "" {
614 lines
[i
] = line
[len(prefix
):]
619 func (p
*printer
) writeComment(comment
*ast
.Comment
) {
621 pos
:= p
.posFor(comment
.Pos())
623 const linePrefix
= "//line "
624 if strings
.HasPrefix(text
, linePrefix
) && (!pos
.IsValid() || pos
.Column
== 1) {
625 // possibly a line directive
626 ldir
:= strings
.TrimSpace(text
[len(linePrefix
):])
627 if i
:= strings
.LastIndex(ldir
, ":"); i
>= 0 {
628 if line
, err
:= strconv
.Atoi(ldir
[i
+1:]); err
== nil && line
> 0 {
629 // The line directive we are about to print changed
630 // the Filename and Line number used for subsequent
631 // tokens. We have to update our AST-space position
632 // accordingly and suspend indentation temporarily.
636 p
.pos
.Filename
= ldir
[:i
]
645 // shortcut common case of //-style comments
647 p
.writeString(pos
, trimRight(text
), true)
651 // for /*-style comments, print line by line and let the
652 // write function take care of the proper indentation
653 lines
:= strings
.Split(text
, "\n")
655 // The comment started in the first column but is going
656 // to be indented. For an idempotent result, add indentation
657 // to all lines such that they look like they were indented
658 // before - this will make sure the common prefix computation
659 // is the same independent of how many times formatting is
660 // applied (was issue 1835).
661 if pos
.IsValid() && pos
.Column
== 1 && p
.indent
> 0 {
662 for i
, line
:= range lines
[1:] {
663 lines
[1+i
] = " " + line
667 stripCommonPrefix(lines
)
669 // write comment lines, separated by formfeed,
670 // without a line break after the last line
671 for i
, line
:= range lines
{
677 p
.writeString(pos
, trimRight(line
), true)
682 // writeCommentSuffix writes a line break after a comment if indicated
683 // and processes any leftover indentation information. If a line break
684 // is needed, the kind of break (newline vs formfeed) depends on the
685 // pending whitespace. The writeCommentSuffix result indicates if a
686 // newline was written or if a formfeed was dropped from the whitespace
689 func (p
*printer
) writeCommentSuffix(needsLinebreak
bool) (wroteNewline
, droppedFF
bool) {
690 for i
, ch
:= range p
.wsbuf
{
693 // ignore trailing whitespace
695 case indent
, unindent
:
696 // don't lose indentation information
697 case newline
, formfeed
:
698 // if we need a line break, keep exactly one
699 // but remember if we dropped any formfeeds
701 needsLinebreak
= false
711 p
.writeWhitespace(len(p
.wsbuf
))
713 // make sure we have a line break
722 // containsLinebreak reports whether the whitespace buffer contains any line breaks.
723 func (p
*printer
) containsLinebreak() bool {
724 for _
, ch
:= range p
.wsbuf
{
725 if ch
== newline || ch
== formfeed
{
732 // intersperseComments consumes all comments that appear before the next token
733 // tok and prints it together with the buffered whitespace (i.e., the whitespace
734 // that needs to be written before the next token). A heuristic is used to mix
735 // the comments and whitespace. The intersperseComments result indicates if a
736 // newline was written or if a formfeed was dropped from the whitespace buffer.
738 func (p
*printer
) intersperseComments(next token
.Position
, tok token
.Token
) (wroteNewline
, droppedFF
bool) {
739 var last
*ast
.Comment
740 for p
.commentBefore(next
) {
741 for _
, c
:= range p
.comment
.List
{
742 p
.writeCommentPrefix(p
.posFor(c
.Pos()), next
, last
, tok
)
750 // If the last comment is a /*-style comment and the next item
751 // follows on the same line but is not a comma, and not a "closing"
752 // token immediately following its corresponding "opening" token,
753 // add an extra separator unless explicitly disabled. Use a blank
754 // as separator unless we have pending linebreaks, they are not
755 // disabled, and we are outside a composite literal, in which case
756 // we want a linebreak (issue 15137).
757 // TODO(gri) This has become overly complicated. We should be able
758 // to track whether we're inside an expression or statement and
759 // use that information to decide more directly.
760 needsLinebreak
:= false
761 if p
.mode
&noExtraBlank
== 0 &&
762 last
.Text
[1] == '*' && p
.lineFor(last
.Pos()) == next
.Line
&&
763 tok
!= token
.COMMA
&&
764 (tok
!= token
.RPAREN || p
.prevOpen
== token
.LPAREN
) &&
765 (tok
!= token
.RBRACK || p
.prevOpen
== token
.LBRACK
) {
766 if p
.containsLinebreak() && p
.mode
&noExtraLinebreak
== 0 && p
.level
== 0 {
767 needsLinebreak
= true
772 // Ensure that there is a line break after a //-style comment,
773 // before EOF, and before a closing '}' unless explicitly disabled.
774 if last
.Text
[1] == '/' ||
776 tok
== token
.RBRACE
&& p
.mode
&noExtraLinebreak
== 0 {
777 needsLinebreak
= true
779 return p
.writeCommentSuffix(needsLinebreak
)
782 // no comment was written - we should never reach here since
783 // intersperseComments should not be called in that case
784 p
.internalError("intersperseComments called without pending comments")
788 // whiteWhitespace writes the first n whitespace entries.
789 func (p
*printer
) writeWhitespace(n
int) {
791 for i
:= 0; i
< n
; i
++ {
792 switch ch
:= p
.wsbuf
[i
]; ch
{
800 p
.internalError("negative indentation:", p
.indent
)
803 case newline
, formfeed
:
804 // A line break immediately followed by a "correcting"
805 // unindent is swapped with the unindent - this permits
806 // proper label positioning. If a comment is between
807 // the line break and the label, the unindent is not
808 // part of the comment whitespace prefix and the comment
809 // will be positioned correctly indented.
810 if i
+1 < n
&& p
.wsbuf
[i
+1] == unindent
{
811 // Use a formfeed to terminate the current section.
812 // Otherwise, a long label name on the next line leading
813 // to a wide column may increase the indentation column
814 // of lines before the label; effectively leading to wrong
816 p
.wsbuf
[i
], p
.wsbuf
[i
+1] = unindent
, formfeed
822 p
.writeByte(byte(ch
), 1)
826 // shift remaining entries down
827 l
:= copy(p
.wsbuf
, p
.wsbuf
[n
:])
828 p
.wsbuf
= p
.wsbuf
[:l
]
831 // ----------------------------------------------------------------------------
832 // Printing interface
834 // nlines limits n to maxNewlines.
835 func nlimit(n
int) int {
842 func mayCombine(prev token
.Token
, next
byte) (b
bool) {
845 b
= next
== '.' // 1.
847 b
= next
== '+' // ++
849 b
= next
== '-' // --
851 b
= next
== '*' // /*
853 b
= next
== '-' || next
== '<' // <- or <<
855 b
= next
== '&' || next
== '^' // && or &^
860 // print prints a list of "items" (roughly corresponding to syntactic
861 // tokens, but also including whitespace and formatting information).
862 // It is the only print function that should be called directly from
863 // any of the AST printing functions in nodes.go.
865 // Whitespace is accumulated until a non-whitespace token appears. Any
866 // comments that need to appear before that token are printed first,
867 // taking into account the amount and structure of any pending white-
868 // space for best comment placement. Then, any leftover whitespace is
869 // printed, followed by the actual token.
871 func (p
*printer
) print(args
...interface{}) {
872 for _
, arg
:= range args
{
873 // information about the current arg
876 var impliedSemi
bool // value for p.impliedSemi after this arg
878 // record previous opening token, if any
881 // ignore (white space)
882 case token
.LPAREN
, token
.LBRACK
:
883 p
.prevOpen
= p
.lastTok
885 // other tokens followed any opening token
886 p
.prevOpen
= token
.ILLEGAL
889 switch x
:= arg
.(type) {
891 // toggle printer mode
897 // don't add ignore's to the buffer; they
898 // may screw up "correcting" unindents (see
903 if i
== cap(p
.wsbuf
) {
904 // Whitespace sequences are very short so this should
905 // never happen. Handle gracefully (but possibly with
906 // bad comment placement) if it does happen.
910 p
.wsbuf
= p
.wsbuf
[0 : i
+1]
912 if x
== newline || x
== formfeed
{
913 // newlines affect the current state (p.impliedSemi)
914 // and not the state after printing arg (impliedSemi)
915 // because comments can be interspersed before the arg
917 p
.impliedSemi
= false
919 p
.lastTok
= token
.ILLEGAL
925 p
.lastTok
= token
.IDENT
935 if mayCombine(p
.lastTok
, s
[0]) {
936 // the previous and the current token must be
937 // separated by a blank otherwise they combine
938 // into a different incorrect token sequence
939 // (except for token.INT followed by a '.' this
940 // should never happen because it is taken care
941 // of via binary expression formatting)
942 if len(p
.wsbuf
) != 0 {
943 p
.internalError("whitespace buffer not empty")
945 p
.wsbuf
= p
.wsbuf
[0:1]
949 // some keywords followed by a newline imply a semicolon
951 case token
.BREAK
, token
.CONTINUE
, token
.FALLTHROUGH
, token
.RETURN
,
952 token
.INC
, token
.DEC
, token
.RPAREN
, token
.RBRACK
, token
.RBRACE
:
959 p
.pos
= p
.posFor(x
) // accurate position of next item
964 // incorrect AST - print error message
968 p
.lastTok
= token
.STRING
971 fmt
.Fprintf(os
.Stderr
, "print: unsupported argument %v (%T)\n", arg
, arg
)
972 panic("go/printer type")
976 next
:= p
.pos
// estimated/accurate position of next item
977 wroteNewline
, droppedFF
:= p
.flush(next
, p
.lastTok
)
979 // intersperse extra newlines if present in the source and
980 // if they don't cause extra semicolons (don't do this in
981 // flush as it will cause extra newlines at the end of a file)
983 n
:= nlimit(next
.Line
- p
.pos
.Line
)
984 // don't exceed maxNewlines if we already wrote one
985 if wroteNewline
&& n
== maxNewlines
{
991 ch
= '\f' // use formfeed since we dropped one before
998 // the next token starts now - record its line number if requested
999 if p
.linePtr
!= nil {
1000 *p
.linePtr
= p
.out
.Line
1004 p
.writeString(next
, data
, isLit
)
1005 p
.impliedSemi
= impliedSemi
1009 // flush prints any pending comments and whitespace occurring textually
1010 // before the position of the next token tok. The flush result indicates
1011 // if a newline was written or if a formfeed was dropped from the whitespace
1014 func (p
*printer
) flush(next token
.Position
, tok token
.Token
) (wroteNewline
, droppedFF
bool) {
1015 if p
.commentBefore(next
) {
1016 // if there are comments before the next item, intersperse them
1017 wroteNewline
, droppedFF
= p
.intersperseComments(next
, tok
)
1019 // otherwise, write any leftover whitespace
1020 p
.writeWhitespace(len(p
.wsbuf
))
1025 // getNode returns the ast.CommentGroup associated with n, if any.
1026 func getDoc(n ast
.Node
) *ast
.CommentGroup
{
1027 switch n
:= n
.(type) {
1030 case *ast
.ImportSpec
:
1032 case *ast
.ValueSpec
:
1046 func getLastComment(n ast
.Node
) *ast
.CommentGroup
{
1047 switch n
:= n
.(type) {
1050 case *ast
.ImportSpec
:
1052 case *ast
.ValueSpec
:
1057 if len(n
.Specs
) > 0 {
1058 return getLastComment(n
.Specs
[len(n
.Specs
)-1])
1061 if len(n
.Comments
) > 0 {
1062 return n
.Comments
[len(n
.Comments
)-1]
1068 func (p
*printer
) printNode(node
interface{}) error
{
1069 // unpack *CommentedNode, if any
1070 var comments
[]*ast
.CommentGroup
1071 if cnode
, ok
:= node
.(*CommentedNode
); ok
{
1073 comments
= cnode
.Comments
1076 if comments
!= nil {
1077 // commented node - restrict comment list to relevant range
1078 n
, ok
:= node
.(ast
.Node
)
1084 // if the node has associated documentation,
1085 // include that commentgroup in the range
1086 // (the comment list is sorted in the order
1087 // of the comment appearance in the source code)
1088 if doc
:= getDoc(n
); doc
!= nil {
1091 if com
:= getLastComment(n
); com
!= nil {
1092 if e
:= com
.End(); e
> end
{
1096 // token.Pos values are global offsets, we can
1097 // compare them directly
1099 for i
< len(comments
) && comments
[i
].End() < beg
{
1103 for j
< len(comments
) && comments
[j
].Pos() < end
{
1107 p
.comments
= comments
[i
:j
]
1109 } else if n
, ok
:= node
.(*ast
.File
); ok
{
1110 // use ast.File comments, if any
1111 p
.comments
= n
.Comments
1114 // if there are no comments, use node comments
1115 p
.useNodeComments
= p
.comments
== nil
1117 // get comments ready for use
1121 switch n
:= node
.(type) {
1125 // A labeled statement will un-indent to position the label.
1126 // Set p.indent to 1 so we don't get indent "underflow".
1127 if _
, ok
:= n
.(*ast
.LabeledStmt
); ok
{
1136 // A labeled statement will un-indent to position the label.
1137 // Set p.indent to 1 so we don't get indent "underflow".
1138 for _
, s
:= range n
{
1139 if _
, ok
:= s
.(*ast
.LabeledStmt
); ok
{
1143 p
.stmtList(n
, 0, false)
1155 return fmt
.Errorf("go/printer: unsupported node type %T", node
)
1158 // ----------------------------------------------------------------------------
1161 // A trimmer is an io.Writer filter for stripping tabwriter.Escape
1162 // characters, trailing blanks and tabs, and for converting formfeed
1163 // and vtab characters into newlines and htabs (in case no tabwriter
1164 // is used). Text bracketed by tabwriter.Escape characters is passed
1165 // through unchanged.
1167 type trimmer
struct {
1173 // trimmer is implemented as a state machine.
1174 // It can be in one of the following states:
1176 inSpace
= iota // inside space
1177 inEscape
// inside text bracketed by tabwriter.Escapes
1178 inText
// inside text
1181 func (p
*trimmer
) resetSpace() {
1183 p
.space
= p
.space
[0:0]
1186 // Design note: It is tempting to eliminate extra blanks occurring in
1187 // whitespace in this function as it could simplify some
1188 // of the blanks logic in the node printing functions.
1189 // However, this would mess up any formatting done by
1192 var aNewline
= []byte("\n")
1194 func (p
*trimmer
) Write(data
[]byte) (n
int, err error
) {
1196 // p.state == inSpace:
1197 // p.space is unwritten
1198 // p.state == inEscape, inText:
1199 // data[m:n] is unwritten
1202 for n
, b
= range data
{
1204 b
= '\t' // convert to htab
1210 p
.space
= append(p
.space
, b
)
1212 p
.resetSpace() // discard trailing space
1213 _
, err
= p
.output
.Write(aNewline
)
1214 case tabwriter
.Escape
:
1215 _
, err
= p
.output
.Write(p
.space
)
1217 m
= n
+ 1 // +1: skip tabwriter.Escape
1219 _
, err
= p
.output
.Write(p
.space
)
1224 if b
== tabwriter
.Escape
{
1225 _
, err
= p
.output
.Write(data
[m
:n
])
1231 _
, err
= p
.output
.Write(data
[m
:n
])
1233 p
.space
= append(p
.space
, b
)
1235 _
, err
= p
.output
.Write(data
[m
:n
])
1238 _
, err
= p
.output
.Write(aNewline
)
1240 case tabwriter
.Escape
:
1241 _
, err
= p
.output
.Write(data
[m
:n
])
1243 m
= n
+ 1 // +1: skip tabwriter.Escape
1246 panic("unreachable")
1255 case inEscape
, inText
:
1256 _
, err
= p
.output
.Write(data
[m
:n
])
1263 // ----------------------------------------------------------------------------
1266 // A Mode value is a set of flags (or 0). They control printing.
1270 RawFormat Mode
= 1 << iota // do not use a tabwriter; if set, UseSpaces is ignored
1271 TabIndent
// use tabs for indentation independent of UseSpaces
1272 UseSpaces
// use spaces instead of tabs for alignment
1273 SourcePos
// emit //line directives to preserve original source positions
1276 // A Config node controls the output of Fprint.
1277 type Config
struct {
1278 Mode Mode
// default: 0
1279 Tabwidth
int // default: 8
1280 Indent
int // default: 0 (all code is indented at least by this much)
1283 // fprint implements Fprint and takes a nodesSizes map for setting up the printer state.
1284 func (cfg
*Config
) fprint(output io
.Writer
, fset
*token
.FileSet
, node
interface{}, nodeSizes
map[ast
.Node
]int) (err error
) {
1287 p
.init(cfg
, fset
, nodeSizes
)
1288 if err
= p
.printNode(node
); err
!= nil {
1291 // print outstanding comments
1292 p
.impliedSemi
= false // EOF acts like a newline
1293 p
.flush(token
.Position
{Offset
: infinity
, Line
: infinity
}, token
.EOF
)
1295 // redirect output through a trimmer to eliminate trailing whitespace
1296 // (Input to a tabwriter must be untrimmed since trailing tabs provide
1297 // formatting information. The tabwriter could provide trimming
1298 // functionality but no tabwriter is used when RawFormat is set.)
1299 output
= &trimmer
{output
: output
}
1301 // redirect output through a tabwriter if necessary
1302 if cfg
.Mode
&RawFormat
== 0 {
1303 minwidth
:= cfg
.Tabwidth
1305 padchar
:= byte('\t')
1306 if cfg
.Mode
&UseSpaces
!= 0 {
1310 twmode
:= tabwriter
.DiscardEmptyColumns
1311 if cfg
.Mode
&TabIndent
!= 0 {
1313 twmode |
= tabwriter
.TabIndent
1316 output
= tabwriter
.NewWriter(output
, minwidth
, cfg
.Tabwidth
, 1, padchar
, twmode
)
1319 // write printer result via tabwriter/trimmer to output
1320 if _
, err
= output
.Write(p
.output
); err
!= nil {
1324 // flush tabwriter, if any
1325 if tw
, _
:= output
.(*tabwriter
.Writer
); tw
!= nil {
1332 // A CommentedNode bundles an AST node and corresponding comments.
1333 // It may be provided as argument to any of the Fprint functions.
1335 type CommentedNode
struct {
1336 Node
interface{} // *ast.File, or ast.Expr, ast.Decl, ast.Spec, or ast.Stmt
1337 Comments
[]*ast
.CommentGroup
1340 // Fprint "pretty-prints" an AST node to output for a given configuration cfg.
1341 // Position information is interpreted relative to the file set fset.
1342 // The node type must be *ast.File, *CommentedNode, []ast.Decl, []ast.Stmt,
1343 // or assignment-compatible to ast.Expr, ast.Decl, ast.Spec, or ast.Stmt.
1345 func (cfg
*Config
) Fprint(output io
.Writer
, fset
*token
.FileSet
, node
interface{}) error
{
1346 return cfg
.fprint(output
, fset
, node
, make(map[ast
.Node
]int))
1349 // Fprint "pretty-prints" an AST node to output.
1350 // It calls Config.Fprint with default settings.
1351 // Note that gofmt uses tabs for indentation but spaces for alignment;
1352 // use format.Node (package go/format) for output that matches gofmt.
1354 func Fprint(output io
.Writer
, fset
*token
.FileSet
, node
interface{}) error
{
1355 return (&Config
{Tabwidth
: 8}).Fprint(output
, fset
, node
)