summaryrefslogtreecommitdiff
path: root/src/pkg/go/printer
diff options
context:
space:
mode:
Diffstat (limited to 'src/pkg/go/printer')
-rw-r--r--src/pkg/go/printer/Makefile2
-rw-r--r--src/pkg/go/printer/nodes.go257
-rw-r--r--src/pkg/go/printer/printer.go241
-rw-r--r--src/pkg/go/printer/printer_test.go24
-rw-r--r--src/pkg/go/printer/testdata/comments.golden32
-rw-r--r--src/pkg/go/printer/testdata/comments.input34
-rw-r--r--src/pkg/go/printer/testdata/declarations.golden115
-rw-r--r--src/pkg/go/printer/testdata/declarations.input83
-rw-r--r--src/pkg/go/printer/testdata/expressions.golden77
-rw-r--r--src/pkg/go/printer/testdata/expressions.input74
-rw-r--r--src/pkg/go/printer/testdata/expressions.raw77
-rw-r--r--src/pkg/go/printer/testdata/statements.golden81
-rw-r--r--src/pkg/go/printer/testdata/statements.input70
13 files changed, 907 insertions, 260 deletions
diff --git a/src/pkg/go/printer/Makefile b/src/pkg/go/printer/Makefile
index a0fe22e42..6a71efc93 100644
--- a/src/pkg/go/printer/Makefile
+++ b/src/pkg/go/printer/Makefile
@@ -2,7 +2,7 @@
# Use of this source code is governed by a BSD-style
# license that can be found in the LICENSE file.
-include ../../../Make.$(GOARCH)
+include ../../../Make.inc
TARG=go/printer
GOFILES=\
diff --git a/src/pkg/go/printer/nodes.go b/src/pkg/go/printer/nodes.go
index a98af4a2a..1ee0846f6 100644
--- a/src/pkg/go/printer/nodes.go
+++ b/src/pkg/go/printer/nodes.go
@@ -10,7 +10,6 @@ package printer
import (
"bytes"
- "container/vector"
"go/ast"
"go/token"
)
@@ -28,32 +27,30 @@ import (
// ----------------------------------------------------------------------------
// Common AST nodes.
-// Print as many newlines as necessary (but at least min and and at most
-// max newlines) to get to the current line. ws is printed before the first
-// line break. If newSection is set, the first line break is printed as
-// formfeed. Returns true if any line break was printed; returns false otherwise.
+// Print as many newlines as necessary (but at least min newlines) to get to
+// the current line. ws is printed before the first line break. If newSection
+// is set, the first line break is printed as formfeed. Returns true if any
+// line break was printed; returns false otherwise.
//
-// TODO(gri): Reconsider signature (provide position instead of line)
+// TODO(gri): linebreak may add too many lines if the next statement at "line"
+// is preceeded by comments because the computation of n assumes
+// the current position before the comment and the target position
+// after the comment. Thus, after interspersing such comments, the
+// space taken up by them is not considered to reduce the number of
+// linebreaks. At the moment there is no easy way to know about
+// future (not yet interspersed) comments in this function.
//
-func (p *printer) linebreak(line, min, max int, ws whiteSpace, newSection bool) (printedBreak bool) {
- n := line - p.pos.Line
- switch {
- case n < min:
- n = min
- case n > max:
- n = max
- }
-
+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--
- printedBreak = true
}
- }
- for ; n > 0; n-- {
- p.print(newline)
+ for ; n > 0; n-- {
+ p.print(newline)
+ }
printedBreak = true
}
return
@@ -75,7 +72,7 @@ func (p *printer) setComment(g *ast.CommentGroup) {
// for some reason there are pending comments; this
// should never happen - handle gracefully and flush
// all comments up to g, ignore anything after that
- p.flush(g.List[0].Pos(), token.ILLEGAL)
+ p.flush(p.fset.Position(g.List[0].Pos()), token.ILLEGAL)
}
p.comments[0] = g
p.cindex = 0
@@ -95,7 +92,7 @@ const (
// Sets multiLine to true if the identifier list spans multiple lines.
-// If ident is set, a multi-line identifier list is indented after the
+// If indent is set, a multi-line identifier list is indented after the
// first linebreak encountered.
func (p *printer) identList(list []*ast.Ident, indent bool, multiLine *bool) {
// convert into an expression list so we can re-use exprList formatting
@@ -107,7 +104,7 @@ func (p *printer) identList(list []*ast.Ident, indent bool, multiLine *bool) {
if !indent {
mode |= noIndent
}
- p.exprList(noPos, xlist, 1, mode, multiLine, noPos)
+ p.exprList(token.NoPos, xlist, 1, mode, multiLine, token.NoPos)
}
@@ -130,7 +127,7 @@ func (p *printer) keySize(pair *ast.KeyValueExpr) int {
// TODO(gri) Consider rewriting this to be independent of []ast.Expr
// so that we can use the algorithm for any kind of list
// (e.g., pass list via a channel over which to range).
-func (p *printer) exprList(prev token.Position, list []ast.Expr, depth int, mode exprListMode, multiLine *bool, next token.Position) {
+func (p *printer) exprList(prev0 token.Pos, list []ast.Expr, depth int, mode exprListMode, multiLine *bool, next0 token.Pos) {
if len(list) == 0 {
return
}
@@ -139,14 +136,10 @@ func (p *printer) exprList(prev token.Position, list []ast.Expr, depth int, mode
p.print(blank)
}
- line := list[0].Pos().Line
- endLine := next.Line
- if endLine == 0 {
- // TODO(gri): endLine may be incorrect as it is really the beginning
- // of the last list entry. There may be only one, very long
- // entry in which case line == endLine.
- endLine = list[len(list)-1].Pos().Line
- }
+ 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 {
// all list entries on a single line
@@ -190,7 +183,7 @@ func (p *printer) exprList(prev token.Position, list []ast.Expr, depth int, mode
// lines for them.
linebreakMin = 0
}
- if prev.IsValid() && prev.Line < line && p.linebreak(line, linebreakMin, 2, ws, true) {
+ if prev.IsValid() && prev.Line < line && p.linebreak(line, linebreakMin, ws, true) {
ws = ignore
*multiLine = true
prevBreak = 0
@@ -202,7 +195,7 @@ func (p *printer) exprList(prev token.Position, list []ast.Expr, depth int, mode
// print all list elements
for i, x := range list {
prevLine := line
- line = x.Pos().Line
+ line = p.fset.Position(x.Pos()).Line
// determine if the next linebreak, if any, needs to use formfeed:
// in general, use the entire node size to make the decision; for
@@ -252,7 +245,7 @@ func (p *printer) exprList(prev token.Position, list []ast.Expr, depth int, mode
// unless forceFF is set or there are multiple expressions on
// the same line in which case formfeed is used
// broken with a formfeed
- if p.linebreak(line, linebreakMin, 2, ws, useFF || prevBreak+1 < i) {
+ if p.linebreak(line, linebreakMin, ws, useFF || prevBreak+1 < i) {
ws = ignore
*multiLine = true
prevBreak = i
@@ -301,15 +294,27 @@ func (p *printer) exprList(prev token.Position, list []ast.Expr, depth int, mode
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, blank)
+ 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)
@@ -337,7 +342,7 @@ func identListSize(list []*ast.Ident, maxSize int) (size int) {
if i > 0 {
size += 2 // ", "
}
- size += len(x.Name())
+ size += len(x.Name)
if size >= maxSize {
break
}
@@ -366,16 +371,21 @@ func (p *printer) isOneLineFieldList(list []*ast.Field) bool {
func (p *printer) setLineComment(text string) {
- p.setComment(&ast.CommentGroup{[]*ast.Comment{&ast.Comment{noPos, []byte(text)}}})
+ p.setComment(&ast.CommentGroup{[]*ast.Comment{&ast.Comment{token.NoPos, []byte(text)}}})
}
func (p *printer) fieldList(fields *ast.FieldList, isIncomplete bool, ctxt exprContext) {
+ p.nesting++
+ defer func() {
+ p.nesting--
+ }()
+
lbrace := fields.Opening
list := fields.List
rbrace := fields.Closing
- if !isIncomplete && !p.commentBefore(rbrace) {
+ if !isIncomplete && !p.commentBefore(p.fset.Position(rbrace)) {
// possibly a one-line struct/interface
if len(list) == 0 {
// no blank between keyword and {} in this case
@@ -413,7 +423,7 @@ func (p *printer) fieldList(fields *ast.FieldList, isIncomplete bool, ctxt exprC
var ml bool
for i, f := range list {
if i > 0 {
- p.linebreak(f.Pos().Line, 1, 2, ignore, ml)
+ p.linebreak(p.fset.Position(f.Pos()).Line, 1, ignore, ml)
}
ml = false
extraTabs := 0
@@ -448,7 +458,7 @@ func (p *printer) fieldList(fields *ast.FieldList, isIncomplete bool, ctxt exprC
if len(list) > 0 {
p.print(formfeed)
}
- p.flush(rbrace, token.RBRACE) // make sure we don't loose the last line comment
+ p.flush(p.fset.Position(rbrace), token.RBRACE) // make sure we don't loose the last line comment
p.setLineComment("// contains unexported fields")
}
@@ -457,7 +467,7 @@ func (p *printer) fieldList(fields *ast.FieldList, isIncomplete bool, ctxt exprC
var ml bool
for i, f := range list {
if i > 0 {
- p.linebreak(f.Pos().Line, 1, 2, ignore, ml)
+ p.linebreak(p.fset.Position(f.Pos()).Line, 1, ignore, ml)
}
ml = false
p.setComment(f.Doc)
@@ -475,7 +485,7 @@ func (p *printer) fieldList(fields *ast.FieldList, isIncomplete bool, ctxt exprC
if len(list) > 0 {
p.print(formfeed)
}
- p.flush(rbrace, token.RBRACE) // make sure we don't loose the last line comment
+ p.flush(p.fset.Position(rbrace), token.RBRACE) // make sure we don't loose the last line comment
p.setLineComment("// contains unexported methods")
}
@@ -540,7 +550,7 @@ func walkBinary(e *ast.BinaryExpr) (has5, has6 bool, maxProblem int) {
case *ast.UnaryExpr:
switch e.Op.String() + r.Op.String() {
- case "/*":
+ case "/*", "&&", "&^":
maxProblem = 6
case "++", "--":
if maxProblem < 5 {
@@ -609,11 +619,14 @@ func reduceDepth(depth int) int {
// 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):
//
-// &^ 7
// /* 7
+// && 7
+// &^ 7
// ++ 6
// -- 6
//
+// (Comparison operators always have spaces around them.)
+//
// 2) If there is a mix of level 6 and level 5 operators, then the cutoff
// is 6 (use spaces to distinguish precedence) in Normal mode
// and 5 (never use spaces) in Compact mode.
@@ -643,12 +656,12 @@ func (p *printer) binaryExpr(x *ast.BinaryExpr, prec1, cutoff, depth int, multiL
p.print(blank)
}
xline := p.pos.Line // before the operator (it may be on the next line!)
- yline := x.Y.Pos().Line
+ yline := p.fset.Position(x.Y.Pos()).Line
p.print(x.OpPos, x.Op)
if xline != yline && xline > 0 && yline > 0 {
// at least one line break, but respect an extra empty line
// in the source
- if p.linebreak(yline, 1, 2, ws, true) {
+ if p.linebreak(yline, 1, ws, true) {
ws = ignore
*multiLine = true
printBlank = false // no blank after line break
@@ -683,19 +696,19 @@ func splitSelector(expr ast.Expr) (body, suffix ast.Expr) {
case *ast.CallExpr:
body, suffix = splitSelector(x.Fun)
if body != nil {
- suffix = &ast.CallExpr{suffix, x.Lparen, x.Args, x.Rparen}
+ 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.Index}
+ 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{suffix, x.Index, x.End}
+ suffix = &ast.SliceExpr{suffix, x.Lbrack, x.Low, x.High, x.Rbrack}
return
}
case *ast.TypeAssertExpr:
@@ -712,23 +725,20 @@ func splitSelector(expr ast.Expr) (body, suffix ast.Expr) {
// Convert an expression into an expression list split at the periods of
// selector expressions.
-func selectorExprList(expr ast.Expr) []ast.Expr {
+func selectorExprList(expr ast.Expr) (list []ast.Expr) {
// split expression
- var list vector.Vector
for expr != nil {
var suffix ast.Expr
expr, suffix = splitSelector(expr)
- list.Push(suffix)
+ list = append(list, suffix)
}
- // convert expression list
- result := make([]ast.Expr, len(list))
- i := len(result)
- for _, x := range list {
- i--
- result[i] = x.(ast.Expr)
+ // reverse list
+ for i, j := 0, len(list)-1; i < j; i, j = i+1, j-1 {
+ list[i], list[j] = list[j], list[i]
}
- return result
+
+ return
}
@@ -791,16 +801,22 @@ func (p *printer) expr1(expr ast.Expr, prec1, depth int, ctxt exprContext, multi
case *ast.FuncLit:
p.expr(x.Type, multiLine)
- p.funcBody(x.Body, distance(x.Type.Pos(), p.pos), true, multiLine)
+ p.funcBody(x.Body, p.distance(x.Type.Pos(), p.pos), true, multiLine)
case *ast.ParenExpr:
- p.print(token.LPAREN)
- p.expr0(x.X, reduceDepth(depth), multiLine) // parentheses undo one level of depth
- p.print(x.Rparen, token.RPAREN)
+ if _, hasParens := x.X.(*ast.ParenExpr); hasParens {
+ // don't print parentheses around an already parenthesized expression
+ // TODO(gri) consider making this more general and incorporate precedence levels
+ p.expr0(x.X, reduceDepth(depth), multiLine) // parentheses undo one level of depth
+ } else {
+ p.print(token.LPAREN)
+ p.expr0(x.X, reduceDepth(depth), multiLine) // parentheses undo one level of depth
+ p.print(x.Rparen, token.RPAREN)
+ }
case *ast.SelectorExpr:
parts := selectorExprList(expr)
- p.exprList(noPos, parts, depth, periodSep, multiLine, noPos)
+ p.exprList(token.NoPos, parts, depth, periodSep, multiLine, token.NoPos)
case *ast.TypeAssertExpr:
p.expr1(x.X, token.HighestPrec, depth, 0, multiLine)
@@ -815,25 +831,27 @@ func (p *printer) expr1(expr ast.Expr, prec1, depth int, ctxt exprContext, multi
case *ast.IndexExpr:
// TODO(gri): should treat[] like parentheses and undo one level of depth
p.expr1(x.X, token.HighestPrec, 1, 0, multiLine)
- p.print(token.LBRACK)
+ p.print(x.Lbrack, token.LBRACK)
p.expr0(x.Index, depth+1, multiLine)
- p.print(token.RBRACK)
+ p.print(x.Rbrack, token.RBRACK)
case *ast.SliceExpr:
// TODO(gri): should treat[] like parentheses and undo one level of depth
p.expr1(x.X, token.HighestPrec, 1, 0, multiLine)
- p.print(token.LBRACK)
- p.expr0(x.Index, depth+1, multiLine)
+ p.print(x.Lbrack, token.LBRACK)
+ if x.Low != nil {
+ p.expr0(x.Low, depth+1, multiLine)
+ }
// blanks around ":" if both sides exist and either side is a binary expression
- if depth <= 1 && x.End != nil && (isBinary(x.Index) || isBinary(x.End)) {
+ 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.End != nil {
- p.expr0(x.End, depth+1, multiLine)
+ if x.High != nil {
+ p.expr0(x.High, depth+1, multiLine)
}
- p.print(token.RBRACK)
+ p.print(x.Rbrack, token.RBRACK)
case *ast.CallExpr:
if len(x.Args) > 1 {
@@ -842,10 +860,16 @@ func (p *printer) expr1(expr ast.Expr, prec1, depth int, ctxt exprContext, multi
p.expr1(x.Fun, token.HighestPrec, depth, 0, 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:
- p.expr1(x.Type, token.HighestPrec, depth, compositeLit, multiLine)
+ // composite literal elements that are composite literals themselves may have the type omitted
+ if x.Type != nil {
+ p.expr1(x.Type, token.HighestPrec, depth, compositeLit, multiLine)
+ }
p.print(x.Lbrace, token.LBRACE)
p.exprList(x.Lbrace, x.Elts, 1, commaSep|commaTerm, multiLine, x.Rbrace)
p.print(x.Rbrace, token.RBRACE)
@@ -917,8 +941,6 @@ func (p *printer) expr(x ast.Expr, multiLine *bool) {
// ----------------------------------------------------------------------------
// Statements
-const maxStmtNewlines = 2 // maximum number of newlines between 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.
@@ -931,7 +953,7 @@ func (p *printer) stmtList(list []ast.Stmt, _indent int, nextIsRBrace bool) {
for i, s := range list {
// _indent == 0 only for lists of switch/select case clauses;
// in those cases each clause is a new section
- p.linebreak(s.Pos().Line, 1, maxStmtNewlines, ignore, i == 0 || _indent == 0 || multiLine)
+ 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)
}
@@ -945,7 +967,7 @@ func (p *printer) stmtList(list []ast.Stmt, _indent int, nextIsRBrace bool) {
func (p *printer) block(s *ast.BlockStmt, indent int) {
p.print(s.Pos(), token.LBRACE)
p.stmtList(s.List, indent, true)
- p.linebreak(s.Rbrace.Line, 1, maxStmtNewlines, ignore, true)
+ p.linebreak(p.fset.Position(s.Rbrace).Line, 1, ignore, true)
p.print(s.Rbrace, token.RBRACE)
}
@@ -961,16 +983,27 @@ func isTypeName(x ast.Expr) bool {
}
-// TODO(gri): Decide if this should be used more broadly. The printing code
-// knows when to insert parentheses for precedence reasons, but
-// need to be careful to keep them around type expressions.
-func stripParens(x ast.Expr, inControlClause bool) ast.Expr {
- for px, hasParens := x.(*ast.ParenExpr); hasParens; px, hasParens = x.(*ast.ParenExpr) {
- x = px.X
- if cx, isCompositeLit := x.(*ast.CompositeLit); inControlClause && isCompositeLit && isTypeName(cx.Type) {
- // composite literals inside control clauses need parens if they start with a type name;
- // don't strip innermost layer
- return px
+func stripParens(x ast.Expr) ast.Expr {
+ if px, strip := x.(*ast.ParenExpr); strip {
+ // parentheses must not be stripped if there are any
+ // unparenthesized composite literals starting with
+ // a type name
+ ast.Inspect(px.X, func(node ast.Node) bool {
+ switch x := node.(type) {
+ case *ast.ParenExpr:
+ // parentheses protect enclosed composite literals
+ return false
+ case *ast.CompositeLit:
+ if isTypeName(x.Type) {
+ strip = false // do not strip parentheses
+ }
+ return false
+ }
+ // in all other cases, keep inspecting
+ return true
+ })
+ if strip {
+ return stripParens(px.X)
}
}
return x
@@ -983,7 +1016,7 @@ func (p *printer) controlClause(isForStmt bool, init ast.Stmt, expr ast.Expr, po
if init == nil && post == nil {
// no semicolons required
if expr != nil {
- p.expr(stripParens(expr, true), ignoreMultiLine)
+ p.expr(stripParens(expr), ignoreMultiLine)
needsBlank = true
}
} else {
@@ -994,7 +1027,7 @@ func (p *printer) controlClause(isForStmt bool, init ast.Stmt, expr ast.Expr, po
}
p.print(token.SEMICOLON, blank)
if expr != nil {
- p.expr(stripParens(expr, true), ignoreMultiLine)
+ p.expr(stripParens(expr), ignoreMultiLine)
needsBlank = true
}
if isForStmt {
@@ -1032,14 +1065,14 @@ func (p *printer) stmt(stmt ast.Stmt, nextIsRBrace bool, multiLine *bool) {
// between (see writeWhitespace)
p.print(unindent)
p.expr(s.Label, multiLine)
- p.print(token.COLON, indent)
+ p.print(s.Colon, token.COLON, indent)
if e, isEmpty := s.Stmt.(*ast.EmptyStmt); isEmpty {
if !nextIsRBrace {
p.print(newline, e.Pos(), token.SEMICOLON)
break
}
} else {
- p.print(newline)
+ p.linebreak(p.fset.Position(s.Stmt.Pos()).Line, 1, ignore, true)
}
p.stmt(s.Stmt, nextIsRBrace, multiLine)
@@ -1050,7 +1083,7 @@ func (p *printer) stmt(stmt ast.Stmt, nextIsRBrace bool, multiLine *bool) {
case *ast.IncDecStmt:
const depth = 1
p.expr0(s.X, depth+1, multiLine)
- p.print(s.Tok)
+ p.print(s.TokPos, s.Tok)
case *ast.AssignStmt:
var depth = 1
@@ -1059,7 +1092,7 @@ func (p *printer) stmt(stmt ast.Stmt, nextIsRBrace bool, multiLine *bool) {
}
p.exprList(s.Pos(), s.Lhs, depth, commaSep, multiLine, s.TokPos)
p.print(blank, s.TokPos, s.Tok)
- p.exprList(s.TokPos, s.Rhs, depth, blankStart|commaSep, multiLine, noPos)
+ p.exprList(s.TokPos, s.Rhs, depth, blankStart|commaSep, multiLine, token.NoPos)
case *ast.GoStmt:
p.print(token.GO, blank)
@@ -1072,7 +1105,7 @@ func (p *printer) stmt(stmt ast.Stmt, nextIsRBrace bool, multiLine *bool) {
case *ast.ReturnStmt:
p.print(token.RETURN)
if s.Results != nil {
- p.exprList(s.Pos(), s.Results, 1, blankStart|commaSep, multiLine, noPos)
+ p.exprList(s.Pos(), s.Results, 1, blankStart|commaSep, multiLine, token.NoPos)
}
case *ast.BranchStmt:
@@ -1175,7 +1208,7 @@ func (p *printer) stmt(stmt ast.Stmt, nextIsRBrace bool, multiLine *bool) {
p.expr(s.Value, multiLine)
}
p.print(blank, s.TokPos, s.Tok, blank, token.RANGE, blank)
- p.expr(stripParens(s.X, true), multiLine)
+ p.expr(stripParens(s.X), multiLine)
p.print(blank)
p.block(s.Body, 1)
*multiLine = true
@@ -1191,25 +1224,25 @@ func (p *printer) stmt(stmt ast.Stmt, nextIsRBrace bool, multiLine *bool) {
// ----------------------------------------------------------------------------
// Declarations
-// The parameter n is the number of specs in the group. If indent is set,
+// The parameter n is the number of specs in the group. If doIndent is set,
// multi-line identifier lists in the spec are indented when the first
// linebreak is encountered.
// Sets multiLine to true if the spec spans multiple lines.
//
-func (p *printer) spec(spec ast.Spec, n int, indent bool, multiLine *bool) {
+func (p *printer) spec(spec ast.Spec, n int, doIndent bool, multiLine *bool) {
switch s := spec.(type) {
case *ast.ImportSpec:
p.setComment(s.Doc)
if s.Name != nil {
p.expr(s.Name, multiLine)
- p.print(blank)
+ p.print(vtab)
}
p.expr(s.Path, multiLine)
p.setComment(s.Comment)
case *ast.ValueSpec:
p.setComment(s.Doc)
- p.identList(s.Names, indent, multiLine) // always present
+ p.identList(s.Names, doIndent, multiLine) // always present
if n == 1 {
if s.Type != nil {
p.print(blank)
@@ -1217,7 +1250,7 @@ func (p *printer) spec(spec ast.Spec, n int, indent bool, multiLine *bool) {
}
if s.Values != nil {
p.print(blank, token.ASSIGN)
- p.exprList(noPos, s.Values, 1, blankStart|commaSep, multiLine, noPos)
+ p.exprList(token.NoPos, s.Values, 1, blankStart|commaSep, multiLine, token.NoPos)
}
p.setComment(s.Comment)
@@ -1230,7 +1263,7 @@ func (p *printer) spec(spec ast.Spec, n int, indent bool, multiLine *bool) {
}
if s.Values != nil {
p.print(vtab, token.ASSIGN)
- p.exprList(noPos, s.Values, 1, blankStart|commaSep, multiLine, noPos)
+ p.exprList(token.NoPos, s.Values, 1, blankStart|commaSep, multiLine, token.NoPos)
extraTabs--
}
if s.Comment != nil {
@@ -1271,7 +1304,7 @@ func (p *printer) genDecl(d *ast.GenDecl, multiLine *bool) {
var ml bool
for i, s := range d.Specs {
if i > 0 {
- p.linebreak(s.Pos().Line, 1, 2, ignore, ml)
+ p.linebreak(p.fset.Position(s.Pos()).Line, 1, ignore, ml)
}
ml = false
p.spec(s, len(d.Specs), false, &ml)
@@ -1300,7 +1333,7 @@ func (p *printer) nodeSize(n ast.Node, maxSize int) (size int) {
// in RawFormat
cfg := Config{Mode: RawFormat}
var buf bytes.Buffer
- if _, err := cfg.Fprint(&buf, n); err != nil {
+ if _, err := cfg.Fprint(&buf, p.fset, n); err != nil {
return
}
if buf.Len() <= maxSize {
@@ -1318,11 +1351,11 @@ func (p *printer) nodeSize(n ast.Node, maxSize int) (size int) {
func (p *printer) isOneLineFunc(b *ast.BlockStmt, headerSize int) bool {
pos1 := b.Pos()
pos2 := b.Rbrace
- if pos1.IsValid() && pos2.IsValid() && pos1.Line != pos2.Line {
+ if pos1.IsValid() && pos2.IsValid() && p.fset.Position(pos1).Line != p.fset.Position(pos2).Line {
// opening and closing brace are on different lines - don't make it a one-liner
return false
}
- if len(b.List) > 5 || p.commentBefore(pos2) {
+ if len(b.List) > 5 || p.commentBefore(p.fset.Position(pos2)) {
// too many statements or there is a comment inside - don't make it a one-liner
return false
}
@@ -1345,6 +1378,11 @@ func (p *printer) funcBody(b *ast.BlockStmt, headerSize int, isLit bool, multiLi
return
}
+ p.nesting++
+ defer func() {
+ p.nesting--
+ }()
+
if p.isOneLineFunc(b, headerSize) {
sep := vtab
if isLit {
@@ -1374,7 +1412,8 @@ func (p *printer) funcBody(b *ast.BlockStmt, headerSize int, isLit bool, multiLi
// distance returns the column difference between from and to if both
// are on the same line; if they are on different lines (or unknown)
// the result is infinity.
-func distance(from, to token.Position) int {
+func (p *printer) distance(from0 token.Pos, to token.Position) int {
+ from := p.fset.Position(from0)
if from.IsValid() && to.IsValid() && from.Line == to.Line {
return to.Column - from.Column
}
@@ -1392,7 +1431,7 @@ func (p *printer) funcDecl(d *ast.FuncDecl, multiLine *bool) {
}
p.expr(d.Name, multiLine)
p.signature(d.Type.Params, d.Type.Results, multiLine)
- p.funcBody(d.Body, distance(d.Pos(), p.pos), false, multiLine)
+ p.funcBody(d.Body, p.distance(d.Pos(), p.pos), false, multiLine)
}
@@ -1414,8 +1453,6 @@ func (p *printer) decl(decl ast.Decl, multiLine *bool) {
// ----------------------------------------------------------------------------
// Files
-const maxDeclNewlines = 3 // maximum number of newlines between declarations
-
func declToken(decl ast.Decl) (tok token.Token) {
tok = token.ILLEGAL
switch d := decl.(type) {
@@ -1444,7 +1481,7 @@ func (p *printer) file(src *ast.File) {
if prev != tok {
min = 2
}
- p.linebreak(d.Pos().Line, min, maxDeclNewlines, ignore, false)
+ p.linebreak(p.fset.Position(d.Pos()).Line, min, ignore, false)
p.decl(d, ignoreMultiLine)
}
}
diff --git a/src/pkg/go/printer/printer.go b/src/pkg/go/printer/printer.go
index 53632c83d..a4ddad50e 100644
--- a/src/pkg/go/printer/printer.go
+++ b/src/pkg/go/printer/printer.go
@@ -18,10 +18,7 @@ import (
)
-const (
- debug = false // enable for debugging
- maxNewlines = 3 // maximum vertical white space
-)
+const debug = false // enable for debugging
type whiteSpace int
@@ -41,8 +38,8 @@ var (
esc = []byte{tabwriter.Escape}
htab = []byte{'\t'}
htabs = []byte("\t\t\t\t\t\t\t\t")
- newlines = []byte("\n\n\n\n\n\n\n\n") // more than maxNewlines
- formfeeds = []byte("\f\f\f\f\f\f\f\f") // more than maxNewlines
+ newlines = []byte("\n\n\n\n\n\n\n\n") // more than the max determined by nlines
+ formfeeds = []byte("\f\f\f\f\f\f\f\f") // more than the max determined by nlines
esc_quot = []byte("&#34;") // shorter than "&quot;"
esc_apos = []byte("&#39;") // shorter than "&apos;"
@@ -65,12 +62,15 @@ type printer struct {
// Configuration (does not change after initialization)
output io.Writer
Config
+ fset *token.FileSet
errors chan os.Error
// Current state
- written int // number of bytes written
- indent int // current indentation
- escape bool // true if in escape sequence
+ nesting int // nesting level (0: top-level (package scope), >0: functions/decls.)
+ written int // number of bytes written
+ indent int // current indentation
+ escape bool // true if in escape sequence
+ lastTok token.Token // the last token printed (token.ILLEGAL if it's whitespace)
// Buffered whitespace
buffer []whiteSpace
@@ -95,9 +95,10 @@ type printer struct {
}
-func (p *printer) init(output io.Writer, cfg *Config) {
+func (p *printer) init(output io.Writer, cfg *Config, fset *token.FileSet) {
p.output = output
p.Config = *cfg
+ p.fset = fset
p.errors = make(chan os.Error)
p.buffer = make([]whiteSpace, 0, 16) // whitespace sequences are short
}
@@ -106,12 +107,31 @@ func (p *printer) init(output io.Writer, cfg *Config) {
func (p *printer) internalError(msg ...interface{}) {
if debug {
fmt.Print(p.pos.String() + ": ")
- fmt.Println(msg)
+ fmt.Println(msg...)
panic("go/printer")
}
}
+// nlines returns the adjusted number of linebreaks given the desired number
+// of breaks n such that min <= result <= max where max depends on the current
+// nesting level.
+//
+func (p *printer) nlines(n, min int) int {
+ if n < min {
+ return min
+ }
+ max := 3 // max. number of newlines at the top level (p.nesting == 0)
+ if p.nesting > 0 {
+ max = 2 // max. number of newlines everywhere else
+ }
+ if n > max {
+ return max
+ }
+ return n
+}
+
+
// write0 writes raw (uninterpreted) data to p.output and handles errors.
// write0 does not indent after newlines, and does not HTML-escape or update p.pos.
//
@@ -192,6 +212,11 @@ func (p *printer) write(data []byte) {
case tabwriter.Escape:
p.escape = !p.escape
+
+ // ignore escape chars introduced by printer - they are
+ // invisible and must not affect p.pos (was issue #1089)
+ p.pos.Offset--
+ p.pos.Column--
}
}
@@ -207,9 +232,7 @@ func (p *printer) write(data []byte) {
func (p *printer) writeNewlines(n int, useFF bool) {
if n > 0 {
- if n > maxNewlines {
- n = maxNewlines
- }
+ n = p.nlines(n, 0)
if useFF {
p.write(formfeeds[0:n])
} else {
@@ -292,8 +315,8 @@ func (p *printer) writeCommentPrefix(pos, next token.Position, isFirst, isKeywor
}
if pos.IsValid() && pos.Filename != p.last.Filename {
- // comment in a different file - separate with newlines
- p.writeNewlines(maxNewlines, true)
+ // comment in a different file - separate with newlines (writeNewlines will limit the number)
+ p.writeNewlines(10, true)
return
}
@@ -380,7 +403,6 @@ func (p *printer) writeCommentPrefix(pos, next token.Position, isFirst, isKeywor
func (p *printer) writeCommentLine(comment *ast.Comment, pos token.Position, line []byte) {
// line must pass through unchanged, bracket it with tabwriter.Escape
- esc := []byte{tabwriter.Escape}
line = bytes.Join([][]byte{esc, line, esc}, nil)
// apply styler, if any
@@ -576,7 +598,7 @@ func (p *printer) writeComment(comment *ast.Comment) {
// shortcut common case of //-style comments
if text[1] == '/' {
- p.writeCommentLine(comment, comment.Pos(), text)
+ p.writeCommentLine(comment, p.fset.Position(comment.Pos()), text)
return
}
@@ -588,7 +610,7 @@ func (p *printer) writeComment(comment *ast.Comment) {
// write comment lines, separated by formfeed,
// without a line break after the last line
linebreak := formfeeds[0:1]
- pos := comment.Pos()
+ pos := p.fset.Position(comment.Pos())
for i, line := range lines {
if i > 0 {
p.write(linebreak)
@@ -649,14 +671,14 @@ func (p *printer) intersperseComments(next token.Position, tok token.Token) (dro
var last *ast.Comment
for ; p.commentBefore(next); p.cindex++ {
for _, c := range p.comments[p.cindex].List {
- p.writeCommentPrefix(c.Pos(), next, last == nil, tok.IsKeyword())
+ p.writeCommentPrefix(p.fset.Position(c.Pos()), next, last == nil, tok.IsKeyword())
p.writeComment(c)
last = c
}
}
if last != nil {
- if last.Text[1] == '*' && last.Pos().Line == next.Line {
+ if last.Text[1] == '*' && p.fset.Position(last.Pos()).Line == next.Line {
// the last comment is a /*-style comment and the next item
// follows on the same line: separate with an extra blank
p.write([]byte{' '})
@@ -726,6 +748,26 @@ func (p *printer) writeWhitespace(n int) {
// ----------------------------------------------------------------------------
// Printing interface
+
+func mayCombine(prev token.Token, next byte) (b bool) {
+ switch prev {
+ case token.INT:
+ b = next == '.' // 1.
+ case token.ADD:
+ b = next == '+' // ++
+ case token.SUB:
+ b = next == '-' // --
+ case token.QUO:
+ b = next == '*' // /*
+ case token.LSS:
+ b = next == '-' || next == '<' // <- or <<
+ case token.AND:
+ b = next == '&' || next == '^' // && 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
@@ -743,6 +785,7 @@ func (p *printer) print(args ...interface{}) {
var data []byte
var tag HTMLTag
var tok token.Token
+
switch x := f.(type) {
case whiteSpace:
if x == ignore {
@@ -765,7 +808,7 @@ func (p *printer) print(args ...interface{}) {
if p.Styler != nil {
data, tag = p.Styler.Ident(x)
} else {
- data = []byte(x.Name())
+ data = []byte(x.Name)
}
tok = token.IDENT
case *ast.BasicLit:
@@ -779,22 +822,38 @@ func (p *printer) print(args ...interface{}) {
// bytes since they do not appear in legal UTF-8 sequences)
// TODO(gri): do this more efficiently.
data = []byte("\xff" + string(data) + "\xff")
- tok = token.INT // representing all literal tokens
+ tok = x.Kind
case token.Token:
+ s := x.String()
+ if mayCombine(p.lastTok, s[0]) {
+ // 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(p.buffer) != 0 {
+ p.internalError("whitespace buffer not empty")
+ }
+ p.buffer = p.buffer[0:1]
+ p.buffer[0] = ' '
+ }
if p.Styler != nil {
data, tag = p.Styler.Token(x)
} else {
- data = []byte(x.String())
+ data = []byte(s)
}
tok = x
- case token.Position:
+ case token.Pos:
if x.IsValid() {
- next = x // accurate position of next item
+ next = p.fset.Position(x) // accurate position of next item
}
+ tok = p.lastTok
default:
fmt.Fprintf(os.Stderr, "print: unsupported argument type %T\n", f)
panic("go/printer type")
}
+ p.lastTok = tok
p.pos = next
if data != nil {
@@ -816,11 +875,11 @@ func (p *printer) print(args ...interface{}) {
// before the next position in the source code.
//
func (p *printer) commentBefore(next token.Position) bool {
- return p.cindex < len(p.comments) && p.comments[p.cindex].List[0].Pos().Offset < next.Offset
+ return p.cindex < len(p.comments) && p.fset.Position(p.comments[p.cindex].List[0].Pos()).Offset < next.Offset
}
-// Flush prints any pending comments and whitespace occuring
+// Flush prints any pending comments and whitespace occurring
// textually before the position of the next token tok. Flush
// returns true if a pending formfeed character was dropped
// from the whitespace buffer as a result of interspersing
@@ -844,81 +903,85 @@ func (p *printer) flush(next token.Position, tok token.Token) (droppedFF bool) {
// 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).
+// is used). Text bracketed by tabwriter.Escape characters is passed
+// through unchanged.
//
type trimmer struct {
output io.Writer
- buf bytes.Buffer
+ space bytes.Buffer
+ state int
}
-// Design note: It is tempting to eliminate extra blanks occuring in
+// trimmer is implemented as a state machine.
+// It can be in one of the following states:
+const (
+ inSpace = iota
+ inEscape
+ inText
+)
+
+
+// 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.
func (p *trimmer) Write(data []byte) (n int, err os.Error) {
- // m < 0: no unwritten data except for whitespace
- // m >= 0: data[m:n] unwritten and no whitespace
- m := 0
- if p.buf.Len() > 0 {
- m = -1
- }
-
+ m := 0 // if p.state != inSpace, data[m:n] is unwritten
var b byte
for n, b = range data {
- switch b {
- default:
- // write any pending whitespace
- if m < 0 {
- if _, err = p.output.Write(p.buf.Bytes()); err != nil {
- return
- }
- p.buf.Reset()
- m = n
- }
-
- case '\v':
+ if b == '\v' {
b = '\t' // convert to htab
- fallthrough
-
- case '\t', ' ', tabwriter.Escape:
- // write any pending (non-whitespace) data
- if m >= 0 {
- if _, err = p.output.Write(data[m:n]); err != nil {
- return
- }
- m = -1
+ }
+ switch p.state {
+ case inSpace:
+ switch b {
+ case '\t', ' ':
+ p.space.WriteByte(b) // WriteByte returns no errors
+ case '\f', '\n':
+ p.space.Reset() // discard trailing space
+ _, err = p.output.Write(newlines[0:1]) // write newline
+ case tabwriter.Escape:
+ _, err = p.output.Write(p.space.Bytes())
+ p.space.Reset()
+ p.state = inEscape
+ m = n + 1 // drop tabwriter.Escape
+ default:
+ _, err = p.output.Write(p.space.Bytes())
+ p.space.Reset()
+ p.state = inText
+ m = n
}
- // collect whitespace but discard tabwriter.Escapes.
- if b != tabwriter.Escape {
- p.buf.WriteByte(b) // WriteByte returns no errors
+ case inEscape:
+ if b == tabwriter.Escape {
+ _, err = p.output.Write(data[m:n])
+ p.state = inSpace
}
-
- case '\f', '\n':
- // discard whitespace
- p.buf.Reset()
- // write any pending (non-whitespace) data
- if m >= 0 {
- if _, err = p.output.Write(data[m:n]); err != nil {
- return
- }
- m = -1
- }
- // convert formfeed into newline
- if _, err = p.output.Write(newlines[0:1]); err != nil {
- return
+ case inText:
+ switch b {
+ case '\t', ' ':
+ _, err = p.output.Write(data[m:n])
+ p.state = inSpace
+ p.space.WriteByte(b) // WriteByte returns no errors
+ case '\f':
+ data[n] = '\n' // convert to newline
+ case tabwriter.Escape:
+ _, err = p.output.Write(data[m:n])
+ p.state = inEscape
+ m = n + 1 // drop tabwriter.Escape
}
}
+ if err != nil {
+ return
+ }
}
n = len(data)
- // write any pending non-whitespace
- if m >= 0 {
- if _, err = p.output.Write(data[m:n]); err != nil {
- return
- }
+ if p.state != inSpace {
+ _, err = p.output.Write(data[m:n])
+ p.state = inSpace
}
return
@@ -965,10 +1028,11 @@ type Config struct {
// Fprint "pretty-prints" an AST node to output and returns the number
// of bytes written and an error (if any) for a given configuration cfg.
+// Position information is interpreted relative to the file set fset.
// The node type must be *ast.File, or assignment-compatible to ast.Expr,
// ast.Decl, ast.Spec, or ast.Stmt.
//
-func (cfg *Config) Fprint(output io.Writer, node interface{}) (int, os.Error) {
+func (cfg *Config) Fprint(output io.Writer, fset *token.FileSet, node interface{}) (int, os.Error) {
// 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
@@ -1000,13 +1064,15 @@ func (cfg *Config) Fprint(output io.Writer, node interface{}) (int, os.Error) {
// setup printer and print node
var p printer
- p.init(output, cfg)
+ p.init(output, cfg, fset)
go func() {
switch n := node.(type) {
case ast.Expr:
+ p.nesting = 1
p.useNodeComments = true
p.expr(n, ignoreMultiLine)
case ast.Stmt:
+ p.nesting = 1
p.useNodeComments = true
// A labeled statement will un-indent to position the
// label. Set indent to 1 so we don't get indent "underflow".
@@ -1015,17 +1081,20 @@ func (cfg *Config) Fprint(output io.Writer, node interface{}) (int, os.Error) {
}
p.stmt(n, false, ignoreMultiLine)
case ast.Decl:
+ p.nesting = 1
p.useNodeComments = true
p.decl(n, ignoreMultiLine)
case ast.Spec:
+ p.nesting = 1
p.useNodeComments = true
p.spec(n, 1, false, ignoreMultiLine)
case *ast.File:
+ p.nesting = 0
p.comments = n.Comments
p.useNodeComments = n.Comments == nil
p.file(n)
default:
- p.errors <- os.NewError(fmt.Sprintf("printer.Fprint: unsupported node type %T", n))
+ p.errors <- fmt.Errorf("printer.Fprint: unsupported node type %T", n)
runtime.Goexit()
}
p.flush(token.Position{Offset: infinity, Line: infinity}, token.EOF)
@@ -1045,7 +1114,7 @@ func (cfg *Config) Fprint(output io.Writer, node interface{}) (int, os.Error) {
// Fprint "pretty-prints" an AST node to output.
// It calls Config.Fprint with default settings.
//
-func Fprint(output io.Writer, node interface{}) os.Error {
- _, err := (&Config{Tabwidth: 8}).Fprint(output, node) // don't care about number of bytes written
+func Fprint(output io.Writer, fset *token.FileSet, node interface{}) os.Error {
+ _, err := (&Config{Tabwidth: 8}).Fprint(output, fset, node) // don't care about number of bytes written
return err
}
diff --git a/src/pkg/go/printer/printer_test.go b/src/pkg/go/printer/printer_test.go
index a5de3774a..c66471b92 100644
--- a/src/pkg/go/printer/printer_test.go
+++ b/src/pkg/go/printer/printer_test.go
@@ -10,6 +10,7 @@ import (
"io/ioutil"
"go/ast"
"go/parser"
+ "go/token"
"path"
"testing"
)
@@ -24,6 +25,9 @@ const (
var update = flag.Bool("update", false, "update golden files")
+var fset = token.NewFileSet()
+
+
func lineString(text []byte, i int) string {
i0 := i
for i < len(text) && text[i] != '\n' {
@@ -43,7 +47,7 @@ const (
func check(t *testing.T, source, golden string, mode checkMode) {
// parse source
- prog, err := parser.ParseFile(source, nil, nil, parser.ParseComments)
+ prog, err := parser.ParseFile(fset, source, nil, parser.ParseComments)
if err != nil {
t.Error(err)
return
@@ -63,7 +67,7 @@ func check(t *testing.T, source, golden string, mode checkMode) {
// format source
var buf bytes.Buffer
- if _, err := cfg.Fprint(&buf, prog); err != nil {
+ if _, err := cfg.Fprint(&buf, fset, prog); err != nil {
t.Error(err)
}
res := buf.Bytes()
@@ -112,14 +116,14 @@ type entry struct {
// Use gotest -update to create/update the respective golden files.
var data = []entry{
- entry{"empty.input", "empty.golden", 0},
- entry{"comments.input", "comments.golden", 0},
- entry{"comments.input", "comments.x", export},
- entry{"linebreaks.input", "linebreaks.golden", 0},
- entry{"expressions.input", "expressions.golden", 0},
- entry{"expressions.input", "expressions.raw", rawFormat},
- entry{"declarations.input", "declarations.golden", 0},
- entry{"statements.input", "statements.golden", 0},
+ {"empty.input", "empty.golden", 0},
+ {"comments.input", "comments.golden", 0},
+ {"comments.input", "comments.x", export},
+ {"linebreaks.input", "linebreaks.golden", 0},
+ {"expressions.input", "expressions.golden", 0},
+ {"expressions.input", "expressions.raw", rawFormat},
+ {"declarations.input", "declarations.golden", 0},
+ {"statements.input", "statements.golden", 0},
}
diff --git a/src/pkg/go/printer/testdata/comments.golden b/src/pkg/go/printer/testdata/comments.golden
index 4c9f71d95..200ea332f 100644
--- a/src/pkg/go/printer/testdata/comments.golden
+++ b/src/pkg/go/printer/testdata/comments.golden
@@ -431,6 +431,38 @@ func _() {
}
+// Comments immediately adjacent to punctuation (for which the go/printer
+// may obly have estimated position information) must remain after the punctuation.
+func _() {
+ _ = T{
+ 1, // comment after comma
+ 2, /* comment after comma */
+ 3, // comment after comma
+ }
+ _ = T{
+ 1, // comment after comma
+ 2, /* comment after comma */
+ 3, // comment after comma
+ }
+ _ = T{
+ /* comment before literal */ 1,
+ 2, /* comment before comma - ok to move after comma */
+ 3, /* comment before comma - ok to move after comma */
+ }
+
+ for i = 0; // comment after semicolon
+ i < 9; /* comment after semicolon */
+ i++ { // comment after opening curly brace
+ }
+
+ // TODO(gri) the last comment in this example should be aligned */
+ for i = 0; // comment after semicolon
+ i < 9; /* comment before semicolon - ok to move after semicolon */
+ i++ /* comment before opening curly brace */ {
+ }
+}
+
+
// Line comments with tabs
func _() {
var finput *bufio.Reader // input file
diff --git a/src/pkg/go/printer/testdata/comments.input b/src/pkg/go/printer/testdata/comments.input
index 335e81391..4a9ea4742 100644
--- a/src/pkg/go/printer/testdata/comments.input
+++ b/src/pkg/go/printer/testdata/comments.input
@@ -429,6 +429,40 @@ func _() {
/* closing curly brace should be on new line */ }
+// Comments immediately adjacent to punctuation (for which the go/printer
+// may obly have estimated position information) must remain after the punctuation.
+func _() {
+ _ = T{
+ 1, // comment after comma
+ 2, /* comment after comma */
+ 3 , // comment after comma
+ }
+ _ = T{
+ 1 ,// comment after comma
+ 2 ,/* comment after comma */
+ 3,// comment after comma
+ }
+ _ = T{
+ /* comment before literal */1,
+ 2/* comment before comma - ok to move after comma */,
+ 3 /* comment before comma - ok to move after comma */ ,
+ }
+
+ for
+ i=0;// comment after semicolon
+ i<9;/* comment after semicolon */
+ i++{// comment after opening curly brace
+ }
+
+ // TODO(gri) the last comment in this example should be aligned */
+ for
+ i=0;// comment after semicolon
+ i<9/* comment before semicolon - ok to move after semicolon */;
+ i++ /* comment before opening curly brace */ {
+ }
+}
+
+
// Line comments with tabs
func _() {
var finput *bufio.Reader // input file
diff --git a/src/pkg/go/printer/testdata/declarations.golden b/src/pkg/go/printer/testdata/declarations.golden
index 67f16b805..1c091b929 100644
--- a/src/pkg/go/printer/testdata/declarations.golden
+++ b/src/pkg/go/printer/testdata/declarations.golden
@@ -7,10 +7,10 @@ package imports
import "io"
import (
- _ "io"
+ _ "io"
)
-import _ "io"
+import _ "io"
import (
"io"
@@ -20,40 +20,60 @@ import (
import (
"io"
- aLongRename "io"
+ aLongRename "io"
- b "io"
+ b "io"
+)
+
+import (
+ "unrenamed"
+ renamed "renameMe"
+ . "io"
+ _ "io"
+ "io"
+ . "os"
)
// no newlines between consecutive single imports, but
// respect extra line breaks in the source (at most one empty line)
-import _ "io"
-import _ "io"
-import _ "io"
+import _ "io"
+import _ "io"
+import _ "io"
-import _ "os"
-import _ "os"
-import _ "os"
+import _ "os"
+import _ "os"
+import _ "os"
-import _ "fmt"
-import _ "fmt"
-import _ "fmt"
+import _ "fmt"
+import _ "fmt"
+import _ "fmt"
import "foo" // a comment
import "bar" // a comment
import (
- _ "foo"
+ _ "foo"
// a comment
"bar"
"foo" // a comment
"bar" // a comment
)
+// comments + renames
+import (
+ "unrenamed" // a comment
+ renamed "renameMe"
+ . "io" /* a comment */
+ _ "io/ioutil" // a comment
+ "io" // testing alignment
+ . "os"
+ // a comment
+)
+
// a case that caused problems in the past (comment placement)
import (
- . "fmt"
+ . "fmt"
"io"
"malloc" // for the malloc count test only
"math"
@@ -63,7 +83,7 @@ import (
// at least one empty line between declarations of different kind
-import _ "io"
+import _ "io"
var _ int
@@ -617,24 +637,79 @@ func _() {
// ellipsis parameters
-func _(...)
func _(...int)
func _(...*int)
func _(...[]int)
func _(...struct{})
func _(bool, ...interface{})
func _(bool, ...func())
-func _(bool, ...func(...))
+func _(bool, ...func(...int))
func _(bool, ...map[string]int)
func _(bool, ...chan int)
-func _(b bool, x ...)
func _(b bool, x ...int)
func _(b bool, x ...*int)
func _(b bool, x ...[]int)
func _(b bool, x ...struct{})
func _(x ...interface{})
func _(x ...func())
-func _(x ...func(...))
+func _(x ...func(...int))
func _(x ...map[string]int)
func _(x ...chan int)
+
+
+// these parameter lists must remain multi-line since they are multi-line in the source
+func _(bool,
+int) {
+}
+func _(x bool,
+y int) {
+}
+func _(x,
+y bool) {
+}
+func _(bool, // comment
+int) {
+}
+func _(x bool, // comment
+y int) {
+}
+func _(x, // comment
+y bool) {
+}
+func _(bool, // comment
+// comment
+int) {
+}
+func _(x bool, // comment
+// comment
+y int) {
+}
+func _(x, // comment
+// comment
+y bool) {
+}
+func _(bool,
+// comment
+int) {
+}
+func _(x bool,
+// comment
+y int) {
+}
+func _(x,
+// comment
+y bool) {
+}
+func _(x, // comment
+y, // comment
+z bool) {
+}
+func _(x, // comment
+y, // comment
+z bool) {
+}
+func _(x int, // comment
+y float, // comment
+z bool) {
+}
diff --git a/src/pkg/go/printer/testdata/declarations.input b/src/pkg/go/printer/testdata/declarations.input
index 095d1ddac..c826462f9 100644
--- a/src/pkg/go/printer/testdata/declarations.input
+++ b/src/pkg/go/printer/testdata/declarations.input
@@ -25,6 +25,15 @@ import (
b "io"
)
+import (
+ "unrenamed"
+ renamed "renameMe"
+ . "io"
+ _ "io"
+ "io"
+ . "os"
+)
+
// no newlines between consecutive single imports, but
// respect extra line breaks in the source (at most one empty line)
import _ "io"
@@ -51,6 +60,17 @@ import (
"bar" // a comment
)
+// comments + renames
+import (
+ "unrenamed" // a comment
+ renamed "renameMe"
+ . "io" /* a comment */
+ _ "io/ioutil" // a comment
+ "io" // testing alignment
+ . "os"
+ // a comment
+)
+
// a case that caused problems in the past (comment placement)
import (
. "fmt"
@@ -605,24 +625,79 @@ func _() {
// ellipsis parameters
-func _(...)
func _(...int)
func _(...*int)
func _(...[]int)
func _(...struct{})
func _(bool, ...interface{})
func _(bool, ...func())
-func _(bool, ...func(...))
+func _(bool, ...func(...int))
func _(bool, ...map[string]int)
func _(bool, ...chan int)
-func _(b bool, x ...)
func _(b bool, x ...int)
func _(b bool, x ...*int)
func _(b bool, x ...[]int)
func _(b bool, x ...struct{})
func _(x ...interface{})
func _(x ...func())
-func _(x ...func(...))
+func _(x ...func(...int))
func _(x ...map[string]int)
func _(x ...chan int)
+
+
+// these parameter lists must remain multi-line since they are multi-line in the source
+func _(bool,
+int) {
+}
+func _(x bool,
+y int) {
+}
+func _(x,
+y bool) {
+}
+func _(bool, // comment
+int) {
+}
+func _(x bool, // comment
+y int) {
+}
+func _(x, // comment
+y bool) {
+}
+func _(bool, // comment
+// comment
+int) {
+}
+func _(x bool, // comment
+// comment
+y int) {
+}
+func _(x, // comment
+// comment
+y bool) {
+}
+func _(bool,
+// comment
+int) {
+}
+func _(x bool,
+// comment
+y int) {
+}
+func _(x,
+// comment
+y bool) {
+}
+func _(x, // comment
+y,// comment
+z bool) {
+}
+func _(x, // comment
+ y,// comment
+ z bool) {
+}
+func _(x int, // comment
+ y float, // comment
+ z bool) {
+}
diff --git a/src/pkg/go/printer/testdata/expressions.golden b/src/pkg/go/printer/testdata/expressions.golden
index 95e5502d3..882c7624c 100644
--- a/src/pkg/go/printer/testdata/expressions.golden
+++ b/src/pkg/go/printer/testdata/expressions.golden
@@ -31,6 +31,9 @@ func _() {
_ = 1 + a
_ = a + 1
_ = a + b + 1
+ _ = s[a]
+ _ = s[a:]
+ _ = s[:b]
_ = s[1:2]
_ = s[a:b]
_ = s[0:len(s)]
@@ -56,6 +59,7 @@ func _() {
_ = s[a : b-c]
_ = s[0:]
_ = s[a+b]
+ _ = s[:b-c]
_ = s[a+b:]
_ = a[a<<b+1]
_ = a[a<<b+1:]
@@ -165,6 +169,45 @@ func _() {
}
+func f(x int, args ...int) {
+ f(0, args...)
+ f(1, args)
+ f(2, args[0])
+
+ // make sure syntactically legal code remains syntactically legal
+ f(3, 42 ...) // a blank must remain between 42 and ...
+ f(4, 42....)
+ f(5, 42....)
+ f(6, 42.0...)
+ f(7, 42.0...)
+ f(8, .42...)
+ f(9, .42...)
+ f(10, 42e0...)
+ f(11, 42e0...)
+
+ _ = 42 .x // a blank must remain between 42 and .x
+ _ = 42..x
+ _ = 42..x
+ _ = 42.0.x
+ _ = 42.0.x
+ _ = .42.x
+ _ = .42.x
+ _ = 42e0.x
+ _ = 42e0.x
+
+ // a blank must remain between the binary operator and the 2nd operand
+ _ = x / *y
+ _ = x < -1
+ _ = x < <-1
+ _ = x + +1
+ _ = x - -1
+ _ = x & &x
+ _ = x & ^x
+
+ _ = f(x / *y, x < -1, x < <-1, x + +1, x - -1, x & &x, x & ^x)
+}
+
+
func _() {
_ = T{}
_ = struct{}{}
@@ -172,13 +215,6 @@ func _() {
_ = [...]T{}
_ = []T{}
_ = map[int]T{}
-
- _ = (T){}
- _ = (struct{}){}
- _ = ([10]T){}
- _ = ([...]T){}
- _ = ([]T){}
- _ = (map[int]T){}
}
@@ -206,6 +242,8 @@ func _() {
`
_ = `foo
bar`
+ _ = `three spaces before the end of the line starting here:
+they must not be removed`
}
@@ -239,6 +277,27 @@ func _() {
func _() {
+ _ = [][]int{
+ []int{1},
+ []int{1, 2},
+ []int{1, 2, 3},
+ }
+ _ = [][]int{
+ {1},
+ []int{1, 2},
+ []int{1, 2, 3},
+ }
+ _ = [][]int{
+ {1},
+ {1, 2},
+ {1, 2, 3},
+ }
+ _ = [][]int{{1}, {1, 2}, {1, 2, 3}}
+}
+
+
+// various multi-line expressions
+func _() {
// do not add extra indentation to multi-line string lists
_ = "foo" + "bar"
_ = "foo" +
@@ -336,7 +395,6 @@ func _() {
2,
3,
)
- // TODO(gri) the cases below are not correct yet
f(1,
2,
3) // comment
@@ -349,8 +407,7 @@ func _() {
3) // comment
f(1,
2,
- 3 // comment
- ,
+ 3, // comment
)
}
diff --git a/src/pkg/go/printer/testdata/expressions.input b/src/pkg/go/printer/testdata/expressions.input
index 13891d971..647706b09 100644
--- a/src/pkg/go/printer/testdata/expressions.input
+++ b/src/pkg/go/printer/testdata/expressions.input
@@ -31,6 +31,9 @@ func _() {
_ = 1+a
_ = a+1
_ = a+b+1
+ _ = s[a]
+ _ = s[a:]
+ _ = s[:b]
_ = s[1:2]
_ = s[a:b]
_ = s[0:len(s)]
@@ -56,6 +59,7 @@ func _() {
_ = s[a : b-c]
_ = s[0:]
_ = s[a+b]
+ _ = s[: b-c]
_ = s[a+b :]
_ = a[a<<b+1]
_ = a[a<<b+1 :]
@@ -165,6 +169,45 @@ func _() {
}
+func f(x int, args ...int) {
+ f(0, args...)
+ f(1, args)
+ f(2, args[0])
+
+ // make sure syntactically legal code remains syntactically legal
+ f(3, 42 ...) // a blank must remain between 42 and ...
+ f(4, 42. ...)
+ f(5, 42....)
+ f(6, 42.0 ...)
+ f(7, 42.0...)
+ f(8, .42 ...)
+ f(9, .42...)
+ f(10, 42e0 ...)
+ f(11, 42e0...)
+
+ _ = 42 .x // a blank must remain between 42 and .x
+ _ = 42. .x
+ _ = 42..x
+ _ = 42.0 .x
+ _ = 42.0.x
+ _ = .42 .x
+ _ = .42.x
+ _ = 42e0 .x
+ _ = 42e0.x
+
+ // a blank must remain between the binary operator and the 2nd operand
+ _ = x/ *y
+ _ = x< -1
+ _ = x< <-1
+ _ = x+ +1
+ _ = x- -1
+ _ = x& &x
+ _ = x& ^x
+
+ _ = f(x/ *y, x< -1, x< <-1, x+ +1, x- -1, x& &x, x& ^x)
+}
+
+
func _() {
_ = T{}
_ = struct{}{}
@@ -172,13 +215,6 @@ func _() {
_ = [...]T{}
_ = []T{}
_ = map[int]T{}
-
- _ = (T){}
- _ = (struct{}){}
- _ = ([10]T){}
- _ = ([...]T){}
- _ = ([]T){}
- _ = (map[int]T){}
}
@@ -202,6 +238,8 @@ func _() {
`
_ = `foo
bar`
+ _ = `three spaces before the end of the line starting here:
+they must not be removed`
}
@@ -231,6 +269,27 @@ func _() {
func _() {
+ _ = [][]int {
+ []int{1},
+ []int{1, 2},
+ []int{1, 2, 3},
+ }
+ _ = [][]int {
+ {1},
+ []int{1, 2},
+ []int{1, 2, 3},
+ }
+ _ = [][]int {
+ {1},
+ {1, 2},
+ {1, 2, 3},
+ }
+ _ = [][]int {{1}, {1, 2}, {1, 2, 3}}
+}
+
+
+// various multi-line expressions
+func _() {
// do not add extra indentation to multi-line string lists
_ = "foo" + "bar"
_ = "foo" +
@@ -329,7 +388,6 @@ func _() {
2,
3,
)
- // TODO(gri) the cases below are not correct yet
f(1,
2,
3) // comment
diff --git a/src/pkg/go/printer/testdata/expressions.raw b/src/pkg/go/printer/testdata/expressions.raw
index dccc8d122..62be00cc3 100644
--- a/src/pkg/go/printer/testdata/expressions.raw
+++ b/src/pkg/go/printer/testdata/expressions.raw
@@ -31,6 +31,9 @@ func _() {
_ = 1 + a
_ = a + 1
_ = a + b + 1
+ _ = s[a]
+ _ = s[a:]
+ _ = s[:b]
_ = s[1:2]
_ = s[a:b]
_ = s[0:len(s)]
@@ -56,6 +59,7 @@ func _() {
_ = s[a : b-c]
_ = s[0:]
_ = s[a+b]
+ _ = s[:b-c]
_ = s[a+b:]
_ = a[a<<b+1]
_ = a[a<<b+1:]
@@ -165,6 +169,45 @@ func _() {
}
+func f(x int, args ...int) {
+ f(0, args...)
+ f(1, args)
+ f(2, args[0])
+
+ // make sure syntactically legal code remains syntactically legal
+ f(3, 42 ...) // a blank must remain between 42 and ...
+ f(4, 42....)
+ f(5, 42....)
+ f(6, 42.0...)
+ f(7, 42.0...)
+ f(8, .42...)
+ f(9, .42...)
+ f(10, 42e0...)
+ f(11, 42e0...)
+
+ _ = 42 .x // a blank must remain between 42 and .x
+ _ = 42..x
+ _ = 42..x
+ _ = 42.0.x
+ _ = 42.0.x
+ _ = .42.x
+ _ = .42.x
+ _ = 42e0.x
+ _ = 42e0.x
+
+ // a blank must remain between the binary operator and the 2nd operand
+ _ = x / *y
+ _ = x < -1
+ _ = x < <-1
+ _ = x + +1
+ _ = x - -1
+ _ = x & &x
+ _ = x & ^x
+
+ _ = f(x / *y, x < -1, x < <-1, x + +1, x - -1, x & &x, x & ^x)
+}
+
+
func _() {
_ = T{}
_ = struct{}{}
@@ -172,13 +215,6 @@ func _() {
_ = [...]T{}
_ = []T{}
_ = map[int]T{}
-
- _ = (T){}
- _ = (struct{}){}
- _ = ([10]T){}
- _ = ([...]T){}
- _ = ([]T){}
- _ = (map[int]T){}
}
@@ -206,6 +242,8 @@ func _() {
`
_ = `foo
bar`
+ _ = `three spaces before the end of the line starting here:
+they must not be removed`
}
@@ -239,6 +277,27 @@ func _() {
func _() {
+ _ = [][]int{
+ []int{1},
+ []int{1, 2},
+ []int{1, 2, 3},
+ }
+ _ = [][]int{
+ {1},
+ []int{1, 2},
+ []int{1, 2, 3},
+ }
+ _ = [][]int{
+ {1},
+ {1, 2},
+ {1, 2, 3},
+ }
+ _ = [][]int{{1}, {1, 2}, {1, 2, 3}}
+}
+
+
+// various multi-line expressions
+func _() {
// do not add extra indentation to multi-line string lists
_ = "foo" + "bar"
_ = "foo" +
@@ -336,7 +395,6 @@ func _() {
2,
3,
)
- // TODO(gri) the cases below are not correct yet
f(1,
2,
3) // comment
@@ -349,8 +407,7 @@ func _() {
3) // comment
f(1,
2,
- 3 // comment
- ,
+ 3, // comment
)
}
diff --git a/src/pkg/go/printer/testdata/statements.golden b/src/pkg/go/printer/testdata/statements.golden
index 73a3e1236..5eceb7dd5 100644
--- a/src/pkg/go/printer/testdata/statements.golden
+++ b/src/pkg/go/printer/testdata/statements.golden
@@ -209,6 +209,38 @@ func _() {
for _ = range (T1{T{42}}) {
}
+
+ if x == (T{42}[0]) {
+ }
+ if (x == T{42}[0]) {
+ }
+ if x == (T{42}[0]) {
+ }
+ if x == (T{42}[0]) {
+ }
+ if x == (T{42}[0]) {
+ }
+ if x == a+b*(T{42}[0]) {
+ }
+ if (x == a+b*T{42}[0]) {
+ }
+ if x == a+b*(T{42}[0]) {
+ }
+ if x == a+(b * (T{42}[0])) {
+ }
+ if x == a+b*(T{42}[0]) {
+ }
+ if (a + b*(T{42}[0])) == x {
+ }
+ if (a + b*(T{42}[0])) == x {
+ }
+
+ if struct{ x bool }{false}.x {
+ }
+ if (struct{ x bool }{false}.x) == false {
+ }
+ if struct{ x bool }{false}.x == false {
+ }
}
@@ -227,7 +259,8 @@ func _() {
var x = 1
// Each use(x) call below should have at most one empty line before and after.
-
+ // Known bug: The first use call may have more than one empty line before
+ // (see go/printer/nodes.go, func linebreak).
use(x)
@@ -336,3 +369,49 @@ AnOverlongLabel:
L:
_ = 0
}
+
+
+func _() {
+ for {
+ goto L
+ }
+L:
+
+ MoreCode()
+}
+
+
+func _() {
+ for {
+ goto L
+ }
+L: // A comment on the same line as the label, followed by a single empty line.
+ // Known bug: There may be more than one empty line before MoreCode()
+ // (see go/printer/nodes.go, func linebreak).
+
+
+ MoreCode()
+}
+
+
+func _() {
+ for {
+ goto L
+ }
+L:
+
+ // There should be a single empty line before this comment.
+ MoreCode()
+}
+
+
+func _() {
+ for {
+ goto AVeryLongLabelThatShouldNotAffectFormatting
+ }
+AVeryLongLabelThatShouldNotAffectFormatting:
+ // There should be a single empty line after this comment.
+
+ // There should be a single empty line before this comment.
+ MoreCode()
+}
diff --git a/src/pkg/go/printer/testdata/statements.input b/src/pkg/go/printer/testdata/statements.input
index 53f16c050..7819820ed 100644
--- a/src/pkg/go/printer/testdata/statements.input
+++ b/src/pkg/go/printer/testdata/statements.input
@@ -146,6 +146,23 @@ func _() {
switch ; ((((T{})))) {}
for _ = range (((T1{T{42}}))) {}
+
+ if x == (T{42}[0]) {}
+ if (x == T{42}[0]) {}
+ if (x == (T{42}[0])) {}
+ if (x == (((T{42}[0])))) {}
+ if (((x == (T{42}[0])))) {}
+ if x == a + b*(T{42}[0]) {}
+ if (x == a + b*T{42}[0]) {}
+ if (x == a + b*(T{42}[0])) {}
+ if (x == a + ((b * (T{42}[0])))) {}
+ if (((x == a + b * (T{42}[0])))) {}
+ if (((a + b * (T{42}[0])) == x)) {}
+ if (((a + b * (T{42}[0])))) == x {}
+
+ if (struct{x bool}{false}.x) {}
+ if (struct{x bool}{false}.x) == false {}
+ if (struct{x bool}{false}.x == false) {}
}
@@ -164,6 +181,8 @@ func _() {
var x = 1
// Each use(x) call below should have at most one empty line before and after.
+ // Known bug: The first use call may have more than one empty line before
+ // (see go/printer/nodes.go, func linebreak).
@@ -266,3 +285,54 @@ AnOverlongLabel:
L: _ = 0
}
+
+
+func _() {
+ for {
+ goto L
+ }
+L:
+
+ MoreCode()
+}
+
+
+func _() {
+ for {
+ goto L
+ }
+L: // A comment on the same line as the label, followed by a single empty line.
+ // Known bug: There may be more than one empty line before MoreCode()
+ // (see go/printer/nodes.go, func linebreak).
+
+
+
+
+ MoreCode()
+}
+
+
+func _() {
+ for {
+ goto L
+ }
+L:
+
+
+
+
+ // There should be a single empty line before this comment.
+ MoreCode()
+}
+
+
+func _() {
+ for {
+ goto AVeryLongLabelThatShouldNotAffectFormatting
+ }
+AVeryLongLabelThatShouldNotAffectFormatting:
+ // There should be a single empty line after this comment.
+
+ // There should be a single empty line before this comment.
+ MoreCode()
+}