package printer
import (
"go/ast"
"go/token"
"math"
"strconv"
"strings"
"unicode"
"unicode/utf8"
)
func (p *printer ) linebreak (line , min int , ws whiteSpace , newSection bool ) (nbreaks int ) {
n := max (nlimit (line -p .pos .Line ), min )
if n > 0 {
p .print (ws )
if newSection {
p .print (formfeed )
n --
nbreaks = 2
}
nbreaks += n
for ; n > 0 ; n -- {
p .print (newline )
}
}
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 .posFor (g .List [0 ].Pos ()), token .ILLEGAL )
p .comments = p .comments [0 :1 ]
p .internalError ("setComment found pending comments" )
}
p .comments [0 ] = g
p .cindex = 0
if p .commentOffset == infinity {
p .nextComment ()
}
}
type exprListMode uint
const (
commaTerm exprListMode = 1 << iota
noIndent
)
func (p *printer ) identList (list []*ast .Ident , indent bool ) {
xlist := make ([]ast .Expr , len (list ))
for i , x := range list {
xlist [i ] = x
}
var mode exprListMode
if !indent {
mode = noIndent
}
p .exprList (token .NoPos , xlist , 1 , mode , token .NoPos , false )
}
const filteredMsg = "contains filtered or unexported fields"
func (p *printer ) exprList (prev0 token .Pos , list []ast .Expr , depth int , mode exprListMode , next0 token .Pos , isIncomplete bool ) {
if len (list ) == 0 {
if isIncomplete {
prev := p .posFor (prev0 )
next := p .posFor (next0 )
if prev .IsValid () && prev .Line == next .Line {
p .print ("/* " + filteredMsg + " */" )
} else {
p .print (newline )
p .print (indent , "// " +filteredMsg , unindent , newline )
}
}
return
}
prev := p .posFor (prev0 )
next := p .posFor (next0 )
line := p .lineFor (list [0 ].Pos ())
endLine := p .lineFor (list [len (list )-1 ].End ())
if prev .IsValid () && prev .Line == line && line == endLine {
for i , x := range list {
if i > 0 {
p .setPos (x .Pos ())
p .print (token .COMMA , blank )
}
p .expr0 (x , depth )
}
if isIncomplete {
p .print (token .COMMA , blank , "/* " +filteredMsg +" */" )
}
return
}
ws := ignore
if mode &noIndent == 0 {
ws = indent
}
prevBreak := -1
if prev .IsValid () && prev .Line < line && p .linebreak (line , 0 , ws , true ) > 0 {
ws = ignore
prevBreak = 0
}
size := 0
lnsum := 0.0
count := 0
prevLine := prev .Line
for i , x := range list {
line = p .lineFor (x .Pos ())
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 = 40
if count == 0 || prevSize <= smallSize && size <= smallSize {
useFF = false
} else {
const r = 2.5
geomean := math .Exp (lnsum / float64 (count ))
ratio := float64 (size ) / geomean
useFF = r *ratio <= 1 || r <= ratio
}
}
needsLinebreak := 0 < prevLine && prevLine < line
if i > 0 {
if !needsLinebreak {
p .setPos (x .Pos ())
}
p .print (token .COMMA )
needsBlank := true
if needsLinebreak {
nbreaks := p .linebreak (line , 0 , ws , useFF || prevBreak +1 < i )
if nbreaks > 0 {
ws = ignore
prevBreak = i
needsBlank = false
}
if nbreaks > 1 {
lnsum = 0
count = 0
}
}
if needsBlank {
p .print (blank )
}
}
if len (list ) > 1 && isPair && size > 0 && needsLinebreak {
p .expr (pair .Key )
p .setPos (pair .Colon )
p .print (token .COLON , vtab )
p .expr (pair .Value )
} else {
p .expr0 (x , depth )
}
if size > 0 {
lnsum += math .Log (float64 (size ))
count ++
}
prevLine = line
}
if mode &commaTerm != 0 && next .IsValid () && p .pos .Line < next .Line {
p .print (token .COMMA )
if isIncomplete {
p .print (newline )
p .print ("// " + filteredMsg )
}
if ws == ignore && mode &noIndent == 0 {
p .print (unindent )
}
p .print (formfeed )
return
}
if isIncomplete {
p .print (token .COMMA , newline )
p .print ("// " +filteredMsg , newline )
}
if ws == ignore && mode &noIndent == 0 {
p .print (unindent )
}
}
type paramMode int
const (
funcParam paramMode = iota
funcTParam
typeTParam
)
func (p *printer ) parameters (fields *ast .FieldList , mode paramMode ) {
openTok , closeTok := token .LPAREN , token .RPAREN
if mode != funcParam {
openTok , closeTok = token .LBRACK , token .RBRACK
}
p .setPos (fields .Opening )
p .print (openTok )
if len (fields .List ) > 0 {
prevLine := p .lineFor (fields .Opening )
ws := indent
for i , par := range fields .List {
parLineBeg := p .lineFor (par .Pos ())
parLineEnd := p .lineFor (par .End ())
needsLinebreak := 0 < prevLine && prevLine < parLineBeg
if i > 0 {
if !needsLinebreak {
p .setPos (par .Pos ())
}
p .print (token .COMMA )
}
if needsLinebreak && p .linebreak (parLineBeg , 0 , ws , true ) > 0 {
ws = ignore
} else if i > 0 {
p .print (blank )
}
if len (par .Names ) > 0 {
p .identList (par .Names , ws == indent )
p .print (blank )
}
p .expr (stripParensAlways (par .Type ))
prevLine = parLineEnd
}
if closing := p .lineFor (fields .Closing ); 0 < prevLine && prevLine < closing {
p .print (token .COMMA )
p .linebreak (closing , 0 , ignore , true )
} else if mode == typeTParam && fields .NumFields () == 1 && combinesWithName (fields .List [0 ].Type ) {
p .print (token .COMMA )
}
if ws == ignore {
p .print (unindent )
}
}
p .setPos (fields .Closing )
p .print (closeTok )
}
func combinesWithName(x ast .Expr ) bool {
switch x := x .(type ) {
case *ast .StarExpr :
return !isTypeElem (x .X )
case *ast .BinaryExpr :
return combinesWithName (x .X ) && !isTypeElem (x .Y )
case *ast .ParenExpr :
panic ("unexpected parenthesized expression" )
}
return false
}
func isTypeElem(x ast .Expr ) bool {
switch x := x .(type ) {
case *ast .ArrayType , *ast .StructType , *ast .FuncType , *ast .InterfaceType , *ast .MapType , *ast .ChanType :
return true
case *ast .UnaryExpr :
return x .Op == token .TILDE
case *ast .BinaryExpr :
return isTypeElem (x .X ) || isTypeElem (x .Y )
case *ast .ParenExpr :
return isTypeElem (x .X )
}
return false
}
func (p *printer ) signature (sig *ast .FuncType ) {
if sig .TypeParams != nil {
p .parameters (sig .TypeParams , funcTParam )
}
if sig .Params != nil {
p .parameters (sig .Params , funcParam )
} else {
p .print (token .LPAREN , token .RPAREN )
}
res := sig .Results
n := res .NumFields ()
if n > 0 {
p .print (blank )
if n == 1 && res .List [0 ].Names == nil {
p .expr (stripParensAlways (res .List [0 ].Type ))
return
}
p .parameters (res , funcParam )
}
}
func identListSize(list []*ast .Ident , maxSize int ) (size int ) {
for i , x := range list {
if i > 0 {
size += len (", " )
}
size += utf8 .RuneCountInString (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 {List : []*ast .Comment {{Slash : token .NoPos , Text : text }}})
}
func (p *printer ) fieldList (fields *ast .FieldList , isStruct , isIncomplete bool ) {
lbrace := fields .Opening
list := fields .List
rbrace := fields .Closing
hasComments := isIncomplete || p .commentBefore (p .posFor (rbrace ))
srcIsOneLine := lbrace .IsValid () && rbrace .IsValid () && p .lineFor (lbrace ) == p .lineFor (rbrace )
if !hasComments && srcIsOneLine {
if len (list ) == 0 {
p .setPos (lbrace )
p .print (token .LBRACE )
p .setPos (rbrace )
p .print (token .RBRACE )
return
} else if p .isOneLineFieldList (list ) {
p .setPos (lbrace )
p .print (token .LBRACE , blank )
f := list [0 ]
if isStruct {
for i , x := range f .Names {
if i > 0 {
p .print (token .COMMA , blank )
}
p .expr (x )
}
if len (f .Names ) > 0 {
p .print (blank )
}
p .expr (f .Type )
} else {
if len (f .Names ) > 0 {
name := f .Names [0 ]
p .expr (name )
p .signature (f .Type .(*ast .FuncType ))
} else {
p .expr (f .Type )
}
}
p .print (blank )
p .setPos (rbrace )
p .print (token .RBRACE )
return
}
}
p .print (blank )
p .setPos (lbrace )
p .print (token .LBRACE , indent )
if hasComments || len (list ) > 0 {
p .print (formfeed )
}
if isStruct {
sep := vtab
if len (list ) == 1 {
sep = blank
}
var line int
for i , f := range list {
if i > 0 {
p .linebreak (p .lineFor (f .Pos ()), 1 , ignore , p .linesFrom (line ) > 0 )
}
extraTabs := 0
p .setComment (f .Doc )
p .recordLine (&line )
if len (f .Names ) > 0 {
p .identList (f .Names , false )
p .print (sep )
p .expr (f .Type )
extraTabs = 1
} else {
p .expr (f .Type )
extraTabs = 2
}
if f .Tag != nil {
if len (f .Names ) > 0 && sep == vtab {
p .print (sep )
}
p .print (sep )
p .expr (f .Tag )
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 .posFor (rbrace ), token .RBRACE )
p .setLineComment ("// " + filteredMsg )
}
} else {
var line int
var prev *ast .Ident
for i , f := range list {
var name *ast .Ident
if len (f .Names ) > 0 {
name = f .Names [0 ]
}
if i > 0 {
min := 1
if prev != nil && name == prev {
min = 0
}
p .linebreak (p .lineFor (f .Pos ()), min , ignore , p .linesFrom (line ) > 0 )
}
p .setComment (f .Doc )
p .recordLine (&line )
if name != nil {
p .expr (name )
p .signature (f .Type .(*ast .FuncType ))
prev = nil
} else {
p .expr (f .Type )
prev = nil
}
p .setComment (f .Comment )
}
if isIncomplete {
if len (list ) > 0 {
p .print (formfeed )
}
p .flush (p .posFor (rbrace ), token .RBRACE )
p .setLineComment ("// contains filtered or unexported methods" )
}
}
p .print (unindent , formfeed )
p .setPos (rbrace )
p .print (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
maxProblem = max (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
maxProblem = max (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 "++" , "--" :
maxProblem = max (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 ) {
prec := x .Op .Precedence ()
if prec < prec1 {
p .print (token .LPAREN )
p .expr0 (x , reduceDepth (depth ))
p .print (token .RPAREN )
return
}
printBlank := prec < cutoff
ws := indent
p .expr1 (x .X , prec , depth +diffPrec (x .X , prec ))
if printBlank {
p .print (blank )
}
xline := p .pos .Line
yline := p .lineFor (x .Y .Pos ())
p .setPos (x .OpPos )
p .print (x .Op )
if xline != yline && xline > 0 && yline > 0 {
if p .linebreak (yline , 1 , ws , true ) > 0 {
ws = ignore
printBlank = false
}
}
if printBlank {
p .print (blank )
}
p .expr1 (x .Y , prec +1 , depth +1 )
if ws == ignore {
p .print (unindent )
}
}
func isBinary(expr ast .Expr ) bool {
_ , ok := expr .(*ast .BinaryExpr )
return ok
}
func (p *printer ) expr1 (expr ast .Expr , prec1 , depth int ) {
p .setPos (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 )
case *ast .KeyValueExpr :
p .expr (x .Key )
p .setPos (x .Colon )
p .print (token .COLON , blank )
p .expr (x .Value )
case *ast .StarExpr :
const prec = token .UnaryPrec
if prec < prec1 {
p .print (token .LPAREN )
p .print (token .MUL )
p .expr (x .X )
p .print (token .RPAREN )
} else {
p .print (token .MUL )
p .expr (x .X )
}
case *ast .UnaryExpr :
const prec = token .UnaryPrec
if prec < prec1 {
p .print (token .LPAREN )
p .expr (x )
p .print (token .RPAREN )
} else {
p .print (x .Op )
if x .Op == token .RANGE {
p .print (blank )
}
p .expr1 (x .X , prec , depth )
}
case *ast .BasicLit :
if p .Config .Mode &normalizeNumbers != 0 {
x = normalizedNumber (x )
}
p .print (x )
case *ast .FuncLit :
p .setPos (x .Type .Pos ())
p .print (token .FUNC )
startCol := p .out .Column - len ("func" )
p .signature (x .Type )
p .funcBody (p .distanceFrom (x .Type .Pos (), startCol ), blank , x .Body )
case *ast .ParenExpr :
if _ , hasParens := x .X .(*ast .ParenExpr ); hasParens {
p .expr0 (x .X , depth )
} else {
p .print (token .LPAREN )
p .expr0 (x .X , reduceDepth (depth ))
p .setPos (x .Rparen )
p .print (token .RPAREN )
}
case *ast .SelectorExpr :
p .selectorExpr (x , depth , false )
case *ast .TypeAssertExpr :
p .expr1 (x .X , token .HighestPrec , depth )
p .print (token .PERIOD )
p .setPos (x .Lparen )
p .print (token .LPAREN )
if x .Type != nil {
p .expr (x .Type )
} else {
p .print (token .TYPE )
}
p .setPos (x .Rparen )
p .print (token .RPAREN )
case *ast .IndexExpr :
p .expr1 (x .X , token .HighestPrec , 1 )
p .setPos (x .Lbrack )
p .print (token .LBRACK )
p .expr0 (x .Index , depth +1 )
p .setPos (x .Rbrack )
p .print (token .RBRACK )
case *ast .IndexListExpr :
p .expr1 (x .X , token .HighestPrec , 1 )
p .setPos (x .Lbrack )
p .print (token .LBRACK )
p .exprList (x .Lbrack , x .Indices , depth +1 , commaTerm , x .Rbrack , false )
p .setPos (x .Rbrack )
p .print (token .RBRACK )
case *ast .SliceExpr :
p .expr1 (x .X , token .HighestPrec , 1 )
p .setPos (x .Lbrack )
p .print (token .LBRACK )
indices := []ast .Expr {x .Low , x .High }
if x .Max != nil {
indices = append (indices , x .Max )
}
var needsBlanks bool
if depth <= 1 {
var indexCount int
var hasBinaries bool
for _ , x := range indices {
if x != nil {
indexCount ++
if isBinary (x ) {
hasBinaries = true
}
}
}
if indexCount > 1 && hasBinaries {
needsBlanks = true
}
}
for i , x := range indices {
if i > 0 {
if indices [i -1 ] != nil && needsBlanks {
p .print (blank )
}
p .print (token .COLON )
if x != nil && needsBlanks {
p .print (blank )
}
}
if x != nil {
p .expr0 (x , depth +1 )
}
}
p .setPos (x .Rbrack )
p .print (token .RBRACK )
case *ast .CallExpr :
if len (x .Args ) > 1 {
depth ++
}
paren := false
switch t := x .Fun .(type ) {
case *ast .FuncType :
paren = true
case *ast .ChanType :
paren = t .Dir == ast .RECV
}
if paren {
p .print (token .LPAREN )
}
wasIndented := p .possibleSelectorExpr (x .Fun , token .HighestPrec , depth )
if paren {
p .print (token .RPAREN )
}
p .setPos (x .Lparen )
p .print (token .LPAREN )
if x .Ellipsis .IsValid () {
p .exprList (x .Lparen , x .Args , depth , 0 , x .Ellipsis , false )
p .setPos (x .Ellipsis )
p .print (token .ELLIPSIS )
if x .Rparen .IsValid () && p .lineFor (x .Ellipsis ) < p .lineFor (x .Rparen ) {
p .print (token .COMMA , formfeed )
}
} else {
p .exprList (x .Lparen , x .Args , depth , commaTerm , x .Rparen , false )
}
p .setPos (x .Rparen )
p .print (token .RPAREN )
if wasIndented {
p .print (unindent )
}
case *ast .CompositeLit :
if x .Type != nil {
p .expr1 (x .Type , token .HighestPrec , depth )
}
p .level ++
p .setPos (x .Lbrace )
p .print (token .LBRACE )
p .exprList (x .Lbrace , x .Elts , 1 , commaTerm , x .Rbrace , x .Incomplete )
mode := noExtraLinebreak
if len (x .Elts ) > 0 {
mode |= noExtraBlank
}
p .print (indent , unindent , mode )
p .setPos (x .Rbrace )
p .print (token .RBRACE , mode )
p .level --
case *ast .Ellipsis :
p .print (token .ELLIPSIS )
if x .Elt != nil {
p .expr (x .Elt )
}
case *ast .ArrayType :
p .print (token .LBRACK )
if x .Len != nil {
p .expr (x .Len )
}
p .print (token .RBRACK )
p .expr (x .Elt )
case *ast .StructType :
p .print (token .STRUCT )
p .fieldList (x .Fields , true , x .Incomplete )
case *ast .FuncType :
p .print (token .FUNC )
p .signature (x )
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 )
p .print (token .RBRACK )
p .expr (x .Value )
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 )
p .setPos (x .Arrow )
p .print (token .ARROW )
}
p .print (blank )
p .expr (x .Value )
default :
panic ("unreachable" )
}
}
func normalizedNumber(lit *ast .BasicLit ) *ast .BasicLit {
if lit .Kind != token .INT && lit .Kind != token .FLOAT && lit .Kind != token .IMAG {
return lit
}
if len (lit .Value ) < 2 {
return lit
}
x := lit .Value
switch x [:2 ] {
default :
if i := strings .LastIndexByte (x , 'E' ); i >= 0 {
x = x [:i ] + "e" + x [i +1 :]
break
}
if x [len (x )-1 ] == 'i' && !strings .ContainsAny (x , ".e" ) {
x = strings .TrimLeft (x , "0_" )
if x == "i" {
x = "0i"
}
}
case "0X" :
x = "0x" + x [2 :]
if i := strings .LastIndexByte (x , 'P' ); i >= 0 {
x = x [:i ] + "p" + x [i +1 :]
}
case "0x" :
i := strings .LastIndexByte (x , 'P' )
if i == -1 {
return lit
}
x = x [:i ] + "p" + x [i +1 :]
case "0O" :
x = "0o" + x [2 :]
case "0o" :
return lit
case "0B" :
x = "0b" + x [2 :]
case "0b" :
return lit
}
return &ast .BasicLit {ValuePos : lit .ValuePos , Kind : lit .Kind , Value : x }
}
func (p *printer ) possibleSelectorExpr (expr ast .Expr , prec1 , depth int ) bool {
if x , ok := expr .(*ast .SelectorExpr ); ok {
return p .selectorExpr (x , depth , true )
}
p .expr1 (expr , prec1 , depth )
return false
}
func (p *printer ) selectorExpr (x *ast .SelectorExpr , depth int , isMethod bool ) bool {
p .expr1 (x .X , token .HighestPrec , depth )
p .print (token .PERIOD )
if line := p .lineFor (x .Sel .Pos ()); p .pos .IsValid () && p .pos .Line < line {
p .print (indent , newline )
p .setPos (x .Sel .Pos ())
p .print (x .Sel )
if !isMethod {
p .print (unindent )
}
return true
}
p .setPos (x .Sel .Pos ())
p .print (x .Sel )
return false
}
func (p *printer ) expr0 (x ast .Expr , depth int ) {
p .expr1 (x , token .LowestPrec , depth )
}
func (p *printer ) expr (x ast .Expr ) {
const depth = 1
p .expr1 (x , token .LowestPrec , depth )
}
func (p *printer ) stmtList (list []ast .Stmt , nindent int , nextIsRBrace bool ) {
if nindent > 0 {
p .print (indent )
}
var line int
i := 0
for _ , s := range list {
if _ , isEmpty := s .(*ast .EmptyStmt ); !isEmpty {
if len (p .output ) > 0 {
p .linebreak (p .lineFor (s .Pos ()), 1 , ignore , i == 0 || nindent == 0 || p .linesFrom (line ) > 0 )
}
p .recordLine (&line )
p .stmt (s , nextIsRBrace && i == len (list )-1 )
for t := s ; ; {
lt , _ := t .(*ast .LabeledStmt )
if lt == nil {
break
}
line ++
t = lt .Stmt
}
i ++
}
}
if nindent > 0 {
p .print (unindent )
}
}
func (p *printer ) block (b *ast .BlockStmt , nindent int ) {
p .setPos (b .Lbrace )
p .print (token .LBRACE )
p .stmtList (b .List , nindent , true )
p .linebreak (p .lineFor (b .Rbrace ), 1 , ignore , true )
p .setPos (b .Rbrace )
p .print (token .RBRACE )
}
func isTypeName(x ast .Expr ) bool {
switch t := x .(type ) {
case *ast .Ident :
return true
case *ast .SelectorExpr :
return isTypeName (t .X )
}
return false
}
func stripParens(x ast .Expr ) ast .Expr {
if px , strip := x .(*ast .ParenExpr ); strip {
ast .Inspect (px .X , func (node ast .Node ) bool {
switch x := node .(type ) {
case *ast .ParenExpr :
return false
case *ast .CompositeLit :
if isTypeName (x .Type ) {
strip = false
}
return false
}
return true
})
if strip {
return stripParens (px .X )
}
}
return x
}
func stripParensAlways(x ast .Expr ) ast .Expr {
if x , ok := x .(*ast .ParenExpr ); ok {
return stripParensAlways (x .X )
}
return x
}
func (p *printer ) controlClause (isForStmt bool , init ast .Stmt , expr ast .Expr , post ast .Stmt ) {
p .print (blank )
needsBlank := false
if init == nil && post == nil {
if expr != nil {
p .expr (stripParens (expr ))
needsBlank = true
}
} else {
if init != nil {
p .stmt (init , false )
}
p .print (token .SEMICOLON , blank )
if expr != nil {
p .expr (stripParens (expr ))
needsBlank = true
}
if isForStmt {
p .print (token .SEMICOLON , blank )
needsBlank = false
if post != nil {
p .stmt (post , false )
needsBlank = true
}
}
}
if needsBlank {
p .print (blank )
}
}
func (p *printer ) indentList (list []ast .Expr ) bool {
if len (list ) >= 2 {
var b = p .lineFor (list [0 ].Pos ())
var e = p .lineFor (list [len (list )-1 ].End ())
if 0 < b && b < e {
n := 0
line := b
for _ , x := range list {
xb := p .lineFor (x .Pos ())
xe := p .lineFor (x .End ())
if line < xb {
return true
}
if xb < xe {
n ++
}
line = xe
}
return n > 1
}
}
return false
}
func (p *printer ) stmt (stmt ast .Stmt , nextIsRBrace bool ) {
p .setPos (stmt .Pos ())
switch s := stmt .(type ) {
case *ast .BadStmt :
p .print ("BadStmt" )
case *ast .DeclStmt :
p .decl (s .Decl )
case *ast .EmptyStmt :
case *ast .LabeledStmt :
p .print (unindent )
p .expr (s .Label )
p .setPos (s .Colon )
p .print (token .COLON , indent )
if e , isEmpty := s .Stmt .(*ast .EmptyStmt ); isEmpty {
if !nextIsRBrace {
p .print (newline )
p .setPos (e .Pos ())
p .print (token .SEMICOLON )
break
}
} else {
p .linebreak (p .lineFor (s .Stmt .Pos ()), 1 , ignore , true )
}
p .stmt (s .Stmt , nextIsRBrace )
case *ast .ExprStmt :
const depth = 1
p .expr0 (s .X , depth )
case *ast .SendStmt :
const depth = 1
p .expr0 (s .Chan , depth )
p .print (blank )
p .setPos (s .Arrow )
p .print (token .ARROW , blank )
p .expr0 (s .Value , depth )
case *ast .IncDecStmt :
const depth = 1
p .expr0 (s .X , depth +1 )
p .setPos (s .TokPos )
p .print (s .Tok )
case *ast .AssignStmt :
var depth = 1
if len (s .Lhs ) > 1 && len (s .Rhs ) > 1 {
depth ++
}
p .exprList (s .Pos (), s .Lhs , depth , 0 , s .TokPos , false )
p .print (blank )
p .setPos (s .TokPos )
p .print (s .Tok , blank )
p .exprList (s .TokPos , s .Rhs , depth , 0 , token .NoPos , false )
case *ast .GoStmt :
p .print (token .GO , blank )
p .expr (s .Call )
case *ast .DeferStmt :
p .print (token .DEFER , blank )
p .expr (s .Call )
case *ast .ReturnStmt :
p .print (token .RETURN )
if s .Results != nil {
p .print (blank )
if p .indentList (s .Results ) {
p .print (indent )
p .exprList (token .NoPos , s .Results , 1 , noIndent , token .NoPos , false )
p .print (unindent )
} else {
p .exprList (token .NoPos , s .Results , 1 , 0 , token .NoPos , false )
}
}
case *ast .BranchStmt :
p .print (s .Tok )
if s .Label != nil {
p .print (blank )
p .expr (s .Label )
}
case *ast .BlockStmt :
p .block (s , 1 )
case *ast .IfStmt :
p .print (token .IF )
p .controlClause (false , s .Init , s .Cond , nil )
p .block (s .Body , 1 )
if s .Else != nil {
p .print (blank , token .ELSE , blank )
switch s .Else .(type ) {
case *ast .BlockStmt , *ast .IfStmt :
p .stmt (s .Else , nextIsRBrace )
default :
p .print (token .LBRACE , indent , formfeed )
p .stmt (s .Else , true )
p .print (unindent , formfeed , token .RBRACE )
}
}
case *ast .CaseClause :
if s .List != nil {
p .print (token .CASE , blank )
p .exprList (s .Pos (), s .List , 1 , 0 , s .Colon , false )
} else {
p .print (token .DEFAULT )
}
p .setPos (s .Colon )
p .print (token .COLON )
p .stmtList (s .Body , 1 , nextIsRBrace )
case *ast .SwitchStmt :
p .print (token .SWITCH )
p .controlClause (false , s .Init , s .Tag , nil )
p .block (s .Body , 0 )
case *ast .TypeSwitchStmt :
p .print (token .SWITCH )
if s .Init != nil {
p .print (blank )
p .stmt (s .Init , false )
p .print (token .SEMICOLON )
}
p .print (blank )
p .stmt (s .Assign , false )
p .print (blank )
p .block (s .Body , 0 )
case *ast .CommClause :
if s .Comm != nil {
p .print (token .CASE , blank )
p .stmt (s .Comm , false )
} else {
p .print (token .DEFAULT )
}
p .setPos (s .Colon )
p .print (token .COLON )
p .stmtList (s .Body , 1 , nextIsRBrace )
case *ast .SelectStmt :
p .print (token .SELECT , blank )
body := s .Body
if len (body .List ) == 0 && !p .commentBefore (p .posFor (body .Rbrace )) {
p .setPos (body .Lbrace )
p .print (token .LBRACE )
p .setPos (body .Rbrace )
p .print (token .RBRACE )
} else {
p .block (body , 0 )
}
case *ast .ForStmt :
p .print (token .FOR )
p .controlClause (true , s .Init , s .Cond , s .Post )
p .block (s .Body , 1 )
case *ast .RangeStmt :
p .print (token .FOR , blank )
if s .Key != nil {
p .expr (s .Key )
if s .Value != nil {
p .setPos (s .Value .Pos ())
p .print (token .COMMA , blank )
p .expr (s .Value )
}
p .print (blank )
p .setPos (s .TokPos )
p .print (s .Tok , blank )
}
p .print (token .RANGE , blank )
p .expr (stripParens (s .X ))
p .print (blank )
p .block (s .Body , 1 )
default :
panic ("unreachable" )
}
}
func keepTypeColumn(specs []ast .Spec ) []bool {
m := make ([]bool , len (specs ))
populate := func (i , j int , keepType bool ) {
if keepType {
for ; i < j ; i ++ {
m [i ] = true
}
}
}
i0 := -1
var keepType bool
for i , s := range specs {
t := s .(*ast .ValueSpec )
if t .Values != nil {
if i0 < 0 {
i0 = i
keepType = false
}
} else {
if i0 >= 0 {
populate (i0 , i , keepType )
i0 = -1
}
}
if t .Type != nil {
keepType = true
}
}
if i0 >= 0 {
populate (i0 , len (specs ), keepType )
}
return m
}
func (p *printer ) valueSpec (s *ast .ValueSpec , keepType bool ) {
p .setComment (s .Doc )
p .identList (s .Names , false )
extraTabs := 3
if s .Type != nil || keepType {
p .print (vtab )
extraTabs --
}
if s .Type != nil {
p .expr (s .Type )
}
if s .Values != nil {
p .print (vtab , token .ASSIGN , blank )
p .exprList (token .NoPos , s .Values , 1 , 0 , token .NoPos , false )
extraTabs --
}
if s .Comment != nil {
for ; extraTabs > 0 ; extraTabs -- {
p .print (vtab )
}
p .setComment (s .Comment )
}
}
func sanitizeImportPath(lit *ast .BasicLit ) *ast .BasicLit {
if lit .Kind != token .STRING {
return lit
}
s , err := strconv .Unquote (lit .Value )
if err != nil {
return lit
}
if s == "" {
return lit
}
const illegalChars = `!"#$%&'()*,:;<=>?[\]^{|}` + "`\uFFFD"
for _ , r := range s {
if !unicode .IsGraphic (r ) || unicode .IsSpace (r ) || strings .ContainsRune (illegalChars , r ) {
return lit
}
}
s = strconv .Quote (s )
if s == lit .Value {
return lit
}
return &ast .BasicLit {ValuePos : lit .ValuePos , Kind : token .STRING , Value : s }
}
func (p *printer ) spec (spec ast .Spec , n int , doIndent bool ) {
switch s := spec .(type ) {
case *ast .ImportSpec :
p .setComment (s .Doc )
if s .Name != nil {
p .expr (s .Name )
p .print (blank )
}
p .expr (sanitizeImportPath (s .Path ))
p .setComment (s .Comment )
p .setPos (s .EndPos )
case *ast .ValueSpec :
if n != 1 {
p .internalError ("expected n = 1; got" , n )
}
p .setComment (s .Doc )
p .identList (s .Names , doIndent )
if s .Type != nil {
p .print (blank )
p .expr (s .Type )
}
if s .Values != nil {
p .print (blank , token .ASSIGN , blank )
p .exprList (token .NoPos , s .Values , 1 , 0 , token .NoPos , false )
}
p .setComment (s .Comment )
case *ast .TypeSpec :
p .setComment (s .Doc )
p .expr (s .Name )
if s .TypeParams != nil {
p .parameters (s .TypeParams , typeTParam )
}
if n == 1 {
p .print (blank )
} else {
p .print (vtab )
}
if s .Assign .IsValid () {
p .print (token .ASSIGN , blank )
}
p .expr (s .Type )
p .setComment (s .Comment )
default :
panic ("unreachable" )
}
}
func (p *printer ) genDecl (d *ast .GenDecl ) {
p .setComment (d .Doc )
p .setPos (d .Pos ())
p .print (d .Tok , blank )
if d .Lparen .IsValid () || len (d .Specs ) != 1 {
p .setPos (d .Lparen )
p .print (token .LPAREN )
if n := len (d .Specs ); n > 0 {
p .print (indent , formfeed )
if n > 1 && (d .Tok == token .CONST || d .Tok == token .VAR ) {
keepType := keepTypeColumn (d .Specs )
var line int
for i , s := range d .Specs {
if i > 0 {
p .linebreak (p .lineFor (s .Pos ()), 1 , ignore , p .linesFrom (line ) > 0 )
}
p .recordLine (&line )
p .valueSpec (s .(*ast .ValueSpec ), keepType [i ])
}
} else {
var line int
for i , s := range d .Specs {
if i > 0 {
p .linebreak (p .lineFor (s .Pos ()), 1 , ignore , p .linesFrom (line ) > 0 )
}
p .recordLine (&line )
p .spec (s , n , false )
}
}
p .print (unindent , formfeed )
}
p .setPos (d .Rparen )
p .print (token .RPAREN )
} else if len (d .Specs ) > 0 {
p .spec (d .Specs [0 ], 1 , true )
}
}
type sizeCounter struct {
hasNewline bool
size int
}
func (c *sizeCounter ) Write (p []byte ) (int , error ) {
if !c .hasNewline {
for _ , b := range p {
if b == '\n' || b == '\f' {
c .hasNewline = true
break
}
}
}
c .size += len (p )
return len (p ), nil
}
func (p *printer ) nodeSize (n ast .Node , maxSize int ) (size int ) {
if size , found := p .nodeSizes [n ]; found {
return size
}
size = maxSize + 1
p .nodeSizes [n ] = size
cfg := Config {Mode : RawFormat }
var counter sizeCounter
if err := cfg .fprint (&counter , p .fset , n , p .nodeSizes ); err != nil {
return
}
if counter .size <= maxSize && !counter .hasNewline {
size = counter .size
p .nodeSizes [n ] = size
}
return
}
func (p *printer ) numLines (n ast .Node ) int {
if from := n .Pos (); from .IsValid () {
if to := n .End (); to .IsValid () {
return p .lineFor (to ) - p .lineFor (from ) + 1
}
}
return infinity
}
func (p *printer ) bodySize (b *ast .BlockStmt , maxSize int ) int {
pos1 := b .Pos ()
pos2 := b .Rbrace
if pos1 .IsValid () && pos2 .IsValid () && p .lineFor (pos1 ) != p .lineFor (pos2 ) {
return maxSize + 1
}
if len (b .List ) > 5 {
return maxSize + 1
}
bodySize := p .commentSizeBefore (p .posFor (pos2 ))
for i , s := range b .List {
if bodySize > maxSize {
break
}
if i > 0 {
bodySize += 2
}
bodySize += p .nodeSize (s , maxSize )
}
return bodySize
}
func (p *printer ) funcBody (headerSize int , sep whiteSpace , b *ast .BlockStmt ) {
if b == nil {
return
}
defer func (level int ) {
p .level = level
}(p .level )
p .level = 0
const maxSize = 100
if headerSize +p .bodySize (b , maxSize ) <= maxSize {
p .print (sep )
p .setPos (b .Lbrace )
p .print (token .LBRACE )
if len (b .List ) > 0 {
p .print (blank )
for i , s := range b .List {
if i > 0 {
p .print (token .SEMICOLON , blank )
}
p .stmt (s , i == len (b .List )-1 )
}
p .print (blank )
}
p .print (noExtraLinebreak )
p .setPos (b .Rbrace )
p .print (token .RBRACE , noExtraLinebreak )
return
}
if sep != ignore {
p .print (blank )
}
p .block (b , 1 )
}
func (p *printer ) distanceFrom (startPos token .Pos , startOutCol int ) int {
if startPos .IsValid () && p .pos .IsValid () && p .posFor (startPos ).Line == p .pos .Line {
return p .out .Column - startOutCol
}
return infinity
}
func (p *printer ) funcDecl (d *ast .FuncDecl ) {
p .setComment (d .Doc )
p .setPos (d .Pos ())
p .print (token .FUNC , blank )
startCol := p .out .Column - len ("func " )
if d .Recv != nil {
p .parameters (d .Recv , funcParam )
p .print (blank )
}
p .expr (d .Name )
p .signature (d .Type )
p .funcBody (p .distanceFrom (d .Pos (), startCol ), vtab , d .Body )
}
func (p *printer ) decl (decl ast .Decl ) {
switch d := decl .(type ) {
case *ast .BadDecl :
p .setPos (d .Pos ())
p .print ("BadDecl" )
case *ast .GenDecl :
p .genDecl (d )
case *ast .FuncDecl :
p .funcDecl (d )
default :
panic ("unreachable" )
}
}
func declToken(decl ast .Decl ) (tok token .Token ) {
tok = token .ILLEGAL
switch d := decl .(type ) {
case *ast .GenDecl :
tok = d .Tok
case *ast .FuncDecl :
tok = token .FUNC
}
return
}
func (p *printer ) declList (list []ast .Decl ) {
tok := token .ILLEGAL
for _ , d := range list {
prev := tok
tok = declToken (d )
if len (p .output ) > 0 {
min := 1
if prev != tok || getDoc (d ) != nil {
min = 2
}
p .linebreak (p .lineFor (d .Pos ()), min , ignore , tok == token .FUNC && p .numLines (d ) > 1 )
}
p .decl (d )
}
}
func (p *printer ) file (src *ast .File ) {
p .setComment (src .Doc )
p .setPos (src .Pos ())
p .print (token .PACKAGE , blank )
p .expr (src .Name )
p .declList (src .Decls )
p .print (newline )
}
The pages are generated with Golds v0.7.0-preview . (GOOS=linux GOARCH=amd64)
Golds is a Go 101 project developed by Tapir Liu .
PR and bug reports are welcome and can be submitted to the issue list .
Please follow @zigo_101 (reachable from the left QR code) to get the latest news of Golds .