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