...
Run Format

Source file src/go/printer/printer.go

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

View as plain text