OSDN Git Service

libgo: Update to weekly.2011-11-18.
[pf3gnuchains/gcc-fork.git] / libgo / go / 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         "bytes"
10         "fmt"
11         "go/ast"
12         "go/token"
13         "io"
14         "os"
15         "path/filepath"
16         "strconv"
17         "strings"
18         "text/tabwriter"
19 )
20
21 const debug = false // enable for debugging
22
23
24 type whiteSpace int
25
26 const (
27         ignore   = whiteSpace(0)
28         blank    = whiteSpace(' ')
29         vtab     = whiteSpace('\v')
30         newline  = whiteSpace('\n')
31         formfeed = whiteSpace('\f')
32         indent   = whiteSpace('>')
33         unindent = whiteSpace('<')
34 )
35
36 var (
37         esc       = []byte{tabwriter.Escape}
38         htab      = []byte{'\t'}
39         htabs     = []byte("\t\t\t\t\t\t\t\t")
40         newlines  = []byte("\n\n\n\n\n\n\n\n") // more than the max determined by nlines
41         formfeeds = []byte("\f\f\f\f\f\f\f\f") // more than the max determined by nlines
42 )
43
44 // Special positions
45 var noPos token.Position // use noPos when a position is needed but not known
46 var infinity = 1 << 30
47
48 // Use ignoreMultiLine if the multiLine information is not important.
49 var ignoreMultiLine = new(bool)
50
51 // A pmode value represents the current printer mode.
52 type pmode int
53
54 const (
55         inLiteral pmode = 1 << iota
56         noExtraLinebreak
57 )
58
59 // local error wrapper so we can distinguish errors we want to return
60 // as errors from genuine panics (which we don't want to return as errors)
61 type osError struct {
62         err error
63 }
64
65 type printer struct {
66         // Configuration (does not change after initialization)
67         output io.Writer
68         Config
69         fset *token.FileSet
70
71         // Current state
72         written int         // number of bytes written
73         indent  int         // current indentation
74         mode    pmode       // current printer mode
75         lastTok token.Token // the last token printed (token.ILLEGAL if it's whitespace)
76
77         // Reused buffers
78         wsbuf  []whiteSpace // delayed white space
79         litbuf bytes.Buffer // for creation of escaped literals and comments
80
81         // The (possibly estimated) position in the generated output;
82         // in AST space (i.e., pos is set whenever a token position is
83         // known accurately, and updated dependending on what has been
84         // written).
85         pos token.Position
86
87         // The value of pos immediately after the last item has been
88         // written using writeItem.
89         last token.Position
90
91         // The list of all source comments, in order of appearance.
92         comments        []*ast.CommentGroup // may be nil
93         cindex          int                 // current comment index
94         useNodeComments bool                // if not set, ignore lead and line comments of nodes
95
96         // Cache of already computed node sizes.
97         nodeSizes map[ast.Node]int
98 }
99
100 func (p *printer) init(output io.Writer, cfg *Config, fset *token.FileSet, nodeSizes map[ast.Node]int) {
101         p.output = output
102         p.Config = *cfg
103         p.fset = fset
104         p.wsbuf = make([]whiteSpace, 0, 16) // whitespace sequences are short
105         p.nodeSizes = nodeSizes
106 }
107
108 func (p *printer) internalError(msg ...interface{}) {
109         if debug {
110                 fmt.Print(p.pos.String() + ": ")
111                 fmt.Println(msg...)
112                 panic("go/printer")
113         }
114 }
115
116 // escape escapes string s by bracketing it with tabwriter.Escape.
117 // Escaped strings pass through tabwriter unchanged. (Note that
118 // valid Go programs cannot contain tabwriter.Escape bytes since
119 // they do not appear in legal UTF-8 sequences).
120 //
121 func (p *printer) escape(s string) string {
122         p.litbuf.Reset()
123         p.litbuf.WriteByte(tabwriter.Escape)
124         p.litbuf.WriteString(s)
125         p.litbuf.WriteByte(tabwriter.Escape)
126         return p.litbuf.String()
127 }
128
129 // nlines returns the adjusted number of linebreaks given the desired number
130 // of breaks n such that min <= result <= max.
131 //
132 func (p *printer) nlines(n, min int) int {
133         const max = 2 // max. number of newlines
134         switch {
135         case n < min:
136                 return min
137         case n > max:
138                 return max
139         }
140         return n
141 }
142
143 // write0 writes raw (uninterpreted) data to p.output and handles errors.
144 // write0 does not indent after newlines, and does not HTML-escape or update p.pos.
145 //
146 func (p *printer) write0(data []byte) {
147         if len(data) > 0 {
148                 n, err := p.output.Write(data)
149                 p.written += n
150                 if err != nil {
151                         panic(osError{err})
152                 }
153         }
154 }
155
156 // write interprets data and writes it to p.output. It inserts indentation
157 // after a line break unless in a tabwriter escape sequence.
158 // It updates p.pos as a side-effect.
159 //
160 func (p *printer) write(data []byte) {
161         i0 := 0
162         for i, b := range data {
163                 switch b {
164                 case '\n', '\f':
165                         // write segment ending in b
166                         p.write0(data[i0 : i+1])
167
168                         // update p.pos
169                         p.pos.Offset += i + 1 - i0
170                         p.pos.Line++
171                         p.pos.Column = 1
172
173                         if p.mode&inLiteral == 0 {
174                                 // write indentation
175                                 // use "hard" htabs - indentation columns
176                                 // must not be discarded by the tabwriter
177                                 j := p.indent
178                                 for ; j > len(htabs); j -= len(htabs) {
179                                         p.write0(htabs)
180                                 }
181                                 p.write0(htabs[0:j])
182
183                                 // update p.pos
184                                 p.pos.Offset += p.indent
185                                 p.pos.Column += p.indent
186                         }
187
188                         // next segment start
189                         i0 = i + 1
190
191                 case tabwriter.Escape:
192                         p.mode ^= inLiteral
193
194                         // ignore escape chars introduced by printer - they are
195                         // invisible and must not affect p.pos (was issue #1089)
196                         p.pos.Offset--
197                         p.pos.Column--
198                 }
199         }
200
201         // write remaining segment
202         p.write0(data[i0:])
203
204         // update p.pos
205         d := len(data) - i0
206         p.pos.Offset += d
207         p.pos.Column += d
208 }
209
210 func (p *printer) writeNewlines(n int, useFF bool) {
211         if n > 0 {
212                 n = p.nlines(n, 0)
213                 if useFF {
214                         p.write(formfeeds[0:n])
215                 } else {
216                         p.write(newlines[0:n])
217                 }
218         }
219 }
220
221 // writeItem writes data at position pos. data is the text corresponding to
222 // a single lexical token, but may also be comment text. pos is the actual
223 // (or at least very accurately estimated) position of the data in the original
224 // source text. writeItem updates p.last to the position immediately following
225 // the data.
226 //
227 func (p *printer) writeItem(pos token.Position, data string) {
228         if pos.IsValid() {
229                 // continue with previous position if we don't have a valid pos
230                 if p.last.IsValid() && p.last.Filename != pos.Filename {
231                         // the file has changed - reset state
232                         // (used when printing merged ASTs of different files
233                         // e.g., the result of ast.MergePackageFiles)
234                         p.indent = 0
235                         p.mode = 0
236                         p.wsbuf = p.wsbuf[0:0]
237                 }
238                 p.pos = pos
239         }
240         if debug {
241                 // do not update p.pos - use write0
242                 _, filename := filepath.Split(pos.Filename)
243                 p.write0([]byte(fmt.Sprintf("[%s:%d:%d]", filename, pos.Line, pos.Column)))
244         }
245         p.write([]byte(data))
246         p.last = p.pos
247 }
248
249 const linePrefix = "//line "
250
251 // writeCommentPrefix writes the whitespace before a comment.
252 // If there is any pending whitespace, it consumes as much of
253 // it as is likely to help position the comment nicely.
254 // pos is the comment position, next the position of the item
255 // after all pending comments, prev is the previous comment in
256 // a group of comments (or nil), and isKeyword indicates if the
257 // next item is a keyword.
258 //
259 func (p *printer) writeCommentPrefix(pos, next token.Position, prev, comment *ast.Comment, isKeyword bool) {
260         if p.written == 0 {
261                 // the comment is the first item to be printed - don't write any whitespace
262                 return
263         }
264
265         if pos.IsValid() && pos.Filename != p.last.Filename {
266                 // comment in a different file - separate with newlines (writeNewlines will limit the number)
267                 p.writeNewlines(10, true)
268                 return
269         }
270
271         if pos.Line == p.last.Line && (prev == nil || prev.Text[1] != '/') {
272                 // comment on the same line as last item:
273                 // separate with at least one separator
274                 hasSep := false
275                 if prev == nil {
276                         // first comment of a comment group
277                         j := 0
278                         for i, ch := range p.wsbuf {
279                                 switch ch {
280                                 case blank:
281                                         // ignore any blanks before a comment
282                                         p.wsbuf[i] = ignore
283                                         continue
284                                 case vtab:
285                                         // respect existing tabs - important
286                                         // for proper formatting of commented structs
287                                         hasSep = true
288                                         continue
289                                 case indent:
290                                         // apply pending indentation
291                                         continue
292                                 }
293                                 j = i
294                                 break
295                         }
296                         p.writeWhitespace(j)
297                 }
298                 // make sure there is at least one separator
299                 if !hasSep {
300                         if pos.Line == next.Line {
301                                 // next item is on the same line as the comment
302                                 // (which must be a /*-style comment): separate
303                                 // with a blank instead of a tab
304                                 p.write([]byte{' '})
305                         } else {
306                                 p.write(htab)
307                         }
308                 }
309
310         } else {
311                 // comment on a different line:
312                 // separate with at least one line break
313                 if prev == nil {
314                         // first comment of a comment group
315                         j := 0
316                         for i, ch := range p.wsbuf {
317                                 switch ch {
318                                 case blank, vtab:
319                                         // ignore any horizontal whitespace before line breaks
320                                         p.wsbuf[i] = ignore
321                                         continue
322                                 case indent:
323                                         // apply pending indentation
324                                         continue
325                                 case unindent:
326                                         // if the next token is a keyword, apply the outdent
327                                         // if it appears that the comment is aligned with the
328                                         // keyword; otherwise assume the outdent is part of a
329                                         // closing block and stop (this scenario appears with
330                                         // comments before a case label where the comments
331                                         // apply to the next case instead of the current one)
332                                         if isKeyword && pos.Column == next.Column {
333                                                 continue
334                                         }
335                                 case newline, formfeed:
336                                         // TODO(gri): may want to keep formfeed info in some cases
337                                         p.wsbuf[i] = ignore
338                                 }
339                                 j = i
340                                 break
341                         }
342                         p.writeWhitespace(j)
343                 }
344
345                 // turn off indent if we're about to print a line directive.
346                 indent := p.indent
347                 if strings.HasPrefix(comment.Text, linePrefix) {
348                         p.indent = 0
349                 }
350
351                 // use formfeeds to break columns before a comment;
352                 // this is analogous to using formfeeds to separate
353                 // individual lines of /*-style comments - but make
354                 // sure there is at least one line break if the previous
355                 // comment was a line comment
356                 n := pos.Line - p.last.Line // if !pos.IsValid(), pos.Line == 0, and n will be 0
357                 if n <= 0 && prev != nil && prev.Text[1] == '/' {
358                         n = 1
359                 }
360                 p.writeNewlines(n, true)
361                 p.indent = indent
362         }
363 }
364
365 // TODO(gri): It should be possible to convert the code below from using
366 //            []byte to string and in the process eliminate some conversions.
367
368 // Split comment text into lines
369 func split(text []byte) [][]byte {
370         // count lines (comment text never ends in a newline)
371         n := 1
372         for _, c := range text {
373                 if c == '\n' {
374                         n++
375                 }
376         }
377
378         // split
379         lines := make([][]byte, n)
380         n = 0
381         i := 0
382         for j, c := range text {
383                 if c == '\n' {
384                         lines[n] = text[i:j] // exclude newline
385                         i = j + 1            // discard newline
386                         n++
387                 }
388         }
389         lines[n] = text[i:]
390
391         return lines
392 }
393
394 func isBlank(s []byte) bool {
395         for _, b := range s {
396                 if b > ' ' {
397                         return false
398                 }
399         }
400         return true
401 }
402
403 func commonPrefix(a, b []byte) []byte {
404         i := 0
405         for i < len(a) && i < len(b) && a[i] == b[i] && (a[i] <= ' ' || a[i] == '*') {
406                 i++
407         }
408         return a[0:i]
409 }
410
411 func stripCommonPrefix(lines [][]byte) {
412         if len(lines) < 2 {
413                 return // at most one line - nothing to do
414         }
415         // len(lines) >= 2
416
417         // The heuristic in this function tries to handle a few
418         // common patterns of /*-style comments: Comments where
419         // the opening /* and closing */ are aligned and the
420         // rest of the comment text is aligned and indented with
421         // blanks or tabs, cases with a vertical "line of stars"
422         // on the left, and cases where the closing */ is on the
423         // same line as the last comment text.
424
425         // Compute maximum common white prefix of all but the first,
426         // last, and blank lines, and replace blank lines with empty
427         // lines (the first line starts with /* and has no prefix).
428         // In case of two-line comments, consider the last line for
429         // the prefix computation since otherwise the prefix would
430         // be empty.
431         //
432         // Note that the first and last line are never empty (they
433         // contain the opening /* and closing */ respectively) and
434         // thus they can be ignored by the blank line check.
435         var prefix []byte
436         if len(lines) > 2 {
437                 for i, line := range lines[1 : len(lines)-1] {
438                         switch {
439                         case isBlank(line):
440                                 lines[1+i] = nil // range starts at line 1
441                         case prefix == nil:
442                                 prefix = commonPrefix(line, line)
443                         default:
444                                 prefix = commonPrefix(prefix, line)
445                         }
446                 }
447         } else { // len(lines) == 2
448                 line := lines[1]
449                 prefix = commonPrefix(line, line)
450         }
451
452         /*
453          * Check for vertical "line of stars" and correct prefix accordingly.
454          */
455         lineOfStars := false
456         if i := bytes.Index(prefix, []byte{'*'}); i >= 0 {
457                 // Line of stars present.
458                 if i > 0 && prefix[i-1] == ' ' {
459                         i-- // remove trailing blank from prefix so stars remain aligned
460                 }
461                 prefix = prefix[0:i]
462                 lineOfStars = true
463         } else {
464                 // No line of stars present.
465                 // Determine the white space on the first line after the /*
466                 // and before the beginning of the comment text, assume two
467                 // blanks instead of the /* unless the first character after
468                 // the /* is a tab. If the first comment line is empty but
469                 // for the opening /*, assume up to 3 blanks or a tab. This
470                 // whitespace may be found as suffix in the common prefix.
471                 first := lines[0]
472                 if isBlank(first[2:]) {
473                         // no comment text on the first line:
474                         // reduce prefix by up to 3 blanks or a tab
475                         // if present - this keeps comment text indented
476                         // relative to the /* and */'s if it was indented
477                         // in the first place
478                         i := len(prefix)
479                         for n := 0; n < 3 && i > 0 && prefix[i-1] == ' '; n++ {
480                                 i--
481                         }
482                         if i == len(prefix) && i > 0 && prefix[i-1] == '\t' {
483                                 i--
484                         }
485                         prefix = prefix[0:i]
486                 } else {
487                         // comment text on the first line
488                         suffix := make([]byte, len(first))
489                         n := 2 // start after opening /*
490                         for n < len(first) && first[n] <= ' ' {
491                                 suffix[n] = first[n]
492                                 n++
493                         }
494                         if n > 2 && suffix[2] == '\t' {
495                                 // assume the '\t' compensates for the /*
496                                 suffix = suffix[2:n]
497                         } else {
498                                 // otherwise assume two blanks
499                                 suffix[0], suffix[1] = ' ', ' '
500                                 suffix = suffix[0:n]
501                         }
502                         // Shorten the computed common prefix by the length of
503                         // suffix, if it is found as suffix of the prefix.
504                         if bytes.HasSuffix(prefix, suffix) {
505                                 prefix = prefix[0 : len(prefix)-len(suffix)]
506                         }
507                 }
508         }
509
510         // Handle last line: If it only contains a closing */, align it
511         // with the opening /*, otherwise align the text with the other
512         // lines.
513         last := lines[len(lines)-1]
514         closing := []byte("*/")
515         i := bytes.Index(last, closing)
516         if isBlank(last[0:i]) {
517                 // last line only contains closing */
518                 var sep []byte
519                 if lineOfStars {
520                         // insert an aligning blank
521                         sep = []byte{' '}
522                 }
523                 lines[len(lines)-1] = bytes.Join([][]byte{prefix, closing}, sep)
524         } else {
525                 // last line contains more comment text - assume
526                 // it is aligned like the other lines
527                 prefix = commonPrefix(prefix, last)
528         }
529
530         // Remove the common prefix from all but the first and empty lines.
531         for i, line := range lines[1:] {
532                 if len(line) != 0 {
533                         lines[1+i] = line[len(prefix):] // range starts at line 1
534                 }
535         }
536 }
537
538 func (p *printer) writeComment(comment *ast.Comment) {
539         text := comment.Text
540
541         if strings.HasPrefix(text, linePrefix) {
542                 pos := strings.TrimSpace(text[len(linePrefix):])
543                 i := strings.LastIndex(pos, ":")
544                 if i >= 0 {
545                         // The line directive we are about to print changed
546                         // the Filename and Line number used by go/token
547                         // as it was reading the input originally.
548                         // In order to match the original input, we have to
549                         // update our own idea of the file and line number
550                         // accordingly, after printing the directive.
551                         file := pos[:i]
552                         line, _ := strconv.Atoi(string(pos[i+1:]))
553                         defer func() {
554                                 p.pos.Filename = string(file)
555                                 p.pos.Line = line
556                                 p.pos.Column = 1
557                         }()
558                 }
559         }
560
561         // shortcut common case of //-style comments
562         if text[1] == '/' {
563                 p.writeItem(p.fset.Position(comment.Pos()), p.escape(text))
564                 return
565         }
566
567         // for /*-style comments, print line by line and let the
568         // write function take care of the proper indentation
569         lines := split([]byte(text))
570         stripCommonPrefix(lines)
571
572         // write comment lines, separated by formfeed,
573         // without a line break after the last line
574         linebreak := formfeeds[0:1]
575         pos := p.fset.Position(comment.Pos())
576         for i, line := range lines {
577                 if i > 0 {
578                         p.write(linebreak)
579                         pos = p.pos
580                 }
581                 if len(line) > 0 {
582                         p.writeItem(pos, p.escape(string(line)))
583                 }
584         }
585 }
586
587 // writeCommentSuffix writes a line break after a comment if indicated
588 // and processes any leftover indentation information. If a line break
589 // is needed, the kind of break (newline vs formfeed) depends on the
590 // pending whitespace. writeCommentSuffix returns true if a pending
591 // formfeed was dropped from the whitespace buffer.
592 //
593 func (p *printer) writeCommentSuffix(needsLinebreak bool) (droppedFF bool) {
594         for i, ch := range p.wsbuf {
595                 switch ch {
596                 case blank, vtab:
597                         // ignore trailing whitespace
598                         p.wsbuf[i] = ignore
599                 case indent, unindent:
600                         // don't lose indentation information
601                 case newline, formfeed:
602                         // if we need a line break, keep exactly one
603                         // but remember if we dropped any formfeeds
604                         if needsLinebreak {
605                                 needsLinebreak = false
606                         } else {
607                                 if ch == formfeed {
608                                         droppedFF = true
609                                 }
610                                 p.wsbuf[i] = ignore
611                         }
612                 }
613         }
614         p.writeWhitespace(len(p.wsbuf))
615
616         // make sure we have a line break
617         if needsLinebreak {
618                 p.write([]byte{'\n'})
619         }
620
621         return
622 }
623
624 // intersperseComments consumes all comments that appear before the next token
625 // tok and prints it together with the buffered whitespace (i.e., the whitespace
626 // that needs to be written before the next token). A heuristic is used to mix
627 // the comments and whitespace. intersperseComments returns true if a pending
628 // formfeed was dropped from the whitespace buffer.
629 //
630 func (p *printer) intersperseComments(next token.Position, tok token.Token) (droppedFF bool) {
631         var last *ast.Comment
632         for ; p.commentBefore(next); p.cindex++ {
633                 for _, c := range p.comments[p.cindex].List {
634                         p.writeCommentPrefix(p.fset.Position(c.Pos()), next, last, c, tok.IsKeyword())
635                         p.writeComment(c)
636                         last = c
637                 }
638         }
639
640         if last != nil {
641                 if last.Text[1] == '*' && p.fset.Position(last.Pos()).Line == next.Line {
642                         // the last comment is a /*-style comment and the next item
643                         // follows on the same line: separate with an extra blank
644                         p.write([]byte{' '})
645                 }
646                 // ensure that there is a line break after a //-style comment,
647                 // before a closing '}' unless explicitly disabled, or at eof
648                 needsLinebreak :=
649                         last.Text[1] == '/' ||
650                                 tok == token.RBRACE && p.mode&noExtraLinebreak == 0 ||
651                                 tok == token.EOF
652                 return p.writeCommentSuffix(needsLinebreak)
653         }
654
655         // no comment was written - we should never reach here since
656         // intersperseComments should not be called in that case
657         p.internalError("intersperseComments called without pending comments")
658         return false
659 }
660
661 // whiteWhitespace writes the first n whitespace entries.
662 func (p *printer) writeWhitespace(n int) {
663         // write entries
664         var data [1]byte
665         for i := 0; i < n; i++ {
666                 switch ch := p.wsbuf[i]; ch {
667                 case ignore:
668                         // ignore!
669                 case indent:
670                         p.indent++
671                 case unindent:
672                         p.indent--
673                         if p.indent < 0 {
674                                 p.internalError("negative indentation:", p.indent)
675                                 p.indent = 0
676                         }
677                 case newline, formfeed:
678                         // A line break immediately followed by a "correcting"
679                         // unindent is swapped with the unindent - this permits
680                         // proper label positioning. If a comment is between
681                         // the line break and the label, the unindent is not
682                         // part of the comment whitespace prefix and the comment
683                         // will be positioned correctly indented.
684                         if i+1 < n && p.wsbuf[i+1] == unindent {
685                                 // Use a formfeed to terminate the current section.
686                                 // Otherwise, a long label name on the next line leading
687                                 // to a wide column may increase the indentation column
688                                 // of lines before the label; effectively leading to wrong
689                                 // indentation.
690                                 p.wsbuf[i], p.wsbuf[i+1] = unindent, formfeed
691                                 i-- // do it again
692                                 continue
693                         }
694                         fallthrough
695                 default:
696                         data[0] = byte(ch)
697                         p.write(data[0:])
698                 }
699         }
700
701         // shift remaining entries down
702         i := 0
703         for ; n < len(p.wsbuf); n++ {
704                 p.wsbuf[i] = p.wsbuf[n]
705                 i++
706         }
707         p.wsbuf = p.wsbuf[0:i]
708 }
709
710 // ----------------------------------------------------------------------------
711 // Printing interface
712
713
714 func mayCombine(prev token.Token, next byte) (b bool) {
715         switch prev {
716         case token.INT:
717                 b = next == '.' // 1.
718         case token.ADD:
719                 b = next == '+' // ++
720         case token.SUB:
721                 b = next == '-' // --
722         case token.QUO:
723                 b = next == '*' // /*
724         case token.LSS:
725                 b = next == '-' || next == '<' // <- or <<
726         case token.AND:
727                 b = next == '&' || next == '^' // && or &^
728         }
729         return
730 }
731
732 // print prints a list of "items" (roughly corresponding to syntactic
733 // tokens, but also including whitespace and formatting information).
734 // It is the only print function that should be called directly from
735 // any of the AST printing functions in nodes.go.
736 //
737 // Whitespace is accumulated until a non-whitespace token appears. Any
738 // comments that need to appear before that token are printed first,
739 // taking into account the amount and structure of any pending white-
740 // space for best comment placement. Then, any leftover whitespace is
741 // printed, followed by the actual token.
742 //
743 func (p *printer) print(args ...interface{}) {
744         for _, f := range args {
745                 next := p.pos // estimated position of next item
746                 var data string
747                 var tok token.Token
748
749                 switch x := f.(type) {
750                 case pmode:
751                         // toggle printer mode
752                         p.mode ^= x
753                 case whiteSpace:
754                         if x == ignore {
755                                 // don't add ignore's to the buffer; they
756                                 // may screw up "correcting" unindents (see
757                                 // LabeledStmt)
758                                 break
759                         }
760                         i := len(p.wsbuf)
761                         if i == cap(p.wsbuf) {
762                                 // Whitespace sequences are very short so this should
763                                 // never happen. Handle gracefully (but possibly with
764                                 // bad comment placement) if it does happen.
765                                 p.writeWhitespace(i)
766                                 i = 0
767                         }
768                         p.wsbuf = p.wsbuf[0 : i+1]
769                         p.wsbuf[i] = x
770                 case *ast.Ident:
771                         data = x.Name
772                         tok = token.IDENT
773                 case *ast.BasicLit:
774                         data = p.escape(x.Value)
775                         tok = x.Kind
776                 case token.Token:
777                         s := x.String()
778                         if mayCombine(p.lastTok, s[0]) {
779                                 // the previous and the current token must be
780                                 // separated by a blank otherwise they combine
781                                 // into a different incorrect token sequence
782                                 // (except for token.INT followed by a '.' this
783                                 // should never happen because it is taken care
784                                 // of via binary expression formatting)
785                                 if len(p.wsbuf) != 0 {
786                                         p.internalError("whitespace buffer not empty")
787                                 }
788                                 p.wsbuf = p.wsbuf[0:1]
789                                 p.wsbuf[0] = ' '
790                         }
791                         data = s
792                         tok = x
793                 case token.Pos:
794                         if x.IsValid() {
795                                 next = p.fset.Position(x) // accurate position of next item
796                         }
797                         tok = p.lastTok
798                 default:
799                         fmt.Fprintf(os.Stderr, "print: unsupported argument type %T\n", f)
800                         panic("go/printer type")
801                 }
802                 p.lastTok = tok
803                 p.pos = next
804
805                 if data != "" {
806                         droppedFF := p.flush(next, tok)
807
808                         // intersperse extra newlines if present in the source
809                         // (don't do this in flush as it will cause extra newlines
810                         // at the end of a file) - use formfeeds if we dropped one
811                         // before
812                         p.writeNewlines(next.Line-p.pos.Line, droppedFF)
813
814                         p.writeItem(next, data)
815                 }
816         }
817 }
818
819 // commentBefore returns true iff the current comment occurs
820 // before the next position in the source code.
821 //
822 func (p *printer) commentBefore(next token.Position) bool {
823         return p.cindex < len(p.comments) && p.fset.Position(p.comments[p.cindex].List[0].Pos()).Offset < next.Offset
824 }
825
826 // Flush prints any pending comments and whitespace occurring
827 // textually before the position of the next token tok. Flush
828 // returns true if a pending formfeed character was dropped
829 // from the whitespace buffer as a result of interspersing
830 // comments.
831 //
832 func (p *printer) flush(next token.Position, tok token.Token) (droppedFF bool) {
833         if p.commentBefore(next) {
834                 // if there are comments before the next item, intersperse them
835                 droppedFF = p.intersperseComments(next, tok)
836         } else {
837                 // otherwise, write any leftover whitespace
838                 p.writeWhitespace(len(p.wsbuf))
839         }
840         return
841 }
842
843 // ----------------------------------------------------------------------------
844 // Trimmer
845
846 // A trimmer is an io.Writer filter for stripping tabwriter.Escape
847 // characters, trailing blanks and tabs, and for converting formfeed
848 // and vtab characters into newlines and htabs (in case no tabwriter
849 // is used). Text bracketed by tabwriter.Escape characters is passed
850 // through unchanged.
851 //
852 type trimmer struct {
853         output io.Writer
854         state  int
855         space  bytes.Buffer
856 }
857
858 // trimmer is implemented as a state machine.
859 // It can be in one of the following states:
860 const (
861         inSpace  = iota // inside space
862         inEscape        // inside text bracketed by tabwriter.Escapes
863         inText          // inside text
864 )
865
866 // Design note: It is tempting to eliminate extra blanks occurring in
867 //              whitespace in this function as it could simplify some
868 //              of the blanks logic in the node printing functions.
869 //              However, this would mess up any formatting done by
870 //              the tabwriter.
871
872 func (p *trimmer) Write(data []byte) (n int, err error) {
873         // invariants:
874         // p.state == inSpace:
875         //      p.space is unwritten
876         // p.state == inEscape, inText:
877         //      data[m:n] is unwritten
878         m := 0
879         var b byte
880         for n, b = range data {
881                 if b == '\v' {
882                         b = '\t' // convert to htab
883                 }
884                 switch p.state {
885                 case inSpace:
886                         switch b {
887                         case '\t', ' ':
888                                 p.space.WriteByte(b) // WriteByte returns no errors
889                         case '\n', '\f':
890                                 p.space.Reset()                        // discard trailing space
891                                 _, err = p.output.Write(newlines[0:1]) // write newline
892                         case tabwriter.Escape:
893                                 _, err = p.output.Write(p.space.Bytes())
894                                 p.state = inEscape
895                                 m = n + 1 // +1: skip tabwriter.Escape
896                         default:
897                                 _, err = p.output.Write(p.space.Bytes())
898                                 p.state = inText
899                                 m = n
900                         }
901                 case inEscape:
902                         if b == tabwriter.Escape {
903                                 _, err = p.output.Write(data[m:n])
904                                 p.state = inSpace
905                                 p.space.Reset()
906                         }
907                 case inText:
908                         switch b {
909                         case '\t', ' ':
910                                 _, err = p.output.Write(data[m:n])
911                                 p.state = inSpace
912                                 p.space.Reset()
913                                 p.space.WriteByte(b) // WriteByte returns no errors
914                         case '\n', '\f':
915                                 _, err = p.output.Write(data[m:n])
916                                 p.state = inSpace
917                                 p.space.Reset()
918                                 _, err = p.output.Write(newlines[0:1]) // write newline
919                         case tabwriter.Escape:
920                                 _, err = p.output.Write(data[m:n])
921                                 p.state = inEscape
922                                 m = n + 1 // +1: skip tabwriter.Escape
923                         }
924                 default:
925                         panic("unreachable")
926                 }
927                 if err != nil {
928                         return
929                 }
930         }
931         n = len(data)
932
933         switch p.state {
934         case inEscape, inText:
935                 _, err = p.output.Write(data[m:n])
936                 p.state = inSpace
937                 p.space.Reset()
938         }
939
940         return
941 }
942
943 // ----------------------------------------------------------------------------
944 // Public interface
945
946 // General printing is controlled with these Config.Mode flags.
947 const (
948         RawFormat uint = 1 << iota // do not use a tabwriter; if set, UseSpaces is ignored
949         TabIndent                  // use tabs for indentation independent of UseSpaces
950         UseSpaces                  // use spaces instead of tabs for alignment
951 )
952
953 // A Config node controls the output of Fprint.
954 type Config struct {
955         Mode     uint // default: 0
956         Tabwidth int  // default: 8
957 }
958
959 // fprint implements Fprint and takes a nodesSizes map for setting up the printer state.
960 func (cfg *Config) fprint(output io.Writer, fset *token.FileSet, node interface{}, nodeSizes map[ast.Node]int) (written int, err error) {
961         // redirect output through a trimmer to eliminate trailing whitespace
962         // (Input to a tabwriter must be untrimmed since trailing tabs provide
963         // formatting information. The tabwriter could provide trimming
964         // functionality but no tabwriter is used when RawFormat is set.)
965         output = &trimmer{output: output}
966
967         // setup tabwriter if needed and redirect output
968         var tw *tabwriter.Writer
969         if cfg.Mode&RawFormat == 0 {
970                 minwidth := cfg.Tabwidth
971
972                 padchar := byte('\t')
973                 if cfg.Mode&UseSpaces != 0 {
974                         padchar = ' '
975                 }
976
977                 twmode := tabwriter.DiscardEmptyColumns
978                 if cfg.Mode&TabIndent != 0 {
979                         minwidth = 0
980                         twmode |= tabwriter.TabIndent
981                 }
982
983                 tw = tabwriter.NewWriter(output, minwidth, cfg.Tabwidth, 1, padchar, twmode)
984                 output = tw
985         }
986
987         // setup printer
988         var p printer
989         p.init(output, cfg, fset, nodeSizes)
990         defer func() {
991                 written = p.written
992                 if e := recover(); e != nil {
993                         err = e.(osError).err // re-panics if it's not a local osError
994                 }
995         }()
996
997         // print node
998         switch n := node.(type) {
999         case ast.Expr:
1000                 p.useNodeComments = true
1001                 p.expr(n, ignoreMultiLine)
1002         case ast.Stmt:
1003                 p.useNodeComments = true
1004                 // A labeled statement will un-indent to position the
1005                 // label. Set indent to 1 so we don't get indent "underflow".
1006                 if _, labeledStmt := n.(*ast.LabeledStmt); labeledStmt {
1007                         p.indent = 1
1008                 }
1009                 p.stmt(n, false, ignoreMultiLine)
1010         case ast.Decl:
1011                 p.useNodeComments = true
1012                 p.decl(n, ignoreMultiLine)
1013         case ast.Spec:
1014                 p.useNodeComments = true
1015                 p.spec(n, 1, false, ignoreMultiLine)
1016         case *ast.File:
1017                 p.comments = n.Comments
1018                 p.useNodeComments = n.Comments == nil
1019                 p.file(n)
1020         default:
1021                 panic(osError{fmt.Errorf("printer.Fprint: unsupported node type %T", n)})
1022         }
1023         p.flush(token.Position{Offset: infinity, Line: infinity}, token.EOF)
1024
1025         // flush tabwriter, if any
1026         if tw != nil {
1027                 tw.Flush() // ignore errors
1028         }
1029
1030         return
1031 }
1032
1033 // Fprint "pretty-prints" an AST node to output and returns the number
1034 // of bytes written and an error (if any) for a given configuration cfg.
1035 // Position information is interpreted relative to the file set fset.
1036 // The node type must be *ast.File, or assignment-compatible to ast.Expr,
1037 // ast.Decl, ast.Spec, or ast.Stmt.
1038 //
1039 func (cfg *Config) Fprint(output io.Writer, fset *token.FileSet, node interface{}) (int, error) {
1040         return cfg.fprint(output, fset, node, make(map[ast.Node]int))
1041 }
1042
1043 // Fprint "pretty-prints" an AST node to output.
1044 // It calls Config.Fprint with default settings.
1045 //
1046 func Fprint(output io.Writer, fset *token.FileSet, node interface{}) error {
1047         _, err := (&Config{Tabwidth: 8}).Fprint(output, fset, node) // don't care about number of bytes written
1048         return err
1049 }