func (p *printer) stmtList(list []ast.Stmt, _indent int, nextIsRBrace bool) {
	if _indent > 0 {
		p.print(indent)
	}
	var multiLine bool
	for i, s := range list {
		p.linebreak(p.fset.Position(s.Pos()).Line, 1, ignore, i == 0 || _indent == 0 || multiLine)
		multiLine = false
		p.stmt(s, nextIsRBrace && i == len(list)-1, &multiLine)
	}
	if _indent > 0 {
		p.print(unindent)
	}
}
----------------------------------------------------------------------------
Statements
Print the statement list indented, but without a newline after the last statement.
Extra line breaks between statements in the source are respected but at most one
empty line is printed between statements.
package printer
import (
	"bytes"
	"github.com/rogpeppe/godef/go/ast"
	"github.com/rogpeppe/godef/go/token"
)
func (p *printer) linebreak(line, min int, ws whiteSpace, newSection bool) (printedBreak bool) {
	n := p.nlines(line-p.pos.Line, min)
	if n > 0 {
		p.print(ws)
		if newSection {
			p.print(formfeed)
			n--
		}
		for ; n > 0; n-- {
			p.print(newline)
		}
		printedBreak = true
	}
	return
}
func (p *printer) setComment(g *ast.CommentGroup) {
	if g == nil || !p.useNodeComments {
		return
	}
	if p.comments == nil {
		p.comments = make([]*ast.CommentGroup, 1)
	} else if p.cindex < len(p.comments) {
		p.flush(p.fset.Position(g.List[0].Pos()), token.ILLEGAL)
	}
	p.comments[0] = g
	p.cindex = 0
}
type exprListMode uint
const (
	blankStart exprListMode = 1 << iota  
	blankEnd                             
	commaSep                             
	commaTerm                            
	noIndent                             
	periodSep                            
)
func (p *printer) identList(list []*ast.Ident, indent bool, multiLine *bool) {
	xlist := make([]ast.Expr, len(list))
	for i, x := range list {
		xlist[i] = x
	}
	mode := commaSep
	if !indent {
		mode |= noIndent
	}
	p.exprList(token.NoPos, xlist, 1, mode, multiLine, token.NoPos)
}
func (p *printer) exprList(prev0 token.Pos, list []ast.Expr, depth int, mode exprListMode, multiLine *bool, next0 token.Pos) {
	if len(list) == 0 {
		return
	}
	if mode&blankStart != 0 {
		p.print(blank)
	}
	prev := p.fset.Position(prev0)
	next := p.fset.Position(next0)
	line := p.fset.Position(list[0].Pos()).Line
	endLine := p.fset.Position(list[len(list)-1].End()).Line
	if prev.IsValid() && prev.Line == line && line == endLine {
		for i, x := range list {
			if i > 0 {
				if mode&commaSep != 0 {
					p.print(token.COMMA)
				}
				p.print(blank)
			}
			p.expr0(x, depth, multiLine)
		}
		if mode&blankEnd != 0 {
			p.print(blank)
		}
		return
	}
	ws := ignore
	if mode&noIndent == 0 {
		ws = indent
	}
	prevBreak := -1  
	if prev.IsValid() && prev.Line < line && p.linebreak(line, 0, ws, true) {
		ws = ignore
		*multiLine = true
		prevBreak = 0
	}
	size := 0
	for i, x := range list {
		prevLine := line
		line = p.fset.Position(x.Pos()).Line
		useFF := true
		prevSize := size
		const infinity = 1e6  
		size = p.nodeSize(x, infinity)
		pair, isPair := x.(*ast.KeyValueExpr)
		if size <= infinity && prev.IsValid() && next.IsValid() {
			if isPair {
				size = p.nodeSize(pair.Key, infinity)  
			}
		} else {
			size = 0
		}
		if prevSize > 0 && size > 0 {
			const smallSize = 20
			if prevSize <= smallSize && size <= smallSize {
				useFF = false
			} else {
				const r = 4  
				ratio := float64(size) / float64(prevSize)
				useFF = ratio <= 1/r || r <= ratio
			}
		}
		if i > 0 {
			switch {
			case mode&commaSep != 0:
				p.print(token.COMMA)
			case mode&periodSep != 0:
				p.print(token.PERIOD)
			}
			needsBlank := mode&periodSep == 0  
			if prevLine < line && prevLine > 0 && line > 0 {
				if p.linebreak(line, 0, ws, useFF || prevBreak+1 < i) {
					ws = ignore
					*multiLine = true
					prevBreak = i
					needsBlank = false  
				}
			}
			if needsBlank {
				p.print(blank)
			}
		}
		if isPair && size > 0 && len(list) > 1 {
			p.expr(pair.Key, multiLine)
			p.print(pair.Colon, token.COLON, vtab)
			p.expr(pair.Value, multiLine)
		} else {
			p.expr0(x, depth, multiLine)
		}
	}
	if mode&commaTerm != 0 && next.IsValid() && p.pos.Line < next.Line {
		p.print(token.COMMA)
		if ws == ignore && mode&noIndent == 0 {
			p.print(unindent)
		}
		p.print(formfeed)  
		return
	}
	if mode&blankEnd != 0 {
		p.print(blank)
	}
	if ws == ignore && mode&noIndent == 0 {
		p.print(unindent)
	}
}
func (p *printer) parameters(fields *ast.FieldList, multiLine *bool) {
	p.print(fields.Opening, token.LPAREN)
	if len(fields.List) > 0 {
		var prevLine, line int
		for i, par := range fields.List {
			if i > 0 {
				p.print(token.COMMA)
				if len(par.Names) > 0 {
					line = p.fset.Position(par.Names[0].Pos()).Line
				} else {
					line = p.fset.Position(par.Type.Pos()).Line
				}
				if 0 < prevLine && prevLine < line && p.linebreak(line, 0, ignore, true) {
					*multiLine = true
				} else {
					p.print(blank)
				}
			}
			if len(par.Names) > 0 {
				p.identList(par.Names, false, multiLine)
				p.print(blank)
			}
			p.expr(par.Type, multiLine)
			prevLine = p.fset.Position(par.Type.Pos()).Line
		}
	}
	p.print(fields.Closing, token.RPAREN)
}
func (p *printer) signature(params, result *ast.FieldList, multiLine *bool) {
	p.parameters(params, multiLine)
	n := result.NumFields()
	if n > 0 {
		p.print(blank)
		if n == 1 && result.List[0].Names == nil {
			p.expr(result.List[0].Type, multiLine)
			return
		}
		p.parameters(result, multiLine)
	}
}
func identListSize(list []*ast.Ident, maxSize int) (size int) {
	for i, x := range list {
		if i > 0 {
			size += 2  
		}
		size += len(x.Name)
		if size >= maxSize {
			break
		}
	}
	return
}
func (p *printer) isOneLineFieldList(list []*ast.Field) bool {
	if len(list) != 1 {
		return false  
	}
	f := list[0]
	if f.Tag != nil || f.Comment != nil {
		return false  
	}
	const maxSize = 30  
	namesSize := identListSize(f.Names, maxSize)
	if namesSize > 0 {
		namesSize = 1  
	}
	typeSize := p.nodeSize(f.Type, maxSize)
	return namesSize+typeSize <= maxSize
}
func (p *printer) setLineComment(text string) {
	p.setComment(&ast.CommentGroup{[]*ast.Comment{&ast.Comment{token.NoPos, text}}})
}
func (p *printer) fieldList(fields *ast.FieldList, isStruct, isIncomplete bool) {
	lbrace := fields.Opening
	list := fields.List
	rbrace := fields.Closing
	srcIsOneLine := lbrace.IsValid() && rbrace.IsValid() && p.fset.Position(lbrace).Line == p.fset.Position(rbrace).Line
	if !isIncomplete && !p.commentBefore(p.fset.Position(rbrace)) && srcIsOneLine {
		if len(list) == 0 {
			p.print(lbrace, token.LBRACE, rbrace, token.RBRACE)
			return
		} else if isStruct && p.isOneLineFieldList(list) {  
			p.print(lbrace, token.LBRACE, blank)
			f := list[0]
			for i, x := range f.Names {
				if i > 0 {
					p.print(token.COMMA, blank)
				}
				p.expr(x, ignoreMultiLine)
			}
			if len(f.Names) > 0 {
				p.print(blank)
			}
			p.expr(f.Type, ignoreMultiLine)
			p.print(blank, rbrace, token.RBRACE)
			return
		}
	}
	p.print(blank, lbrace, token.LBRACE, indent, formfeed)
	if isStruct {
		sep := vtab
		if len(list) == 1 {
			sep = blank
		}
		var ml bool
		for i, f := range list {
			if i > 0 {
				p.linebreak(p.fset.Position(f.Pos()).Line, 1, ignore, ml)
			}
			ml = false
			extraTabs := 0
			p.setComment(f.Doc)
			if len(f.Names) > 0 {
				p.identList(f.Names, false, &ml)
				p.print(sep)
				p.expr(f.Type, &ml)
				extraTabs = 1
			} else {
				p.expr(f.Type, &ml)
				extraTabs = 2
			}
			if f.Tag != nil {
				if len(f.Names) > 0 && sep == vtab {
					p.print(sep)
				}
				p.print(sep)
				p.expr(f.Tag, &ml)
				extraTabs = 0
			}
			if f.Comment != nil {
				for ; extraTabs > 0; extraTabs-- {
					p.print(sep)
				}
				p.setComment(f.Comment)
			}
		}
		if isIncomplete {
			if len(list) > 0 {
				p.print(formfeed)
			}
			p.flush(p.fset.Position(rbrace), token.RBRACE)  
			p.setLineComment("// contains filtered or unexported fields")
		}
	} else {  
		var ml bool
		for i, f := range list {
			if i > 0 {
				p.linebreak(p.fset.Position(f.Pos()).Line, 1, ignore, ml)
			}
			ml = false
			p.setComment(f.Doc)
			if ftyp, isFtyp := f.Type.(*ast.FuncType); isFtyp {
				p.expr(f.Names[0], &ml)
				p.signature(ftyp.Params, ftyp.Results, &ml)
			} else {
				p.expr(f.Type, &ml)
			}
			p.setComment(f.Comment)
		}
		if isIncomplete {
			if len(list) > 0 {
				p.print(formfeed)
			}
			p.flush(p.fset.Position(rbrace), token.RBRACE)  
			p.setLineComment("// contains filtered or unexported methods")
		}
	}
	p.print(unindent, formfeed, rbrace, token.RBRACE)
}
func walkBinary(e *ast.BinaryExpr) (has4, has5 bool, maxProblem int) {
	switch e.Op.Precedence() {
	case 4:
		has4 = true
	case 5:
		has5 = true
	}
	switch l := e.X.(type) {
	case *ast.BinaryExpr:
		if l.Op.Precedence() < e.Op.Precedence() {
			break
		}
		h4, h5, mp := walkBinary(l)
		has4 = has4 || h4
		has5 = has5 || h5
		if maxProblem < mp {
			maxProblem = mp
		}
	}
	switch r := e.Y.(type) {
	case *ast.BinaryExpr:
		if r.Op.Precedence() <= e.Op.Precedence() {
			break
		}
		h4, h5, mp := walkBinary(r)
		has4 = has4 || h4
		has5 = has5 || h5
		if maxProblem < mp {
			maxProblem = mp
		}
	case *ast.StarExpr:
		if e.Op == token.QUO {  
			maxProblem = 5
		}
	case *ast.UnaryExpr:
		switch e.Op.String() + r.Op.String() {
		case "/*", "&&", "&^":
			maxProblem = 5
		case "++", "--":
			if maxProblem < 4 {
				maxProblem = 4
			}
		}
	}
	return
}
func cutoff(e *ast.BinaryExpr, depth int) int {
	has4, has5, maxProblem := walkBinary(e)
	if maxProblem > 0 {
		return maxProblem + 1
	}
	if has4 && has5 {
		if depth == 1 {
			return 5
		}
		return 4
	}
	if depth == 1 {
		return 6
	}
	return 4
}
func diffPrec(expr ast.Expr, prec int) int {
	x, ok := expr.(*ast.BinaryExpr)
	if !ok || prec != x.Op.Precedence() {
		return 1
	}
	return 0
}
func reduceDepth(depth int) int {
	depth--
	if depth < 1 {
		depth = 1
	}
	return depth
}
func (p *printer) binaryExpr(x *ast.BinaryExpr, prec1, cutoff, depth int, multiLine *bool) {
	prec := x.Op.Precedence()
	if prec < prec1 {
		p.print(token.LPAREN)
		p.expr0(x, reduceDepth(depth), multiLine)  
		p.print(token.RPAREN)
		return
	}
	printBlank := prec < cutoff
	ws := indent
	p.expr1(x.X, prec, depth+diffPrec(x.X, prec), multiLine)
	if printBlank {
		p.print(blank)
	}
	xline := p.pos.Line  
	yline := p.fset.Position(x.Y.Pos()).Line
	p.print(x.OpPos, x.Op)
	if xline != yline && xline > 0 && yline > 0 {
		if p.linebreak(yline, 1, ws, true) {
			ws = ignore
			*multiLine = true
			printBlank = false  
		}
	}
	if printBlank {
		p.print(blank)
	}
	p.expr1(x.Y, prec+1, depth+1, multiLine)
	if ws == ignore {
		p.print(unindent)
	}
}
func isBinary(expr ast.Expr) bool {
	_, ok := expr.(*ast.BinaryExpr)
	return ok
}
func splitSelector(expr ast.Expr) (body, suffix ast.Expr) {
	switch x := expr.(type) {
	case *ast.SelectorExpr:
		body, suffix = x.X, x.Sel
		return
	case *ast.CallExpr:
		body, suffix = splitSelector(x.Fun)
		if body != nil {
			suffix = &ast.CallExpr{suffix, x.Lparen, x.Args, x.Ellipsis, x.Rparen}
			return
		}
	case *ast.IndexExpr:
		body, suffix = splitSelector(x.X)
		if body != nil {
			suffix = &ast.IndexExpr{suffix, x.Lbrack, x.Index, x.Rbrack}
			return
		}
	case *ast.SliceExpr:
		body, suffix = splitSelector(x.X)
		if body != nil {
			suffix = &ast.SliceExpr{X: suffix, Lbrack: x.Lbrack, Low: x.Low, High: x.High, Rbrack: x.Rbrack, Slice3: false}
			return
		}
	case *ast.TypeAssertExpr:
		body, suffix = splitSelector(x.X)
		if body != nil {
			suffix = &ast.TypeAssertExpr{suffix, x.Type}
			return
		}
	}
	suffix = expr
	return
}
func selectorExprList(expr ast.Expr) (list []ast.Expr) {
	for expr != nil {
		var suffix ast.Expr
		expr, suffix = splitSelector(expr)
		list = append(list, suffix)
	}
	for i, j := 0, len(list)-1; i < j; i, j = i+1, j-1 {
		list[i], list[j] = list[j], list[i]
	}
	return
}
func (p *printer) expr1(expr ast.Expr, prec1, depth int, multiLine *bool) {
	p.print(expr.Pos())
	switch x := expr.(type) {
	case *ast.BadExpr:
		p.print("BadExpr")
	case *ast.Ident:
		p.print(x)
	case *ast.BinaryExpr:
		if depth < 1 {
			p.internalError("depth < 1:", depth)
			depth = 1
		}
		p.binaryExpr(x, prec1, cutoff(x, depth), depth, multiLine)
	case *ast.KeyValueExpr:
		p.expr(x.Key, multiLine)
		p.print(x.Colon, token.COLON, blank)
		p.expr(x.Value, multiLine)
	case *ast.StarExpr:
		const prec = token.UnaryPrec
		if prec < prec1 {
			p.print(token.LPAREN)
			p.print(token.MUL)
			p.expr(x.X, multiLine)
			p.print(token.RPAREN)
		} else {
			p.print(token.MUL)
			p.expr(x.X, multiLine)
		}
	case *ast.UnaryExpr:
		const prec = token.UnaryPrec
		if prec < prec1 {
			p.print(token.LPAREN)
			p.expr(x, multiLine)
			p.print(token.RPAREN)
		} else {
			p.print(x.Op)
			if x.Op == token.RANGE {
				p.print(blank)
			}
			p.expr1(x.X, prec, depth, multiLine)
		}
	case *ast.BasicLit:
		p.print(x)
	case *ast.FuncLit:
		p.expr(x.Type, multiLine)
		p.funcBody(x.Body, p.distance(x.Type.Pos(), p.pos), true, multiLine)
	case *ast.ParenExpr:
		if _, hasParens := x.X.(*ast.ParenExpr); hasParens {
			p.expr0(x.X, reduceDepth(depth), multiLine)  
		} else {
			p.print(token.LPAREN)
			p.expr0(x.X, reduceDepth(depth), multiLine)  
			p.print(x.Rparen, token.RPAREN)
		}
	case *ast.SelectorExpr:
		parts := selectorExprList(expr)
		p.exprList(token.NoPos, parts, depth, periodSep, multiLine, token.NoPos)
	case *ast.TypeAssertExpr:
		p.expr1(x.X, token.HighestPrec, depth, multiLine)
		p.print(token.PERIOD, token.LPAREN)
		if x.Type != nil {
			p.expr(x.Type, multiLine)
		} else {
			p.print(token.TYPE)
		}
		p.print(token.RPAREN)
	case *ast.IndexExpr:
		p.expr1(x.X, token.HighestPrec, 1, multiLine)
		p.print(x.Lbrack, token.LBRACK)
		p.expr0(x.Index, depth+1, multiLine)
		p.print(x.Rbrack, token.RBRACK)
	case *ast.SliceExpr:
		p.expr1(x.X, token.HighestPrec, 1, multiLine)
		p.print(x.Lbrack, token.LBRACK)
		if x.Low != nil {
			p.expr0(x.Low, depth+1, multiLine)
		}
		if depth <= 1 && x.Low != nil && x.High != nil && (isBinary(x.Low) || isBinary(x.High)) {
			p.print(blank, token.COLON, blank)
		} else {
			p.print(token.COLON)
		}
		if x.High != nil {
			p.expr0(x.High, depth+1, multiLine)
		}
		p.print(x.Rbrack, token.RBRACK)
	case *ast.CallExpr:
		if len(x.Args) > 1 {
			depth++
		}
		p.expr1(x.Fun, token.HighestPrec, depth, multiLine)
		p.print(x.Lparen, token.LPAREN)
		p.exprList(x.Lparen, x.Args, depth, commaSep|commaTerm, multiLine, x.Rparen)
		if x.Ellipsis.IsValid() {
			p.print(x.Ellipsis, token.ELLIPSIS)
		}
		p.print(x.Rparen, token.RPAREN)
	case *ast.CompositeLit:
		if x.Type != nil {
			p.expr1(x.Type, token.HighestPrec, depth, multiLine)
		}
		p.print(x.Lbrace, token.LBRACE)
		p.exprList(x.Lbrace, x.Elts, 1, commaSep|commaTerm, multiLine, x.Rbrace)
		p.print(noExtraLinebreak, x.Rbrace, token.RBRACE, noExtraLinebreak)
	case *ast.Ellipsis:
		p.print(token.ELLIPSIS)
		if x.Elt != nil {
			p.expr(x.Elt, multiLine)
		}
	case *ast.ArrayType:
		p.print(token.LBRACK)
		if x.Len != nil {
			p.expr(x.Len, multiLine)
		}
		p.print(token.RBRACK)
		p.expr(x.Elt, multiLine)
	case *ast.StructType:
		p.print(token.STRUCT)
		p.fieldList(x.Fields, true, x.Incomplete)
	case *ast.FuncType:
		p.print(token.FUNC)
		p.signature(x.Params, x.Results, multiLine)
	case *ast.InterfaceType:
		p.print(token.INTERFACE)
		p.fieldList(x.Methods, false, x.Incomplete)
	case *ast.MapType:
		p.print(token.MAP, token.LBRACK)
		p.expr(x.Key, multiLine)
		p.print(token.RBRACK)
		p.expr(x.Value, multiLine)
	case *ast.ChanType:
		switch x.Dir {
		case ast.SEND | ast.RECV:
			p.print(token.CHAN)
		case ast.RECV:
			p.print(token.ARROW, token.CHAN)
		case ast.SEND:
			p.print(token.CHAN, token.ARROW)
		}
		p.print(blank)
		p.expr(x.Value, multiLine)
	default:
		panic("unreachable")
	}
	return
}
func (p *printer) expr0(x ast.Expr, depth int, multiLine *bool) {
	p.expr1(x, token.LowestPrec, depth, multiLine)
}
func (p *printer) expr(x ast.Expr, multiLine *bool) {
	const depth = 1
	p.expr1(x, token.LowestPrec, depth, multiLine)
}