github.com/varialus/godfly@v0.0.0-20130904042352-1934f9f095ab/src/pkg/go/printer/printer.go (about)

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