OSDN Git Service

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