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)
	}
}
Format the binary expression: decide the cutoff and then format.
Let's call depth == 1 Normal mode, and depth > 1 Compact mode.
(Algorithm suggestion by Russ Cox.)

The precedences are:
5             *  /  %  <<  >>  &  &^
4             +  -  |  ^
3             ==  !=  <  <=  >  >=
2             &&
1             ||

The only decision is whether there will be spaces around levels 4 and 5.
There are never spaces at level 6 (unary), and always spaces at levels 3 and below.

To choose the cutoff, look at the whole expression but excluding primary
expressions (function calls, parenthesized exprs), and apply these rules:

1) If there is a binary operator with a right side unary operand
that would clash without a space, the cutoff must be (in order):

/*	6
&&	6
&^	6
++	5
--	5

(Comparison operators always have spaces around them.)

2) If there is a mix of level 5 and level 4 operators, then the cutoff
is 5 (use spaces to distinguish precedence) in Normal mode
and 4 (never use spaces) in Compact mode.

3) If there are no level 4 operators or no level 5 operators, then the
cutoff is 6 (always use spaces) in Normal mode
and 4 (never use spaces) in Compact mode.

Sets multiLine to true if the binary expression spans multiple lines.
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
}