Copyright 2009 The Go Authors. All rights reserved. Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
Package printer implements printing of AST nodes.
package printer

import (
	
	
	
	
	
	
	
	
)

const (
	maxNewlines = 2     // max. number of newlines between source text
	debug       = false // enable for debugging
	infinity    = 1 << 30
)

type whiteSpace byte

const (
	ignore   = whiteSpace(0)
	blank    = whiteSpace(' ')
	vtab     = whiteSpace('\v')
	newline  = whiteSpace('\n')
	formfeed = whiteSpace('\f')
	indent   = whiteSpace('>')
	unindent = whiteSpace('<')
)
A pmode value represents the current printer mode.
type pmode int

const (
	noExtraBlank     pmode = 1 << iota // disables extra blank after /*-style comment
	noExtraLinebreak                   // disables extra line break after /*-style comment
)

type commentInfo struct {
	cindex         int               // current comment index
	comment        *ast.CommentGroup // = printer.comments[cindex]; or nil
	commentOffset  int               // = printer.posFor(printer.comments[cindex].List[0].Pos()).Offset; or infinity
	commentNewline bool              // true if the comment group contains newlines
}

Configuration (does not change after initialization)
Current state
	output       []byte       // raw printer result
	indent       int          // current indentation
	level        int          // level == 0: outside composite literal; level > 0: inside composite literal
	mode         pmode        // current printer mode
	endAlignment bool         // if set, terminate alignment immediately
	impliedSemi  bool         // if set, a linebreak implies a semicolon
	lastTok      token.Token  // last token printed (token.ILLEGAL if it's whitespace)
	prevOpen     token.Token  // previous non-brace "open" token (, [, or token.ILLEGAL
	wsbuf        []whiteSpace // delayed white space
Positions The out position differs from the pos position when the result formatting differs from the source formatting (in the amount of white space). If there's a difference and SourcePos is set in ConfigMode, //line directives are used in the output to restore original source positions for a reader.
	pos     token.Position // current position in AST (source) space
	out     token.Position // current position in output space
	last    token.Position // value of pos after calling writeString
	linePtr *int           // if set, record out.Line for the next token in *linePtr
The list of all source comments, in order of appearance.
	comments        []*ast.CommentGroup // may be nil
	useNodeComments bool                // if not set, ignore lead and line comments of nodes
Information about p.comments[p.cindex]; set up by nextComment.
Cache of already computed node sizes.
Cache of most recently computed line position.
	cachedPos  token.Pos
	cachedLine int // line corresponding to cachedPos
}

func ( *printer) ( *Config,  *token.FileSet,  map[ast.Node]int) {
	.Config = *
	.fset = 
	.pos = token.Position{Line: 1, Column: 1}
	.out = token.Position{Line: 1, Column: 1}
	.wsbuf = make([]whiteSpace, 0, 16) // whitespace sequences are short
	.nodeSizes = 
	.cachedPos = -1
}

func ( *printer) ( ...interface{}) {
	if debug {
		fmt.Print(.pos.String() + ": ")
		fmt.Println(...)
		panic("go/printer")
	}
}
commentsHaveNewline reports whether a list of comments belonging to an *ast.CommentGroup contains newlines. Because the position information may only be partially correct, we also have to read the comment text.
len(list) > 0
	 := .lineFor([0].Pos())
	for ,  := range  {
not all comments on the same line
			return true
		}
		if  := .Text; len() >= 2 && ([1] == '/' || strings.Contains(, "\n")) {
			return true
		}
	}
	_ = 
	return false
}

func ( *printer) () {
	for .cindex < len(.comments) {
		 := .comments[.cindex]
		.cindex++
		if  := .List; len() > 0 {
			.comment = 
			.commentOffset = .posFor([0].Pos()).Offset
			.commentNewline = .commentsHaveNewline()
			return
we should not reach here (correct ASTs don't have empty ast.CommentGroup nodes), but be conservative and try again
no more comments
commentBefore reports whether the current comment group occurs before the next position in the source code and printing it does not introduce implicit semicolons.
func ( *printer) ( token.Position) bool {
	return .commentOffset < .Offset && (!.impliedSemi || !.commentNewline)
}
commentSizeBefore returns the estimated size of the comments on the same line before the next position.
save/restore current p.commentInfo (p.nextComment() modifies it)
	defer func( commentInfo) {
		.commentInfo = 
	}(.commentInfo)

	 := 0
	for .commentBefore() {
		for ,  := range .comment.List {
			 += len(.Text)
		}
		.nextComment()
	}
	return 
}
recordLine records the output line number for the next non-whitespace token in *linePtr. It is used to compute an accurate line number for a formatted construct, independent of pending (not yet emitted) whitespace or comments.
func ( *printer) ( *int) {
	.linePtr = 
}
linesFrom returns the number of output lines between the current output line and the line argument, ignoring any pending (not yet emitted) whitespace or comments. It is used to compute an accurate size (in number of lines) for a formatted construct.
func ( *printer) ( int) int {
	return .out.Line - 
}

not used frequently enough to cache entire token.Position
	return .fset.PositionFor(, false /* absolute position */)
}

func ( *printer) ( token.Pos) int {
	if  != .cachedPos {
		.cachedPos = 
		.cachedLine = .fset.PositionFor(, false /* absolute position */).Line
	}
	return .cachedLine
}
writeLineDirective writes a //line directive if necessary.
func ( *printer) ( token.Position) {
	if .IsValid() && (.out.Line != .Line || .out.Filename != .Filename) {
		.output = append(.output, tabwriter.Escape) // protect '\n' in //line from tabwriter interpretation
		.output = append(.output, fmt.Sprintf("//line %s:%d\n", .Filename, .Line)...)
p.out must match the //line directive
		.out.Filename = .Filename
		.out.Line = .Line
	}
}
writeIndent writes indentation.
use "hard" htabs - indentation columns must not be discarded by the tabwriter
	 := .Config.Indent + .indent // include base indentation
	for  := 0;  < ; ++ {
		.output = append(.output, '\t')
	}
update positions
	.pos.Offset += 
	.pos.Column += 
	.out.Column += 
}
writeByte writes ch n times to p.output and updates p.pos. Only used to write formatting (white space) characters.
func ( *printer) ( byte,  int) {
Ignore any alignment control character; and at the end of the line, break with a formfeed to indicate termination of existing columns.
		switch  {
		case '\t', '\v':
			 = ' '
		case '\n', '\f':
			 = '\f'
			.endAlignment = false
		}
	}

no need to write line directives before white space
		.writeIndent()
	}

	for  := 0;  < ; ++ {
		.output = append(.output, )
	}
update positions
	.pos.Offset += 
	if  == '\n' ||  == '\f' {
		.pos.Line += 
		.out.Line += 
		.pos.Column = 1
		.out.Column = 1
		return
	}
	.pos.Column += 
	.out.Column += 
}
writeString writes the string s to p.output and updates p.pos, p.out, and p.last. If isLit is set, s is escaped w/ tabwriter.Escape characters to protect s from being interpreted by the tabwriter. Note: writeString is only used to write Go tokens, literals, and comments, all of which must be written literally. Thus, it is correct to always set isLit = true. However, setting it explicitly only when needed (i.e., when we don't know that s contains no tabs or line breaks) avoids processing extra escape characters and reduces run time of the printer benchmark by up to 10%.
func ( *printer) ( token.Position,  string,  bool) {
	if .out.Column == 1 {
		if .Config.Mode&SourcePos != 0 {
			.writeLineDirective()
		}
		.writeIndent()
	}

update p.pos (if pos is invalid, continue with existing p.pos) Note: Must do this after handling line beginnings because writeIndent updates p.pos if there's indentation, but p.pos is the position of s.
		.pos = 
	}

Protect s such that is passes through the tabwriter unchanged. Note that valid Go programs cannot contain tabwriter.Escape bytes since they do not appear in legal UTF-8 sequences.
		.output = append(.output, tabwriter.Escape)
	}

	if debug {
		.output = append(.output, fmt.Sprintf("/*%s*/", )...) // do not update p.pos!
	}
	.output = append(.output, ...)
update positions
	 := 0
	var  int // index of last newline; valid if nlines > 0
Raw string literals may contain any character except back quote (`).
account for line break
			++
A line break inside a literal will break whatever column formatting is in place; ignore any further alignment through the end of the line.
			.endAlignment = true
		}
	}
	.pos.Offset += len()
	if  > 0 {
		.pos.Line += 
		.out.Line += 
		 := len() - 
		.pos.Column = 
		.out.Column = 
	} else {
		.pos.Column += len()
		.out.Column += len()
	}

	if  {
		.output = append(.output, tabwriter.Escape)
	}

	.last = .pos
}
writeCommentPrefix writes the whitespace before a comment. If there is any pending whitespace, it consumes as much of it as is likely to help position the comment nicely. pos is the comment position, next the position of the item after all pending comments, prev is the previous comment in a group of comments (or nil), and tok is the next token.
func ( *printer) (,  token.Position,  *ast.Comment,  token.Token) {
the comment is the first item to be printed - don't write any whitespace
		return
	}

comment in a different file - separate with newlines
		.writeByte('\f', maxNewlines)
		return
	}

comment on the same line as last item: separate with at least one separator
		 := false
first comment of a comment group
			 := 0
			for ,  := range .wsbuf {
				switch  {
ignore any blanks before a comment
					.wsbuf[] = ignore
					continue
respect existing tabs - important for proper formatting of commented structs
					 = true
					continue
apply pending indentation
					continue
				}
				 = 
				break
			}
			.writeWhitespace()
make sure there is at least one separator
		if ! {
			 := byte('\t')
next item is on the same line as the comment (which must be a -style comment): separate with a blank instead of a tab
				 = ' '
			}
			.writeByte(, 1)
		}

comment on a different line: separate with at least one line break
		 := false
		 := 0
		for ,  := range .wsbuf {
			switch  {
ignore any horizontal whitespace before line breaks
				.wsbuf[] = ignore
				continue
apply pending indentation
				continue
if this is not the last unindent, apply it as it is (likely) belonging to the last construct (e.g., a multi-line expression list) and is not part of closing a block
				if +1 < len(.wsbuf) && .wsbuf[+1] == unindent {
					continue
if the next token is not a closing }, apply the unindent if it appears that the comment is aligned with the token; otherwise assume the unindent is part of a closing block and stop (this scenario appears with comments before a case label where the comments apply to the next case instead of the current one)
				if  != token.RBRACE && .Column == .Column {
					continue
				}
			case newline, formfeed:
				.wsbuf[] = ignore
				 =  == nil // record only if first comment of a group
			}
			 = 
			break
		}
		.writeWhitespace()
determine number of linebreaks before the comment
		 := 0
		if .IsValid() && .last.IsValid() {
			 = .Line - .last.Line
			if  < 0 { // should never happen
				 = 0
			}
		}
at the package scope level only (p.indent == 0), add an extra newline if we dropped one before: this preserves a blank line before documentation comments at the package scope level (issue 2570)
		if .indent == 0 &&  {
			++
		}
make sure there is at least one line break if the previous comment was a line comment
		if  == 0 &&  != nil && .Text[1] == '/' {
			 = 1
		}

use formfeeds to break columns before a comment; this is analogous to using formfeeds to separate individual lines of -style comments
			.writeByte('\f', nlimit())
		}
	}
}
Returns true if s contains only white space (only tabs and blanks can appear in the printer's context).
func ( string) bool {
	for  := 0;  < len(); ++ {
		if [] > ' ' {
			return false
		}
	}
	return true
}
commonPrefix returns the common prefix of a and b.
func (,  string) string {
	 := 0
	for  < len() &&  < len() && [] == [] && ([] <= ' ' || [] == '*') {
		++
	}
	return [0:]
}
trimRight returns s with trailing whitespace removed.
stripCommonPrefix removes a common prefix from -style comment lines (unless no comment line is indented, all but the first line have some form of space prefix). The prefix is computed using heuristics such that is likely that the comment contents are nicely laid out after re-printing each line using the printer's current indentation.
func ( []string) {
	if len() <= 1 {
		return // at most one line - nothing to do
len(lines) > 1
The heuristic in this function tries to handle a few common patterns of -style comments: Comments where the opening and closing are aligned and the rest of the comment text is aligned and indented with blanks or tabs, cases with a vertical "line of stars" on the left, and cases where the closing is on the same line as the last comment text.
Compute maximum common white prefix of all but the first, last, and blank lines, and replace blank lines with empty lines (the first line starts with and has no prefix). In cases where only the first and last lines are not blank, such as two-line comments, or comments where all inner lines are blank, consider the last line for the prefix computation since otherwise the prefix would be empty. Note that the first and last line are never empty (they contain the opening and closing respectively) and thus they can be ignored by the blank line check.
	 := ""
	 := false
	if len() > 2 {
		for ,  := range [1 : len()-1] {
			if isBlank() {
				[1+] = "" // range starts with lines[1]
			} else {
				if ! {
					 = 
					 = true
				}
				 = commonPrefix(, )
			}

		}
If we don't have a prefix yet, consider the last line.
	if ! {
		 := [len()-1]
		 = commonPrefix(, )
	}
* Check for vertical "line of stars" and correct prefix accordingly.
	 := false
Line of stars present.
		if  > 0 && [-1] == ' ' {
			-- // remove trailing blank from prefix so stars remain aligned
		}
		 = [0:]
		 = true
No line of stars present. Determine the white space on the first line after the and before the beginning of the comment text, assume two blanks instead of the unless the first character after the is a tab. If the first comment line is empty but for the opening , assume up to 3 blanks or a tab. This whitespace may be found as suffix in the common prefix.
		 := [0]
no comment text on the first line: reduce prefix by up to 3 blanks or a tab if present - this keeps comment text indented relative to the and 's if it was indented in the first place
			 := len()
			for  := 0;  < 3 &&  > 0 && [-1] == ' '; ++ {
				--
			}
			if  == len() &&  > 0 && [-1] == '\t' {
				--
			}
			 = [0:]
comment text on the first line
			 := make([]byte, len())
			 := 2 // start after opening /*
			for  < len() && [] <= ' ' {
				[] = []
				++
			}
assume the '\t' compensates for the
				 = [2:]
otherwise assume two blanks
				[0], [1] = ' ', ' '
				 = [0:]
Shorten the computed common prefix by the length of suffix, if it is found as suffix of the prefix.
			 = strings.TrimSuffix(, string())
		}
	}
Handle last line: If it only contains a closing , align it with the opening , otherwise align the text with the other lines.
	 := [len()-1]
	 := "*/"
	 := strings.Index(, ) // i >= 0 (closing is always present)
last line only contains closing
		if  {
			 = " */" // add blank to align final star
		}
		[len()-1] =  + 
last line contains more comment text - assume it is aligned like the other lines and include in prefix computation
		 = commonPrefix(, )
	}
Remove the common prefix from all but the first and empty lines.
	for ,  := range  {
		if  > 0 &&  != "" {
			[] = [len():]
		}
	}
}

func ( *printer) ( *ast.Comment) {
	 := .Text
	 := .posFor(.Pos())

	const  = "//line "
Possibly a //-style line directive. Suspend indentation temporarily to keep line directive valid.
		defer func( int) { .indent =  }(.indent)
		.indent = 0
	}
shortcut common case of //-style comments
	if [1] == '/' {
		.writeString(, trimRight(), true)
		return
	}
for -style comments, print line by line and let the write function take care of the proper indentation
	 := strings.Split(, "\n")
The comment started in the first column but is going to be indented. For an idempotent result, add indentation to all lines such that they look like they were indented before - this will make sure the common prefix computation is the same independent of how many times formatting is applied (was issue 1835).
	if .IsValid() && .Column == 1 && .indent > 0 {
		for ,  := range [1:] {
			[1+] = "   " + 
		}
	}

	stripCommonPrefix()
write comment lines, separated by formfeed, without a line break after the last line
	for ,  := range  {
		if  > 0 {
			.writeByte('\f', 1)
			 = .pos
		}
		if len() > 0 {
			.writeString(, trimRight(), true)
		}
	}
}
writeCommentSuffix writes a line break after a comment if indicated and processes any leftover indentation information. If a line break is needed, the kind of break (newline vs formfeed) depends on the pending whitespace. The writeCommentSuffix result indicates if a newline was written or if a formfeed was dropped from the whitespace buffer.
func ( *printer) ( bool) (,  bool) {
	for ,  := range .wsbuf {
		switch  {
ignore trailing whitespace
			.wsbuf[] = ignore
don't lose indentation information
if we need a line break, keep exactly one but remember if we dropped any formfeeds
			if  {
				 = false
				 = true
			} else {
				if  == formfeed {
					 = true
				}
				.wsbuf[] = ignore
			}
		}
	}
	.writeWhitespace(len(.wsbuf))
make sure we have a line break
	if  {
		.writeByte('\n', 1)
		 = true
	}

	return
}
containsLinebreak reports whether the whitespace buffer contains any line breaks.
func ( *printer) () bool {
	for ,  := range .wsbuf {
		if  == newline ||  == formfeed {
			return true
		}
	}
	return false
}
intersperseComments consumes all comments that appear before the next token tok and prints it together with the buffered whitespace (i.e., the whitespace that needs to be written before the next token). A heuristic is used to mix the comments and whitespace. The intersperseComments result indicates if a newline was written or if a formfeed was dropped from the whitespace buffer.
func ( *printer) ( token.Position,  token.Token) (,  bool) {
	var  *ast.Comment
	for .commentBefore() {
		for ,  := range .comment.List {
			.writeCommentPrefix(.posFor(.Pos()), , , )
			.writeComment()
			 = 
		}
		.nextComment()
	}

If the last comment is a -style comment and the next item follows on the same line but is not a comma, and not a "closing" token immediately following its corresponding "opening" token, add an extra separator unless explicitly disabled. Use a blank as separator unless we have pending linebreaks, they are not disabled, and we are outside a composite literal, in which case we want a linebreak (issue 15137). TODO(gri) This has become overly complicated. We should be able to track whether we're inside an expression or statement and use that information to decide more directly.
		 := false
		if .mode&noExtraBlank == 0 &&
			.Text[1] == '*' && .lineFor(.Pos()) == .Line &&
			 != token.COMMA &&
			( != token.RPAREN || .prevOpen == token.LPAREN) &&
			( != token.RBRACK || .prevOpen == token.LBRACK) {
			if .containsLinebreak() && .mode&noExtraLinebreak == 0 && .level == 0 {
				 = true
			} else {
				.writeByte(' ', 1)
			}
Ensure that there is a line break after a //-style comment, before EOF, and before a closing '}' unless explicitly disabled.
		if .Text[1] == '/' ||
			 == token.EOF ||
			 == token.RBRACE && .mode&noExtraLinebreak == 0 {
			 = true
		}
		return .writeCommentSuffix()
	}
no comment was written - we should never reach here since intersperseComments should not be called in that case
	.internalError("intersperseComments called without pending comments")
	return
}
whiteWhitespace writes the first n whitespace entries.
write entries
	for  := 0;  < ; ++ {
		switch  := .wsbuf[];  {
ignore!
		case indent:
			.indent++
		case unindent:
			.indent--
			if .indent < 0 {
				.internalError("negative indentation:", .indent)
				.indent = 0
			}
A line break immediately followed by a "correcting" unindent is swapped with the unindent - this permits proper label positioning. If a comment is between the line break and the label, the unindent is not part of the comment whitespace prefix and the comment will be positioned correctly indented.
Use a formfeed to terminate the current section. Otherwise, a long label name on the next line leading to a wide column may increase the indentation column of lines before the label; effectively leading to wrong indentation.
				.wsbuf[], .wsbuf[+1] = unindent, formfeed
				-- // do it again
				continue
			}
			fallthrough
		default:
			.writeByte(byte(), 1)
		}
	}
shift remaining entries down
	 := copy(.wsbuf, .wsbuf[:])
	.wsbuf = .wsbuf[:]
}
---------------------------------------------------------------------------- Printing interface
nlines limits n to maxNewlines.
func ( int) int {
	if  > maxNewlines {
		 = maxNewlines
	}
	return 
}

func ( token.Token,  byte) ( bool) {
	switch  {
	case token.INT:
		 =  == '.' // 1.
	case token.ADD:
		 =  == '+' // ++
	case token.SUB:
		 =  == '-' // --
	case token.QUO:
		 =  == '*' // /*
	case token.LSS:
		 =  == '-' ||  == '<' // <- or <<
	case token.AND:
		 =  == '&' ||  == '^' // && or &^
	}
	return
}
print prints a list of "items" (roughly corresponding to syntactic tokens, but also including whitespace and formatting information). It is the only print function that should be called directly from any of the AST printing functions in nodes.go. Whitespace is accumulated until a non-whitespace token appears. Any comments that need to appear before that token are printed first, taking into account the amount and structure of any pending white- space for best comment placement. Then, any leftover whitespace is printed, followed by the actual token.
func ( *printer) ( ...interface{}) {
information about the current arg
		var  string
		var  bool
		var  bool // value for p.impliedSemi after this arg
record previous opening token, if any
		switch .lastTok {
ignore (white space)
other tokens followed any opening token
			.prevOpen = token.ILLEGAL
		}

		switch x := .(type) {
toggle printer mode
			.mode ^= 
			continue

		case whiteSpace:
don't add ignore's to the buffer; they may screw up "correcting" unindents (see LabeledStmt)
				continue
			}
			 := len(.wsbuf)
Whitespace sequences are very short so this should never happen. Handle gracefully (but possibly with bad comment placement) if it does happen.
				.writeWhitespace()
				 = 0
			}
			.wsbuf = .wsbuf[0 : +1]
			.wsbuf[] = 
newlines affect the current state (p.impliedSemi) and not the state after printing arg (impliedSemi) because comments can be interspersed before the arg in this case
				.impliedSemi = false
			}
			.lastTok = token.ILLEGAL
			continue

		case *ast.Ident:
			 = .Name
			 = true
			.lastTok = token.IDENT

		case *ast.BasicLit:
			 = .Value
			 = true
			 = true
			.lastTok = .Kind

		case token.Token:
			 := .String()
the previous and the current token must be separated by a blank otherwise they combine into a different incorrect token sequence (except for token.INT followed by a '.' this should never happen because it is taken care of via binary expression formatting)
				if len(.wsbuf) != 0 {
					.internalError("whitespace buffer not empty")
				}
				.wsbuf = .wsbuf[0:1]
				.wsbuf[0] = ' '
			}
some keywords followed by a newline imply a semicolon
			switch  {
			case token.BREAK, token.CONTINUE, token.FALLTHROUGH, token.RETURN,
				token.INC, token.DEC, token.RPAREN, token.RBRACK, token.RBRACE:
				 = true
			}
			.lastTok = 

		case token.Pos:
			if .IsValid() {
				.pos = .posFor() // accurate position of next item
			}
			continue

incorrect AST - print error message
			 = 
			 = true
			 = true
			.lastTok = token.STRING

		default:
			fmt.Fprintf(os.Stderr, "print: unsupported argument %v (%T)\n", , )
			panic("go/printer type")
data != ""

		 := .pos // estimated/accurate position of next item
		,  := .flush(, .lastTok)
intersperse extra newlines if present in the source and if they don't cause extra semicolons (don't do this in flush as it will cause extra newlines at the end of a file)
		if !.impliedSemi {
don't exceed maxNewlines if we already wrote one
			if  &&  == maxNewlines {
				 = maxNewlines - 1
			}
			if  > 0 {
				 := byte('\n')
				if  {
					 = '\f' // use formfeed since we dropped one before
				}
				.writeByte(, )
				 = false
			}
		}
the next token starts now - record its line number if requested
		if .linePtr != nil {
			*.linePtr = .out.Line
			.linePtr = nil
		}

		.writeString(, , )
		.impliedSemi = 
	}
}
flush prints any pending comments and whitespace occurring textually before the position of the next token tok. The flush result indicates if a newline was written or if a formfeed was dropped from the whitespace buffer.
func ( *printer) ( token.Position,  token.Token) (,  bool) {
if there are comments before the next item, intersperse them
		,  = .intersperseComments(, )
otherwise, write any leftover whitespace
		.writeWhitespace(len(.wsbuf))
	}
	return
}
getNode returns the ast.CommentGroup associated with n, if any.
func ( ast.Node) *ast.CommentGroup {
	switch n := .(type) {
	case *ast.Field:
		return .Doc
	case *ast.ImportSpec:
		return .Doc
	case *ast.ValueSpec:
		return .Doc
	case *ast.TypeSpec:
		return .Doc
	case *ast.GenDecl:
		return .Doc
	case *ast.FuncDecl:
		return .Doc
	case *ast.File:
		return .Doc
	}
	return nil
}

func ( ast.Node) *ast.CommentGroup {
	switch n := .(type) {
	case *ast.Field:
		return .Comment
	case *ast.ImportSpec:
		return .Comment
	case *ast.ValueSpec:
		return .Comment
	case *ast.TypeSpec:
		return .Comment
	case *ast.GenDecl:
		if len(.Specs) > 0 {
			return (.Specs[len(.Specs)-1])
		}
	case *ast.File:
		if len(.Comments) > 0 {
			return .Comments[len(.Comments)-1]
		}
	}
	return nil
}

unpack *CommentedNode, if any
	var  []*ast.CommentGroup
	if ,  := .(*CommentedNode);  {
		 = .Node
		 = .Comments
	}

commented node - restrict comment list to relevant range
		,  := .(ast.Node)
		if ! {
			goto 
		}
		 := .Pos()
if the node has associated documentation, include that commentgroup in the range (the comment list is sorted in the order of the comment appearance in the source code)
		if  := getDoc();  != nil {
			 = .Pos()
		}
		if  := getLastComment();  != nil {
			if  := .End();  >  {
				 = 
			}
token.Pos values are global offsets, we can compare them directly
		 := 0
		for  < len() && [].End() <  {
			++
		}
		 := 
		for  < len() && [].Pos() <  {
			++
		}
		if  <  {
			.comments = [:]
		}
use ast.File comments, if any
		.comments = .Comments
	}
if there are no comments, use node comments
get comments ready for use
format node
	switch n := .(type) {
	case ast.Expr:
		.expr()
A labeled statement will un-indent to position the label. Set p.indent to 1 so we don't get indent "underflow".
		if ,  := .(*ast.LabeledStmt);  {
			.indent = 1
		}
		.stmt(, false)
	case ast.Decl:
		.decl()
	case ast.Spec:
		.spec(, 1, false)
A labeled statement will un-indent to position the label. Set p.indent to 1 so we don't get indent "underflow".
		for ,  := range  {
			if ,  := .(*ast.LabeledStmt);  {
				.indent = 1
			}
		}
		.stmtList(, 0, false)
	case []ast.Decl:
		.declList()
	case *ast.File:
		.file()
	default:
		goto 
	}

	return nil

:
	return fmt.Errorf("go/printer: unsupported node type %T", )
}
---------------------------------------------------------------------------- Trimmer
A trimmer is an io.Writer filter for stripping tabwriter.Escape characters, trailing blanks and tabs, and for converting formfeed and vtab characters into newlines and htabs (in case no tabwriter is used). Text bracketed by tabwriter.Escape characters is passed through unchanged.
type trimmer struct {
	output io.Writer
	state  int
	space  []byte
}
trimmer is implemented as a state machine. It can be in one of the following states:
const (
	inSpace  = iota // inside space
	inEscape        // inside text bracketed by tabwriter.Escapes
	inText          // inside text
)

func ( *trimmer) () {
	.state = inSpace
	.space = .space[0:0]
}
Design note: It is tempting to eliminate extra blanks occurring in whitespace in this function as it could simplify some of the blanks logic in the node printing functions. However, this would mess up any formatting done by the tabwriter.

var aNewline = []byte("\n")

invariants: p.state == inSpace: p.space is unwritten p.state == inEscape, inText: data[m:n] is unwritten
	 := 0
	var  byte
	for ,  = range  {
		if  == '\v' {
			 = '\t' // convert to htab
		}
		switch .state {
		case inSpace:
			switch  {
			case '\t', ' ':
				.space = append(.space, )
			case '\n', '\f':
				.resetSpace() // discard trailing space
				_,  = .output.Write(aNewline)
			case tabwriter.Escape:
				_,  = .output.Write(.space)
				.state = inEscape
				 =  + 1 // +1: skip tabwriter.Escape
			default:
				_,  = .output.Write(.space)
				.state = inText
				 = 
			}
		case inEscape:
			if  == tabwriter.Escape {
				_,  = .output.Write([:])
				.resetSpace()
			}
		case inText:
			switch  {
			case '\t', ' ':
				_,  = .output.Write([:])
				.resetSpace()
				.space = append(.space, )
			case '\n', '\f':
				_,  = .output.Write([:])
				.resetSpace()
				if  == nil {
					_,  = .output.Write(aNewline)
				}
			case tabwriter.Escape:
				_,  = .output.Write([:])
				.state = inEscape
				 =  + 1 // +1: skip tabwriter.Escape
			}
		default:
			panic("unreachable")
		}
		if  != nil {
			return
		}
	}
	 = len()

	switch .state {
	case inEscape, inText:
		_,  = .output.Write([:])
		.resetSpace()
	}

	return
}
---------------------------------------------------------------------------- Public interface
A Mode value is a set of flags (or 0). They control printing.
type Mode uint

const (
	RawFormat Mode = 1 << iota // do not use a tabwriter; if set, UseSpaces is ignored
	TabIndent                  // use tabs for indentation independent of UseSpaces
	UseSpaces                  // use spaces instead of tabs for alignment
	SourcePos                  // emit //line directives to preserve original source positions
)
The mode below is not included in printer's public API because editing code text is deemed out of scope. Because this mode is unexported, it's also possible to modify or remove it based on the evolving needs of go/format and cmd/gofmt without breaking users. See discussion in CL 240683.
normalizeNumbers means to canonicalize number literal prefixes and exponents while printing. This value is known in and used by go/format and cmd/gofmt. It is currently more convenient and performant for those packages to apply number normalization during printing, rather than by modifying the AST in advance.
	normalizeNumbers Mode = 1 << 30
)
A Config node controls the output of Fprint.
type Config struct {
	Mode     Mode // default: 0
	Tabwidth int  // default: 8
	Indent   int  // default: 0 (all code is indented at least by this much)
}
fprint implements Fprint and takes a nodesSizes map for setting up the printer state.
print node
	var  printer
	.init(, , )
	if  = .printNode();  != nil {
		return
print outstanding comments
	.impliedSemi = false // EOF acts like a newline
	.flush(token.Position{Offset: infinity, Line: infinity}, token.EOF)
redirect output through a trimmer to eliminate trailing whitespace (Input to a tabwriter must be untrimmed since trailing tabs provide formatting information. The tabwriter could provide trimming functionality but no tabwriter is used when RawFormat is set.)
	 = &trimmer{output: }
redirect output through a tabwriter if necessary
	if .Mode&RawFormat == 0 {
		 := .Tabwidth

		 := byte('\t')
		if .Mode&UseSpaces != 0 {
			 = ' '
		}

		 := tabwriter.DiscardEmptyColumns
		if .Mode&TabIndent != 0 {
			 = 0
			 |= tabwriter.TabIndent
		}

		 = tabwriter.NewWriter(, , .Tabwidth, 1, , )
	}
write printer result via tabwriter/trimmer to output
	if _,  = .Write(.output);  != nil {
		return
	}
flush tabwriter, if any
	if ,  := .(*tabwriter.Writer);  != nil {
		 = .Flush()
	}

	return
}
A CommentedNode bundles an AST node and corresponding comments. It may be provided as argument to any of the Fprint functions.
type CommentedNode struct {
	Node     interface{} // *ast.File, or ast.Expr, ast.Decl, ast.Spec, or ast.Stmt
	Comments []*ast.CommentGroup
}
Fprint "pretty-prints" an AST node to output for a given configuration cfg. Position information is interpreted relative to the file set fset. The node type must be *ast.File, *CommentedNode, []ast.Decl, []ast.Stmt, or assignment-compatible to ast.Expr, ast.Decl, ast.Spec, or ast.Stmt.
func ( *Config) ( io.Writer,  *token.FileSet,  interface{}) error {
	return .fprint(, , , make(map[ast.Node]int))
}
Fprint "pretty-prints" an AST node to output. It calls Config.Fprint with default settings. Note that gofmt uses tabs for indentation but spaces for alignment; use format.Node (package go/format) for output that matches gofmt.
func ( io.Writer,  *token.FileSet,  interface{}) error {
	return (&Config{Tabwidth: 8}).Fprint(, , )