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