...
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 reports whether 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 cases where only the first and last lines are not blank,
   500		// such as two-line comments, or comments where all inner lines
   501		// are blank, consider the last line for the prefix computation
   502		// since otherwise the prefix would be empty.
   503		//
   504		// Note that the first and last line are never empty (they
   505		// contain the opening /* and closing */ respectively) and
   506		// thus they can be ignored by the blank line check.
   507		prefix := ""
   508		prefixSet := false
   509		if len(lines) > 2 {
   510			for i, line := range lines[1 : len(lines)-1] {
   511				if isBlank(line) {
   512					lines[1+i] = "" // range starts with lines[1]
   513				} else {
   514					if !prefixSet {
   515						prefix = line
   516						prefixSet = true
   517					}
   518					prefix = commonPrefix(prefix, line)
   519				}
   520	
   521			}
   522		}
   523		// If we don't have a prefix yet, consider the last line.
   524		if !prefixSet {
   525			line := lines[len(lines)-1]
   526			prefix = commonPrefix(line, line)
   527		}
   528	
   529		/*
   530		 * Check for vertical "line of stars" and correct prefix accordingly.
   531		 */
   532		lineOfStars := false
   533		if i := strings.Index(prefix, "*"); i >= 0 {
   534			// Line of stars present.
   535			if i > 0 && prefix[i-1] == ' ' {
   536				i-- // remove trailing blank from prefix so stars remain aligned
   537			}
   538			prefix = prefix[0:i]
   539			lineOfStars = true
   540		} else {
   541			// No line of stars present.
   542			// Determine the white space on the first line after the /*
   543			// and before the beginning of the comment text, assume two
   544			// blanks instead of the /* unless the first character after
   545			// the /* is a tab. If the first comment line is empty but
   546			// for the opening /*, assume up to 3 blanks or a tab. This
   547			// whitespace may be found as suffix in the common prefix.
   548			first := lines[0]
   549			if isBlank(first[2:]) {
   550				// no comment text on the first line:
   551				// reduce prefix by up to 3 blanks or a tab
   552				// if present - this keeps comment text indented
   553				// relative to the /* and */'s if it was indented
   554				// in the first place
   555				i := len(prefix)
   556				for n := 0; n < 3 && i > 0 && prefix[i-1] == ' '; n++ {
   557					i--
   558				}
   559				if i == len(prefix) && i > 0 && prefix[i-1] == '\t' {
   560					i--
   561				}
   562				prefix = prefix[0:i]
   563			} else {
   564				// comment text on the first line
   565				suffix := make([]byte, len(first))
   566				n := 2 // start after opening /*
   567				for n < len(first) && first[n] <= ' ' {
   568					suffix[n] = first[n]
   569					n++
   570				}
   571				if n > 2 && suffix[2] == '\t' {
   572					// assume the '\t' compensates for the /*
   573					suffix = suffix[2:n]
   574				} else {
   575					// otherwise assume two blanks
   576					suffix[0], suffix[1] = ' ', ' '
   577					suffix = suffix[0:n]
   578				}
   579				// Shorten the computed common prefix by the length of
   580				// suffix, if it is found as suffix of the prefix.
   581				prefix = strings.TrimSuffix(prefix, string(suffix))
   582			}
   583		}
   584	
   585		// Handle last line: If it only contains a closing */, align it
   586		// with the opening /*, otherwise align the text with the other
   587		// lines.
   588		last := lines[len(lines)-1]
   589		closing := "*/"
   590		i := strings.Index(last, closing) // i >= 0 (closing is always present)
   591		if isBlank(last[0:i]) {
   592			// last line only contains closing */
   593			if lineOfStars {
   594				closing = " */" // add blank to align final star
   595			}
   596			lines[len(lines)-1] = prefix + closing
   597		} else {
   598			// last line contains more comment text - assume
   599			// it is aligned like the other lines and include
   600			// in prefix computation
   601			prefix = commonPrefix(prefix, last)
   602		}
   603	
   604		// Remove the common prefix from all but the first and empty lines.
   605		for i, line := range lines {
   606			if i > 0 && line != "" {
   607				lines[i] = line[len(prefix):]
   608			}
   609		}
   610	}
   611	
   612	func (p *printer) writeComment(comment *ast.Comment) {
   613		text := comment.Text
   614		pos := p.posFor(comment.Pos())
   615	
   616		const linePrefix = "//line "
   617		if strings.HasPrefix(text, linePrefix) && (!pos.IsValid() || pos.Column == 1) {
   618			// possibly a line directive
   619			ldir := strings.TrimSpace(text[len(linePrefix):])
   620			if i := strings.LastIndex(ldir, ":"); i >= 0 {
   621				if line, err := strconv.Atoi(ldir[i+1:]); err == nil && line > 0 {
   622					// The line directive we are about to print changed
   623					// the Filename and Line number used for subsequent
   624					// tokens. We have to update our AST-space position
   625					// accordingly and suspend indentation temporarily.
   626					indent := p.indent
   627					p.indent = 0
   628					defer func() {
   629						p.pos.Filename = ldir[:i]
   630						p.pos.Line = line
   631						p.pos.Column = 1
   632						p.indent = indent
   633					}()
   634				}
   635			}
   636		}
   637	
   638		// shortcut common case of //-style comments
   639		if text[1] == '/' {
   640			p.writeString(pos, trimRight(text), true)
   641			return
   642		}
   643	
   644		// for /*-style comments, print line by line and let the
   645		// write function take care of the proper indentation
   646		lines := strings.Split(text, "\n")
   647	
   648		// The comment started in the first column but is going
   649		// to be indented. For an idempotent result, add indentation
   650		// to all lines such that they look like they were indented
   651		// before - this will make sure the common prefix computation
   652		// is the same independent of how many times formatting is
   653		// applied (was issue 1835).
   654		if pos.IsValid() && pos.Column == 1 && p.indent > 0 {
   655			for i, line := range lines[1:] {
   656				lines[1+i] = "   " + line
   657			}
   658		}
   659	
   660		stripCommonPrefix(lines)
   661	
   662		// write comment lines, separated by formfeed,
   663		// without a line break after the last line
   664		for i, line := range lines {
   665			if i > 0 {
   666				p.writeByte('\f', 1)
   667				pos = p.pos
   668			}
   669			if len(line) > 0 {
   670				p.writeString(pos, trimRight(line), true)
   671			}
   672		}
   673	}
   674	
   675	// writeCommentSuffix writes a line break after a comment if indicated
   676	// and processes any leftover indentation information. If a line break
   677	// is needed, the kind of break (newline vs formfeed) depends on the
   678	// pending whitespace. The writeCommentSuffix result indicates if a
   679	// newline was written or if a formfeed was dropped from the whitespace
   680	// buffer.
   681	//
   682	func (p *printer) writeCommentSuffix(needsLinebreak bool) (wroteNewline, droppedFF bool) {
   683		for i, ch := range p.wsbuf {
   684			switch ch {
   685			case blank, vtab:
   686				// ignore trailing whitespace
   687				p.wsbuf[i] = ignore
   688			case indent, unindent:
   689				// don't lose indentation information
   690			case newline, formfeed:
   691				// if we need a line break, keep exactly one
   692				// but remember if we dropped any formfeeds
   693				if needsLinebreak {
   694					needsLinebreak = false
   695					wroteNewline = true
   696				} else {
   697					if ch == formfeed {
   698						droppedFF = true
   699					}
   700					p.wsbuf[i] = ignore
   701				}
   702			}
   703		}
   704		p.writeWhitespace(len(p.wsbuf))
   705	
   706		// make sure we have a line break
   707		if needsLinebreak {
   708			p.writeByte('\n', 1)
   709			wroteNewline = true
   710		}
   711	
   712		return
   713	}
   714	
   715	// intersperseComments consumes all comments that appear before the next token
   716	// tok and prints it together with the buffered whitespace (i.e., the whitespace
   717	// that needs to be written before the next token). A heuristic is used to mix
   718	// the comments and whitespace. The intersperseComments result indicates if a
   719	// newline was written or if a formfeed was dropped from the whitespace buffer.
   720	//
   721	func (p *printer) intersperseComments(next token.Position, tok token.Token) (wroteNewline, droppedFF bool) {
   722		var last *ast.Comment
   723		for p.commentBefore(next) {
   724			for _, c := range p.comment.List {
   725				p.writeCommentPrefix(p.posFor(c.Pos()), next, last, c, tok)
   726				p.writeComment(c)
   727				last = c
   728			}
   729			p.nextComment()
   730		}
   731	
   732		if last != nil {
   733			// if the last comment is a /*-style comment and the next item
   734			// follows on the same line but is not a comma, and not a "closing"
   735			// token immediately following its corresponding "opening" token,
   736			// add an extra blank for separation unless explicitly disabled
   737			if p.mode&noExtraBlank == 0 &&
   738				last.Text[1] == '*' && p.lineFor(last.Pos()) == next.Line &&
   739				tok != token.COMMA &&
   740				(tok != token.RPAREN || p.prevOpen == token.LPAREN) &&
   741				(tok != token.RBRACK || p.prevOpen == token.LBRACK) {
   742				p.writeByte(' ', 1)
   743			}
   744			// ensure that there is a line break after a //-style comment,
   745			// before a closing '}' unless explicitly disabled, or at eof
   746			needsLinebreak :=
   747				last.Text[1] == '/' ||
   748					tok == token.RBRACE && p.mode&noExtraLinebreak == 0 ||
   749					tok == token.EOF
   750			return p.writeCommentSuffix(needsLinebreak)
   751		}
   752	
   753		// no comment was written - we should never reach here since
   754		// intersperseComments should not be called in that case
   755		p.internalError("intersperseComments called without pending comments")
   756		return
   757	}
   758	
   759	// whiteWhitespace writes the first n whitespace entries.
   760	func (p *printer) writeWhitespace(n int) {
   761		// write entries
   762		for i := 0; i < n; i++ {
   763			switch ch := p.wsbuf[i]; ch {
   764			case ignore:
   765				// ignore!
   766			case indent:
   767				p.indent++
   768			case unindent:
   769				p.indent--
   770				if p.indent < 0 {
   771					p.internalError("negative indentation:", p.indent)
   772					p.indent = 0
   773				}
   774			case newline, formfeed:
   775				// A line break immediately followed by a "correcting"
   776				// unindent is swapped with the unindent - this permits
   777				// proper label positioning. If a comment is between
   778				// the line break and the label, the unindent is not
   779				// part of the comment whitespace prefix and the comment
   780				// will be positioned correctly indented.
   781				if i+1 < n && p.wsbuf[i+1] == unindent {
   782					// Use a formfeed to terminate the current section.
   783					// Otherwise, a long label name on the next line leading
   784					// to a wide column may increase the indentation column
   785					// of lines before the label; effectively leading to wrong
   786					// indentation.
   787					p.wsbuf[i], p.wsbuf[i+1] = unindent, formfeed
   788					i-- // do it again
   789					continue
   790				}
   791				fallthrough
   792			default:
   793				p.writeByte(byte(ch), 1)
   794			}
   795		}
   796	
   797		// shift remaining entries down
   798		l := copy(p.wsbuf, p.wsbuf[n:])
   799		p.wsbuf = p.wsbuf[:l]
   800	}
   801	
   802	// ----------------------------------------------------------------------------
   803	// Printing interface
   804	
   805	// nlines limits n to maxNewlines.
   806	func nlimit(n int) int {
   807		if n > maxNewlines {
   808			n = maxNewlines
   809		}
   810		return n
   811	}
   812	
   813	func mayCombine(prev token.Token, next byte) (b bool) {
   814		switch prev {
   815		case token.INT:
   816			b = next == '.' // 1.
   817		case token.ADD:
   818			b = next == '+' // ++
   819		case token.SUB:
   820			b = next == '-' // --
   821		case token.QUO:
   822			b = next == '*' // /*
   823		case token.LSS:
   824			b = next == '-' || next == '<' // <- or <<
   825		case token.AND:
   826			b = next == '&' || next == '^' // && or &^
   827		}
   828		return
   829	}
   830	
   831	// print prints a list of "items" (roughly corresponding to syntactic
   832	// tokens, but also including whitespace and formatting information).
   833	// It is the only print function that should be called directly from
   834	// any of the AST printing functions in nodes.go.
   835	//
   836	// Whitespace is accumulated until a non-whitespace token appears. Any
   837	// comments that need to appear before that token are printed first,
   838	// taking into account the amount and structure of any pending white-
   839	// space for best comment placement. Then, any leftover whitespace is
   840	// printed, followed by the actual token.
   841	//
   842	func (p *printer) print(args ...interface{}) {
   843		for _, arg := range args {
   844			// information about the current arg
   845			var data string
   846			var isLit bool
   847			var impliedSemi bool // value for p.impliedSemi after this arg
   848	
   849			// record previous opening token, if any
   850			switch p.lastTok {
   851			case token.ILLEGAL:
   852				// ignore (white space)
   853			case token.LPAREN, token.LBRACK:
   854				p.prevOpen = p.lastTok
   855			default:
   856				// other tokens followed any opening token
   857				p.prevOpen = token.ILLEGAL
   858			}
   859	
   860			switch x := arg.(type) {
   861			case pmode:
   862				// toggle printer mode
   863				p.mode ^= x
   864				continue
   865	
   866			case whiteSpace:
   867				if x == ignore {
   868					// don't add ignore's to the buffer; they
   869					// may screw up "correcting" unindents (see
   870					// LabeledStmt)
   871					continue
   872				}
   873				i := len(p.wsbuf)
   874				if i == cap(p.wsbuf) {
   875					// Whitespace sequences are very short so this should
   876					// never happen. Handle gracefully (but possibly with
   877					// bad comment placement) if it does happen.
   878					p.writeWhitespace(i)
   879					i = 0
   880				}
   881				p.wsbuf = p.wsbuf[0 : i+1]
   882				p.wsbuf[i] = x
   883				if x == newline || x == formfeed {
   884					// newlines affect the current state (p.impliedSemi)
   885					// and not the state after printing arg (impliedSemi)
   886					// because comments can be interspersed before the arg
   887					// in this case
   888					p.impliedSemi = false
   889				}
   890				p.lastTok = token.ILLEGAL
   891				continue
   892	
   893			case *ast.Ident:
   894				data = x.Name
   895				impliedSemi = true
   896				p.lastTok = token.IDENT
   897	
   898			case *ast.BasicLit:
   899				data = x.Value
   900				isLit = true
   901				impliedSemi = true
   902				p.lastTok = x.Kind
   903	
   904			case token.Token:
   905				s := x.String()
   906				if mayCombine(p.lastTok, s[0]) {
   907					// the previous and the current token must be
   908					// separated by a blank otherwise they combine
   909					// into a different incorrect token sequence
   910					// (except for token.INT followed by a '.' this
   911					// should never happen because it is taken care
   912					// of via binary expression formatting)
   913					if len(p.wsbuf) != 0 {
   914						p.internalError("whitespace buffer not empty")
   915					}
   916					p.wsbuf = p.wsbuf[0:1]
   917					p.wsbuf[0] = ' '
   918				}
   919				data = s
   920				// some keywords followed by a newline imply a semicolon
   921				switch x {
   922				case token.BREAK, token.CONTINUE, token.FALLTHROUGH, token.RETURN,
   923					token.INC, token.DEC, token.RPAREN, token.RBRACK, token.RBRACE:
   924					impliedSemi = true
   925				}
   926				p.lastTok = x
   927	
   928			case token.Pos:
   929				if x.IsValid() {
   930					p.pos = p.posFor(x) // accurate position of next item
   931				}
   932				continue
   933	
   934			case string:
   935				// incorrect AST - print error message
   936				data = x
   937				isLit = true
   938				impliedSemi = true
   939				p.lastTok = token.STRING
   940	
   941			default:
   942				fmt.Fprintf(os.Stderr, "print: unsupported argument %v (%T)\n", arg, arg)
   943				panic("go/printer type")
   944			}
   945			// data != ""
   946	
   947			next := p.pos // estimated/accurate position of next item
   948			wroteNewline, droppedFF := p.flush(next, p.lastTok)
   949	
   950			// intersperse extra newlines if present in the source and
   951			// if they don't cause extra semicolons (don't do this in
   952			// flush as it will cause extra newlines at the end of a file)
   953			if !p.impliedSemi {
   954				n := nlimit(next.Line - p.pos.Line)
   955				// don't exceed maxNewlines if we already wrote one
   956				if wroteNewline && n == maxNewlines {
   957					n = maxNewlines - 1
   958				}
   959				if n > 0 {
   960					ch := byte('\n')
   961					if droppedFF {
   962						ch = '\f' // use formfeed since we dropped one before
   963					}
   964					p.writeByte(ch, n)
   965					impliedSemi = false
   966				}
   967			}
   968	
   969			// the next token starts now - record its line number if requested
   970			if p.linePtr != nil {
   971				*p.linePtr = p.out.Line
   972				p.linePtr = nil
   973			}
   974	
   975			p.writeString(next, data, isLit)
   976			p.impliedSemi = impliedSemi
   977		}
   978	}
   979	
   980	// flush prints any pending comments and whitespace occurring textually
   981	// before the position of the next token tok. The flush result indicates
   982	// if a newline was written or if a formfeed was dropped from the whitespace
   983	// buffer.
   984	//
   985	func (p *printer) flush(next token.Position, tok token.Token) (wroteNewline, droppedFF bool) {
   986		if p.commentBefore(next) {
   987			// if there are comments before the next item, intersperse them
   988			wroteNewline, droppedFF = p.intersperseComments(next, tok)
   989		} else {
   990			// otherwise, write any leftover whitespace
   991			p.writeWhitespace(len(p.wsbuf))
   992		}
   993		return
   994	}
   995	
   996	// getNode returns the ast.CommentGroup associated with n, if any.
   997	func getDoc(n ast.Node) *ast.CommentGroup {
   998		switch n := n.(type) {
   999		case *ast.Field:
  1000			return n.Doc
  1001		case *ast.ImportSpec:
  1002			return n.Doc
  1003		case *ast.ValueSpec:
  1004			return n.Doc
  1005		case *ast.TypeSpec:
  1006			return n.Doc
  1007		case *ast.GenDecl:
  1008			return n.Doc
  1009		case *ast.FuncDecl:
  1010			return n.Doc
  1011		case *ast.File:
  1012			return n.Doc
  1013		}
  1014		return nil
  1015	}
  1016	
  1017	func (p *printer) printNode(node interface{}) error {
  1018		// unpack *CommentedNode, if any
  1019		var comments []*ast.CommentGroup
  1020		if cnode, ok := node.(*CommentedNode); ok {
  1021			node = cnode.Node
  1022			comments = cnode.Comments
  1023		}
  1024	
  1025		if comments != nil {
  1026			// commented node - restrict comment list to relevant range
  1027			n, ok := node.(ast.Node)
  1028			if !ok {
  1029				goto unsupported
  1030			}
  1031			beg := n.Pos()
  1032			end := n.End()
  1033			// if the node has associated documentation,
  1034			// include that commentgroup in the range
  1035			// (the comment list is sorted in the order
  1036			// of the comment appearance in the source code)
  1037			if doc := getDoc(n); doc != nil {
  1038				beg = doc.Pos()
  1039			}
  1040			// token.Pos values are global offsets, we can
  1041			// compare them directly
  1042			i := 0
  1043			for i < len(comments) && comments[i].End() < beg {
  1044				i++
  1045			}
  1046			j := i
  1047			for j < len(comments) && comments[j].Pos() < end {
  1048				j++
  1049			}
  1050			if i < j {
  1051				p.comments = comments[i:j]
  1052			}
  1053		} else if n, ok := node.(*ast.File); ok {
  1054			// use ast.File comments, if any
  1055			p.comments = n.Comments
  1056		}
  1057	
  1058		// if there are no comments, use node comments
  1059		p.useNodeComments = p.comments == nil
  1060	
  1061		// get comments ready for use
  1062		p.nextComment()
  1063	
  1064		// format node
  1065		switch n := node.(type) {
  1066		case ast.Expr:
  1067			p.expr(n)
  1068		case ast.Stmt:
  1069			// A labeled statement will un-indent to position the label.
  1070			// Set p.indent to 1 so we don't get indent "underflow".
  1071			if _, ok := n.(*ast.LabeledStmt); ok {
  1072				p.indent = 1
  1073			}
  1074			p.stmt(n, false)
  1075		case ast.Decl:
  1076			p.decl(n)
  1077		case ast.Spec:
  1078			p.spec(n, 1, false)
  1079		case []ast.Stmt:
  1080			// A labeled statement will un-indent to position the label.
  1081			// Set p.indent to 1 so we don't get indent "underflow".
  1082			for _, s := range n {
  1083				if _, ok := s.(*ast.LabeledStmt); ok {
  1084					p.indent = 1
  1085				}
  1086			}
  1087			p.stmtList(n, 0, false)
  1088		case []ast.Decl:
  1089			p.declList(n)
  1090		case *ast.File:
  1091			p.file(n)
  1092		default:
  1093			goto unsupported
  1094		}
  1095	
  1096		return nil
  1097	
  1098	unsupported:
  1099		return fmt.Errorf("go/printer: unsupported node type %T", node)
  1100	}
  1101	
  1102	// ----------------------------------------------------------------------------
  1103	// Trimmer
  1104	
  1105	// A trimmer is an io.Writer filter for stripping tabwriter.Escape
  1106	// characters, trailing blanks and tabs, and for converting formfeed
  1107	// and vtab characters into newlines and htabs (in case no tabwriter
  1108	// is used). Text bracketed by tabwriter.Escape characters is passed
  1109	// through unchanged.
  1110	//
  1111	type trimmer struct {
  1112		output io.Writer
  1113		state  int
  1114		space  []byte
  1115	}
  1116	
  1117	// trimmer is implemented as a state machine.
  1118	// It can be in one of the following states:
  1119	const (
  1120		inSpace  = iota // inside space
  1121		inEscape        // inside text bracketed by tabwriter.Escapes
  1122		inText          // inside text
  1123	)
  1124	
  1125	func (p *trimmer) resetSpace() {
  1126		p.state = inSpace
  1127		p.space = p.space[0:0]
  1128	}
  1129	
  1130	// Design note: It is tempting to eliminate extra blanks occurring in
  1131	//              whitespace in this function as it could simplify some
  1132	//              of the blanks logic in the node printing functions.
  1133	//              However, this would mess up any formatting done by
  1134	//              the tabwriter.
  1135	
  1136	var aNewline = []byte("\n")
  1137	
  1138	func (p *trimmer) Write(data []byte) (n int, err error) {
  1139		// invariants:
  1140		// p.state == inSpace:
  1141		//	p.space is unwritten
  1142		// p.state == inEscape, inText:
  1143		//	data[m:n] is unwritten
  1144		m := 0
  1145		var b byte
  1146		for n, b = range data {
  1147			if b == '\v' {
  1148				b = '\t' // convert to htab
  1149			}
  1150			switch p.state {
  1151			case inSpace:
  1152				switch b {
  1153				case '\t', ' ':
  1154					p.space = append(p.space, b)
  1155				case '\n', '\f':
  1156					p.resetSpace() // discard trailing space
  1157					_, err = p.output.Write(aNewline)
  1158				case tabwriter.Escape:
  1159					_, err = p.output.Write(p.space)
  1160					p.state = inEscape
  1161					m = n + 1 // +1: skip tabwriter.Escape
  1162				default:
  1163					_, err = p.output.Write(p.space)
  1164					p.state = inText
  1165					m = n
  1166				}
  1167			case inEscape:
  1168				if b == tabwriter.Escape {
  1169					_, err = p.output.Write(data[m:n])
  1170					p.resetSpace()
  1171				}
  1172			case inText:
  1173				switch b {
  1174				case '\t', ' ':
  1175					_, err = p.output.Write(data[m:n])
  1176					p.resetSpace()
  1177					p.space = append(p.space, b)
  1178				case '\n', '\f':
  1179					_, err = p.output.Write(data[m:n])
  1180					p.resetSpace()
  1181					_, err = p.output.Write(aNewline)
  1182				case tabwriter.Escape:
  1183					_, err = p.output.Write(data[m:n])
  1184					p.state = inEscape
  1185					m = n + 1 // +1: skip tabwriter.Escape
  1186				}
  1187			default:
  1188				panic("unreachable")
  1189			}
  1190			if err != nil {
  1191				return
  1192			}
  1193		}
  1194		n = len(data)
  1195	
  1196		switch p.state {
  1197		case inEscape, inText:
  1198			_, err = p.output.Write(data[m:n])
  1199			p.resetSpace()
  1200		}
  1201	
  1202		return
  1203	}
  1204	
  1205	// ----------------------------------------------------------------------------
  1206	// Public interface
  1207	
  1208	// A Mode value is a set of flags (or 0). They control printing.
  1209	type Mode uint
  1210	
  1211	const (
  1212		RawFormat Mode = 1 << iota // do not use a tabwriter; if set, UseSpaces is ignored
  1213		TabIndent                  // use tabs for indentation independent of UseSpaces
  1214		UseSpaces                  // use spaces instead of tabs for alignment
  1215		SourcePos                  // emit //line comments to preserve original source positions
  1216	)
  1217	
  1218	// A Config node controls the output of Fprint.
  1219	type Config struct {
  1220		Mode     Mode // default: 0
  1221		Tabwidth int  // default: 8
  1222		Indent   int  // default: 0 (all code is indented at least by this much)
  1223	}
  1224	
  1225	// fprint implements Fprint and takes a nodesSizes map for setting up the printer state.
  1226	func (cfg *Config) fprint(output io.Writer, fset *token.FileSet, node interface{}, nodeSizes map[ast.Node]int) (err error) {
  1227		// print node
  1228		var p printer
  1229		p.init(cfg, fset, nodeSizes)
  1230		if err = p.printNode(node); err != nil {
  1231			return
  1232		}
  1233		// print outstanding comments
  1234		p.impliedSemi = false // EOF acts like a newline
  1235		p.flush(token.Position{Offset: infinity, Line: infinity}, token.EOF)
  1236	
  1237		// redirect output through a trimmer to eliminate trailing whitespace
  1238		// (Input to a tabwriter must be untrimmed since trailing tabs provide
  1239		// formatting information. The tabwriter could provide trimming
  1240		// functionality but no tabwriter is used when RawFormat is set.)
  1241		output = &trimmer{output: output}
  1242	
  1243		// redirect output through a tabwriter if necessary
  1244		if cfg.Mode&RawFormat == 0 {
  1245			minwidth := cfg.Tabwidth
  1246	
  1247			padchar := byte('\t')
  1248			if cfg.Mode&UseSpaces != 0 {
  1249				padchar = ' '
  1250			}
  1251	
  1252			twmode := tabwriter.DiscardEmptyColumns
  1253			if cfg.Mode&TabIndent != 0 {
  1254				minwidth = 0
  1255				twmode |= tabwriter.TabIndent
  1256			}
  1257	
  1258			output = tabwriter.NewWriter(output, minwidth, cfg.Tabwidth, 1, padchar, twmode)
  1259		}
  1260	
  1261		// write printer result via tabwriter/trimmer to output
  1262		if _, err = output.Write(p.output); err != nil {
  1263			return
  1264		}
  1265	
  1266		// flush tabwriter, if any
  1267		if tw, _ := output.(*tabwriter.Writer); tw != nil {
  1268			err = tw.Flush()
  1269		}
  1270	
  1271		return
  1272	}
  1273	
  1274	// A CommentedNode bundles an AST node and corresponding comments.
  1275	// It may be provided as argument to any of the Fprint functions.
  1276	//
  1277	type CommentedNode struct {
  1278		Node     interface{} // *ast.File, or ast.Expr, ast.Decl, ast.Spec, or ast.Stmt
  1279		Comments []*ast.CommentGroup
  1280	}
  1281	
  1282	// Fprint "pretty-prints" an AST node to output for a given configuration cfg.
  1283	// Position information is interpreted relative to the file set fset.
  1284	// The node type must be *ast.File, *CommentedNode, []ast.Decl, []ast.Stmt,
  1285	// or assignment-compatible to ast.Expr, ast.Decl, ast.Spec, or ast.Stmt.
  1286	//
  1287	func (cfg *Config) Fprint(output io.Writer, fset *token.FileSet, node interface{}) error {
  1288		return cfg.fprint(output, fset, node, make(map[ast.Node]int))
  1289	}
  1290	
  1291	// Fprint "pretty-prints" an AST node to output.
  1292	// It calls Config.Fprint with default settings.
  1293	//
  1294	func Fprint(output io.Writer, fset *token.FileSet, node interface{}) error {
  1295		return (&Config{Tabwidth: 8}).Fprint(output, fset, node)
  1296	}
  1297	

View as plain text