OSDN Git Service

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