diff options
Diffstat (limited to 'src/pkg/go/printer')
-rw-r--r-- | src/pkg/go/printer/Makefile | 2 | ||||
-rw-r--r-- | src/pkg/go/printer/nodes.go | 257 | ||||
-rw-r--r-- | src/pkg/go/printer/printer.go | 241 | ||||
-rw-r--r-- | src/pkg/go/printer/printer_test.go | 24 | ||||
-rw-r--r-- | src/pkg/go/printer/testdata/comments.golden | 32 | ||||
-rw-r--r-- | src/pkg/go/printer/testdata/comments.input | 34 | ||||
-rw-r--r-- | src/pkg/go/printer/testdata/declarations.golden | 115 | ||||
-rw-r--r-- | src/pkg/go/printer/testdata/declarations.input | 83 | ||||
-rw-r--r-- | src/pkg/go/printer/testdata/expressions.golden | 77 | ||||
-rw-r--r-- | src/pkg/go/printer/testdata/expressions.input | 74 | ||||
-rw-r--r-- | src/pkg/go/printer/testdata/expressions.raw | 77 | ||||
-rw-r--r-- | src/pkg/go/printer/testdata/statements.golden | 81 | ||||
-rw-r--r-- | src/pkg/go/printer/testdata/statements.input | 70 |
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(""") // shorter than """ esc_apos = []byte("'") // shorter than "'" @@ -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() +} |