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/Makefile12
-rw-r--r--src/pkg/go/printer/nodes.go1510
-rw-r--r--src/pkg/go/printer/performance_test.go58
-rw-r--r--src/pkg/go/printer/printer.go1012
-rw-r--r--src/pkg/go/printer/printer_test.go194
-rw-r--r--src/pkg/go/printer/testdata/comments.golden473
-rw-r--r--src/pkg/go/printer/testdata/comments.input483
-rw-r--r--src/pkg/go/printer/testdata/comments.x56
-rw-r--r--src/pkg/go/printer/testdata/declarations.golden747
-rw-r--r--src/pkg/go/printer/testdata/declarations.input757
-rw-r--r--src/pkg/go/printer/testdata/empty.golden5
-rw-r--r--src/pkg/go/printer/testdata/empty.input5
-rw-r--r--src/pkg/go/printer/testdata/expressions.golden627
-rw-r--r--src/pkg/go/printer/testdata/expressions.input656
-rw-r--r--src/pkg/go/printer/testdata/expressions.raw627
-rw-r--r--src/pkg/go/printer/testdata/linebreaks.golden223
-rw-r--r--src/pkg/go/printer/testdata/linebreaks.input223
-rw-r--r--src/pkg/go/printer/testdata/parser.go2153
-rw-r--r--src/pkg/go/printer/testdata/slow.golden85
-rw-r--r--src/pkg/go/printer/testdata/slow.input85
-rw-r--r--src/pkg/go/printer/testdata/statements.golden412
-rw-r--r--src/pkg/go/printer/testdata/statements.input351
22 files changed, 10754 insertions, 0 deletions
diff --git a/src/pkg/go/printer/Makefile b/src/pkg/go/printer/Makefile
new file mode 100644
index 000000000..6a71efc93
--- /dev/null
+++ b/src/pkg/go/printer/Makefile
@@ -0,0 +1,12 @@
+# Copyright 2009 The Go Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style
+# license that can be found in the LICENSE file.
+
+include ../../../Make.inc
+
+TARG=go/printer
+GOFILES=\
+ printer.go\
+ nodes.go\
+
+include ../../../Make.pkg
diff --git a/src/pkg/go/printer/nodes.go b/src/pkg/go/printer/nodes.go
new file mode 100644
index 000000000..9cd975ec1
--- /dev/null
+++ b/src/pkg/go/printer/nodes.go
@@ -0,0 +1,1510 @@
+// Copyright 2009 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// This file implements printing of AST nodes; specifically
+// expressions, statements, declarations, and files. It uses
+// the print functionality implemented in printer.go.
+
+package printer
+
+import (
+ "bytes"
+ "go/ast"
+ "go/token"
+)
+
+// Other formatting issues:
+// - better comment formatting for /*-style comments at the end of a line (e.g. a declaration)
+// when the comment spans multiple lines; if such a comment is just two lines, formatting is
+// not idempotent
+// - formatting of expression lists
+// - should use blank instead of tab to separate one-line function bodies from
+// the function header unless there is a group of consecutive one-liners
+
+// ----------------------------------------------------------------------------
+// Common AST nodes.
+
+// 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): linebreak may add too many lines if the next statement at "line"
+// is preceded 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 int, ws whiteSpace, newSection bool) (printedBreak bool) {
+ n := p.nlines(line-p.pos.Line, min)
+ if n > 0 {
+ p.print(ws)
+ if newSection {
+ p.print(formfeed)
+ n--
+ }
+ for ; n > 0; n-- {
+ p.print(newline)
+ }
+ printedBreak = true
+ }
+ return
+}
+
+// setComment sets g as the next comment if g != nil and if node comments
+// are enabled - this mode is used when printing source code fragments such
+// as exports only. It assumes that there are no other pending comments to
+// intersperse.
+func (p *printer) setComment(g *ast.CommentGroup) {
+ if g == nil || !p.useNodeComments {
+ return
+ }
+ if p.comments == nil {
+ // initialize p.comments lazily
+ p.comments = make([]*ast.CommentGroup, 1)
+ } else if p.cindex < len(p.comments) {
+ // 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(p.fset.Position(g.List[0].Pos()), token.ILLEGAL)
+ }
+ p.comments[0] = g
+ p.cindex = 0
+}
+
+type exprListMode uint
+
+const (
+ blankStart exprListMode = 1 << iota // print a blank before a non-empty list
+ blankEnd // print a blank after a non-empty list
+ commaSep // elements are separated by commas
+ commaTerm // list is optionally terminated by a comma
+ noIndent // no extra indentation in multi-line lists
+ periodSep // elements are separated by periods
+)
+
+// Sets multiLine to true if the identifier list spans multiple lines.
+// 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
+ xlist := make([]ast.Expr, len(list))
+ for i, x := range list {
+ xlist[i] = x
+ }
+ mode := commaSep
+ if !indent {
+ mode |= noIndent
+ }
+ p.exprList(token.NoPos, xlist, 1, mode, multiLine, token.NoPos)
+}
+
+// Print a list of expressions. If the list spans multiple
+// source lines, the original line breaks are respected between
+// expressions. Sets multiLine to true if the list spans multiple
+// lines.
+//
+// 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(prev0 token.Pos, list []ast.Expr, depth int, mode exprListMode, multiLine *bool, next0 token.Pos) {
+ if len(list) == 0 {
+ return
+ }
+
+ if mode&blankStart != 0 {
+ p.print(blank)
+ }
+
+ prev := p.fset.Position(prev0)
+ next := p.fset.Position(next0)
+ line := p.fset.Position(list[0].Pos()).Line
+ endLine := p.fset.Position(list[len(list)-1].End()).Line
+
+ if prev.IsValid() && prev.Line == line && line == endLine {
+ // all list entries on a single line
+ for i, x := range list {
+ if i > 0 {
+ if mode&commaSep != 0 {
+ p.print(token.COMMA)
+ }
+ p.print(blank)
+ }
+ p.expr0(x, depth, multiLine)
+ }
+ if mode&blankEnd != 0 {
+ p.print(blank)
+ }
+ return
+ }
+
+ // list entries span multiple lines;
+ // use source code positions to guide line breaks
+
+ // don't add extra indentation if noIndent is set;
+ // i.e., pretend that the first line is already indented
+ ws := ignore
+ if mode&noIndent == 0 {
+ ws = indent
+ }
+
+ // the first linebreak is always a formfeed since this section must not
+ // depend on any previous formatting
+ prevBreak := -1 // index of last expression that was followed by a linebreak
+ if prev.IsValid() && prev.Line < line && p.linebreak(line, 0, ws, true) {
+ ws = ignore
+ *multiLine = true
+ prevBreak = 0
+ }
+
+ // initialize expression/key size: a zero value indicates expr/key doesn't fit on a single line
+ size := 0
+
+ // print all list elements
+ for i, x := range list {
+ prevLine := 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
+ // key:value expressions, use the key size
+ // TODO(gri) for a better result, should probably incorporate both
+ // the key and the node size into the decision process
+ useFF := true
+
+ // determine element size: all bets are off if we don't have
+ // position information for the previous and next token (likely
+ // generated code - simply ignore the size in this case by setting
+ // it to 0)
+ prevSize := size
+ const infinity = 1e6 // larger than any source line
+ size = p.nodeSize(x, infinity)
+ pair, isPair := x.(*ast.KeyValueExpr)
+ if size <= infinity && prev.IsValid() && next.IsValid() {
+ // x fits on a single line
+ if isPair {
+ size = p.nodeSize(pair.Key, infinity) // size <= infinity
+ }
+ } else {
+ // size too large or we don't have good layout information
+ size = 0
+ }
+
+ // if the previous line and the current line had single-
+ // line-expressions and the key sizes are small or the
+ // the ratio between the key sizes does not exceed a
+ // threshold, align columns and do not use formfeed
+ if prevSize > 0 && size > 0 {
+ const smallSize = 20
+ if prevSize <= smallSize && size <= smallSize {
+ useFF = false
+ } else {
+ const r = 4 // threshold
+ ratio := float64(size) / float64(prevSize)
+ useFF = ratio <= 1/r || r <= ratio
+ }
+ }
+
+ if i > 0 {
+ switch {
+ case mode&commaSep != 0:
+ p.print(token.COMMA)
+ case mode&periodSep != 0:
+ p.print(token.PERIOD)
+ }
+ needsBlank := mode&periodSep == 0 // period-separated list elements don't need a blank
+ if prevLine < line && prevLine > 0 && line > 0 {
+ // lines are broken using newlines so comments remain aligned
+ // unless forceFF is set or there are multiple expressions on
+ // the same line in which case formfeed is used
+ if p.linebreak(line, 0, ws, useFF || prevBreak+1 < i) {
+ ws = ignore
+ *multiLine = true
+ prevBreak = i
+ needsBlank = false // we got a line break instead
+ }
+ }
+ if needsBlank {
+ p.print(blank)
+ }
+ }
+
+ if isPair && size > 0 && len(list) > 1 {
+ // we have a key:value expression that fits onto one line and
+ // is in a list with more then one entry: use a column for the
+ // key such that consecutive entries can align if possible
+ p.expr(pair.Key, multiLine)
+ p.print(pair.Colon, token.COLON, vtab)
+ p.expr(pair.Value, multiLine)
+ } else {
+ p.expr0(x, depth, multiLine)
+ }
+ }
+
+ if mode&commaTerm != 0 && next.IsValid() && p.pos.Line < next.Line {
+ // print a terminating comma if the next token is on a new line
+ p.print(token.COMMA)
+ if ws == ignore && mode&noIndent == 0 {
+ // unindent if we indented
+ p.print(unindent)
+ }
+ p.print(formfeed) // terminating comma needs a line break to look good
+ return
+ }
+
+ if mode&blankEnd != 0 {
+ p.print(blank)
+ }
+
+ if ws == ignore && mode&noIndent == 0 {
+ // unindent if we indented
+ p.print(unindent)
+ }
+}
+
+// Sets multiLine to true if the the parameter list spans multiple lines.
+func (p *printer) parameters(fields *ast.FieldList, multiLine *bool) {
+ p.print(fields.Opening, token.LPAREN)
+ if len(fields.List) > 0 {
+ var prevLine, line int
+ for i, par := range fields.List {
+ if i > 0 {
+ p.print(token.COMMA)
+ if len(par.Names) > 0 {
+ line = p.fset.Position(par.Names[0].Pos()).Line
+ } else {
+ line = p.fset.Position(par.Type.Pos()).Line
+ }
+ if 0 < prevLine && prevLine < line && p.linebreak(line, 0, ignore, true) {
+ *multiLine = true
+ } else {
+ p.print(blank)
+ }
+ }
+ if len(par.Names) > 0 {
+ p.identList(par.Names, false, multiLine)
+ p.print(blank)
+ }
+ p.expr(par.Type, multiLine)
+ prevLine = p.fset.Position(par.Type.Pos()).Line
+ }
+ }
+ p.print(fields.Closing, token.RPAREN)
+}
+
+// Sets multiLine to true if the signature spans multiple lines.
+func (p *printer) signature(params, result *ast.FieldList, multiLine *bool) {
+ p.parameters(params, multiLine)
+ n := result.NumFields()
+ if n > 0 {
+ p.print(blank)
+ if n == 1 && result.List[0].Names == nil {
+ // single anonymous result; no ()'s
+ p.expr(result.List[0].Type, multiLine)
+ return
+ }
+ p.parameters(result, multiLine)
+ }
+}
+
+func identListSize(list []*ast.Ident, maxSize int) (size int) {
+ for i, x := range list {
+ if i > 0 {
+ size += 2 // ", "
+ }
+ size += len(x.Name)
+ if size >= maxSize {
+ break
+ }
+ }
+ return
+}
+
+func (p *printer) isOneLineFieldList(list []*ast.Field) bool {
+ if len(list) != 1 {
+ return false // allow only one field
+ }
+ f := list[0]
+ if f.Tag != nil || f.Comment != nil {
+ return false // don't allow tags or comments
+ }
+ // only name(s) and type
+ const maxSize = 30 // adjust as appropriate, this is an approximate value
+ namesSize := identListSize(f.Names, maxSize)
+ if namesSize > 0 {
+ namesSize = 1 // blank between names and types
+ }
+ typeSize := p.nodeSize(f.Type, maxSize)
+ return namesSize+typeSize <= maxSize
+}
+
+func (p *printer) setLineComment(text string) {
+ p.setComment(&ast.CommentGroup{[]*ast.Comment{&ast.Comment{token.NoPos, text}}})
+}
+
+func (p *printer) fieldList(fields *ast.FieldList, isStruct, isIncomplete bool) {
+ lbrace := fields.Opening
+ list := fields.List
+ rbrace := fields.Closing
+ srcIsOneLine := lbrace.IsValid() && rbrace.IsValid() && p.fset.Position(lbrace).Line == p.fset.Position(rbrace).Line
+
+ if !isIncomplete && !p.commentBefore(p.fset.Position(rbrace)) && srcIsOneLine {
+ // possibly a one-line struct/interface
+ if len(list) == 0 {
+ // no blank between keyword and {} in this case
+ p.print(lbrace, token.LBRACE, rbrace, token.RBRACE)
+ return
+ } else if isStruct && p.isOneLineFieldList(list) { // for now ignore interfaces
+ // small enough - print on one line
+ // (don't use identList and ignore source line breaks)
+ p.print(lbrace, token.LBRACE, blank)
+ f := list[0]
+ for i, x := range f.Names {
+ if i > 0 {
+ p.print(token.COMMA, blank)
+ }
+ p.expr(x, ignoreMultiLine)
+ }
+ if len(f.Names) > 0 {
+ p.print(blank)
+ }
+ p.expr(f.Type, ignoreMultiLine)
+ p.print(blank, rbrace, token.RBRACE)
+ return
+ }
+ }
+
+ // at least one entry or incomplete
+ p.print(blank, lbrace, token.LBRACE, indent, formfeed)
+ if isStruct {
+
+ sep := vtab
+ if len(list) == 1 {
+ sep = blank
+ }
+ var ml bool
+ for i, f := range list {
+ if i > 0 {
+ p.linebreak(p.fset.Position(f.Pos()).Line, 1, ignore, ml)
+ }
+ ml = false
+ extraTabs := 0
+ p.setComment(f.Doc)
+ if len(f.Names) > 0 {
+ // named fields
+ p.identList(f.Names, false, &ml)
+ p.print(sep)
+ p.expr(f.Type, &ml)
+ extraTabs = 1
+ } else {
+ // anonymous field
+ p.expr(f.Type, &ml)
+ extraTabs = 2
+ }
+ if f.Tag != nil {
+ if len(f.Names) > 0 && sep == vtab {
+ p.print(sep)
+ }
+ p.print(sep)
+ p.expr(f.Tag, &ml)
+ extraTabs = 0
+ }
+ if f.Comment != nil {
+ for ; extraTabs > 0; extraTabs-- {
+ p.print(sep)
+ }
+ p.setComment(f.Comment)
+ }
+ }
+ if isIncomplete {
+ if len(list) > 0 {
+ p.print(formfeed)
+ }
+ p.flush(p.fset.Position(rbrace), token.RBRACE) // make sure we don't lose the last line comment
+ p.setLineComment("// contains filtered or unexported fields")
+ }
+
+ } else { // interface
+
+ var ml bool
+ for i, f := range list {
+ if i > 0 {
+ p.linebreak(p.fset.Position(f.Pos()).Line, 1, ignore, ml)
+ }
+ ml = false
+ p.setComment(f.Doc)
+ if ftyp, isFtyp := f.Type.(*ast.FuncType); isFtyp {
+ // method
+ p.expr(f.Names[0], &ml)
+ p.signature(ftyp.Params, ftyp.Results, &ml)
+ } else {
+ // embedded interface
+ p.expr(f.Type, &ml)
+ }
+ p.setComment(f.Comment)
+ }
+ if isIncomplete {
+ if len(list) > 0 {
+ p.print(formfeed)
+ }
+ p.flush(p.fset.Position(rbrace), token.RBRACE) // make sure we don't lose the last line comment
+ p.setLineComment("// contains filtered or unexported methods")
+ }
+
+ }
+ p.print(unindent, formfeed, rbrace, token.RBRACE)
+}
+
+// ----------------------------------------------------------------------------
+// Expressions
+
+func walkBinary(e *ast.BinaryExpr) (has4, has5 bool, maxProblem int) {
+ switch e.Op.Precedence() {
+ case 4:
+ has4 = true
+ case 5:
+ has5 = true
+ }
+
+ switch l := e.X.(type) {
+ case *ast.BinaryExpr:
+ if l.Op.Precedence() < e.Op.Precedence() {
+ // parens will be inserted.
+ // pretend this is an *ast.ParenExpr and do nothing.
+ break
+ }
+ h4, h5, mp := walkBinary(l)
+ has4 = has4 || h4
+ has5 = has5 || h5
+ if maxProblem < mp {
+ maxProblem = mp
+ }
+ }
+
+ switch r := e.Y.(type) {
+ case *ast.BinaryExpr:
+ if r.Op.Precedence() <= e.Op.Precedence() {
+ // parens will be inserted.
+ // pretend this is an *ast.ParenExpr and do nothing.
+ break
+ }
+ h4, h5, mp := walkBinary(r)
+ has4 = has4 || h4
+ has5 = has5 || h5
+ if maxProblem < mp {
+ maxProblem = mp
+ }
+
+ case *ast.StarExpr:
+ if e.Op == token.QUO { // `*/`
+ maxProblem = 5
+ }
+
+ case *ast.UnaryExpr:
+ switch e.Op.String() + r.Op.String() {
+ case "/*", "&&", "&^":
+ maxProblem = 5
+ case "++", "--":
+ if maxProblem < 4 {
+ maxProblem = 4
+ }
+ }
+ }
+ return
+}
+
+func cutoff(e *ast.BinaryExpr, depth int) int {
+ has4, has5, maxProblem := walkBinary(e)
+ if maxProblem > 0 {
+ return maxProblem + 1
+ }
+ if has4 && has5 {
+ if depth == 1 {
+ return 5
+ }
+ return 4
+ }
+ if depth == 1 {
+ return 6
+ }
+ return 4
+}
+
+func diffPrec(expr ast.Expr, prec int) int {
+ x, ok := expr.(*ast.BinaryExpr)
+ if !ok || prec != x.Op.Precedence() {
+ return 1
+ }
+ return 0
+}
+
+func reduceDepth(depth int) int {
+ depth--
+ if depth < 1 {
+ depth = 1
+ }
+ return depth
+}
+
+// Format the binary expression: decide the cutoff and then format.
+// Let's call depth == 1 Normal mode, and depth > 1 Compact mode.
+// (Algorithm suggestion by Russ Cox.)
+//
+// The precedences are:
+// 5 * / % << >> & &^
+// 4 + - | ^
+// 3 == != < <= > >=
+// 2 &&
+// 1 ||
+//
+// The only decision is whether there will be spaces around levels 4 and 5.
+// There are never spaces at level 6 (unary), and always spaces at levels 3 and below.
+//
+// To choose the cutoff, look at the whole expression but excluding primary
+// expressions (function calls, parenthesized exprs), and apply these rules:
+//
+// 1) If there is a binary operator with a right side unary operand
+// that would clash without a space, the cutoff must be (in order):
+//
+// /* 6
+// && 6
+// &^ 6
+// ++ 5
+// -- 5
+//
+// (Comparison operators always have spaces around them.)
+//
+// 2) If there is a mix of level 5 and level 4 operators, then the cutoff
+// is 5 (use spaces to distinguish precedence) in Normal mode
+// and 4 (never use spaces) in Compact mode.
+//
+// 3) If there are no level 4 operators or no level 5 operators, then the
+// cutoff is 6 (always use spaces) in Normal mode
+// and 4 (never use spaces) in Compact mode.
+//
+// Sets multiLine to true if the binary expression spans multiple lines.
+func (p *printer) binaryExpr(x *ast.BinaryExpr, prec1, cutoff, depth int, multiLine *bool) {
+ prec := x.Op.Precedence()
+ if prec < prec1 {
+ // parenthesis needed
+ // Note: The parser inserts an ast.ParenExpr node; thus this case
+ // can only occur if the AST is created in a different way.
+ p.print(token.LPAREN)
+ p.expr0(x, reduceDepth(depth), multiLine) // parentheses undo one level of depth
+ p.print(token.RPAREN)
+ return
+ }
+
+ printBlank := prec < cutoff
+
+ ws := indent
+ p.expr1(x.X, prec, depth+diffPrec(x.X, prec), multiLine)
+ if printBlank {
+ p.print(blank)
+ }
+ xline := p.pos.Line // before the operator (it may be on the next 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, ws, true) {
+ ws = ignore
+ *multiLine = true
+ printBlank = false // no blank after line break
+ }
+ }
+ if printBlank {
+ p.print(blank)
+ }
+ p.expr1(x.Y, prec+1, depth+1, multiLine)
+ if ws == ignore {
+ p.print(unindent)
+ }
+}
+
+func isBinary(expr ast.Expr) bool {
+ _, ok := expr.(*ast.BinaryExpr)
+ return ok
+}
+
+// If the expression contains one or more selector expressions, splits it into
+// two expressions at the rightmost period. Writes entire expr to suffix when
+// selector isn't found. Rewrites AST nodes for calls, index expressions and
+// type assertions, all of which may be found in selector chains, to make them
+// parts of the chain.
+func splitSelector(expr ast.Expr) (body, suffix ast.Expr) {
+ switch x := expr.(type) {
+ case *ast.SelectorExpr:
+ body, suffix = x.X, x.Sel
+ return
+ case *ast.CallExpr:
+ body, suffix = splitSelector(x.Fun)
+ if body != nil {
+ suffix = &ast.CallExpr{suffix, x.Lparen, x.Args, x.Ellipsis, x.Rparen}
+ return
+ }
+ case *ast.IndexExpr:
+ body, suffix = splitSelector(x.X)
+ if body != nil {
+ suffix = &ast.IndexExpr{suffix, x.Lbrack, x.Index, x.Rbrack}
+ return
+ }
+ case *ast.SliceExpr:
+ body, suffix = splitSelector(x.X)
+ if body != nil {
+ suffix = &ast.SliceExpr{suffix, x.Lbrack, x.Low, x.High, x.Rbrack}
+ return
+ }
+ case *ast.TypeAssertExpr:
+ body, suffix = splitSelector(x.X)
+ if body != nil {
+ suffix = &ast.TypeAssertExpr{suffix, x.Type}
+ return
+ }
+ }
+ suffix = expr
+ return
+}
+
+// Convert an expression into an expression list split at the periods of
+// selector expressions.
+func selectorExprList(expr ast.Expr) (list []ast.Expr) {
+ // split expression
+ for expr != nil {
+ var suffix ast.Expr
+ expr, suffix = splitSelector(expr)
+ list = append(list, suffix)
+ }
+
+ // 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
+}
+
+// Sets multiLine to true if the expression spans multiple lines.
+func (p *printer) expr1(expr ast.Expr, prec1, depth int, multiLine *bool) {
+ p.print(expr.Pos())
+
+ switch x := expr.(type) {
+ case *ast.BadExpr:
+ p.print("BadExpr")
+
+ case *ast.Ident:
+ p.print(x)
+
+ case *ast.BinaryExpr:
+ if depth < 1 {
+ p.internalError("depth < 1:", depth)
+ depth = 1
+ }
+ p.binaryExpr(x, prec1, cutoff(x, depth), depth, multiLine)
+
+ case *ast.KeyValueExpr:
+ p.expr(x.Key, multiLine)
+ p.print(x.Colon, token.COLON, blank)
+ p.expr(x.Value, multiLine)
+
+ case *ast.StarExpr:
+ const prec = token.UnaryPrec
+ if prec < prec1 {
+ // parenthesis needed
+ p.print(token.LPAREN)
+ p.print(token.MUL)
+ p.expr(x.X, multiLine)
+ p.print(token.RPAREN)
+ } else {
+ // no parenthesis needed
+ p.print(token.MUL)
+ p.expr(x.X, multiLine)
+ }
+
+ case *ast.UnaryExpr:
+ const prec = token.UnaryPrec
+ if prec < prec1 {
+ // parenthesis needed
+ p.print(token.LPAREN)
+ p.expr(x, multiLine)
+ p.print(token.RPAREN)
+ } else {
+ // no parenthesis needed
+ p.print(x.Op)
+ if x.Op == token.RANGE {
+ // TODO(gri) Remove this code if it cannot be reached.
+ p.print(blank)
+ }
+ p.expr1(x.X, prec, depth, multiLine)
+ }
+
+ case *ast.BasicLit:
+ p.print(x)
+
+ case *ast.FuncLit:
+ p.expr(x.Type, multiLine)
+ p.funcBody(x.Body, p.distance(x.Type.Pos(), p.pos), true, multiLine)
+
+ case *ast.ParenExpr:
+ if _, hasParens := x.X.(*ast.ParenExpr); hasParens {
+ // 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(token.NoPos, parts, depth, periodSep, multiLine, token.NoPos)
+
+ case *ast.TypeAssertExpr:
+ p.expr1(x.X, token.HighestPrec, depth, multiLine)
+ p.print(token.PERIOD, token.LPAREN)
+ if x.Type != nil {
+ p.expr(x.Type, multiLine)
+ } else {
+ p.print(token.TYPE)
+ }
+ p.print(token.RPAREN)
+
+ case *ast.IndexExpr:
+ // TODO(gri): should treat[] like parentheses and undo one level of depth
+ p.expr1(x.X, token.HighestPrec, 1, multiLine)
+ p.print(x.Lbrack, token.LBRACK)
+ p.expr0(x.Index, depth+1, multiLine)
+ p.print(x.Rbrack, token.RBRACK)
+
+ case *ast.SliceExpr:
+ // TODO(gri): should treat[] like parentheses and undo one level of depth
+ p.expr1(x.X, token.HighestPrec, 1, multiLine)
+ p.print(x.Lbrack, token.LBRACK)
+ if x.Low != nil {
+ p.expr0(x.Low, depth+1, multiLine)
+ }
+ // blanks around ":" if both sides exist and either side is a binary expression
+ if depth <= 1 && x.Low != nil && x.High != nil && (isBinary(x.Low) || isBinary(x.High)) {
+ p.print(blank, token.COLON, blank)
+ } else {
+ p.print(token.COLON)
+ }
+ if x.High != nil {
+ p.expr0(x.High, depth+1, multiLine)
+ }
+ p.print(x.Rbrack, token.RBRACK)
+
+ case *ast.CallExpr:
+ if len(x.Args) > 1 {
+ depth++
+ }
+ p.expr1(x.Fun, token.HighestPrec, depth, multiLine)
+ p.print(x.Lparen, token.LPAREN)
+ p.exprList(x.Lparen, x.Args, depth, commaSep|commaTerm, multiLine, x.Rparen)
+ if x.Ellipsis.IsValid() {
+ p.print(x.Ellipsis, token.ELLIPSIS)
+ }
+ p.print(x.Rparen, token.RPAREN)
+
+ case *ast.CompositeLit:
+ // composite literal elements that are composite literals themselves may have the type omitted
+ if x.Type != nil {
+ p.expr1(x.Type, token.HighestPrec, depth, multiLine)
+ }
+ p.print(x.Lbrace, token.LBRACE)
+ p.exprList(x.Lbrace, x.Elts, 1, commaSep|commaTerm, multiLine, x.Rbrace)
+ // do not insert extra line breaks because of comments before
+ // the closing '}' as it might break the code if there is no
+ // trailing ','
+ p.print(noExtraLinebreak, x.Rbrace, token.RBRACE, noExtraLinebreak)
+
+ case *ast.Ellipsis:
+ p.print(token.ELLIPSIS)
+ if x.Elt != nil {
+ p.expr(x.Elt, multiLine)
+ }
+
+ case *ast.ArrayType:
+ p.print(token.LBRACK)
+ if x.Len != nil {
+ p.expr(x.Len, multiLine)
+ }
+ p.print(token.RBRACK)
+ p.expr(x.Elt, multiLine)
+
+ case *ast.StructType:
+ p.print(token.STRUCT)
+ p.fieldList(x.Fields, true, x.Incomplete)
+
+ case *ast.FuncType:
+ p.print(token.FUNC)
+ p.signature(x.Params, x.Results, multiLine)
+
+ case *ast.InterfaceType:
+ p.print(token.INTERFACE)
+ p.fieldList(x.Methods, false, x.Incomplete)
+
+ case *ast.MapType:
+ p.print(token.MAP, token.LBRACK)
+ p.expr(x.Key, multiLine)
+ p.print(token.RBRACK)
+ p.expr(x.Value, multiLine)
+
+ case *ast.ChanType:
+ switch x.Dir {
+ case ast.SEND | ast.RECV:
+ p.print(token.CHAN)
+ case ast.RECV:
+ p.print(token.ARROW, token.CHAN)
+ case ast.SEND:
+ p.print(token.CHAN, token.ARROW)
+ }
+ p.print(blank)
+ p.expr(x.Value, multiLine)
+
+ default:
+ panic("unreachable")
+ }
+
+ return
+}
+
+func (p *printer) expr0(x ast.Expr, depth int, multiLine *bool) {
+ p.expr1(x, token.LowestPrec, depth, multiLine)
+}
+
+// Sets multiLine to true if the expression spans multiple lines.
+func (p *printer) expr(x ast.Expr, multiLine *bool) {
+ const depth = 1
+ p.expr1(x, token.LowestPrec, depth, multiLine)
+}
+
+// ----------------------------------------------------------------------------
+// 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.
+func (p *printer) stmtList(list []ast.Stmt, _indent int, nextIsRBrace bool) {
+ // TODO(gri): fix _indent code
+ if _indent > 0 {
+ p.print(indent)
+ }
+ var multiLine 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(p.fset.Position(s.Pos()).Line, 1, ignore, i == 0 || _indent == 0 || multiLine)
+ multiLine = false
+ p.stmt(s, nextIsRBrace && i == len(list)-1, &multiLine)
+ }
+ if _indent > 0 {
+ p.print(unindent)
+ }
+}
+
+// block prints an *ast.BlockStmt; it always spans at least two lines.
+func (p *printer) block(s *ast.BlockStmt, indent int) {
+ p.print(s.Pos(), token.LBRACE)
+ p.stmtList(s.List, indent, true)
+ p.linebreak(p.fset.Position(s.Rbrace).Line, 1, ignore, true)
+ p.print(s.Rbrace, token.RBRACE)
+}
+
+func isTypeName(x ast.Expr) bool {
+ switch t := x.(type) {
+ case *ast.Ident:
+ return true
+ case *ast.SelectorExpr:
+ return isTypeName(t.X)
+ }
+ return false
+}
+
+func stripParens(x ast.Expr) ast.Expr {
+ if px, strip := x.(*ast.ParenExpr); strip {
+ // 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
+}
+
+func (p *printer) controlClause(isForStmt bool, init ast.Stmt, expr ast.Expr, post ast.Stmt) {
+ p.print(blank)
+ needsBlank := false
+ if init == nil && post == nil {
+ // no semicolons required
+ if expr != nil {
+ p.expr(stripParens(expr), ignoreMultiLine)
+ needsBlank = true
+ }
+ } else {
+ // all semicolons required
+ // (they are not separators, print them explicitly)
+ if init != nil {
+ p.stmt(init, false, ignoreMultiLine)
+ }
+ p.print(token.SEMICOLON, blank)
+ if expr != nil {
+ p.expr(stripParens(expr), ignoreMultiLine)
+ needsBlank = true
+ }
+ if isForStmt {
+ p.print(token.SEMICOLON, blank)
+ needsBlank = false
+ if post != nil {
+ p.stmt(post, false, ignoreMultiLine)
+ needsBlank = true
+ }
+ }
+ }
+ if needsBlank {
+ p.print(blank)
+ }
+}
+
+// Sets multiLine to true if the statements spans multiple lines.
+func (p *printer) stmt(stmt ast.Stmt, nextIsRBrace bool, multiLine *bool) {
+ p.print(stmt.Pos())
+
+ switch s := stmt.(type) {
+ case *ast.BadStmt:
+ p.print("BadStmt")
+
+ case *ast.DeclStmt:
+ p.decl(s.Decl, multiLine)
+
+ case *ast.EmptyStmt:
+ // nothing to do
+
+ case *ast.LabeledStmt:
+ // a "correcting" unindent immediately following a line break
+ // is applied before the line break if there is no comment
+ // between (see writeWhitespace)
+ p.print(unindent)
+ p.expr(s.Label, multiLine)
+ 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.linebreak(p.fset.Position(s.Stmt.Pos()).Line, 1, ignore, true)
+ }
+ p.stmt(s.Stmt, nextIsRBrace, multiLine)
+
+ case *ast.ExprStmt:
+ const depth = 1
+ p.expr0(s.X, depth, multiLine)
+
+ case *ast.SendStmt:
+ const depth = 1
+ p.expr0(s.Chan, depth, multiLine)
+ p.print(blank, s.Arrow, token.ARROW, blank)
+ p.expr0(s.Value, depth, multiLine)
+
+ case *ast.IncDecStmt:
+ const depth = 1
+ p.expr0(s.X, depth+1, multiLine)
+ p.print(s.TokPos, s.Tok)
+
+ case *ast.AssignStmt:
+ var depth = 1
+ if len(s.Lhs) > 1 && len(s.Rhs) > 1 {
+ depth++
+ }
+ p.exprList(s.Pos(), s.Lhs, depth, commaSep, multiLine, s.TokPos)
+ p.print(blank, s.TokPos, s.Tok)
+ p.exprList(s.TokPos, s.Rhs, depth, blankStart|commaSep, multiLine, token.NoPos)
+
+ case *ast.GoStmt:
+ p.print(token.GO, blank)
+ p.expr(s.Call, multiLine)
+
+ case *ast.DeferStmt:
+ p.print(token.DEFER, blank)
+ p.expr(s.Call, multiLine)
+
+ case *ast.ReturnStmt:
+ p.print(token.RETURN)
+ if s.Results != nil {
+ p.exprList(s.Pos(), s.Results, 1, blankStart|commaSep, multiLine, token.NoPos)
+ }
+
+ case *ast.BranchStmt:
+ p.print(s.Tok)
+ if s.Label != nil {
+ p.print(blank)
+ p.expr(s.Label, multiLine)
+ }
+
+ case *ast.BlockStmt:
+ p.block(s, 1)
+ *multiLine = true
+
+ case *ast.IfStmt:
+ p.print(token.IF)
+ p.controlClause(false, s.Init, s.Cond, nil)
+ p.block(s.Body, 1)
+ *multiLine = true
+ if s.Else != nil {
+ p.print(blank, token.ELSE, blank)
+ switch s.Else.(type) {
+ case *ast.BlockStmt, *ast.IfStmt:
+ p.stmt(s.Else, nextIsRBrace, ignoreMultiLine)
+ default:
+ p.print(token.LBRACE, indent, formfeed)
+ p.stmt(s.Else, true, ignoreMultiLine)
+ p.print(unindent, formfeed, token.RBRACE)
+ }
+ }
+
+ case *ast.CaseClause:
+ if s.List != nil {
+ p.print(token.CASE)
+ p.exprList(s.Pos(), s.List, 1, blankStart|commaSep, multiLine, s.Colon)
+ } else {
+ p.print(token.DEFAULT)
+ }
+ p.print(s.Colon, token.COLON)
+ p.stmtList(s.Body, 1, nextIsRBrace)
+
+ case *ast.SwitchStmt:
+ p.print(token.SWITCH)
+ p.controlClause(false, s.Init, s.Tag, nil)
+ p.block(s.Body, 0)
+ *multiLine = true
+
+ case *ast.TypeSwitchStmt:
+ p.print(token.SWITCH)
+ if s.Init != nil {
+ p.print(blank)
+ p.stmt(s.Init, false, ignoreMultiLine)
+ p.print(token.SEMICOLON)
+ }
+ p.print(blank)
+ p.stmt(s.Assign, false, ignoreMultiLine)
+ p.print(blank)
+ p.block(s.Body, 0)
+ *multiLine = true
+
+ case *ast.CommClause:
+ if s.Comm != nil {
+ p.print(token.CASE, blank)
+ p.stmt(s.Comm, false, ignoreMultiLine)
+ } else {
+ p.print(token.DEFAULT)
+ }
+ p.print(s.Colon, token.COLON)
+ p.stmtList(s.Body, 1, nextIsRBrace)
+
+ case *ast.SelectStmt:
+ p.print(token.SELECT, blank)
+ body := s.Body
+ if len(body.List) == 0 && !p.commentBefore(p.fset.Position(body.Rbrace)) {
+ // print empty select statement w/o comments on one line
+ p.print(body.Lbrace, token.LBRACE, body.Rbrace, token.RBRACE)
+ } else {
+ p.block(body, 0)
+ *multiLine = true
+ }
+
+ case *ast.ForStmt:
+ p.print(token.FOR)
+ p.controlClause(true, s.Init, s.Cond, s.Post)
+ p.block(s.Body, 1)
+ *multiLine = true
+
+ case *ast.RangeStmt:
+ p.print(token.FOR, blank)
+ p.expr(s.Key, multiLine)
+ if s.Value != nil {
+ p.print(token.COMMA, blank)
+ p.expr(s.Value, multiLine)
+ }
+ p.print(blank, s.TokPos, s.Tok, blank, token.RANGE, blank)
+ p.expr(stripParens(s.X), multiLine)
+ p.print(blank)
+ p.block(s.Body, 1)
+ *multiLine = true
+
+ default:
+ panic("unreachable")
+ }
+
+ return
+}
+
+// ----------------------------------------------------------------------------
+// Declarations
+
+// The keepTypeColumn function determines if the type column of a series of
+// consecutive const or var declarations must be kept, or if initialization
+// values (V) can be placed in the type column (T) instead. The i'th entry
+// in the result slice is true if the type column in spec[i] must be kept.
+//
+// For example, the declaration:
+//
+// const (
+// foobar int = 42 // comment
+// x = 7 // comment
+// foo
+// bar = 991
+// )
+//
+// leads to the type/values matrix below. A run of value columns (V) can
+// be moved into the type column if there is no type for any of the values
+// in that column (we only move entire columns so that they align properly).
+//
+// matrix formatted result
+// matrix
+// T V -> T V -> true there is a T and so the type
+// - V - V true column must be kept
+// - - - - false
+// - V V - false V is moved into T column
+//
+func keepTypeColumn(specs []ast.Spec) []bool {
+ m := make([]bool, len(specs))
+
+ populate := func(i, j int, keepType bool) {
+ if keepType {
+ for ; i < j; i++ {
+ m[i] = true
+ }
+ }
+ }
+
+ i0 := -1 // if i0 >= 0 we are in a run and i0 is the start of the run
+ var keepType bool
+ for i, s := range specs {
+ t := s.(*ast.ValueSpec)
+ if t.Values != nil {
+ if i0 < 0 {
+ // start of a run of ValueSpecs with non-nil Values
+ i0 = i
+ keepType = false
+ }
+ } else {
+ if i0 >= 0 {
+ // end of a run
+ populate(i0, i, keepType)
+ i0 = -1
+ }
+ }
+ if t.Type != nil {
+ keepType = true
+ }
+ }
+ if i0 >= 0 {
+ // end of a run
+ populate(i0, len(specs), keepType)
+ }
+
+ return m
+}
+
+func (p *printer) valueSpec(s *ast.ValueSpec, keepType, doIndent bool, multiLine *bool) {
+ p.setComment(s.Doc)
+ p.identList(s.Names, doIndent, multiLine) // always present
+ extraTabs := 3
+ if s.Type != nil || keepType {
+ p.print(vtab)
+ extraTabs--
+ }
+ if s.Type != nil {
+ p.expr(s.Type, multiLine)
+ }
+ if s.Values != nil {
+ p.print(vtab, token.ASSIGN)
+ p.exprList(token.NoPos, s.Values, 1, blankStart|commaSep, multiLine, token.NoPos)
+ extraTabs--
+ }
+ if s.Comment != nil {
+ for ; extraTabs > 0; extraTabs-- {
+ p.print(vtab)
+ }
+ p.setComment(s.Comment)
+ }
+}
+
+// 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, 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.expr(s.Path, multiLine)
+ p.setComment(s.Comment)
+
+ case *ast.ValueSpec:
+ if n != 1 {
+ p.internalError("expected n = 1; got", n)
+ }
+ p.setComment(s.Doc)
+ p.identList(s.Names, doIndent, multiLine) // always present
+ if s.Type != nil {
+ p.print(blank)
+ p.expr(s.Type, multiLine)
+ }
+ if s.Values != nil {
+ p.print(blank, token.ASSIGN)
+ p.exprList(token.NoPos, s.Values, 1, blankStart|commaSep, multiLine, token.NoPos)
+ }
+ p.setComment(s.Comment)
+
+ case *ast.TypeSpec:
+ p.setComment(s.Doc)
+ p.expr(s.Name, multiLine)
+ if n == 1 {
+ p.print(blank)
+ } else {
+ p.print(vtab)
+ }
+ p.expr(s.Type, multiLine)
+ p.setComment(s.Comment)
+
+ default:
+ panic("unreachable")
+ }
+}
+
+// Sets multiLine to true if the declaration spans multiple lines.
+func (p *printer) genDecl(d *ast.GenDecl, multiLine *bool) {
+ p.setComment(d.Doc)
+ p.print(d.Pos(), d.Tok, blank)
+
+ if d.Lparen.IsValid() {
+ // group of parenthesized declarations
+ p.print(d.Lparen, token.LPAREN)
+ if n := len(d.Specs); n > 0 {
+ p.print(indent, formfeed)
+ if n > 1 && (d.Tok == token.CONST || d.Tok == token.VAR) {
+ // two or more grouped const/var declarations:
+ // determine if the type column must be kept
+ keepType := keepTypeColumn(d.Specs)
+ var ml bool
+ for i, s := range d.Specs {
+ if i > 0 {
+ p.linebreak(p.fset.Position(s.Pos()).Line, 1, ignore, ml)
+ }
+ ml = false
+ p.valueSpec(s.(*ast.ValueSpec), keepType[i], false, &ml)
+ }
+ } else {
+ var ml bool
+ for i, s := range d.Specs {
+ if i > 0 {
+ p.linebreak(p.fset.Position(s.Pos()).Line, 1, ignore, ml)
+ }
+ ml = false
+ p.spec(s, n, false, &ml)
+ }
+ }
+ p.print(unindent, formfeed)
+ *multiLine = true
+ }
+ p.print(d.Rparen, token.RPAREN)
+
+ } else {
+ // single declaration
+ p.spec(d.Specs[0], 1, true, multiLine)
+ }
+}
+
+// nodeSize determines the size of n in chars after formatting.
+// The result is <= maxSize if the node fits on one line with at
+// most maxSize chars and the formatted output doesn't contain
+// any control chars. Otherwise, the result is > maxSize.
+//
+func (p *printer) nodeSize(n ast.Node, maxSize int) (size int) {
+ // nodeSize invokes the printer, which may invoke nodeSize
+ // recursively. For deep composite literal nests, this can
+ // lead to an exponential algorithm. Remember previous
+ // results to prune the recursion (was issue 1628).
+ if size, found := p.nodeSizes[n]; found {
+ return size
+ }
+
+ size = maxSize + 1 // assume n doesn't fit
+ p.nodeSizes[n] = size
+
+ // nodeSize computation must be independent of particular
+ // style so that we always get the same decision; print
+ // in RawFormat
+ cfg := Config{Mode: RawFormat}
+ var buf bytes.Buffer
+ if _, err := cfg.fprint(&buf, p.fset, n, p.nodeSizes); err != nil {
+ return
+ }
+ if buf.Len() <= maxSize {
+ for _, ch := range buf.Bytes() {
+ if ch < ' ' {
+ return
+ }
+ }
+ size = buf.Len() // n fits
+ p.nodeSizes[n] = size
+ }
+ return
+}
+
+func (p *printer) isOneLineFunc(b *ast.BlockStmt, headerSize int) bool {
+ pos1 := b.Pos()
+ pos2 := b.Rbrace
+ 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(p.fset.Position(pos2)) {
+ // too many statements or there is a comment inside - don't make it a one-liner
+ return false
+ }
+ // otherwise, estimate body size
+ const maxSize = 100
+ bodySize := 0
+ for i, s := range b.List {
+ if i > 0 {
+ bodySize += 2 // space for a semicolon and blank
+ }
+ bodySize += p.nodeSize(s, maxSize)
+ }
+ return headerSize+bodySize <= maxSize
+}
+
+// Sets multiLine to true if the function body spans multiple lines.
+func (p *printer) funcBody(b *ast.BlockStmt, headerSize int, isLit bool, multiLine *bool) {
+ if b == nil {
+ return
+ }
+
+ if p.isOneLineFunc(b, headerSize) {
+ sep := vtab
+ if isLit {
+ sep = blank
+ }
+ p.print(sep, b.Lbrace, token.LBRACE)
+ if len(b.List) > 0 {
+ p.print(blank)
+ for i, s := range b.List {
+ if i > 0 {
+ p.print(token.SEMICOLON, blank)
+ }
+ p.stmt(s, i == len(b.List)-1, ignoreMultiLine)
+ }
+ p.print(blank)
+ }
+ p.print(b.Rbrace, token.RBRACE)
+ return
+ }
+
+ p.print(blank)
+ p.block(b, 1)
+ *multiLine = true
+}
+
+// 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 (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
+ }
+ return infinity
+}
+
+// Sets multiLine to true if the declaration spans multiple lines.
+func (p *printer) funcDecl(d *ast.FuncDecl, multiLine *bool) {
+ p.setComment(d.Doc)
+ p.print(d.Pos(), token.FUNC, blank)
+ if d.Recv != nil {
+ p.parameters(d.Recv, multiLine) // method: print receiver
+ p.print(blank)
+ }
+ p.expr(d.Name, multiLine)
+ p.signature(d.Type.Params, d.Type.Results, multiLine)
+ p.funcBody(d.Body, p.distance(d.Pos(), p.pos), false, multiLine)
+}
+
+// Sets multiLine to true if the declaration spans multiple lines.
+func (p *printer) decl(decl ast.Decl, multiLine *bool) {
+ switch d := decl.(type) {
+ case *ast.BadDecl:
+ p.print(d.Pos(), "BadDecl")
+ case *ast.GenDecl:
+ p.genDecl(d, multiLine)
+ case *ast.FuncDecl:
+ p.funcDecl(d, multiLine)
+ default:
+ panic("unreachable")
+ }
+}
+
+// ----------------------------------------------------------------------------
+// Files
+
+func declToken(decl ast.Decl) (tok token.Token) {
+ tok = token.ILLEGAL
+ switch d := decl.(type) {
+ case *ast.GenDecl:
+ tok = d.Tok
+ case *ast.FuncDecl:
+ tok = token.FUNC
+ }
+ return
+}
+
+func (p *printer) file(src *ast.File) {
+ p.setComment(src.Doc)
+ p.print(src.Pos(), token.PACKAGE, blank)
+ p.expr(src.Name, ignoreMultiLine)
+
+ if len(src.Decls) > 0 {
+ tok := token.ILLEGAL
+ for _, d := range src.Decls {
+ prev := tok
+ tok = declToken(d)
+ // if the declaration token changed (e.g., from CONST to TYPE)
+ // print an empty line between top-level declarations
+ min := 1
+ if prev != tok {
+ min = 2
+ }
+ p.linebreak(p.fset.Position(d.Pos()).Line, min, ignore, false)
+ p.decl(d, ignoreMultiLine)
+ }
+ }
+
+ p.print(newline)
+}
diff --git a/src/pkg/go/printer/performance_test.go b/src/pkg/go/printer/performance_test.go
new file mode 100644
index 000000000..84fb2808e
--- /dev/null
+++ b/src/pkg/go/printer/performance_test.go
@@ -0,0 +1,58 @@
+// Copyright 2009 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// This file implements a simple printer performance benchmark:
+// gotest -bench=BenchmarkPrint
+
+package printer
+
+import (
+ "bytes"
+ "go/ast"
+ "go/parser"
+ "io"
+ "io/ioutil"
+ "log"
+ "testing"
+)
+
+var testfile *ast.File
+
+func testprint(out io.Writer, file *ast.File) {
+ if _, err := (&Config{TabIndent | UseSpaces, 8}).Fprint(out, fset, file); err != nil {
+ log.Fatalf("print error: %s", err)
+ }
+}
+
+// cannot initialize in init because (printer) Fprint launches goroutines.
+func initialize() {
+ const filename = "testdata/parser.go"
+
+ src, err := ioutil.ReadFile(filename)
+ if err != nil {
+ log.Fatalf("%s", err)
+ }
+
+ file, err := parser.ParseFile(fset, filename, src, parser.ParseComments)
+ if err != nil {
+ log.Fatalf("%s", err)
+ }
+
+ var buf bytes.Buffer
+ testprint(&buf, file)
+ if !bytes.Equal(buf.Bytes(), src) {
+ log.Fatalf("print error: %s not idempotent", filename)
+ }
+
+ testfile = file
+}
+
+func BenchmarkPrint(b *testing.B) {
+ if testfile == nil {
+ initialize()
+ }
+ for i := 0; i < b.N; i++ {
+ testprint(ioutil.Discard, testfile)
+ }
+}
diff --git a/src/pkg/go/printer/printer.go b/src/pkg/go/printer/printer.go
new file mode 100644
index 000000000..871fefa0c
--- /dev/null
+++ b/src/pkg/go/printer/printer.go
@@ -0,0 +1,1012 @@
+// Copyright 2009 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Package printer implements printing of AST nodes.
+package printer
+
+import (
+ "bytes"
+ "fmt"
+ "go/ast"
+ "go/token"
+ "io"
+ "os"
+ "path/filepath"
+ "runtime"
+ "tabwriter"
+)
+
+const debug = false // enable for debugging
+
+
+type whiteSpace int
+
+const (
+ ignore = whiteSpace(0)
+ blank = whiteSpace(' ')
+ vtab = whiteSpace('\v')
+ newline = whiteSpace('\n')
+ formfeed = whiteSpace('\f')
+ indent = whiteSpace('>')
+ unindent = whiteSpace('<')
+)
+
+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 the max determined by nlines
+ formfeeds = []byte("\f\f\f\f\f\f\f\f") // more than the max determined by nlines
+)
+
+// Special positions
+var noPos token.Position // use noPos when a position is needed but not known
+var infinity = 1 << 30
+
+// Use ignoreMultiLine if the multiLine information is not important.
+var ignoreMultiLine = new(bool)
+
+// A pmode value represents the current printer mode.
+type pmode int
+
+const (
+ inLiteral pmode = 1 << iota
+ noExtraLinebreak
+)
+
+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
+ mode pmode // current printer mode
+ lastTok token.Token // the last token printed (token.ILLEGAL if it's whitespace)
+
+ // Reused buffers
+ wsbuf []whiteSpace // delayed white space
+ litbuf bytes.Buffer // for creation of escaped literals and comments
+
+ // The (possibly estimated) position in the generated output;
+ // in AST space (i.e., pos is set whenever a token position is
+ // known accurately, and updated dependending on what has been
+ // written).
+ pos token.Position
+
+ // The value of pos immediately after the last item has been
+ // written using writeItem.
+ last token.Position
+
+ // The list of all source comments, in order of appearance.
+ comments []*ast.CommentGroup // may be nil
+ cindex int // current comment index
+ useNodeComments bool // if not set, ignore lead and line comments of nodes
+
+ // Cache of already computed node sizes.
+ nodeSizes map[ast.Node]int
+}
+
+func (p *printer) init(output io.Writer, cfg *Config, fset *token.FileSet, nodeSizes map[ast.Node]int) {
+ p.output = output
+ p.Config = *cfg
+ p.fset = fset
+ p.errors = make(chan os.Error)
+ p.wsbuf = make([]whiteSpace, 0, 16) // whitespace sequences are short
+ p.nodeSizes = nodeSizes
+}
+
+func (p *printer) internalError(msg ...interface{}) {
+ if debug {
+ fmt.Print(p.pos.String() + ": ")
+ fmt.Println(msg...)
+ panic("go/printer")
+ }
+}
+
+// escape escapes string s by bracketing it with tabwriter.Escape.
+// Escaped strings pass through tabwriter unchanged. (Note that
+// valid Go programs cannot contain tabwriter.Escape bytes since
+// they do not appear in legal UTF-8 sequences).
+//
+func (p *printer) escape(s string) string {
+ p.litbuf.Reset()
+ p.litbuf.WriteByte(tabwriter.Escape)
+ p.litbuf.WriteString(s)
+ p.litbuf.WriteByte(tabwriter.Escape)
+ return p.litbuf.String()
+}
+
+// nlines returns the adjusted number of linebreaks given the desired number
+// of breaks n such that min <= result <= max.
+//
+func (p *printer) nlines(n, min int) int {
+ const max = 2 // max. number of newlines
+ switch {
+ case n < min:
+ return min
+ case 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.
+//
+func (p *printer) write0(data []byte) {
+ if len(data) > 0 {
+ n, err := p.output.Write(data)
+ p.written += n
+ if err != nil {
+ p.errors <- err
+ runtime.Goexit()
+ }
+ }
+}
+
+// write interprets data and writes it to p.output. It inserts indentation
+// after a line break unless in a tabwriter escape sequence.
+// It updates p.pos as a side-effect.
+//
+func (p *printer) write(data []byte) {
+ i0 := 0
+ for i, b := range data {
+ switch b {
+ case '\n', '\f':
+ // write segment ending in b
+ p.write0(data[i0 : i+1])
+
+ // update p.pos
+ p.pos.Offset += i + 1 - i0
+ p.pos.Line++
+ p.pos.Column = 1
+
+ if p.mode&inLiteral == 0 {
+ // write indentation
+ // use "hard" htabs - indentation columns
+ // must not be discarded by the tabwriter
+ j := p.indent
+ for ; j > len(htabs); j -= len(htabs) {
+ p.write0(htabs)
+ }
+ p.write0(htabs[0:j])
+
+ // update p.pos
+ p.pos.Offset += p.indent
+ p.pos.Column += p.indent
+ }
+
+ // next segment start
+ i0 = i + 1
+
+ case tabwriter.Escape:
+ p.mode ^= inLiteral
+
+ // ignore escape chars introduced by printer - they are
+ // invisible and must not affect p.pos (was issue #1089)
+ p.pos.Offset--
+ p.pos.Column--
+ }
+ }
+
+ // write remaining segment
+ p.write0(data[i0:])
+
+ // update p.pos
+ d := len(data) - i0
+ p.pos.Offset += d
+ p.pos.Column += d
+}
+
+func (p *printer) writeNewlines(n int, useFF bool) {
+ if n > 0 {
+ n = p.nlines(n, 0)
+ if useFF {
+ p.write(formfeeds[0:n])
+ } else {
+ p.write(newlines[0:n])
+ }
+ }
+}
+
+// writeItem writes data at position pos. data is the text corresponding to
+// a single lexical token, but may also be comment text. pos is the actual
+// (or at least very accurately estimated) position of the data in the original
+// source text. writeItem updates p.last to the position immediately following
+// the data.
+//
+func (p *printer) writeItem(pos token.Position, data string) {
+ if pos.IsValid() {
+ // continue with previous position if we don't have a valid pos
+ if p.last.IsValid() && p.last.Filename != pos.Filename {
+ // the file has changed - reset state
+ // (used when printing merged ASTs of different files
+ // e.g., the result of ast.MergePackageFiles)
+ p.indent = 0
+ p.mode = 0
+ p.wsbuf = p.wsbuf[0:0]
+ }
+ p.pos = pos
+ }
+ if debug {
+ // do not update p.pos - use write0
+ _, filename := filepath.Split(pos.Filename)
+ p.write0([]byte(fmt.Sprintf("[%s:%d:%d]", filename, pos.Line, pos.Column)))
+ }
+ p.write([]byte(data))
+ p.last = p.pos
+}
+
+// writeCommentPrefix writes the whitespace before a comment.
+// If there is any pending whitespace, it consumes as much of
+// it as is likely to help position the comment nicely.
+// pos is the comment position, next the position of the item
+// after all pending comments, prev is the previous comment in
+// a group of comments (or nil), and isKeyword indicates if the
+// next item is a keyword.
+//
+func (p *printer) writeCommentPrefix(pos, next token.Position, prev *ast.Comment, isKeyword bool) {
+ if p.written == 0 {
+ // the comment is the first item to be printed - don't write any whitespace
+ return
+ }
+
+ if pos.IsValid() && pos.Filename != p.last.Filename {
+ // comment in a different file - separate with newlines (writeNewlines will limit the number)
+ p.writeNewlines(10, true)
+ return
+ }
+
+ if pos.Line == p.last.Line && (prev == nil || prev.Text[1] != '/') {
+ // comment on the same line as last item:
+ // separate with at least one separator
+ hasSep := false
+ if prev == nil {
+ // first comment of a comment group
+ j := 0
+ for i, ch := range p.wsbuf {
+ switch ch {
+ case blank:
+ // ignore any blanks before a comment
+ p.wsbuf[i] = ignore
+ continue
+ case vtab:
+ // respect existing tabs - important
+ // for proper formatting of commented structs
+ hasSep = true
+ continue
+ case indent:
+ // apply pending indentation
+ continue
+ }
+ j = i
+ break
+ }
+ p.writeWhitespace(j)
+ }
+ // make sure there is at least one separator
+ if !hasSep {
+ if pos.Line == next.Line {
+ // next item is on the same line as the comment
+ // (which must be a /*-style comment): separate
+ // with a blank instead of a tab
+ p.write([]byte{' '})
+ } else {
+ p.write(htab)
+ }
+ }
+
+ } else {
+ // comment on a different line:
+ // separate with at least one line break
+ if prev == nil {
+ // first comment of a comment group
+ j := 0
+ for i, ch := range p.wsbuf {
+ switch ch {
+ case blank, vtab:
+ // ignore any horizontal whitespace before line breaks
+ p.wsbuf[i] = ignore
+ continue
+ case indent:
+ // apply pending indentation
+ continue
+ case unindent:
+ // if the next token is a keyword, apply the outdent
+ // if it appears that the comment is aligned with the
+ // keyword; otherwise assume the outdent is part of a
+ // closing block and stop (this scenario appears with
+ // comments before a case label where the comments
+ // apply to the next case instead of the current one)
+ if isKeyword && pos.Column == next.Column {
+ continue
+ }
+ case newline, formfeed:
+ // TODO(gri): may want to keep formfeed info in some cases
+ p.wsbuf[i] = ignore
+ }
+ j = i
+ break
+ }
+ p.writeWhitespace(j)
+ }
+ // use formfeeds to break columns before a comment;
+ // this is analogous to using formfeeds to separate
+ // individual lines of /*-style comments - but make
+ // sure there is at least one line break if the previous
+ // comment was a line comment
+ n := pos.Line - p.last.Line // if !pos.IsValid(), pos.Line == 0, and n will be 0
+ if n <= 0 && prev != nil && prev.Text[1] == '/' {
+ n = 1
+ }
+ p.writeNewlines(n, true)
+ }
+}
+
+// TODO(gri): It should be possible to convert the code below from using
+// []byte to string and in the process eliminate some conversions.
+
+// Split comment text into lines
+func split(text []byte) [][]byte {
+ // count lines (comment text never ends in a newline)
+ n := 1
+ for _, c := range text {
+ if c == '\n' {
+ n++
+ }
+ }
+
+ // split
+ lines := make([][]byte, n)
+ n = 0
+ i := 0
+ for j, c := range text {
+ if c == '\n' {
+ lines[n] = text[i:j] // exclude newline
+ i = j + 1 // discard newline
+ n++
+ }
+ }
+ lines[n] = text[i:]
+
+ return lines
+}
+
+func isBlank(s []byte) bool {
+ for _, b := range s {
+ if b > ' ' {
+ return false
+ }
+ }
+ return true
+}
+
+func commonPrefix(a, b []byte) []byte {
+ i := 0
+ for i < len(a) && i < len(b) && a[i] == b[i] && (a[i] <= ' ' || a[i] == '*') {
+ i++
+ }
+ return a[0:i]
+}
+
+func stripCommonPrefix(lines [][]byte) {
+ if len(lines) < 2 {
+ return // at most one line - nothing to do
+ }
+ // len(lines) >= 2
+
+ // The heuristic in this function tries to handle a few
+ // common patterns of /*-style comments: Comments where
+ // the opening /* and closing */ are aligned and the
+ // rest of the comment text is aligned and indented with
+ // blanks or tabs, cases with a vertical "line of stars"
+ // on the left, and cases where the closing */ is on the
+ // same line as the last comment text.
+
+ // Compute maximum common white prefix of all but the first,
+ // last, and blank lines, and replace blank lines with empty
+ // lines (the first line starts with /* and has no prefix).
+ // In case of two-line comments, consider the last line for
+ // the prefix computation since otherwise the prefix would
+ // be empty.
+ //
+ // Note that the first and last line are never empty (they
+ // contain the opening /* and closing */ respectively) and
+ // thus they can be ignored by the blank line check.
+ var prefix []byte
+ if len(lines) > 2 {
+ for i, line := range lines[1 : len(lines)-1] {
+ switch {
+ case isBlank(line):
+ lines[1+i] = nil // range starts at line 1
+ case prefix == nil:
+ prefix = commonPrefix(line, line)
+ default:
+ prefix = commonPrefix(prefix, line)
+ }
+ }
+ } else { // len(lines) == 2
+ line := lines[1]
+ prefix = commonPrefix(line, line)
+ }
+
+ /*
+ * Check for vertical "line of stars" and correct prefix accordingly.
+ */
+ lineOfStars := false
+ if i := bytes.Index(prefix, []byte{'*'}); i >= 0 {
+ // Line of stars present.
+ if i > 0 && prefix[i-1] == ' ' {
+ i-- // remove trailing blank from prefix so stars remain aligned
+ }
+ prefix = prefix[0:i]
+ lineOfStars = true
+ } else {
+ // No line of stars present.
+ // Determine the white space on the first line after the /*
+ // and before the beginning of the comment text, assume two
+ // blanks instead of the /* unless the first character after
+ // the /* is a tab. If the first comment line is empty but
+ // for the opening /*, assume up to 3 blanks or a tab. This
+ // whitespace may be found as suffix in the common prefix.
+ first := lines[0]
+ if isBlank(first[2:]) {
+ // no comment text on the first line:
+ // reduce prefix by up to 3 blanks or a tab
+ // if present - this keeps comment text indented
+ // relative to the /* and */'s if it was indented
+ // in the first place
+ i := len(prefix)
+ for n := 0; n < 3 && i > 0 && prefix[i-1] == ' '; n++ {
+ i--
+ }
+ if i == len(prefix) && i > 0 && prefix[i-1] == '\t' {
+ i--
+ }
+ prefix = prefix[0:i]
+ } else {
+ // comment text on the first line
+ suffix := make([]byte, len(first))
+ n := 2 // start after opening /*
+ for n < len(first) && first[n] <= ' ' {
+ suffix[n] = first[n]
+ n++
+ }
+ if n > 2 && suffix[2] == '\t' {
+ // assume the '\t' compensates for the /*
+ suffix = suffix[2:n]
+ } else {
+ // otherwise assume two blanks
+ suffix[0], suffix[1] = ' ', ' '
+ suffix = suffix[0:n]
+ }
+ // Shorten the computed common prefix by the length of
+ // suffix, if it is found as suffix of the prefix.
+ if bytes.HasSuffix(prefix, suffix) {
+ prefix = prefix[0 : len(prefix)-len(suffix)]
+ }
+ }
+ }
+
+ // Handle last line: If it only contains a closing */, align it
+ // with the opening /*, otherwise align the text with the other
+ // lines.
+ last := lines[len(lines)-1]
+ closing := []byte("*/")
+ i := bytes.Index(last, closing)
+ if isBlank(last[0:i]) {
+ // last line only contains closing */
+ var sep []byte
+ if lineOfStars {
+ // insert an aligning blank
+ sep = []byte{' '}
+ }
+ lines[len(lines)-1] = bytes.Join([][]byte{prefix, closing}, sep)
+ } else {
+ // last line contains more comment text - assume
+ // it is aligned like the other lines
+ prefix = commonPrefix(prefix, last)
+ }
+
+ // Remove the common prefix from all but the first and empty lines.
+ for i, line := range lines[1:] {
+ if len(line) != 0 {
+ lines[1+i] = line[len(prefix):] // range starts at line 1
+ }
+ }
+}
+
+func (p *printer) writeComment(comment *ast.Comment) {
+ text := comment.Text
+
+ // shortcut common case of //-style comments
+ if text[1] == '/' {
+ p.writeItem(p.fset.Position(comment.Pos()), p.escape(text))
+ return
+ }
+
+ // for /*-style comments, print line by line and let the
+ // write function take care of the proper indentation
+ lines := split([]byte(text))
+ stripCommonPrefix(lines)
+
+ // write comment lines, separated by formfeed,
+ // without a line break after the last line
+ linebreak := formfeeds[0:1]
+ pos := p.fset.Position(comment.Pos())
+ for i, line := range lines {
+ if i > 0 {
+ p.write(linebreak)
+ pos = p.pos
+ }
+ if len(line) > 0 {
+ p.writeItem(pos, p.escape(string(line)))
+ }
+ }
+}
+
+// writeCommentSuffix writes a line break after a comment if indicated
+// and processes any leftover indentation information. If a line break
+// is needed, the kind of break (newline vs formfeed) depends on the
+// pending whitespace. writeCommentSuffix returns true if a pending
+// formfeed was dropped from the whitespace buffer.
+//
+func (p *printer) writeCommentSuffix(needsLinebreak bool) (droppedFF bool) {
+ for i, ch := range p.wsbuf {
+ switch ch {
+ case blank, vtab:
+ // ignore trailing whitespace
+ p.wsbuf[i] = ignore
+ case indent, unindent:
+ // don't lose indentation information
+ case newline, formfeed:
+ // if we need a line break, keep exactly one
+ // but remember if we dropped any formfeeds
+ if needsLinebreak {
+ needsLinebreak = false
+ } else {
+ if ch == formfeed {
+ droppedFF = true
+ }
+ p.wsbuf[i] = ignore
+ }
+ }
+ }
+ p.writeWhitespace(len(p.wsbuf))
+
+ // make sure we have a line break
+ if needsLinebreak {
+ p.write([]byte{'\n'})
+ }
+
+ return
+}
+
+// intersperseComments consumes all comments that appear before the next token
+// tok and prints it together with the buffered whitespace (i.e., the whitespace
+// that needs to be written before the next token). A heuristic is used to mix
+// the comments and whitespace. intersperseComments returns true if a pending
+// formfeed was dropped from the whitespace buffer.
+//
+func (p *printer) intersperseComments(next token.Position, tok token.Token) (droppedFF bool) {
+ var last *ast.Comment
+ for ; p.commentBefore(next); p.cindex++ {
+ for _, c := range p.comments[p.cindex].List {
+ p.writeCommentPrefix(p.fset.Position(c.Pos()), next, last, tok.IsKeyword())
+ p.writeComment(c)
+ last = c
+ }
+ }
+
+ if last != nil {
+ 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{' '})
+ }
+ // ensure that there is a line break after a //-style comment,
+ // before a closing '}' unless explicitly disabled, or at eof
+ needsLinebreak :=
+ last.Text[1] == '/' ||
+ tok == token.RBRACE && p.mode&noExtraLinebreak == 0 ||
+ tok == token.EOF
+ return p.writeCommentSuffix(needsLinebreak)
+ }
+
+ // no comment was written - we should never reach here since
+ // intersperseComments should not be called in that case
+ p.internalError("intersperseComments called without pending comments")
+ return false
+}
+
+// whiteWhitespace writes the first n whitespace entries.
+func (p *printer) writeWhitespace(n int) {
+ // write entries
+ var data [1]byte
+ for i := 0; i < n; i++ {
+ switch ch := p.wsbuf[i]; ch {
+ case ignore:
+ // ignore!
+ case indent:
+ p.indent++
+ case unindent:
+ p.indent--
+ if p.indent < 0 {
+ p.internalError("negative indentation:", p.indent)
+ p.indent = 0
+ }
+ case newline, formfeed:
+ // A line break immediately followed by a "correcting"
+ // unindent is swapped with the unindent - this permits
+ // proper label positioning. If a comment is between
+ // the line break and the label, the unindent is not
+ // part of the comment whitespace prefix and the comment
+ // will be positioned correctly indented.
+ if i+1 < n && p.wsbuf[i+1] == unindent {
+ // Use a formfeed to terminate the current section.
+ // Otherwise, a long label name on the next line leading
+ // to a wide column may increase the indentation column
+ // of lines before the label; effectively leading to wrong
+ // indentation.
+ p.wsbuf[i], p.wsbuf[i+1] = unindent, formfeed
+ i-- // do it again
+ continue
+ }
+ fallthrough
+ default:
+ data[0] = byte(ch)
+ p.write(data[0:])
+ }
+ }
+
+ // shift remaining entries down
+ i := 0
+ for ; n < len(p.wsbuf); n++ {
+ p.wsbuf[i] = p.wsbuf[n]
+ i++
+ }
+ p.wsbuf = p.wsbuf[0:i]
+}
+
+// ----------------------------------------------------------------------------
+// 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
+// any of the AST printing functions in nodes.go.
+//
+// Whitespace is accumulated until a non-whitespace token appears. Any
+// comments that need to appear before that token are printed first,
+// taking into account the amount and structure of any pending white-
+// space for best comment placement. Then, any leftover whitespace is
+// printed, followed by the actual token.
+//
+func (p *printer) print(args ...interface{}) {
+ for _, f := range args {
+ next := p.pos // estimated position of next item
+ var data string
+ var tok token.Token
+
+ switch x := f.(type) {
+ case pmode:
+ // toggle printer mode
+ p.mode ^= x
+ case whiteSpace:
+ if x == ignore {
+ // don't add ignore's to the buffer; they
+ // may screw up "correcting" unindents (see
+ // LabeledStmt)
+ break
+ }
+ i := len(p.wsbuf)
+ if i == cap(p.wsbuf) {
+ // Whitespace sequences are very short so this should
+ // never happen. Handle gracefully (but possibly with
+ // bad comment placement) if it does happen.
+ p.writeWhitespace(i)
+ i = 0
+ }
+ p.wsbuf = p.wsbuf[0 : i+1]
+ p.wsbuf[i] = x
+ case *ast.Ident:
+ data = x.Name
+ tok = token.IDENT
+ case *ast.BasicLit:
+ data = p.escape(x.Value)
+ 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.wsbuf) != 0 {
+ p.internalError("whitespace buffer not empty")
+ }
+ p.wsbuf = p.wsbuf[0:1]
+ p.wsbuf[0] = ' '
+ }
+ data = s
+ tok = x
+ case token.Pos:
+ if x.IsValid() {
+ 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 != "" {
+ droppedFF := p.flush(next, tok)
+
+ // intersperse extra newlines if present in the source
+ // (don't do this in flush as it will cause extra newlines
+ // at the end of a file) - use formfeeds if we dropped one
+ // before
+ p.writeNewlines(next.Line-p.pos.Line, droppedFF)
+
+ p.writeItem(next, data)
+ }
+ }
+}
+
+// commentBefore returns true iff the current comment occurs
+// before the next position in the source code.
+//
+func (p *printer) commentBefore(next token.Position) bool {
+ 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 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
+// comments.
+//
+func (p *printer) flush(next token.Position, tok token.Token) (droppedFF bool) {
+ if p.commentBefore(next) {
+ // if there are comments before the next item, intersperse them
+ droppedFF = p.intersperseComments(next, tok)
+ } else {
+ // otherwise, write any leftover whitespace
+ p.writeWhitespace(len(p.wsbuf))
+ }
+ return
+}
+
+// ----------------------------------------------------------------------------
+// Trimmer
+
+// 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). Text bracketed by tabwriter.Escape characters is passed
+// through unchanged.
+//
+type trimmer struct {
+ output io.Writer
+ state int
+ space bytes.Buffer
+}
+
+// trimmer is implemented as a state machine.
+// It can be in one of the following states:
+const (
+ inSpace = iota // inside space
+ inEscape // inside text bracketed by tabwriter.Escapes
+ inText // inside text
+)
+
+// 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) {
+ // invariants:
+ // p.state == inSpace:
+ // p.space is unwritten
+ // p.state == inEscape, inText:
+ // data[m:n] is unwritten
+ m := 0
+ var b byte
+ for n, b = range data {
+ if b == '\v' {
+ b = '\t' // convert to htab
+ }
+ switch p.state {
+ case inSpace:
+ switch b {
+ case '\t', ' ':
+ p.space.WriteByte(b) // WriteByte returns no errors
+ case '\n', '\f':
+ 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.state = inEscape
+ m = n + 1 // +1: skip tabwriter.Escape
+ default:
+ _, err = p.output.Write(p.space.Bytes())
+ p.state = inText
+ m = n
+ }
+ case inEscape:
+ if b == tabwriter.Escape {
+ _, err = p.output.Write(data[m:n])
+ p.state = inSpace
+ p.space.Reset()
+ }
+ case inText:
+ switch b {
+ case '\t', ' ':
+ _, err = p.output.Write(data[m:n])
+ p.state = inSpace
+ p.space.Reset()
+ p.space.WriteByte(b) // WriteByte returns no errors
+ case '\n', '\f':
+ _, err = p.output.Write(data[m:n])
+ p.state = inSpace
+ p.space.Reset()
+ _, err = p.output.Write(newlines[0:1]) // write newline
+ case tabwriter.Escape:
+ _, err = p.output.Write(data[m:n])
+ p.state = inEscape
+ m = n + 1 // +1: skip tabwriter.Escape
+ }
+ default:
+ panic("unreachable")
+ }
+ if err != nil {
+ return
+ }
+ }
+ n = len(data)
+
+ switch p.state {
+ case inEscape, inText:
+ _, err = p.output.Write(data[m:n])
+ p.state = inSpace
+ p.space.Reset()
+ }
+
+ return
+}
+
+// ----------------------------------------------------------------------------
+// Public interface
+
+// General printing is controlled with these Config.Mode flags.
+const (
+ RawFormat uint = 1 << iota // do not use a tabwriter; if set, UseSpaces is ignored
+ TabIndent // use tabs for indentation independent of UseSpaces
+ UseSpaces // use spaces instead of tabs for alignment
+)
+
+// A Config node controls the output of Fprint.
+type Config struct {
+ Mode uint // default: 0
+ Tabwidth int // default: 8
+}
+
+// fprint implements Fprint and takes a nodesSizes map for setting up the printer state.
+func (cfg *Config) fprint(output io.Writer, fset *token.FileSet, node interface{}, nodeSizes map[ast.Node]int) (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
+ // functionality but no tabwriter is used when RawFormat is set.)
+ output = &trimmer{output: output}
+
+ // setup tabwriter if needed and redirect output
+ var tw *tabwriter.Writer
+ if cfg.Mode&RawFormat == 0 {
+ minwidth := cfg.Tabwidth
+
+ padchar := byte('\t')
+ if cfg.Mode&UseSpaces != 0 {
+ padchar = ' '
+ }
+
+ twmode := tabwriter.DiscardEmptyColumns
+ if cfg.Mode&TabIndent != 0 {
+ minwidth = 0
+ twmode |= tabwriter.TabIndent
+ }
+
+ tw = tabwriter.NewWriter(output, minwidth, cfg.Tabwidth, 1, padchar, twmode)
+ output = tw
+ }
+
+ // setup printer and print node
+ var p printer
+ p.init(output, cfg, fset, nodeSizes)
+ go func() {
+ switch n := node.(type) {
+ case ast.Expr:
+ p.useNodeComments = true
+ p.expr(n, ignoreMultiLine)
+ case ast.Stmt:
+ p.useNodeComments = true
+ // A labeled statement will un-indent to position the
+ // label. Set indent to 1 so we don't get indent "underflow".
+ if _, labeledStmt := n.(*ast.LabeledStmt); labeledStmt {
+ p.indent = 1
+ }
+ p.stmt(n, false, ignoreMultiLine)
+ case ast.Decl:
+ p.useNodeComments = true
+ p.decl(n, ignoreMultiLine)
+ case ast.Spec:
+ p.useNodeComments = true
+ p.spec(n, 1, false, ignoreMultiLine)
+ case *ast.File:
+ p.comments = n.Comments
+ p.useNodeComments = n.Comments == nil
+ p.file(n)
+ default:
+ p.errors <- fmt.Errorf("printer.Fprint: unsupported node type %T", n)
+ runtime.Goexit()
+ }
+ p.flush(token.Position{Offset: infinity, Line: infinity}, token.EOF)
+ p.errors <- nil // no errors
+ }()
+ err := <-p.errors // wait for completion of goroutine
+
+ // flush tabwriter, if any
+ if tw != nil {
+ tw.Flush() // ignore errors
+ }
+
+ return p.written, err
+}
+
+// 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, fset *token.FileSet, node interface{}) (int, os.Error) {
+ return cfg.fprint(output, fset, node, make(map[ast.Node]int))
+}
+
+// Fprint "pretty-prints" an AST node to output.
+// It calls Config.Fprint with default settings.
+//
+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
new file mode 100644
index 000000000..ff2d906b5
--- /dev/null
+++ b/src/pkg/go/printer/printer_test.go
@@ -0,0 +1,194 @@
+// Copyright 2009 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package printer
+
+import (
+ "bytes"
+ "flag"
+ "io/ioutil"
+ "go/ast"
+ "go/parser"
+ "go/token"
+ "path/filepath"
+ "testing"
+ "time"
+)
+
+const (
+ dataDir = "testdata"
+ tabwidth = 8
+)
+
+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' {
+ i++
+ }
+ return string(text[i0:i])
+}
+
+type checkMode uint
+
+const (
+ export checkMode = 1 << iota
+ rawFormat
+)
+
+func runcheck(t *testing.T, source, golden string, mode checkMode) {
+ // parse source
+ prog, err := parser.ParseFile(fset, source, nil, parser.ParseComments)
+ if err != nil {
+ t.Error(err)
+ return
+ }
+
+ // filter exports if necessary
+ if mode&export != 0 {
+ ast.FileExports(prog) // ignore result
+ prog.Comments = nil // don't print comments that are not in AST
+ }
+
+ // determine printer configuration
+ cfg := Config{Tabwidth: tabwidth}
+ if mode&rawFormat != 0 {
+ cfg.Mode |= RawFormat
+ }
+
+ // format source
+ var buf bytes.Buffer
+ if _, err := cfg.Fprint(&buf, fset, prog); err != nil {
+ t.Error(err)
+ }
+ res := buf.Bytes()
+
+ // update golden files if necessary
+ if *update {
+ if err := ioutil.WriteFile(golden, res, 0644); err != nil {
+ t.Error(err)
+ }
+ return
+ }
+
+ // get golden
+ gld, err := ioutil.ReadFile(golden)
+ if err != nil {
+ t.Error(err)
+ return
+ }
+
+ // compare lengths
+ if len(res) != len(gld) {
+ t.Errorf("len = %d, expected %d (= len(%s))", len(res), len(gld), golden)
+ }
+
+ // compare contents
+ for i, line, offs := 0, 1, 0; i < len(res) && i < len(gld); i++ {
+ ch := res[i]
+ if ch != gld[i] {
+ t.Errorf("%s:%d:%d: %s", source, line, i-offs+1, lineString(res, offs))
+ t.Errorf("%s:%d:%d: %s", golden, line, i-offs+1, lineString(gld, offs))
+ t.Error()
+ return
+ }
+ if ch == '\n' {
+ line++
+ offs = i + 1
+ }
+ }
+}
+
+func check(t *testing.T, source, golden string, mode checkMode) {
+ // start a timer to produce a time-out signal
+ tc := make(chan int)
+ go func() {
+ time.Sleep(10e9) // plenty of a safety margin, even for very slow machines
+ tc <- 0
+ }()
+
+ // run the test
+ cc := make(chan int)
+ go func() {
+ runcheck(t, source, golden, mode)
+ cc <- 0
+ }()
+
+ // wait for the first finisher
+ select {
+ case <-tc:
+ // test running past time out
+ t.Errorf("%s: running too slowly", source)
+ case <-cc:
+ // test finished within alloted time margin
+ }
+}
+
+type entry struct {
+ source, golden string
+ mode checkMode
+}
+
+// Use gotest -update to create/update the respective golden files.
+var data = []entry{
+ {"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},
+ {"slow.input", "slow.golden", 0},
+}
+
+func TestFiles(t *testing.T) {
+ for i, e := range data {
+ source := filepath.Join(dataDir, e.source)
+ golden := filepath.Join(dataDir, e.golden)
+ check(t, source, golden, e.mode)
+ // TODO(gri) check that golden is idempotent
+ //check(t, golden, golden, e.mode)
+ if testing.Short() && i >= 3 {
+ break
+ }
+ }
+}
+
+// TestLineComments, using a simple test case, checks that consequtive line
+// comments are properly terminated with a newline even if the AST position
+// information is incorrect.
+//
+func TestLineComments(t *testing.T) {
+ const src = `// comment 1
+ // comment 2
+ // comment 3
+ package main
+ `
+
+ fset := token.NewFileSet()
+ ast1, err1 := parser.ParseFile(fset, "", src, parser.ParseComments)
+ if err1 != nil {
+ panic(err1)
+ }
+
+ var buf bytes.Buffer
+ fset = token.NewFileSet() // use the wrong file set
+ Fprint(&buf, fset, ast1)
+
+ nlines := 0
+ for _, ch := range buf.Bytes() {
+ if ch == '\n' {
+ nlines++
+ }
+ }
+
+ const expected = 3
+ if nlines < expected {
+ t.Errorf("got %d, expected %d\n", nlines, expected)
+ }
+}
diff --git a/src/pkg/go/printer/testdata/comments.golden b/src/pkg/go/printer/testdata/comments.golden
new file mode 100644
index 000000000..7b332252c
--- /dev/null
+++ b/src/pkg/go/printer/testdata/comments.golden
@@ -0,0 +1,473 @@
+// Copyright 2009 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// This is a package for testing comment placement by go/printer.
+//
+package main
+
+import "fmt" // fmt
+
+const c0 = 0 // zero
+const (
+ c1 = iota // c1
+ c2 // c2
+)
+
+// Alignment of comments in declarations>
+const (
+ _ T = iota // comment
+ _ // comment
+ _ // comment
+ _ = iota + 10
+ _ // comments
+
+ _ = 10 // comment
+ _ T = 20 // comment
+)
+
+const (
+ _____ = iota // foo
+ _ // bar
+ _ = 0 // bal
+ _ // bat
+)
+
+const (
+ _ T = iota // comment
+ _ // comment
+ _ // comment
+ _ = iota + 10
+ _ // comment
+ _ = 10
+ _ = 20 // comment
+ _ T = 0 // comment
+)
+
+// The SZ struct; it is empty.
+type SZ struct{}
+
+// The S0 struct; no field is exported.
+type S0 struct {
+ int
+ x, y, z int // 3 unexported fields
+}
+
+// The S1 struct; some fields are not exported.
+type S1 struct {
+ S0
+ A, B, C float // 3 exported fields
+ D, b, c int // 2 unexported fields
+}
+
+// The S2 struct; all fields are exported.
+type S2 struct {
+ S1
+ A, B, C float // 3 exported fields
+}
+
+// The IZ interface; it is empty.
+type SZ interface{}
+
+// The I0 interface; no method is exported.
+type I0 interface {
+ f(x int) int // unexported method
+}
+
+// The I1 interface; some methods are not exported.
+type I1 interface {
+ I0
+ F(x float) float // exported methods
+ g(x int) int // unexported method
+}
+
+// The I2 interface; all methods are exported.
+type I2 interface {
+ I0
+ F(x float) float // exported method
+ G(x float) float // exported method
+}
+
+// The S3 struct; all comments except for the last one must appear in the export.
+type S3 struct {
+ // lead comment for F1
+ F1 int // line comment for F1
+ // lead comment for F2
+ F2 int // line comment for F2
+ f3 int // f3 is not exported
+}
+
+// This comment group should be separated
+// with a newline from the next comment
+// group.
+
+// This comment should NOT be associated with the next declaration.
+
+var x int // x
+var ()
+
+// This comment SHOULD be associated with the next declaration.
+func f0() {
+ const pi = 3.14 // pi
+ var s1 struct{} /* an empty struct */ /* foo */
+ // a struct constructor
+ // --------------------
+ var s2 struct{} = struct{}{}
+ x := pi
+}
+//
+// NO SPACE HERE
+//
+func f1() {
+ f0()
+ /* 1 */
+ // 2
+ /* 3 */
+ /* 4 */
+ f0()
+}
+
+func _() {
+ // this comment should be properly indented
+}
+
+func _(x int) int {
+ if x < 0 { // the tab printed before this comment's // must not affect the remaining lines
+ return -x // this statement should be properly indented
+ }
+ if x < 0 { /* the tab printed before this comment's /* must not affect the remaining lines */
+ return -x // this statement should be properly indented
+ }
+ return x
+}
+
+func typeswitch(x interface{}) {
+ switch v := x.(type) {
+ case bool, int, float:
+ case string:
+ default:
+ }
+
+ switch x.(type) {
+ }
+
+ switch v0, ok := x.(int); v := x.(type) {
+ }
+
+ switch v0, ok := x.(int); x.(type) {
+ case byte: // this comment should be on the same line as the keyword
+ // this comment should be normally indented
+ _ = 0
+ case bool, int, float:
+ // this comment should be indented
+ case string:
+ default:
+ // this comment should be indented
+ }
+ // this comment should not be indented
+}
+
+func _() {
+ /* freestanding comment
+ aligned line
+ aligned line
+ */
+}
+
+func _() {
+ /* freestanding comment
+ aligned line
+ aligned line
+ */
+}
+
+func _() {
+ /* freestanding comment
+ aligned line
+ aligned line */
+}
+
+func _() {
+ /* freestanding comment
+ aligned line
+ aligned line
+ */
+}
+
+func _() {
+ /* freestanding comment
+ aligned line
+ aligned line
+ */
+}
+
+func _() {
+ /* freestanding comment
+ aligned line
+ aligned line */
+}
+
+func _() {
+ /*
+ freestanding comment
+ aligned line
+ aligned line
+ */
+}
+
+func _() {
+ /*
+ freestanding comment
+ aligned line
+ aligned line
+ */
+}
+
+func _() {
+ /*
+ freestanding comment
+ aligned line
+ aligned line */
+}
+
+func _() {
+ /*
+ freestanding comment
+ aligned line
+ aligned line
+ */
+}
+
+func _() {
+ /*
+ freestanding comment
+ aligned line
+ aligned line
+ */
+}
+
+func _() {
+ /*
+ freestanding comment
+ aligned line
+ aligned line */
+}
+
+func _() {
+ /* freestanding comment
+ aligned line
+ */
+}
+
+func _() {
+ /* freestanding comment
+ aligned line
+ */
+}
+
+func _() {
+ /* freestanding comment
+ aligned line */
+}
+
+func _() {
+ /* freestanding comment
+ aligned line
+ */
+}
+
+func _() {
+ /* freestanding comment
+ aligned line
+ */
+}
+
+func _() {
+ /* freestanding comment
+ aligned line */
+}
+
+func _() {
+ /*
+ freestanding comment
+ aligned line
+ */
+}
+
+func _() {
+ /*
+ freestanding comment
+ aligned line
+ */
+}
+
+func _() {
+ /*
+ freestanding comment
+ aligned line */
+}
+
+func _() {
+ /*
+ freestanding comment
+ aligned line
+ */
+}
+
+func _() {
+ /*
+ freestanding comment
+ aligned line
+ */
+}
+
+func _() {
+ /*
+ freestanding comment
+ aligned line */
+}
+
+/*
+ * line
+ * of
+ * stars
+ */
+
+/* another line
+ * of
+ * stars */
+
+/* and another line
+ * of
+ * stars */
+
+/* a line of
+ * stars */
+
+/* and another line of
+ * stars */
+
+/* a line of stars
+ */
+
+/* and another line of
+ */
+
+/* a line of stars
+ */
+
+/* and another line of
+ */
+
+/*
+aligned in middle
+here
+ not here
+*/
+
+/*
+blank line in middle:
+
+with no leading spaces on blank line.
+*/
+
+/*
+ aligned in middle
+ here
+ not here
+*/
+
+/*
+ blank line in middle:
+
+ with no leading spaces on blank line.
+*/
+
+func _() {
+ /*
+ * line
+ * of
+ * stars
+ */
+
+ /*
+ aligned in middle
+ here
+ not here
+ */
+
+ /*
+ blank line in middle:
+
+ with no leading spaces on blank line.
+ */
+}
+
+// Some interesting interspersed comments
+func _( /* this */ x /* is */ /* an */ int) {
+}
+
+func _( /* no params */ ) {}
+
+func _() {
+ f( /* no args */ )
+}
+
+func ( /* comment1 */ T /* comment2 */ ) _() {}
+
+func _() { /* one-line functions with comments are formatted as multi-line functions */
+}
+
+func _() {
+ _ = 0
+ /* closing curly brace should be on new line */
+}
+
+func _() {
+ _ = []int{0, 1 /* don't introduce a newline after this comment - was issue 1365 */ }
+}
+
+// Comments immediately adjacent to punctuation (for which the go/printer
+// may only 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
+ var stderr *bufio.Writer
+ var ftable *bufio.Writer // y.go file
+ var foutput *bufio.Writer // y.output file
+
+ var oflag string // -o [y.go] - y.go file
+ var vflag string // -v [y.output] - y.output file
+ var lflag bool // -l - disable line directives
+}
+
+/* This comment is the last entry in this file. It must be printed and should be followed by a newline */
diff --git a/src/pkg/go/printer/testdata/comments.input b/src/pkg/go/printer/testdata/comments.input
new file mode 100644
index 000000000..2a9a86b68
--- /dev/null
+++ b/src/pkg/go/printer/testdata/comments.input
@@ -0,0 +1,483 @@
+// Copyright 2009 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// This is a package for testing comment placement by go/printer.
+//
+package main
+
+import "fmt" // fmt
+
+const c0 = 0 // zero
+const (
+ c1 = iota // c1
+ c2 // c2
+)
+
+// Alignment of comments in declarations>
+const (
+ _ T = iota // comment
+ _ // comment
+ _ // comment
+ _ = iota+10
+ _ // comments
+
+ _ = 10 // comment
+ _ T = 20 // comment
+)
+
+const (
+ _____ = iota // foo
+ _ // bar
+ _ = 0 // bal
+ _ // bat
+)
+
+const (
+ _ T = iota // comment
+ _ // comment
+ _ // comment
+ _ = iota + 10
+ _ // comment
+ _ = 10
+ _ = 20 // comment
+ _ T = 0 // comment
+)
+
+// The SZ struct; it is empty.
+type SZ struct {}
+
+// The S0 struct; no field is exported.
+type S0 struct {
+ int
+ x, y, z int // 3 unexported fields
+}
+
+// The S1 struct; some fields are not exported.
+type S1 struct {
+ S0
+ A, B, C float // 3 exported fields
+ D, b, c int // 2 unexported fields
+}
+
+// The S2 struct; all fields are exported.
+type S2 struct {
+ S1
+ A, B, C float // 3 exported fields
+}
+
+// The IZ interface; it is empty.
+type SZ interface {}
+
+// The I0 interface; no method is exported.
+type I0 interface {
+ f(x int) int // unexported method
+}
+
+// The I1 interface; some methods are not exported.
+type I1 interface {
+ I0
+ F(x float) float // exported methods
+ g(x int) int // unexported method
+}
+
+// The I2 interface; all methods are exported.
+type I2 interface {
+ I0
+ F(x float) float // exported method
+ G(x float) float // exported method
+}
+
+// The S3 struct; all comments except for the last one must appear in the export.
+type S3 struct {
+ // lead comment for F1
+ F1 int // line comment for F1
+ // lead comment for F2
+ F2 int // line comment for F2
+ f3 int // f3 is not exported
+}
+
+// This comment group should be separated
+// with a newline from the next comment
+// group.
+
+// This comment should NOT be associated with the next declaration.
+
+var x int // x
+var ()
+
+
+// This comment SHOULD be associated with the next declaration.
+func f0() {
+ const pi = 3.14 // pi
+ var s1 struct {} /* an empty struct */ /* foo */
+ // a struct constructor
+ // --------------------
+ var s2 struct {} = struct {}{}
+ x := pi
+}
+//
+// NO SPACE HERE
+//
+func f1() {
+ f0()
+ /* 1 */
+ // 2
+ /* 3 */
+ /* 4 */
+ f0()
+}
+
+
+func _() {
+ // this comment should be properly indented
+}
+
+
+func _(x int) int {
+ if x < 0 { // the tab printed before this comment's // must not affect the remaining lines
+ return -x // this statement should be properly indented
+ }
+ if x < 0 { /* the tab printed before this comment's /* must not affect the remaining lines */
+ return -x // this statement should be properly indented
+ }
+ return x
+}
+
+
+func typeswitch(x interface{}) {
+ switch v := x.(type) {
+ case bool, int, float:
+ case string:
+ default:
+ }
+
+ switch x.(type) {
+ }
+
+ switch v0, ok := x.(int); v := x.(type) {
+ }
+
+ switch v0, ok := x.(int); x.(type) {
+ case byte: // this comment should be on the same line as the keyword
+ // this comment should be normally indented
+ _ = 0
+ case bool, int, float:
+ // this comment should be indented
+ case string:
+ default:
+ // this comment should be indented
+ }
+ // this comment should not be indented
+}
+
+func _() {
+ /* freestanding comment
+ aligned line
+ aligned line
+ */
+}
+
+func _() {
+ /* freestanding comment
+ aligned line
+ aligned line
+ */
+}
+
+func _() {
+ /* freestanding comment
+ aligned line
+ aligned line */
+}
+
+func _() {
+ /* freestanding comment
+ aligned line
+ aligned line
+ */
+}
+
+func _() {
+ /* freestanding comment
+ aligned line
+ aligned line
+ */
+}
+
+func _() {
+ /* freestanding comment
+ aligned line
+ aligned line */
+}
+
+
+func _() {
+ /*
+ freestanding comment
+ aligned line
+ aligned line
+ */
+}
+
+func _() {
+ /*
+ freestanding comment
+ aligned line
+ aligned line
+ */
+}
+
+func _() {
+ /*
+ freestanding comment
+ aligned line
+ aligned line */
+}
+
+func _() {
+ /*
+ freestanding comment
+ aligned line
+ aligned line
+ */
+}
+
+func _() {
+ /*
+ freestanding comment
+ aligned line
+ aligned line
+ */
+}
+
+func _() {
+ /*
+ freestanding comment
+ aligned line
+ aligned line */
+}
+
+func _() {
+ /* freestanding comment
+ aligned line
+ */
+}
+
+func _() {
+ /* freestanding comment
+ aligned line
+ */
+}
+
+func _() {
+ /* freestanding comment
+ aligned line */
+}
+
+func _() {
+ /* freestanding comment
+ aligned line
+ */
+}
+
+func _() {
+ /* freestanding comment
+ aligned line
+ */
+}
+
+func _() {
+ /* freestanding comment
+ aligned line */
+}
+
+
+func _() {
+ /*
+ freestanding comment
+ aligned line
+ */
+}
+
+func _() {
+ /*
+ freestanding comment
+ aligned line
+ */
+}
+
+func _() {
+ /*
+ freestanding comment
+ aligned line */
+}
+
+func _() {
+ /*
+ freestanding comment
+ aligned line
+ */
+}
+
+func _() {
+ /*
+ freestanding comment
+ aligned line
+ */
+}
+
+func _() {
+ /*
+ freestanding comment
+ aligned line */
+}
+
+/*
+ * line
+ * of
+ * stars
+ */
+
+/* another line
+ * of
+ * stars */
+
+/* and another line
+ * of
+ * stars */
+
+/* a line of
+ * stars */
+
+/* and another line of
+ * stars */
+
+/* a line of stars
+*/
+
+/* and another line of
+*/
+
+/* a line of stars
+ */
+
+/* and another line of
+ */
+
+/*
+aligned in middle
+here
+ not here
+*/
+
+/*
+blank line in middle:
+
+with no leading spaces on blank line.
+*/
+
+/*
+ aligned in middle
+ here
+ not here
+*/
+
+/*
+ blank line in middle:
+
+ with no leading spaces on blank line.
+*/
+
+func _() {
+ /*
+ * line
+ * of
+ * stars
+ */
+
+ /*
+ aligned in middle
+ here
+ not here
+ */
+
+ /*
+ blank line in middle:
+
+ with no leading spaces on blank line.
+*/
+}
+
+
+// Some interesting interspersed comments
+func _(/* this */x/* is *//* an */ int) {
+}
+
+func _(/* no params */) {}
+
+func _() {
+ f(/* no args */)
+}
+
+func (/* comment1 */ T /* comment2 */) _() {}
+
+func _() { /* one-line functions with comments are formatted as multi-line functions */ }
+
+func _() {
+ _ = 0
+ /* closing curly brace should be on new line */ }
+
+func _() {
+ _ = []int{0, 1 /* don't introduce a newline after this comment - was issue 1365 */}
+}
+
+
+// Comments immediately adjacent to punctuation (for which the go/printer
+// may only 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
+var stderr *bufio.Writer
+var ftable *bufio.Writer // y.go file
+var foutput *bufio.Writer // y.output file
+
+var oflag string // -o [y.go] - y.go file
+var vflag string // -v [y.output] - y.output file
+var lflag bool // -l - disable line directives
+}
+
+
+/* This comment is the last entry in this file. It must be printed and should be followed by a newline */
diff --git a/src/pkg/go/printer/testdata/comments.x b/src/pkg/go/printer/testdata/comments.x
new file mode 100644
index 000000000..ae7729286
--- /dev/null
+++ b/src/pkg/go/printer/testdata/comments.x
@@ -0,0 +1,56 @@
+// This is a package for testing comment placement by go/printer.
+//
+package main
+
+// The SZ struct; it is empty.
+type SZ struct{}
+
+// The S0 struct; no field is exported.
+type S0 struct {
+ // contains filtered or unexported fields
+}
+
+// The S1 struct; some fields are not exported.
+type S1 struct {
+ S0
+ A, B, C float // 3 exported fields
+ D int // 2 unexported fields
+ // contains filtered or unexported fields
+}
+
+// The S2 struct; all fields are exported.
+type S2 struct {
+ S1
+ A, B, C float // 3 exported fields
+}
+
+// The IZ interface; it is empty.
+type SZ interface{}
+
+// The I0 interface; no method is exported.
+type I0 interface {
+ // contains filtered or unexported methods
+}
+
+// The I1 interface; some methods are not exported.
+type I1 interface {
+ I0
+ F(x float) float // exported methods
+ // contains filtered or unexported methods
+}
+
+// The I2 interface; all methods are exported.
+type I2 interface {
+ I0
+ F(x float) float // exported method
+ G(x float) float // exported method
+}
+
+// The S3 struct; all comments except for the last one must appear in the export.
+type S3 struct {
+ // lead comment for F1
+ F1 int // line comment for F1
+ // lead comment for F2
+ F2 int // line comment for F2
+ // contains filtered or unexported fields
+}
diff --git a/src/pkg/go/printer/testdata/declarations.golden b/src/pkg/go/printer/testdata/declarations.golden
new file mode 100644
index 000000000..970533e8c
--- /dev/null
+++ b/src/pkg/go/printer/testdata/declarations.golden
@@ -0,0 +1,747 @@
+// Copyright 2009 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package imports
+
+import "io"
+
+import (
+ _ "io"
+)
+
+import _ "io"
+
+import (
+ "io"
+ "io"
+ "io"
+)
+
+import (
+ "io"
+ aLongRename "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 _ "os"
+import _ "os"
+import _ "os"
+
+import _ "fmt"
+import _ "fmt"
+import _ "fmt"
+
+import "foo" // a comment
+import "bar" // a comment
+
+import (
+ _ "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"
+ "io"
+ "malloc" // for the malloc count test only
+ "math"
+ "strings"
+ "testing"
+)
+
+// more import examples
+import (
+ "xxx"
+ "much longer name" // comment
+ "short name" // comment
+)
+
+import (
+ _ "xxx"
+ "much longer name" // comment
+)
+
+import (
+ mymath "math"
+ "/foo/bar/long_package_path" // a comment
+)
+
+import (
+ "package_a" // comment
+ "package_b"
+ my_better_c "package_c" // comment
+ "package_d" // comment
+ my_e "package_e" // comment
+
+ "package_a" // comment
+ "package_bb"
+ "package_ccc" // comment
+ "package_dddd" // comment
+)
+
+// at least one empty line between declarations of different kind
+import _ "io"
+
+var _ int
+
+// printing of constant literals
+const (
+ _ = "foobar"
+ _ = "a۰۱۸"
+ _ = "foo६४"
+ _ = "bar9876"
+ _ = 0
+ _ = 1
+ _ = 123456789012345678890
+ _ = 01234567
+ _ = 0xcafebabe
+ _ = 0.
+ _ = .0
+ _ = 3.14159265
+ _ = 1e0
+ _ = 1e+100
+ _ = 1e-100
+ _ = 2.71828e-1000
+ _ = 0i
+ _ = 1i
+ _ = 012345678901234567889i
+ _ = 123456789012345678890i
+ _ = 0.i
+ _ = .0i
+ _ = 3.14159265i
+ _ = 1e0i
+ _ = 1e+100i
+ _ = 1e-100i
+ _ = 2.71828e-1000i
+ _ = 'a'
+ _ = '\000'
+ _ = '\xFF'
+ _ = '\uff16'
+ _ = '\U0000ff16'
+ _ = `foobar`
+ _ = `foo
+---
+---
+bar`
+)
+
+func _() {
+ type _ int
+ type _ *int
+ type _ []int
+ type _ map[string]int
+ type _ chan int
+ type _ func() int
+
+ var _ int
+ var _ *int
+ var _ []int
+ var _ map[string]int
+ var _ chan int
+ var _ func() int
+
+ type _ struct{}
+ type _ *struct{}
+ type _ []struct{}
+ type _ map[string]struct{}
+ type _ chan struct{}
+ type _ func() struct{}
+
+ type _ interface{}
+ type _ *interface{}
+ type _ []interface{}
+ type _ map[string]interface{}
+ type _ chan interface{}
+ type _ func() interface{}
+
+ var _ struct{}
+ var _ *struct{}
+ var _ []struct{}
+ var _ map[string]struct{}
+ var _ chan struct{}
+ var _ func() struct{}
+
+ var _ interface{}
+ var _ *interface{}
+ var _ []interface{}
+ var _ map[string]interface{}
+ var _ chan interface{}
+ var _ func() interface{}
+}
+
+// don't lose blank lines in grouped declarations
+const (
+ _ int = 0
+ _ float = 1
+
+ _ string = "foo"
+
+ _ = iota
+ _
+
+ // a comment
+ _
+
+ _
+)
+
+type (
+ _ int
+ _ struct{}
+
+ _ interface{}
+
+ // a comment
+ _ map[string]int
+)
+
+var (
+ _ int = 0
+ _ float = 1
+
+ _ string = "foo"
+
+ _ bool
+
+ // a comment
+ _ bool
+)
+
+// don't lose blank lines in this struct
+type _ struct {
+ String struct {
+ Str, Len int
+ }
+ Slice struct {
+ Array, Len, Cap int
+ }
+ Eface struct {
+ Typ, Ptr int
+ }
+
+ UncommonType struct {
+ Name, PkgPath int
+ }
+ CommonType struct {
+ Size, Hash, Alg, Align, FieldAlign, String, UncommonType int
+ }
+ Type struct {
+ Typ, Ptr int
+ }
+ StructField struct {
+ Name, PkgPath, Typ, Tag, Offset int
+ }
+ StructType struct {
+ Fields int
+ }
+ PtrType struct {
+ Elem int
+ }
+ SliceType struct {
+ Elem int
+ }
+ ArrayType struct {
+ Elem, Len int
+ }
+
+ Stktop struct {
+ Stackguard, Stackbase, Gobuf int
+ }
+ Gobuf struct {
+ Sp, Pc, G int
+ }
+ G struct {
+ Stackbase, Sched, Status, Alllink int
+ }
+}
+
+// no tabs for single or ungrouped decls
+func _() {
+ const xxxxxx = 0
+ type x int
+ var xxx int
+ var yyyy float = 3.14
+ var zzzzz = "bar"
+
+ const (
+ xxxxxx = 0
+ )
+ type (
+ x int
+ )
+ var (
+ xxx int
+ )
+ var (
+ yyyy float = 3.14
+ )
+ var (
+ zzzzz = "bar"
+ )
+}
+
+// tabs for multiple or grouped decls
+func _() {
+ // no entry has a type
+ const (
+ zzzzzz = 1
+ z = 2
+ zzz = 3
+ )
+ // some entries have a type
+ const (
+ xxxxxx = 1
+ x = 2
+ xxx = 3
+ yyyyyyyy float = iota
+ yyyy = "bar"
+ yyy
+ yy = 2
+ )
+}
+
+func _() {
+ // no entry has a type
+ var (
+ zzzzzz = 1
+ z = 2
+ zzz = 3
+ )
+ // no entry has a value
+ var (
+ _ int
+ _ float
+ _ string
+
+ _ int // comment
+ _ float // comment
+ _ string // comment
+ )
+ // some entries have a type
+ var (
+ xxxxxx int
+ x float
+ xxx string
+ yyyyyyyy int = 1234
+ y float = 3.14
+ yyyy = "bar"
+ yyy string = "foo"
+ )
+ // mixed entries - all comments should be aligned
+ var (
+ a, b, c int
+ x = 10
+ d int // comment
+ y = 20 // comment
+ f, ff, fff, ffff int = 0, 1, 2, 3 // comment
+ )
+ // respect original line breaks
+ var _ = []T{
+ T{0x20, "Telugu"},
+ }
+ var _ = []T{
+ // respect original line breaks
+ T{0x20, "Telugu"},
+ }
+}
+
+func _() {
+ type (
+ xxxxxx int
+ x float
+ xxx string
+ xxxxx []x
+ xx struct{}
+ xxxxxxx struct {
+ _, _ int
+ _ float
+ }
+ xxxx chan<- string
+ )
+}
+
+// alignment of "=" in consecutive lines (extended example from issue 1414)
+const (
+ umax uint = ^uint(0) // maximum value for a uint
+ bpu = 1 << (5 + umax>>63) // bits per uint
+ foo
+ bar = -1
+)
+
+// typical enum
+const (
+ a MyType = iota
+ abcd
+ b
+ c
+ def
+)
+
+// excerpt from godoc.go
+var (
+ goroot = flag.String("goroot", runtime.GOROOT(), "Go root directory")
+ testDir = flag.String("testdir", "", "Go root subdirectory - for testing only (faster startups)")
+ pkgPath = flag.String("path", "", "additional package directories (colon-separated)")
+ filter = flag.String("filter", "", "filter file containing permitted package directory paths")
+ filterMin = flag.Int("filter_minutes", 0, "filter file update interval in minutes; disabled if <= 0")
+ filterDelay delayTime // actual filter update interval in minutes; usually filterDelay == filterMin, but filterDelay may back off exponentially
+)
+
+// formatting of structs
+type _ struct{}
+
+type _ struct { /* this comment should be visible */
+}
+
+type _ struct {
+ // this comment should be visible and properly indented
+}
+
+type _ struct { // this comment must not change indentation
+ f int
+ f, ff, fff, ffff int
+}
+
+type _ struct {
+ string
+}
+
+type _ struct {
+ string // comment
+}
+
+type _ struct {
+ string "tag"
+}
+
+type _ struct {
+ string "tag" // comment
+}
+
+type _ struct {
+ f int
+}
+
+type _ struct {
+ f int // comment
+}
+
+type _ struct {
+ f int "tag"
+}
+
+type _ struct {
+ f int "tag" // comment
+}
+
+type _ struct {
+ bool
+ a, b, c int
+ int "tag"
+ ES // comment
+ float "tag" // comment
+ f int // comment
+ f, ff, fff, ffff int // comment
+ g float "tag"
+ h float "tag" // comment
+}
+
+type _ struct {
+ a, b,
+ c, d int // this line should be indented
+ u, v, w, x float // this line should be indented
+ p, q,
+ r, s float // this line should be indented
+}
+
+// difficult cases
+type _ struct {
+ bool // comment
+ text []byte // comment
+}
+
+// formatting of interfaces
+type EI interface{}
+
+type _ interface {
+ EI
+}
+
+type _ interface {
+ f()
+ fffff()
+}
+
+type _ interface {
+ EI
+ f()
+ fffffg()
+}
+
+type _ interface { // this comment must not change indentation
+ EI // here's a comment
+ f() // no blank between identifier and ()
+ fffff() // no blank between identifier and ()
+ gggggggggggg(x, y, z int) // hurray
+}
+
+// formatting of variable declarations
+func _() {
+ type day struct {
+ n int
+ short, long string
+ }
+ var (
+ Sunday = day{0, "SUN", "Sunday"}
+ Monday = day{1, "MON", "Monday"}
+ Tuesday = day{2, "TUE", "Tuesday"}
+ Wednesday = day{3, "WED", "Wednesday"}
+ Thursday = day{4, "THU", "Thursday"}
+ Friday = day{5, "FRI", "Friday"}
+ Saturday = day{6, "SAT", "Saturday"}
+ )
+}
+
+// formatting of multi-line variable declarations
+var a1, b1, c1 int // all on one line
+
+var a2, b2,
+ c2 int // this line should be indented
+
+var (
+ a3, b3,
+ c3, d3 int // this line should be indented
+ a4, b4, c4 int // this line should be indented
+)
+
+func _() {
+ var privateKey2 = &Block{Type: "RSA PRIVATE KEY",
+ Headers: map[string]string{},
+ Bytes: []uint8{0x30, 0x82, 0x1, 0x3a, 0x2, 0x1, 0x0, 0x2,
+ 0x41, 0x0, 0xb2, 0x99, 0xf, 0x49, 0xc4, 0x7d, 0xfa, 0x8c,
+ 0xd4, 0x0, 0xae, 0x6a, 0x4d, 0x1b, 0x8a, 0x3b, 0x6a, 0x13,
+ 0x64, 0x2b, 0x23, 0xf2, 0x8b, 0x0, 0x3b, 0xfb, 0x97, 0x79,
+ },
+ }
+}
+
+func _() {
+ var Universe = Scope{
+ Names: map[string]*Ident{
+ // basic types
+ "bool": nil,
+ "byte": nil,
+ "int8": nil,
+ "int16": nil,
+ "int32": nil,
+ "int64": nil,
+ "uint8": nil,
+ "uint16": nil,
+ "uint32": nil,
+ "uint64": nil,
+ "float32": nil,
+ "float64": nil,
+ "string": nil,
+
+ // convenience types
+ "int": nil,
+ "uint": nil,
+ "uintptr": nil,
+ "float": nil,
+
+ // constants
+ "false": nil,
+ "true": nil,
+ "iota": nil,
+ "nil": nil,
+
+ // functions
+ "cap": nil,
+ "len": nil,
+ "new": nil,
+ "make": nil,
+ "panic": nil,
+ "panicln": nil,
+ "print": nil,
+ "println": nil,
+ },
+ }
+}
+
+// alignment of map composite entries
+var _ = map[int]int{
+ // small key sizes: always align even if size ratios are large
+ a: a,
+ abcdefghabcdefgh: a,
+ ab: a,
+ abc: a,
+ abcdefgabcdefg: a,
+ abcd: a,
+ abcde: a,
+ abcdef: a,
+
+ // mixed key sizes: align when key sizes change within accepted ratio
+ abcdefgh: a,
+ abcdefghabcdefg: a,
+ abcdefghij: a,
+ abcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghij: a, // outlier - do not align with previous line
+ abcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghij: a, // align with previous line
+
+ ab: a, // do not align with previous line
+ abcde: a, // align with previous line
+}
+
+func _() {
+ var _ = T{
+ a, // must introduce trailing comma
+ }
+}
+
+// formatting of function results
+func _() func() {}
+func _() func(int) { return nil }
+func _() func(int) int { return nil }
+func _() func(int) func(int) func() { return nil }
+
+// formatting of consecutive single-line functions
+func _() {}
+func _() {}
+func _() {}
+
+func _() {} // an empty line before this function
+func _() {}
+func _() {}
+
+func _() { f(1, 2, 3) }
+func _(x int) int { y := x; return y + 1 }
+func _() int { type T struct{}; var x T; return x }
+
+// these must remain multi-line since they are multi-line in the source
+func _() {
+ f(1, 2, 3)
+}
+func _(x int) int {
+ y := x
+ return y + 1
+}
+func _() int {
+ type T struct{}
+ var x T
+ return x
+}
+
+// making function declarations safe for new semicolon rules
+func _() { /* multi-line func because of comment */
+}
+
+func _() {
+ /* multi-line func because block is on multiple lines */
+}
+
+// ellipsis parameters
+func _(...int)
+func _(...*int)
+func _(...[]int)
+func _(...struct{})
+func _(bool, ...interface{})
+func _(bool, ...func())
+func _(bool, ...func(...int))
+func _(bool, ...map[string]int)
+func _(bool, ...chan int)
+
+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(...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
new file mode 100644
index 000000000..c6134096b
--- /dev/null
+++ b/src/pkg/go/printer/testdata/declarations.input
@@ -0,0 +1,757 @@
+// Copyright 2009 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package imports
+
+import "io"
+
+import (
+ _ "io"
+)
+
+import _ "io"
+
+import (
+ "io"
+ "io"
+ "io"
+)
+
+import (
+ "io"
+ aLongRename "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 _ "os"
+import _ "os"
+import _ "os"
+
+
+import _ "fmt"
+import _ "fmt"
+import _ "fmt"
+
+import "foo" // a comment
+import "bar" // a comment
+
+import (
+ _ "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"
+ "io"
+ "malloc" // for the malloc count test only
+ "math"
+ "strings"
+ "testing"
+)
+
+// more import examples
+import (
+ "xxx"
+ "much longer name" // comment
+ "short name" // comment
+)
+
+import (
+ _ "xxx"
+ "much longer name" // comment
+)
+
+import (
+ mymath "math"
+ "/foo/bar/long_package_path" // a comment
+)
+
+import (
+ "package_a" // comment
+ "package_b"
+ my_better_c "package_c" // comment
+ "package_d" // comment
+ my_e "package_e" // comment
+
+ "package_a" // comment
+ "package_bb"
+ "package_ccc" // comment
+ "package_dddd" // comment
+)
+
+// at least one empty line between declarations of different kind
+import _ "io"
+var _ int
+
+
+// printing of constant literals
+const (
+ _ = "foobar"
+ _ = "a۰۱۸"
+ _ = "foo६४"
+ _ = "bar9876"
+ _ = 0
+ _ = 1
+ _ = 123456789012345678890
+ _ = 01234567
+ _ = 0xcafebabe
+ _ = 0.
+ _ = .0
+ _ = 3.14159265
+ _ = 1e0
+ _ = 1e+100
+ _ = 1e-100
+ _ = 2.71828e-1000
+ _ = 0i
+ _ = 1i
+ _ = 012345678901234567889i
+ _ = 123456789012345678890i
+ _ = 0.i
+ _ = .0i
+ _ = 3.14159265i
+ _ = 1e0i
+ _ = 1e+100i
+ _ = 1e-100i
+ _ = 2.71828e-1000i
+ _ = 'a'
+ _ = '\000'
+ _ = '\xFF'
+ _ = '\uff16'
+ _ = '\U0000ff16'
+ _ = `foobar`
+ _ = `foo
+---
+---
+bar`
+)
+
+
+func _() {
+ type _ int
+ type _ *int
+ type _ []int
+ type _ map[string]int
+ type _ chan int
+ type _ func() int
+
+ var _ int
+ var _ *int
+ var _ []int
+ var _ map[string]int
+ var _ chan int
+ var _ func() int
+
+ type _ struct{}
+ type _ *struct{}
+ type _ []struct{}
+ type _ map[string]struct{}
+ type _ chan struct{}
+ type _ func() struct{}
+
+ type _ interface{}
+ type _ *interface{}
+ type _ []interface{}
+ type _ map[string]interface{}
+ type _ chan interface{}
+ type _ func() interface{}
+
+ var _ struct{}
+ var _ *struct{}
+ var _ []struct{}
+ var _ map[string]struct{}
+ var _ chan struct{}
+ var _ func() struct{}
+
+ var _ interface{}
+ var _ *interface{}
+ var _ []interface{}
+ var _ map[string]interface{}
+ var _ chan interface{}
+ var _ func() interface{}
+}
+
+
+// don't lose blank lines in grouped declarations
+const (
+ _ int = 0
+ _ float = 1
+
+ _ string = "foo"
+
+ _ = iota
+ _
+
+ // a comment
+ _
+
+ _
+)
+
+
+type (
+ _ int
+ _ struct {}
+
+ _ interface{}
+
+ // a comment
+ _ map[string]int
+)
+
+
+var (
+ _ int = 0
+ _ float = 1
+
+ _ string = "foo"
+
+ _ bool
+
+ // a comment
+ _ bool
+)
+
+
+// don't lose blank lines in this struct
+type _ struct {
+ String struct {
+ Str, Len int
+ }
+ Slice struct {
+ Array, Len, Cap int
+ }
+ Eface struct {
+ Typ, Ptr int
+ }
+
+ UncommonType struct {
+ Name, PkgPath int
+ }
+ CommonType struct {
+ Size, Hash, Alg, Align, FieldAlign, String, UncommonType int
+ }
+ Type struct {
+ Typ, Ptr int
+ }
+ StructField struct {
+ Name, PkgPath, Typ, Tag, Offset int
+ }
+ StructType struct {
+ Fields int
+ }
+ PtrType struct {
+ Elem int
+ }
+ SliceType struct {
+ Elem int
+ }
+ ArrayType struct {
+ Elem, Len int
+ }
+
+ Stktop struct {
+ Stackguard, Stackbase, Gobuf int
+ }
+ Gobuf struct {
+ Sp, Pc, G int
+ }
+ G struct {
+ Stackbase, Sched, Status, Alllink int
+ }
+}
+
+
+// no tabs for single or ungrouped decls
+func _() {
+ const xxxxxx = 0
+ type x int
+ var xxx int
+ var yyyy float = 3.14
+ var zzzzz = "bar"
+
+ const (
+ xxxxxx = 0
+ )
+ type (
+ x int
+ )
+ var (
+ xxx int
+ )
+ var (
+ yyyy float = 3.14
+ )
+ var (
+ zzzzz = "bar"
+ )
+}
+
+// tabs for multiple or grouped decls
+func _() {
+ // no entry has a type
+ const (
+ zzzzzz = 1
+ z = 2
+ zzz = 3
+ )
+ // some entries have a type
+ const (
+ xxxxxx = 1
+ x = 2
+ xxx = 3
+ yyyyyyyy float = iota
+ yyyy = "bar"
+ yyy
+ yy = 2
+ )
+}
+
+func _() {
+ // no entry has a type
+ var (
+ zzzzzz = 1
+ z = 2
+ zzz = 3
+ )
+ // no entry has a value
+ var (
+ _ int
+ _ float
+ _ string
+
+ _ int // comment
+ _ float // comment
+ _ string // comment
+ )
+ // some entries have a type
+ var (
+ xxxxxx int
+ x float
+ xxx string
+ yyyyyyyy int = 1234
+ y float = 3.14
+ yyyy = "bar"
+ yyy string = "foo"
+ )
+ // mixed entries - all comments should be aligned
+ var (
+ a, b, c int
+ x = 10
+ d int // comment
+ y = 20 // comment
+ f, ff, fff, ffff int = 0, 1, 2, 3 // comment
+ )
+ // respect original line breaks
+ var _ = []T {
+ T{0x20, "Telugu"},
+ }
+ var _ = []T {
+ // respect original line breaks
+ T{0x20, "Telugu"},
+ }
+}
+
+func _() {
+ type (
+ xxxxxx int
+ x float
+ xxx string
+ xxxxx []x
+ xx struct{}
+ xxxxxxx struct {
+ _, _ int
+ _ float
+ }
+ xxxx chan<- string
+ )
+}
+
+// alignment of "=" in consecutive lines (extended example from issue 1414)
+const (
+ umax uint = ^uint(0) // maximum value for a uint
+ bpu = 1 << (5 + umax>>63) // bits per uint
+ foo
+ bar = -1
+)
+
+// typical enum
+const (
+ a MyType = iota
+ abcd
+ b
+ c
+ def
+)
+
+// excerpt from godoc.go
+var (
+ goroot = flag.String("goroot", runtime.GOROOT(), "Go root directory")
+ testDir = flag.String("testdir", "", "Go root subdirectory - for testing only (faster startups)")
+ pkgPath = flag.String("path", "", "additional package directories (colon-separated)")
+ filter = flag.String("filter", "", "filter file containing permitted package directory paths")
+ filterMin = flag.Int("filter_minutes", 0, "filter file update interval in minutes; disabled if <= 0")
+ filterDelay delayTime // actual filter update interval in minutes; usually filterDelay == filterMin, but filterDelay may back off exponentially
+)
+
+
+// formatting of structs
+type _ struct{}
+
+type _ struct{ /* this comment should be visible */ }
+
+type _ struct{
+ // this comment should be visible and properly indented
+}
+
+type _ struct { // this comment must not change indentation
+ f int
+ f, ff, fff, ffff int
+}
+
+type _ struct {
+ string
+}
+
+type _ struct {
+ string // comment
+}
+
+type _ struct {
+ string "tag"
+}
+
+type _ struct {
+ string "tag" // comment
+}
+
+type _ struct {
+ f int
+}
+
+type _ struct {
+ f int // comment
+}
+
+type _ struct {
+ f int "tag"
+}
+
+type _ struct {
+ f int "tag" // comment
+}
+
+type _ struct {
+ bool
+ a, b, c int
+ int "tag"
+ ES // comment
+ float "tag" // comment
+ f int // comment
+ f, ff, fff, ffff int // comment
+ g float "tag"
+ h float "tag" // comment
+}
+
+type _ struct { a, b,
+c, d int // this line should be indented
+u, v, w, x float // this line should be indented
+p, q,
+r, s float // this line should be indented
+}
+
+
+// difficult cases
+type _ struct {
+ bool // comment
+ text []byte // comment
+}
+
+
+// formatting of interfaces
+type EI interface{}
+
+type _ interface {
+ EI
+}
+
+type _ interface {
+ f()
+ fffff()
+}
+
+type _ interface {
+ EI
+ f()
+ fffffg()
+}
+
+type _ interface { // this comment must not change indentation
+ EI // here's a comment
+ f() // no blank between identifier and ()
+ fffff() // no blank between identifier and ()
+ gggggggggggg(x, y, z int) () // hurray
+}
+
+
+// formatting of variable declarations
+func _() {
+ type day struct { n int; short, long string }
+ var (
+ Sunday = day{ 0, "SUN", "Sunday" }
+ Monday = day{ 1, "MON", "Monday" }
+ Tuesday = day{ 2, "TUE", "Tuesday" }
+ Wednesday = day{ 3, "WED", "Wednesday" }
+ Thursday = day{ 4, "THU", "Thursday" }
+ Friday = day{ 5, "FRI", "Friday" }
+ Saturday = day{ 6, "SAT", "Saturday" }
+ )
+}
+
+
+// formatting of multi-line variable declarations
+var a1, b1, c1 int // all on one line
+
+var a2, b2,
+c2 int // this line should be indented
+
+var (a3, b3,
+c3, d3 int // this line should be indented
+a4, b4, c4 int // this line should be indented
+)
+
+
+func _() {
+ var privateKey2 = &Block{Type: "RSA PRIVATE KEY",
+ Headers: map[string]string{},
+ Bytes: []uint8{0x30, 0x82, 0x1, 0x3a, 0x2, 0x1, 0x0, 0x2,
+ 0x41, 0x0, 0xb2, 0x99, 0xf, 0x49, 0xc4, 0x7d, 0xfa, 0x8c,
+ 0xd4, 0x0, 0xae, 0x6a, 0x4d, 0x1b, 0x8a, 0x3b, 0x6a, 0x13,
+ 0x64, 0x2b, 0x23, 0xf2, 0x8b, 0x0, 0x3b, 0xfb, 0x97, 0x79,
+ },
+ }
+}
+
+
+func _() {
+ var Universe = Scope {
+ Names: map[string]*Ident {
+ // basic types
+ "bool": nil,
+ "byte": nil,
+ "int8": nil,
+ "int16": nil,
+ "int32": nil,
+ "int64": nil,
+ "uint8": nil,
+ "uint16": nil,
+ "uint32": nil,
+ "uint64": nil,
+ "float32": nil,
+ "float64": nil,
+ "string": nil,
+
+ // convenience types
+ "int": nil,
+ "uint": nil,
+ "uintptr": nil,
+ "float": nil,
+
+ // constants
+ "false": nil,
+ "true": nil,
+ "iota": nil,
+ "nil": nil,
+
+ // functions
+ "cap": nil,
+ "len": nil,
+ "new": nil,
+ "make": nil,
+ "panic": nil,
+ "panicln": nil,
+ "print": nil,
+ "println": nil,
+ },
+ }
+}
+
+
+// alignment of map composite entries
+var _ = map[int]int{
+ // small key sizes: always align even if size ratios are large
+ a: a,
+ abcdefghabcdefgh: a,
+ ab: a,
+ abc: a,
+ abcdefgabcdefg: a,
+ abcd: a,
+ abcde: a,
+ abcdef: a,
+
+ // mixed key sizes: align when key sizes change within accepted ratio
+ abcdefgh: a,
+ abcdefghabcdefg: a,
+ abcdefghij: a,
+ abcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghij: a, // outlier - do not align with previous line
+ abcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghij: a, // align with previous line
+
+ ab: a, // do not align with previous line
+ abcde: a, // align with previous line
+}
+
+
+func _() {
+ var _ = T{
+ a, // must introduce trailing comma
+ }
+}
+
+
+// formatting of function results
+func _() func() {}
+func _() func(int) { return nil }
+func _() func(int) int { return nil }
+func _() func(int) func(int) func() { return nil }
+
+
+// formatting of consecutive single-line functions
+func _() {}
+func _() {}
+func _() {}
+
+func _() {} // an empty line before this function
+func _() {}
+func _() {}
+
+func _() { f(1, 2, 3) }
+func _(x int) int { y := x; return y+1 }
+func _() int { type T struct{}; var x T; return x }
+
+// these must remain multi-line since they are multi-line in the source
+func _() {
+ f(1, 2, 3)
+}
+func _(x int) int {
+ y := x; return y+1
+}
+func _() int {
+ type T struct{}; var x T; return x
+}
+
+
+// making function declarations safe for new semicolon rules
+func _() { /* multi-line func because of comment */ }
+
+func _() {
+/* multi-line func because block is on multiple lines */ }
+
+
+// ellipsis parameters
+func _(...int)
+func _(...*int)
+func _(...[]int)
+func _(...struct{})
+func _(bool, ...interface{})
+func _(bool, ...func())
+func _(bool, ...func(...int))
+func _(bool, ...map[string]int)
+func _(bool, ...chan int)
+
+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(...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/empty.golden b/src/pkg/go/printer/testdata/empty.golden
new file mode 100644
index 000000000..a055f4758
--- /dev/null
+++ b/src/pkg/go/printer/testdata/empty.golden
@@ -0,0 +1,5 @@
+// a comment at the beginning of the file
+
+package empty
+
+// a comment at the end of the file
diff --git a/src/pkg/go/printer/testdata/empty.input b/src/pkg/go/printer/testdata/empty.input
new file mode 100644
index 000000000..a055f4758
--- /dev/null
+++ b/src/pkg/go/printer/testdata/empty.input
@@ -0,0 +1,5 @@
+// a comment at the beginning of the file
+
+package empty
+
+// a comment at the end of the file
diff --git a/src/pkg/go/printer/testdata/expressions.golden b/src/pkg/go/printer/testdata/expressions.golden
new file mode 100644
index 000000000..d0cf24ad6
--- /dev/null
+++ b/src/pkg/go/printer/testdata/expressions.golden
@@ -0,0 +1,627 @@
+// Copyright 2009 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package expressions
+
+type T struct {
+ x, y, z int
+}
+
+var (
+ a, b, c, d, e int
+ under_bar int
+ longIdentifier1, longIdentifier2, longIdentifier3 int
+ t0, t1, t2 T
+ s string
+ p *int
+)
+
+func _() {
+ // no spaces around simple or parenthesized expressions
+ _ = (a + 0)
+ _ = a + b
+ _ = a + b + c
+ _ = a + b - c
+ _ = a - b - c
+ _ = a + (b * c)
+ _ = a + (b / c)
+ _ = a - (b % c)
+ _ = 1 + a
+ _ = a + 1
+ _ = a + b + 1
+ _ = s[a]
+ _ = s[a:]
+ _ = s[:b]
+ _ = s[1:2]
+ _ = s[a:b]
+ _ = s[0:len(s)]
+ _ = s[0] << 1
+ _ = (s[0] << 1) & 0xf
+ _ = s[0]<<2 | s[1]>>4
+ _ = "foo" + s
+ _ = s + "foo"
+ _ = 'a' + 'b'
+ _ = len(s) / 2
+ _ = len(t0.x) / a
+
+ // spaces around expressions of different precedence or expressions containing spaces
+ _ = a + -b
+ _ = a - ^b
+ _ = a / *p
+ _ = a + b*c
+ _ = 1 + b*c
+ _ = a + 2*c
+ _ = a + c*2
+ _ = 1 + 2*3
+ _ = s[1 : 2*3]
+ _ = s[a : b-c]
+ _ = s[0:]
+ _ = s[a+b]
+ _ = s[:b-c]
+ _ = s[a+b:]
+ _ = a[a<<b+1]
+ _ = a[a<<b+1:]
+ _ = s[a+b : len(s)]
+ _ = s[len(s):-a]
+ _ = s[a : len(s)+1]
+ _ = s[a:len(s)+1] + s
+
+ // spaces around operators with equal or lower precedence than comparisons
+ _ = a == b
+ _ = a != b
+ _ = a > b
+ _ = a >= b
+ _ = a < b
+ _ = a <= b
+ _ = a < b && c > d
+ _ = a < b || c > d
+
+ // spaces around "long" operands
+ _ = a + longIdentifier1
+ _ = longIdentifier1 + a
+ _ = longIdentifier1 + longIdentifier2*longIdentifier3
+ _ = s + "a longer string"
+
+ // some selected cases
+ _ = a + t0.x
+ _ = a + t0.x + t1.x*t2.x
+ _ = a + b + c + d + e + 2*3
+ _ = a + b + c + 2*3 + d + e
+ _ = (a + b + c) * 2
+ _ = a - b + c - d + (a + b + c) + d&e
+ _ = under_bar - 1
+ _ = Open(dpath+"/file", O_WRONLY|O_CREAT, 0666)
+ _ = int(c0&_Mask4)<<18 | int(c1&_Maskx)<<12 | int(c2&_Maskx)<<6 | int(c3&_Maskx)
+
+ // the parser does not restrict expressions that may appear as statements
+ true
+ 42
+ "foo"
+ x
+ (x)
+ a + b
+ a + b + c
+ a + (b * c)
+ a + (b / c)
+ 1 + a
+ a + 1
+ s[a]
+ x << 1
+ (s[0] << 1) & 0xf
+ "foo" + s
+ x == y
+ x < y || z > 42
+}
+
+func _() {
+ _ = a + b
+ _ = a + b + c
+ _ = a + b*c
+ _ = a + (b * c)
+ _ = (a + b) * c
+ _ = a + (b * c * d)
+ _ = a + (b*c + d)
+
+ _ = 1 << x
+ _ = -1 << x
+ _ = 1<<x - 1
+ _ = -1<<x - 1
+
+ _ = f(a + b)
+ _ = f(a + b + c)
+ _ = f(a + b*c)
+ _ = f(a + (b * c))
+ _ = f(1<<x-1, 1<<x-2)
+
+ _ = 1<<d.logWindowSize - 1
+
+ buf = make(x, 2*cap(b.buf)+n)
+
+ dst[i*3+2] = dbuf[0] << 2
+ dst[i*3+2] = dbuf[0]<<2 | dbuf[1]>>4
+
+ b.buf = b.buf[0 : b.off+m+n]
+ b.buf = b.buf[0 : b.off+m*n]
+ f(b.buf[0 : b.off+m+n])
+
+ signed += ' ' * 8
+ tw.octal(header[148:155], chksum)
+
+ _ = x > 0 && i >= 0
+
+ x1, x0 := x>>w2, x&m2
+ z0 = t1<<w2 + t0
+ z1 = (t1 + t0>>w2) >> w2
+ q1, r1 := x1/d1, x1%d1
+ r1 = r1*b2 | x0>>w2
+ x1 = (x1 << z) | (x0 >> (uint(w) - z))
+ x1 = x1<<z | x0>>(uint(w)-z)
+
+ _ = buf[0 : len(buf)+1]
+ _ = buf[0 : n+1]
+
+ a, b = b, a
+ a = b + c
+ a = b*c + d
+ _ = a*b + c
+ _ = a - b - c
+ _ = a - (b - c)
+ _ = a - b*c
+ _ = a - (b * c)
+ _ = a * b / c
+ _ = a / *b
+ _ = x[a|^b]
+ _ = x[a / *b]
+ _ = a & ^b
+ _ = a + +b
+ _ = a - -b
+ _ = x[a*-b]
+ _ = x[a + +b]
+ _ = x ^ y ^ z
+ _ = b[a>>24] ^ b[(a>>16)&0xFF] ^ b[(a>>8)&0xFF] ^ b[a&0xFF]
+ _ = len(longVariableName) * 2
+
+ _ = token(matchType + xlength<<lengthShift + xoffset)
+}
+
+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{}{}
+ _ = [10]T{}
+ _ = [...]T{}
+ _ = []T{}
+ _ = map[int]T{}
+}
+
+// one-line structs/interfaces in composite literals (up to a threshold)
+func _() {
+ _ = struct{}{}
+ _ = struct{ x int }{0}
+ _ = struct{ x, y, z int }{0, 1, 2}
+ _ = struct{ int }{0}
+ _ = struct{ s struct{ int } }{struct{ int }{0}}
+}
+
+func _() {
+ // do not modify literals
+ _ = "tab1 tab2 tab3 end" // string contains 3 tabs
+ _ = "tab1 tab2 tab3 end" // same string with 3 blanks - may be unaligned because editors see tabs in strings
+ _ = "" // this comment should be aligned with the one on the previous line
+ _ = ``
+ _ = `
+`
+ _ = `foo
+ bar`
+ _ = `three spaces before the end of the line starting here:
+they must not be removed`
+}
+
+func _() {
+ // smart handling of indentation for multi-line raw strings
+ var _ = ``
+ var _ = `foo`
+ var _ = `foo
+bar`
+
+ var _ = ``
+ var _ = `foo`
+ var _ =
+ // the next line should remain indented
+ `foo
+bar`
+
+ var _ = // comment
+ ``
+ var _ = // comment
+ `foo`
+ var _ = // comment
+ // the next line should remain indented
+ `foo
+bar`
+
+ var _ = /* comment */ ``
+ var _ = /* comment */ `foo`
+ var _ = /* comment */ `foo
+bar`
+
+ var _ = /* comment */
+ ``
+ var _ = /* comment */
+ `foo`
+ var _ = /* comment */
+ // the next line should remain indented
+ `foo
+bar`
+
+ var board = []int(
+ `...........
+...........
+....●●●....
+....●●●....
+..●●●●●●●..
+..●●●○●●●..
+..●●●●●●●..
+....●●●....
+....●●●....
+...........
+...........
+`)
+
+ var state = S{
+ "foo",
+ // the next line should remain indented
+ `...........
+...........
+....●●●....
+....●●●....
+..●●●●●●●..
+..●●●○●●●..
+..●●●●●●●..
+....●●●....
+....●●●....
+...........
+...........
+`,
+ "bar",
+ }
+}
+
+func _() {
+ // one-line function literals (body is on a single line)
+ _ = func() {}
+ _ = func() int { return 0 }
+ _ = func(x, y int) bool { m := (x + y) / 2; return m < 0 }
+
+ // multi-line function literals (body is not on one line)
+ _ = func() {
+ }
+ _ = func() int {
+ return 0
+ }
+ _ = func(x, y int) bool {
+ m := (x + y) / 2
+ return x < y
+ }
+
+ f(func() {
+ })
+ f(func() int {
+ return 0
+ })
+ f(func(x, y int) bool {
+ m := (x + y) / 2
+ return x < y
+ })
+}
+
+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" +
+ "bar" +
+ "bah"
+ _ = []string{
+ "abc" +
+ "def",
+ "foo" +
+ "bar",
+ }
+}
+
+const _ = F1 +
+ `string = "%s";` +
+ `ptr = *;` +
+ `datafmt.T2 = s ["-" p "-"];`
+
+const _ = `datafmt "datafmt";` +
+ `default = "%v";` +
+ `array = *;` +
+ `datafmt.T3 = s {" " a a / ","};`
+
+const _ = `datafmt "datafmt";` +
+ `default = "%v";` +
+ `array = *;` +
+ `datafmt.T3 = s {" " a a / ","};`
+
+func _() {
+ _ = F1 +
+ `string = "%s";` +
+ `ptr = *;` +
+ `datafmt.T2 = s ["-" p "-"];`
+
+ _ =
+ `datafmt "datafmt";` +
+ `default = "%v";` +
+ `array = *;` +
+ `datafmt.T3 = s {" " a a / ","};`
+
+ _ = `datafmt "datafmt";` +
+ `default = "%v";` +
+ `array = *;` +
+ `datafmt.T3 = s {" " a a / ","};`
+}
+
+func _() {
+ // respect source lines in multi-line expressions
+ _ = a +
+ b +
+ c
+ _ = a < b ||
+ b < a
+ _ = "933262154439441526816992388562667004907159682643816214685929" +
+ "638952175999932299156089414639761565182862536979208272237582" +
+ "51185210916864000000000000000000000000" // 100!
+ _ = "170141183460469231731687303715884105727" // prime
+}
+
+// Alignment after overlong lines
+const (
+ _ = "991"
+ _ = "2432902008176640000" // 20!
+ _ = "933262154439441526816992388562667004907159682643816214685929" +
+ "638952175999932299156089414639761565182862536979208272237582" +
+ "51185210916864000000000000000000000000" // 100!
+ _ = "170141183460469231731687303715884105727" // prime
+)
+
+// Correct placement of operators and comments in multi-line expressions
+func _() {
+ _ = a + // comment
+ b + // comment
+ c
+ _ = "a" +
+ "b" + // comment
+ "c"
+ _ = "ba0408" + "7265717569726564" // field 71, encoding 2, string "required"
+}
+
+// Correct placement of terminating comma/closing parentheses in multi-line calls.
+func _() {
+ f(1,
+ 2,
+ 3)
+ f(1,
+ 2,
+ 3,
+ )
+ f(1,
+ 2,
+ 3) // comment
+ f(1,
+ 2,
+ 3, // comment
+ )
+ f(1,
+ 2,
+ 3) // comment
+ f(1,
+ 2,
+ 3, // comment
+ )
+}
+
+// Align comments in multi-line lists of single-line expressions.
+var txpix = [NCOL]draw.Color{
+ draw.Yellow, // yellow
+ draw.Cyan, // cyan
+ draw.Green, // lime green
+ draw.GreyBlue, // slate
+ draw.Red, /* red */
+ draw.GreyGreen, /* olive green */
+ draw.Blue, /* blue */
+ draw.Color(0xFF55AAFF), /* pink */
+ draw.Color(0xFFAAFFFF), /* lavender */
+ draw.Color(0xBB005DFF), /* maroon */
+}
+
+func same(t, u *Time) bool {
+ // respect source lines in multi-line expressions
+ return t.Year == u.Year &&
+ t.Month == u.Month &&
+ t.Day == u.Day &&
+ t.Hour == u.Hour &&
+ t.Minute == u.Minute &&
+ t.Second == u.Second &&
+ t.Weekday == u.Weekday &&
+ t.ZoneOffset == u.ZoneOffset &&
+ t.Zone == u.Zone
+}
+
+func (p *parser) charClass() {
+ // respect source lines in multi-line expressions
+ if cc.negate && len(cc.ranges) == 2 &&
+ cc.ranges[0] == '\n' && cc.ranges[1] == '\n' {
+ nl := new(_NotNl)
+ p.re.add(nl)
+ }
+}
+
+func addState(s []state, inst instr, match []int) {
+ // handle comments correctly in multi-line expressions
+ for i := 0; i < l; i++ {
+ if s[i].inst.index() == index && // same instruction
+ s[i].match[0] < pos { // earlier match already going; leftmost wins
+ return s
+ }
+ }
+}
+
+func (self *T) foo(x int) *T { return self }
+
+func _() { module.Func1().Func2() }
+
+func _() {
+ _ = new(T).
+ foo(1).
+ foo(2).
+ foo(3)
+
+ _ = new(T).
+ foo(1).
+ foo(2). // inline comments
+ foo(3)
+
+ _ = new(T).foo(1).foo(2).foo(3)
+
+ // handle multiline argument list correctly
+ _ = new(T).
+ foo(
+ 1).
+ foo(2)
+
+ _ = new(T).foo(
+ 1).foo(2)
+
+ _ = Array[3+
+ 4]
+
+ _ = Method(1, 2,
+ 3)
+
+ _ = new(T).
+ foo().
+ bar().(*Type)
+
+ _ = new(T).
+ foo().
+ bar().(*Type).
+ baz()
+
+ _ = new(T).
+ foo().
+ bar()["idx"]
+
+ _ = new(T).
+ foo().
+ bar()["idx"].
+ baz()
+
+ _ = new(T).
+ foo().
+ bar()[1:2]
+
+ _ = new(T).
+ foo().
+ bar()[1:2].
+ baz()
+
+ _ = new(T).
+ Field.
+ Array[3+
+ 4].
+ Table["foo"].
+ Blob.(*Type).
+ Slices[1:4].
+ Method(1, 2,
+ 3).
+ Thingy
+
+ _ = a.b.c
+ _ = a.
+ b.
+ c
+ _ = a.b().c
+ _ = a.
+ b().
+ c
+ _ = a.b[0].c
+ _ = a.
+ b[0].
+ c
+ _ = a.b[0:].c
+ _ = a.
+ b[0:].
+ c
+ _ = a.b.(T).c
+ _ = a.
+ b.(T).
+ c
+}
+
+// Don't introduce extra newlines in strangely formatted expression lists.
+func f() {
+ // os.Open parameters should remain on two lines
+ if writer, err = os.Open(outfile, s.O_WRONLY|os.O_CREATE|
+ os.O_TRUNC, 0666); err != nil {
+ log.Fatal(err)
+ }
+}
diff --git a/src/pkg/go/printer/testdata/expressions.input b/src/pkg/go/printer/testdata/expressions.input
new file mode 100644
index 000000000..d11314983
--- /dev/null
+++ b/src/pkg/go/printer/testdata/expressions.input
@@ -0,0 +1,656 @@
+// Copyright 2009 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package expressions
+
+type T struct {
+ x, y, z int
+}
+
+var (
+ a, b, c, d, e int
+ under_bar int
+ longIdentifier1, longIdentifier2, longIdentifier3 int
+ t0, t1, t2 T
+ s string
+ p *int
+)
+
+
+func _() {
+ // no spaces around simple or parenthesized expressions
+ _ = (a+0)
+ _ = a+b
+ _ = a+b+c
+ _ = a+b-c
+ _ = a-b-c
+ _ = a+(b*c)
+ _ = a+(b/c)
+ _ = a-(b%c)
+ _ = 1+a
+ _ = a+1
+ _ = a+b+1
+ _ = s[a]
+ _ = s[a:]
+ _ = s[:b]
+ _ = s[1:2]
+ _ = s[a:b]
+ _ = s[0:len(s)]
+ _ = s[0]<<1
+ _ = (s[0]<<1)&0xf
+ _ = s[0] << 2 | s[1] >> 4
+ _ = "foo"+s
+ _ = s+"foo"
+ _ = 'a'+'b'
+ _ = len(s)/2
+ _ = len(t0.x)/a
+
+ // spaces around expressions of different precedence or expressions containing spaces
+ _ = a + -b
+ _ = a - ^b
+ _ = a / *p
+ _ = a + b*c
+ _ = 1 + b*c
+ _ = a + 2*c
+ _ = a + c*2
+ _ = 1 + 2*3
+ _ = s[1 : 2*3]
+ _ = s[a : b-c]
+ _ = s[0:]
+ _ = s[a+b]
+ _ = s[: b-c]
+ _ = s[a+b :]
+ _ = a[a<<b+1]
+ _ = a[a<<b+1 :]
+ _ = s[a+b : len(s)]
+ _ = s[len(s) : -a]
+ _ = s[a : len(s)+1]
+ _ = s[a : len(s)+1]+s
+
+ // spaces around operators with equal or lower precedence than comparisons
+ _ = a == b
+ _ = a != b
+ _ = a > b
+ _ = a >= b
+ _ = a < b
+ _ = a <= b
+ _ = a < b && c > d
+ _ = a < b || c > d
+
+ // spaces around "long" operands
+ _ = a + longIdentifier1
+ _ = longIdentifier1 + a
+ _ = longIdentifier1 + longIdentifier2 * longIdentifier3
+ _ = s + "a longer string"
+
+ // some selected cases
+ _ = a + t0.x
+ _ = a + t0.x + t1.x * t2.x
+ _ = a + b + c + d + e + 2*3
+ _ = a + b + c + 2*3 + d + e
+ _ = (a+b+c)*2
+ _ = a - b + c - d + (a+b+c) + d&e
+ _ = under_bar-1
+ _ = Open(dpath + "/file", O_WRONLY | O_CREAT, 0666)
+ _ = int(c0&_Mask4)<<18 | int(c1&_Maskx)<<12 | int(c2&_Maskx)<<6 | int(c3&_Maskx)
+
+ // the parser does not restrict expressions that may appear as statements
+ true
+ 42
+ "foo"
+ x
+ (x)
+ a+b
+ a+b+c
+ a+(b*c)
+ a+(b/c)
+ 1+a
+ a+1
+ s[a]
+ x<<1
+ (s[0]<<1)&0xf
+ "foo"+s
+ x == y
+ x < y || z > 42
+}
+
+
+func _() {
+ _ = a+b
+ _ = a+b+c
+ _ = a+b*c
+ _ = a+(b*c)
+ _ = (a+b)*c
+ _ = a+(b*c*d)
+ _ = a+(b*c+d)
+
+ _ = 1<<x
+ _ = -1<<x
+ _ = 1<<x-1
+ _ = -1<<x-1
+
+ _ = f(a+b)
+ _ = f(a+b+c)
+ _ = f(a+b*c)
+ _ = f(a+(b*c))
+ _ = f(1<<x-1, 1<<x-2)
+
+ _ = 1<<d.logWindowSize-1
+
+ buf = make(x, 2*cap(b.buf) + n)
+
+ dst[i*3+2] = dbuf[0]<<2
+ dst[i*3+2] = dbuf[0]<<2 | dbuf[1]>>4
+
+ b.buf = b.buf[0:b.off+m+n]
+ b.buf = b.buf[0:b.off+m*n]
+ f(b.buf[0:b.off+m+n])
+
+ signed += ' '*8
+ tw.octal(header[148:155], chksum)
+
+ _ = x > 0 && i >= 0
+
+ x1, x0 := x>>w2, x&m2
+ z0 = t1<<w2+t0
+ z1 = (t1+t0>>w2)>>w2
+ q1, r1 := x1/d1, x1%d1
+ r1 = r1*b2 | x0>>w2
+ x1 = (x1<<z)|(x0>>(uint(w)-z))
+ x1 = x1<<z | x0>>(uint(w)-z)
+
+ _ = buf[0:len(buf)+1]
+ _ = buf[0:n+1]
+
+ a,b = b,a
+ a = b+c
+ a = b*c+d
+ _ = a*b+c
+ _ = a-b-c
+ _ = a-(b-c)
+ _ = a-b*c
+ _ = a-(b*c)
+ _ = a*b/c
+ _ = a/ *b
+ _ = x[a|^b]
+ _ = x[a/ *b]
+ _ = a& ^b
+ _ = a+ +b
+ _ = a- -b
+ _ = x[a*-b]
+ _ = x[a+ +b]
+ _ = x^y^z
+ _ = b[a>>24] ^ b[(a>>16)&0xFF] ^ b[(a>>8)&0xFF] ^ b[a&0xFF]
+ _ = len(longVariableName)*2
+
+ _ = token(matchType + xlength<<lengthShift + xoffset)
+}
+
+
+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{}{}
+ _ = [10]T{}
+ _ = [...]T{}
+ _ = []T{}
+ _ = map[int]T{}
+}
+
+
+// one-line structs/interfaces in composite literals (up to a threshold)
+func _() {
+ _ = struct{}{}
+ _ = struct{ x int }{0}
+ _ = struct{ x, y, z int }{0, 1, 2}
+ _ = struct{ int }{0}
+ _ = struct{ s struct { int } }{struct{ int}{0} }
+}
+
+
+func _() {
+ // do not modify literals
+ _ = "tab1 tab2 tab3 end" // string contains 3 tabs
+ _ = "tab1 tab2 tab3 end" // same string with 3 blanks - may be unaligned because editors see tabs in strings
+ _ = "" // this comment should be aligned with the one on the previous line
+ _ = ``
+ _ = `
+`
+_ = `foo
+ bar`
+ _ = `three spaces before the end of the line starting here:
+they must not be removed`
+}
+
+
+func _() {
+ // smart handling of indentation for multi-line raw strings
+ var _ = ``
+ var _ = `foo`
+ var _ = `foo
+bar`
+
+
+var _ =
+ ``
+var _ =
+ `foo`
+var _ =
+ // the next line should remain indented
+ `foo
+bar`
+
+
+ var _ = // comment
+ ``
+ var _ = // comment
+ `foo`
+ var _ = // comment
+ // the next line should remain indented
+ `foo
+bar`
+
+
+var _ = /* comment */ ``
+var _ = /* comment */ `foo`
+var _ = /* comment */ `foo
+bar`
+
+
+ var _ = /* comment */
+ ``
+ var _ = /* comment */
+ `foo`
+ var _ = /* comment */
+ // the next line should remain indented
+ `foo
+bar`
+
+
+var board = []int(
+ `...........
+...........
+....●●●....
+....●●●....
+..●●●●●●●..
+..●●●○●●●..
+..●●●●●●●..
+....●●●....
+....●●●....
+...........
+...........
+`)
+
+
+ var state = S{
+ "foo",
+ // the next line should remain indented
+ `...........
+...........
+....●●●....
+....●●●....
+..●●●●●●●..
+..●●●○●●●..
+..●●●●●●●..
+....●●●....
+....●●●....
+...........
+...........
+`,
+ "bar",
+ }
+}
+
+
+func _() {
+ // one-line function literals (body is on a single line)
+ _ = func() {}
+ _ = func() int { return 0 }
+ _ = func(x, y int) bool { m := (x+y)/2; return m < 0 }
+
+ // multi-line function literals (body is not on one line)
+ _ = func() {
+ }
+ _ = func() int {
+ return 0
+ }
+ _ = func(x, y int) bool {
+ m := (x+y)/2; return x < y }
+
+ f(func() {
+ })
+ f(func() int {
+ return 0
+ })
+ f(func(x, y int) bool {
+ m := (x+y)/2; return x < y })
+}
+
+
+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" +
+ "bar" +
+ "bah"
+ _ = []string {
+ "abc" +
+ "def",
+ "foo" +
+ "bar",
+ }
+}
+
+
+const _ = F1 +
+ `string = "%s";` +
+ `ptr = *;` +
+ `datafmt.T2 = s ["-" p "-"];`
+
+
+const _ =
+ `datafmt "datafmt";` +
+ `default = "%v";` +
+ `array = *;` +
+ `datafmt.T3 = s {" " a a / ","};`
+
+
+const _ = `datafmt "datafmt";` +
+`default = "%v";` +
+`array = *;` +
+`datafmt.T3 = s {" " a a / ","};`
+
+
+func _() {
+ _ = F1 +
+ `string = "%s";` +
+ `ptr = *;` +
+ `datafmt.T2 = s ["-" p "-"];`
+
+ _ =
+ `datafmt "datafmt";` +
+ `default = "%v";` +
+ `array = *;` +
+ `datafmt.T3 = s {" " a a / ","};`
+
+ _ = `datafmt "datafmt";` +
+ `default = "%v";` +
+ `array = *;` +
+ `datafmt.T3 = s {" " a a / ","};`
+}
+
+
+func _() {
+ // respect source lines in multi-line expressions
+ _ = a+
+ b+
+ c
+ _ = a < b ||
+ b < a
+ _ = "933262154439441526816992388562667004907159682643816214685929" +
+ "638952175999932299156089414639761565182862536979208272237582" +
+ "51185210916864000000000000000000000000" // 100!
+ _ = "170141183460469231731687303715884105727" // prime
+}
+
+
+// Alignment after overlong lines
+const (
+ _ = "991"
+ _ = "2432902008176640000" // 20!
+ _ = "933262154439441526816992388562667004907159682643816214685929" +
+ "638952175999932299156089414639761565182862536979208272237582" +
+ "51185210916864000000000000000000000000" // 100!
+ _ = "170141183460469231731687303715884105727" // prime
+)
+
+
+// Correct placement of operators and comments in multi-line expressions
+func _() {
+ _ = a + // comment
+ b + // comment
+ c
+ _ = "a" +
+ "b" + // comment
+ "c"
+ _ = "ba0408" + "7265717569726564" // field 71, encoding 2, string "required"
+}
+
+
+// Correct placement of terminating comma/closing parentheses in multi-line calls.
+func _() {
+ f(1,
+ 2,
+ 3)
+ f(1,
+ 2,
+ 3,
+ )
+ f(1,
+ 2,
+ 3) // comment
+ f(1,
+ 2,
+ 3, // comment
+ )
+ f(1,
+ 2,
+ 3)// comment
+ f(1,
+ 2,
+ 3,// comment
+ )
+}
+
+
+// Align comments in multi-line lists of single-line expressions.
+var txpix = [NCOL]draw.Color{
+ draw.Yellow, // yellow
+ draw.Cyan, // cyan
+ draw.Green, // lime green
+ draw.GreyBlue, // slate
+ draw.Red, /* red */
+ draw.GreyGreen, /* olive green */
+ draw.Blue, /* blue */
+ draw.Color(0xFF55AAFF), /* pink */
+ draw.Color(0xFFAAFFFF), /* lavender */
+ draw.Color(0xBB005DFF), /* maroon */
+}
+
+
+func same(t, u *Time) bool {
+ // respect source lines in multi-line expressions
+ return t.Year == u.Year &&
+ t.Month == u.Month &&
+ t.Day == u.Day &&
+ t.Hour == u.Hour &&
+ t.Minute == u.Minute &&
+ t.Second == u.Second &&
+ t.Weekday == u.Weekday &&
+ t.ZoneOffset == u.ZoneOffset &&
+ t.Zone == u.Zone
+}
+
+
+func (p *parser) charClass() {
+ // respect source lines in multi-line expressions
+ if cc.negate && len(cc.ranges) == 2 &&
+ cc.ranges[0] == '\n' && cc.ranges[1] == '\n' {
+ nl := new(_NotNl)
+ p.re.add(nl)
+ }
+}
+
+
+func addState(s []state, inst instr, match []int) {
+ // handle comments correctly in multi-line expressions
+ for i := 0; i < l; i++ {
+ if s[i].inst.index() == index && // same instruction
+ s[i].match[0] < pos { // earlier match already going; leftmost wins
+ return s
+ }
+ }
+}
+
+func (self *T) foo(x int) *T { return self }
+
+func _() { module.Func1().Func2() }
+
+func _() {
+ _ = new(T).
+ foo(1).
+ foo(2).
+ foo(3)
+
+ _ = new(T).
+ foo(1).
+ foo(2). // inline comments
+ foo(3)
+
+ _ = new(T).foo(1).foo(2).foo(3)
+
+ // handle multiline argument list correctly
+ _ = new(T).
+ foo(
+ 1).
+ foo(2)
+
+ _ = new(T).foo(
+ 1).foo(2)
+
+ _ = Array[3 +
+4]
+
+ _ = Method(1, 2,
+ 3)
+
+ _ = new(T).
+ foo().
+ bar() . (*Type)
+
+ _ = new(T).
+foo().
+bar().(*Type).
+baz()
+
+ _ = new(T).
+ foo().
+ bar()["idx"]
+
+ _ = new(T).
+ foo().
+ bar()["idx"] .
+ baz()
+
+ _ = new(T).
+ foo().
+ bar()[1:2]
+
+ _ = new(T).
+ foo().
+ bar()[1:2].
+ baz()
+
+ _ = new(T).
+ Field.
+ Array[3+
+ 4].
+ Table ["foo"].
+ Blob. (*Type).
+ Slices[1:4].
+ Method(1, 2,
+ 3).
+ Thingy
+
+ _ = a.b.c
+ _ = a.
+ b.
+ c
+ _ = a.b().c
+ _ = a.
+ b().
+ c
+ _ = a.b[0].c
+ _ = a.
+ b[0].
+ c
+ _ = a.b[0:].c
+ _ = a.
+ b[0:].
+ c
+ _ = a.b.(T).c
+ _ = a.
+ b.
+ (T).
+ c
+}
+
+
+// Don't introduce extra newlines in strangely formatted expression lists.
+func f() {
+ // os.Open parameters should remain on two lines
+ if writer, err = os.Open(outfile, s.O_WRONLY|os.O_CREATE|
+ os.O_TRUNC, 0666); err != nil {
+ log.Fatal(err)
+ }
+}
diff --git a/src/pkg/go/printer/testdata/expressions.raw b/src/pkg/go/printer/testdata/expressions.raw
new file mode 100644
index 000000000..d7819a3ba
--- /dev/null
+++ b/src/pkg/go/printer/testdata/expressions.raw
@@ -0,0 +1,627 @@
+// Copyright 2009 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package expressions
+
+type T struct {
+ x, y, z int
+}
+
+var (
+ a, b, c, d, e int
+ under_bar int
+ longIdentifier1, longIdentifier2, longIdentifier3 int
+ t0, t1, t2 T
+ s string
+ p *int
+)
+
+func _() {
+ // no spaces around simple or parenthesized expressions
+ _ = (a + 0)
+ _ = a + b
+ _ = a + b + c
+ _ = a + b - c
+ _ = a - b - c
+ _ = a + (b * c)
+ _ = a + (b / c)
+ _ = a - (b % c)
+ _ = 1 + a
+ _ = a + 1
+ _ = a + b + 1
+ _ = s[a]
+ _ = s[a:]
+ _ = s[:b]
+ _ = s[1:2]
+ _ = s[a:b]
+ _ = s[0:len(s)]
+ _ = s[0] << 1
+ _ = (s[0] << 1) & 0xf
+ _ = s[0]<<2 | s[1]>>4
+ _ = "foo" + s
+ _ = s + "foo"
+ _ = 'a' + 'b'
+ _ = len(s) / 2
+ _ = len(t0.x) / a
+
+ // spaces around expressions of different precedence or expressions containing spaces
+ _ = a + -b
+ _ = a - ^b
+ _ = a / *p
+ _ = a + b*c
+ _ = 1 + b*c
+ _ = a + 2*c
+ _ = a + c*2
+ _ = 1 + 2*3
+ _ = s[1 : 2*3]
+ _ = s[a : b-c]
+ _ = s[0:]
+ _ = s[a+b]
+ _ = s[:b-c]
+ _ = s[a+b:]
+ _ = a[a<<b+1]
+ _ = a[a<<b+1:]
+ _ = s[a+b : len(s)]
+ _ = s[len(s):-a]
+ _ = s[a : len(s)+1]
+ _ = s[a:len(s)+1] + s
+
+ // spaces around operators with equal or lower precedence than comparisons
+ _ = a == b
+ _ = a != b
+ _ = a > b
+ _ = a >= b
+ _ = a < b
+ _ = a <= b
+ _ = a < b && c > d
+ _ = a < b || c > d
+
+ // spaces around "long" operands
+ _ = a + longIdentifier1
+ _ = longIdentifier1 + a
+ _ = longIdentifier1 + longIdentifier2*longIdentifier3
+ _ = s + "a longer string"
+
+ // some selected cases
+ _ = a + t0.x
+ _ = a + t0.x + t1.x*t2.x
+ _ = a + b + c + d + e + 2*3
+ _ = a + b + c + 2*3 + d + e
+ _ = (a + b + c) * 2
+ _ = a - b + c - d + (a + b + c) + d&e
+ _ = under_bar - 1
+ _ = Open(dpath+"/file", O_WRONLY|O_CREAT, 0666)
+ _ = int(c0&_Mask4)<<18 | int(c1&_Maskx)<<12 | int(c2&_Maskx)<<6 | int(c3&_Maskx)
+
+ // the parser does not restrict expressions that may appear as statements
+ true
+ 42
+ "foo"
+ x
+ (x)
+ a + b
+ a + b + c
+ a + (b * c)
+ a + (b / c)
+ 1 + a
+ a + 1
+ s[a]
+ x << 1
+ (s[0] << 1) & 0xf
+ "foo" + s
+ x == y
+ x < y || z > 42
+}
+
+func _() {
+ _ = a + b
+ _ = a + b + c
+ _ = a + b*c
+ _ = a + (b * c)
+ _ = (a + b) * c
+ _ = a + (b * c * d)
+ _ = a + (b*c + d)
+
+ _ = 1 << x
+ _ = -1 << x
+ _ = 1<<x - 1
+ _ = -1<<x - 1
+
+ _ = f(a + b)
+ _ = f(a + b + c)
+ _ = f(a + b*c)
+ _ = f(a + (b * c))
+ _ = f(1<<x-1, 1<<x-2)
+
+ _ = 1<<d.logWindowSize - 1
+
+ buf = make(x, 2*cap(b.buf)+n)
+
+ dst[i*3+2] = dbuf[0] << 2
+ dst[i*3+2] = dbuf[0]<<2 | dbuf[1]>>4
+
+ b.buf = b.buf[0 : b.off+m+n]
+ b.buf = b.buf[0 : b.off+m*n]
+ f(b.buf[0 : b.off+m+n])
+
+ signed += ' ' * 8
+ tw.octal(header[148:155], chksum)
+
+ _ = x > 0 && i >= 0
+
+ x1, x0 := x>>w2, x&m2
+ z0 = t1<<w2 + t0
+ z1 = (t1 + t0>>w2) >> w2
+ q1, r1 := x1/d1, x1%d1
+ r1 = r1*b2 | x0>>w2
+ x1 = (x1 << z) | (x0 >> (uint(w) - z))
+ x1 = x1<<z | x0>>(uint(w)-z)
+
+ _ = buf[0 : len(buf)+1]
+ _ = buf[0 : n+1]
+
+ a, b = b, a
+ a = b + c
+ a = b*c + d
+ _ = a*b + c
+ _ = a - b - c
+ _ = a - (b - c)
+ _ = a - b*c
+ _ = a - (b * c)
+ _ = a * b / c
+ _ = a / *b
+ _ = x[a|^b]
+ _ = x[a / *b]
+ _ = a & ^b
+ _ = a + +b
+ _ = a - -b
+ _ = x[a*-b]
+ _ = x[a + +b]
+ _ = x ^ y ^ z
+ _ = b[a>>24] ^ b[(a>>16)&0xFF] ^ b[(a>>8)&0xFF] ^ b[a&0xFF]
+ _ = len(longVariableName) * 2
+
+ _ = token(matchType + xlength<<lengthShift + xoffset)
+}
+
+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{}{}
+ _ = [10]T{}
+ _ = [...]T{}
+ _ = []T{}
+ _ = map[int]T{}
+}
+
+// one-line structs/interfaces in composite literals (up to a threshold)
+func _() {
+ _ = struct{}{}
+ _ = struct{ x int }{0}
+ _ = struct{ x, y, z int }{0, 1, 2}
+ _ = struct{ int }{0}
+ _ = struct{ s struct{ int } }{struct{ int }{0}}
+}
+
+func _() {
+ // do not modify literals
+ _ = "tab1 tab2 tab3 end" // string contains 3 tabs
+ _ = "tab1 tab2 tab3 end" // same string with 3 blanks - may be unaligned because editors see tabs in strings
+ _ = "" // this comment should be aligned with the one on the previous line
+ _ = ``
+ _ = `
+`
+ _ = `foo
+ bar`
+ _ = `three spaces before the end of the line starting here:
+they must not be removed`
+}
+
+func _() {
+ // smart handling of indentation for multi-line raw strings
+ var _ = ``
+ var _ = `foo`
+ var _ = `foo
+bar`
+
+ var _ = ``
+ var _ = `foo`
+ var _ =
+ // the next line should remain indented
+ `foo
+bar`
+
+ var _ = // comment
+ ``
+ var _ = // comment
+ `foo`
+ var _ = // comment
+ // the next line should remain indented
+ `foo
+bar`
+
+ var _ = /* comment */ ``
+ var _ = /* comment */ `foo`
+ var _ = /* comment */ `foo
+bar`
+
+ var _ = /* comment */
+ ``
+ var _ = /* comment */
+ `foo`
+ var _ = /* comment */
+ // the next line should remain indented
+ `foo
+bar`
+
+ var board = []int(
+ `...........
+...........
+....●●●....
+....●●●....
+..●●●●●●●..
+..●●●○●●●..
+..●●●●●●●..
+....●●●....
+....●●●....
+...........
+...........
+`)
+
+ var state = S{
+ "foo",
+ // the next line should remain indented
+ `...........
+...........
+....●●●....
+....●●●....
+..●●●●●●●..
+..●●●○●●●..
+..●●●●●●●..
+....●●●....
+....●●●....
+...........
+...........
+`,
+ "bar",
+ }
+}
+
+func _() {
+ // one-line function literals (body is on a single line)
+ _ = func() {}
+ _ = func() int { return 0 }
+ _ = func(x, y int) bool { m := (x + y) / 2; return m < 0 }
+
+ // multi-line function literals (body is not on one line)
+ _ = func() {
+ }
+ _ = func() int {
+ return 0
+ }
+ _ = func(x, y int) bool {
+ m := (x + y) / 2
+ return x < y
+ }
+
+ f(func() {
+ })
+ f(func() int {
+ return 0
+ })
+ f(func(x, y int) bool {
+ m := (x + y) / 2
+ return x < y
+ })
+}
+
+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" +
+ "bar" +
+ "bah"
+ _ = []string{
+ "abc" +
+ "def",
+ "foo" +
+ "bar",
+ }
+}
+
+const _ = F1 +
+ `string = "%s";` +
+ `ptr = *;` +
+ `datafmt.T2 = s ["-" p "-"];`
+
+const _ = `datafmt "datafmt";` +
+ `default = "%v";` +
+ `array = *;` +
+ `datafmt.T3 = s {" " a a / ","};`
+
+const _ = `datafmt "datafmt";` +
+ `default = "%v";` +
+ `array = *;` +
+ `datafmt.T3 = s {" " a a / ","};`
+
+func _() {
+ _ = F1 +
+ `string = "%s";` +
+ `ptr = *;` +
+ `datafmt.T2 = s ["-" p "-"];`
+
+ _ =
+ `datafmt "datafmt";` +
+ `default = "%v";` +
+ `array = *;` +
+ `datafmt.T3 = s {" " a a / ","};`
+
+ _ = `datafmt "datafmt";` +
+ `default = "%v";` +
+ `array = *;` +
+ `datafmt.T3 = s {" " a a / ","};`
+}
+
+func _() {
+ // respect source lines in multi-line expressions
+ _ = a +
+ b +
+ c
+ _ = a < b ||
+ b < a
+ _ = "933262154439441526816992388562667004907159682643816214685929" +
+ "638952175999932299156089414639761565182862536979208272237582" +
+ "51185210916864000000000000000000000000" // 100!
+ _ = "170141183460469231731687303715884105727" // prime
+}
+
+// Alignment after overlong lines
+const (
+ _ = "991"
+ _ = "2432902008176640000" // 20!
+ _ = "933262154439441526816992388562667004907159682643816214685929" +
+ "638952175999932299156089414639761565182862536979208272237582" +
+ "51185210916864000000000000000000000000" // 100!
+ _ = "170141183460469231731687303715884105727" // prime
+)
+
+// Correct placement of operators and comments in multi-line expressions
+func _() {
+ _ = a + // comment
+ b + // comment
+ c
+ _ = "a" +
+ "b" + // comment
+ "c"
+ _ = "ba0408" + "7265717569726564" // field 71, encoding 2, string "required"
+}
+
+// Correct placement of terminating comma/closing parentheses in multi-line calls.
+func _() {
+ f(1,
+ 2,
+ 3)
+ f(1,
+ 2,
+ 3,
+ )
+ f(1,
+ 2,
+ 3) // comment
+ f(1,
+ 2,
+ 3, // comment
+ )
+ f(1,
+ 2,
+ 3) // comment
+ f(1,
+ 2,
+ 3, // comment
+ )
+}
+
+// Align comments in multi-line lists of single-line expressions.
+var txpix = [NCOL]draw.Color{
+ draw.Yellow, // yellow
+ draw.Cyan, // cyan
+ draw.Green, // lime green
+ draw.GreyBlue, // slate
+ draw.Red, /* red */
+ draw.GreyGreen, /* olive green */
+ draw.Blue, /* blue */
+ draw.Color(0xFF55AAFF), /* pink */
+ draw.Color(0xFFAAFFFF), /* lavender */
+ draw.Color(0xBB005DFF), /* maroon */
+}
+
+func same(t, u *Time) bool {
+ // respect source lines in multi-line expressions
+ return t.Year == u.Year &&
+ t.Month == u.Month &&
+ t.Day == u.Day &&
+ t.Hour == u.Hour &&
+ t.Minute == u.Minute &&
+ t.Second == u.Second &&
+ t.Weekday == u.Weekday &&
+ t.ZoneOffset == u.ZoneOffset &&
+ t.Zone == u.Zone
+}
+
+func (p *parser) charClass() {
+ // respect source lines in multi-line expressions
+ if cc.negate && len(cc.ranges) == 2 &&
+ cc.ranges[0] == '\n' && cc.ranges[1] == '\n' {
+ nl := new(_NotNl)
+ p.re.add(nl)
+ }
+}
+
+func addState(s []state, inst instr, match []int) {
+ // handle comments correctly in multi-line expressions
+ for i := 0; i < l; i++ {
+ if s[i].inst.index() == index && // same instruction
+ s[i].match[0] < pos { // earlier match already going; leftmost wins
+ return s
+ }
+ }
+}
+
+func (self *T) foo(x int) *T { return self }
+
+func _() { module.Func1().Func2() }
+
+func _() {
+ _ = new(T).
+ foo(1).
+ foo(2).
+ foo(3)
+
+ _ = new(T).
+ foo(1).
+ foo(2). // inline comments
+ foo(3)
+
+ _ = new(T).foo(1).foo(2).foo(3)
+
+ // handle multiline argument list correctly
+ _ = new(T).
+ foo(
+ 1).
+ foo(2)
+
+ _ = new(T).foo(
+ 1).foo(2)
+
+ _ = Array[3+
+ 4]
+
+ _ = Method(1, 2,
+ 3)
+
+ _ = new(T).
+ foo().
+ bar().(*Type)
+
+ _ = new(T).
+ foo().
+ bar().(*Type).
+ baz()
+
+ _ = new(T).
+ foo().
+ bar()["idx"]
+
+ _ = new(T).
+ foo().
+ bar()["idx"].
+ baz()
+
+ _ = new(T).
+ foo().
+ bar()[1:2]
+
+ _ = new(T).
+ foo().
+ bar()[1:2].
+ baz()
+
+ _ = new(T).
+ Field.
+ Array[3+
+ 4].
+ Table["foo"].
+ Blob.(*Type).
+ Slices[1:4].
+ Method(1, 2,
+ 3).
+ Thingy
+
+ _ = a.b.c
+ _ = a.
+ b.
+ c
+ _ = a.b().c
+ _ = a.
+ b().
+ c
+ _ = a.b[0].c
+ _ = a.
+ b[0].
+ c
+ _ = a.b[0:].c
+ _ = a.
+ b[0:].
+ c
+ _ = a.b.(T).c
+ _ = a.
+ b.(T).
+ c
+}
+
+// Don't introduce extra newlines in strangely formatted expression lists.
+func f() {
+ // os.Open parameters should remain on two lines
+ if writer, err = os.Open(outfile, s.O_WRONLY|os.O_CREATE|
+ os.O_TRUNC, 0666); err != nil {
+ log.Fatal(err)
+ }
+}
diff --git a/src/pkg/go/printer/testdata/linebreaks.golden b/src/pkg/go/printer/testdata/linebreaks.golden
new file mode 100644
index 000000000..be780da67
--- /dev/null
+++ b/src/pkg/go/printer/testdata/linebreaks.golden
@@ -0,0 +1,223 @@
+// Copyright 2009 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package linebreaks
+
+import (
+ "bytes"
+ "fmt"
+ "io"
+ "os"
+ "reflect"
+ "strings"
+ "testing"
+)
+
+type writerTestEntry struct {
+ header *Header
+ contents string
+}
+
+type writerTest struct {
+ file string // filename of expected output
+ entries []*writerTestEntry
+}
+
+var writerTests = []*writerTest{
+ &writerTest{
+ file: "testdata/writer.tar",
+ entries: []*writerTestEntry{
+ &writerTestEntry{
+ header: &Header{
+ Name: "small.txt",
+ Mode: 0640,
+ Uid: 73025,
+ Gid: 5000,
+ Size: 5,
+ Mtime: 1246508266,
+ Typeflag: '0',
+ Uname: "dsymonds",
+ Gname: "eng",
+ },
+ contents: "Kilts",
+ },
+ &writerTestEntry{
+ header: &Header{
+ Name: "small2.txt",
+ Mode: 0640,
+ Uid: 73025,
+ Gid: 5000,
+ Size: 11,
+ Mtime: 1245217492,
+ Typeflag: '0',
+ Uname: "dsymonds",
+ Gname: "eng",
+ },
+ contents: "Google.com\n",
+ },
+ },
+ },
+ // The truncated test file was produced using these commands:
+ // dd if=/dev/zero bs=1048576 count=16384 > /tmp/16gig.txt
+ // tar -b 1 -c -f- /tmp/16gig.txt | dd bs=512 count=8 > writer-big.tar
+ &writerTest{
+ file: "testdata/writer-big.tar",
+ entries: []*writerTestEntry{
+ &writerTestEntry{
+ header: &Header{
+ Name: "tmp/16gig.txt",
+ Mode: 0640,
+ Uid: 73025,
+ Gid: 5000,
+ Size: 16 << 30,
+ Mtime: 1254699560,
+ Typeflag: '0',
+ Uname: "dsymonds",
+ Gname: "eng",
+ },
+ // no contents
+ },
+ },
+ },
+}
+
+type untarTest struct {
+ file string
+ headers []*Header
+}
+
+var untarTests = []*untarTest{
+ &untarTest{
+ file: "testdata/gnu.tar",
+ headers: []*Header{
+ &Header{
+ Name: "small.txt",
+ Mode: 0640,
+ Uid: 73025,
+ Gid: 5000,
+ Size: 5,
+ Mtime: 1244428340,
+ Typeflag: '0',
+ Uname: "dsymonds",
+ Gname: "eng",
+ },
+ &Header{
+ Name: "small2.txt",
+ Mode: 0640,
+ Uid: 73025,
+ Gid: 5000,
+ Size: 11,
+ Mtime: 1244436044,
+ Typeflag: '0',
+ Uname: "dsymonds",
+ Gname: "eng",
+ },
+ },
+ },
+ &untarTest{
+ file: "testdata/star.tar",
+ headers: []*Header{
+ &Header{
+ Name: "small.txt",
+ Mode: 0640,
+ Uid: 73025,
+ Gid: 5000,
+ Size: 5,
+ Mtime: 1244592783,
+ Typeflag: '0',
+ Uname: "dsymonds",
+ Gname: "eng",
+ Atime: 1244592783,
+ Ctime: 1244592783,
+ },
+ &Header{
+ Name: "small2.txt",
+ Mode: 0640,
+ Uid: 73025,
+ Gid: 5000,
+ Size: 11,
+ Mtime: 1244592783,
+ Typeflag: '0',
+ Uname: "dsymonds",
+ Gname: "eng",
+ Atime: 1244592783,
+ Ctime: 1244592783,
+ },
+ },
+ },
+ &untarTest{
+ file: "testdata/v7.tar",
+ headers: []*Header{
+ &Header{
+ Name: "small.txt",
+ Mode: 0444,
+ Uid: 73025,
+ Gid: 5000,
+ Size: 5,
+ Mtime: 1244593104,
+ Typeflag: '\x00',
+ },
+ &Header{
+ Name: "small2.txt",
+ Mode: 0444,
+ Uid: 73025,
+ Gid: 5000,
+ Size: 11,
+ Mtime: 1244593104,
+ Typeflag: '\x00',
+ },
+ },
+ },
+}
+
+var facts = map[int]string{
+ 0: "1",
+ 1: "1",
+ 2: "2",
+ 10: "3628800",
+ 20: "2432902008176640000",
+ 100: "933262154439441526816992388562667004907159682643816214685929" +
+ "638952175999932299156089414639761565182862536979208272237582" +
+ "51185210916864000000000000000000000000",
+}
+
+func usage() {
+ fmt.Fprintf(os.Stderr,
+ // TODO(gri): the 2nd string of this string list should not be indented
+ "usage: godoc package [name ...]\n"+
+ " godoc -http=:6060\n")
+ flag.PrintDefaults()
+ os.Exit(2)
+}
+
+func TestReader(t *testing.T) {
+testLoop:
+ for i, test := range untarTests {
+ f, err := os.Open(test.file, os.O_RDONLY, 0444)
+ if err != nil {
+ t.Errorf("test %d: Unexpected error: %v", i, err)
+ continue
+ }
+ tr := NewReader(f)
+ for j, header := range test.headers {
+ hdr, err := tr.Next()
+ if err != nil || hdr == nil {
+ t.Errorf("test %d, entry %d: Didn't get entry: %v", i, j, err)
+ f.Close()
+ continue testLoop
+ }
+ if !reflect.DeepEqual(hdr, header) {
+ t.Errorf("test %d, entry %d: Incorrect header:\nhave %+v\nwant %+v",
+ i, j, *hdr, *header)
+ }
+ }
+ hdr, err := tr.Next()
+ if hdr != nil || err != nil {
+ t.Errorf("test %d: Unexpected entry or error: hdr=%v err=%v", i, err)
+ }
+ f.Close()
+ }
+}
+
+// There should be exactly one linebreak after this comment.
diff --git a/src/pkg/go/printer/testdata/linebreaks.input b/src/pkg/go/printer/testdata/linebreaks.input
new file mode 100644
index 000000000..457b491e6
--- /dev/null
+++ b/src/pkg/go/printer/testdata/linebreaks.input
@@ -0,0 +1,223 @@
+// Copyright 2009 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package linebreaks
+
+import (
+ "bytes"
+ "fmt"
+ "io"
+ "os"
+ "reflect"
+ "strings"
+ "testing"
+)
+
+type writerTestEntry struct {
+ header *Header
+ contents string
+}
+
+type writerTest struct {
+ file string // filename of expected output
+ entries []*writerTestEntry
+}
+
+var writerTests = []*writerTest{
+ &writerTest{
+ file: "testdata/writer.tar",
+ entries: []*writerTestEntry{
+ &writerTestEntry{
+ header: &Header{
+ Name: "small.txt",
+ Mode: 0640,
+ Uid: 73025,
+ Gid: 5000,
+ Size: 5,
+ Mtime: 1246508266,
+ Typeflag: '0',
+ Uname: "dsymonds",
+ Gname: "eng",
+ },
+ contents: "Kilts",
+ },
+ &writerTestEntry{
+ header: &Header{
+ Name: "small2.txt",
+ Mode: 0640,
+ Uid: 73025,
+ Gid: 5000,
+ Size: 11,
+ Mtime: 1245217492,
+ Typeflag: '0',
+ Uname: "dsymonds",
+ Gname: "eng",
+ },
+ contents: "Google.com\n",
+ },
+ },
+ },
+ // The truncated test file was produced using these commands:
+ // dd if=/dev/zero bs=1048576 count=16384 > /tmp/16gig.txt
+ // tar -b 1 -c -f- /tmp/16gig.txt | dd bs=512 count=8 > writer-big.tar
+ &writerTest{
+ file: "testdata/writer-big.tar",
+ entries: []*writerTestEntry{
+ &writerTestEntry{
+ header: &Header{
+ Name: "tmp/16gig.txt",
+ Mode: 0640,
+ Uid: 73025,
+ Gid: 5000,
+ Size: 16 << 30,
+ Mtime: 1254699560,
+ Typeflag: '0',
+ Uname: "dsymonds",
+ Gname: "eng",
+ },
+ // no contents
+ },
+ },
+ },
+}
+
+type untarTest struct {
+ file string
+ headers []*Header
+}
+
+var untarTests = []*untarTest{
+ &untarTest{
+ file: "testdata/gnu.tar",
+ headers: []*Header{
+ &Header{
+ Name: "small.txt",
+ Mode: 0640,
+ Uid: 73025,
+ Gid: 5000,
+ Size: 5,
+ Mtime: 1244428340,
+ Typeflag: '0',
+ Uname: "dsymonds",
+ Gname: "eng",
+ },
+ &Header{
+ Name: "small2.txt",
+ Mode: 0640,
+ Uid: 73025,
+ Gid: 5000,
+ Size: 11,
+ Mtime: 1244436044,
+ Typeflag: '0',
+ Uname: "dsymonds",
+ Gname: "eng",
+ },
+ },
+ },
+ &untarTest{
+ file: "testdata/star.tar",
+ headers: []*Header{
+ &Header{
+ Name: "small.txt",
+ Mode: 0640,
+ Uid: 73025,
+ Gid: 5000,
+ Size: 5,
+ Mtime: 1244592783,
+ Typeflag: '0',
+ Uname: "dsymonds",
+ Gname: "eng",
+ Atime: 1244592783,
+ Ctime: 1244592783,
+ },
+ &Header{
+ Name: "small2.txt",
+ Mode: 0640,
+ Uid: 73025,
+ Gid: 5000,
+ Size: 11,
+ Mtime: 1244592783,
+ Typeflag: '0',
+ Uname: "dsymonds",
+ Gname: "eng",
+ Atime: 1244592783,
+ Ctime: 1244592783,
+ },
+ },
+ },
+ &untarTest{
+ file: "testdata/v7.tar",
+ headers: []*Header{
+ &Header{
+ Name: "small.txt",
+ Mode: 0444,
+ Uid: 73025,
+ Gid: 5000,
+ Size: 5,
+ Mtime: 1244593104,
+ Typeflag: '\x00',
+ },
+ &Header{
+ Name: "small2.txt",
+ Mode: 0444,
+ Uid: 73025,
+ Gid: 5000,
+ Size: 11,
+ Mtime: 1244593104,
+ Typeflag: '\x00',
+ },
+ },
+ },
+}
+
+var facts = map[int] string {
+ 0: "1",
+ 1: "1",
+ 2: "2",
+ 10: "3628800",
+ 20: "2432902008176640000",
+ 100: "933262154439441526816992388562667004907159682643816214685929" +
+ "638952175999932299156089414639761565182862536979208272237582" +
+ "51185210916864000000000000000000000000",
+}
+
+func usage() {
+ fmt.Fprintf(os.Stderr,
+ // TODO(gri): the 2nd string of this string list should not be indented
+ "usage: godoc package [name ...]\n" +
+ " godoc -http=:6060\n")
+ flag.PrintDefaults()
+ os.Exit(2)
+}
+
+func TestReader(t *testing.T) {
+testLoop:
+ for i, test := range untarTests {
+ f, err := os.Open(test.file, os.O_RDONLY, 0444)
+ if err != nil {
+ t.Errorf("test %d: Unexpected error: %v", i, err)
+ continue
+ }
+ tr := NewReader(f)
+ for j, header := range test.headers {
+ hdr, err := tr.Next()
+ if err != nil || hdr == nil {
+ t.Errorf("test %d, entry %d: Didn't get entry: %v", i, j, err)
+ f.Close()
+ continue testLoop
+ }
+ if !reflect.DeepEqual(hdr, header) {
+ t.Errorf("test %d, entry %d: Incorrect header:\nhave %+v\nwant %+v",
+ i, j, *hdr, *header)
+ }
+ }
+ hdr, err := tr.Next()
+ if hdr != nil || err != nil {
+ t.Errorf("test %d: Unexpected entry or error: hdr=%v err=%v", i, err)
+ }
+ f.Close()
+ }
+}
+
+// There should be exactly one linebreak after this comment.
diff --git a/src/pkg/go/printer/testdata/parser.go b/src/pkg/go/printer/testdata/parser.go
new file mode 100644
index 000000000..2d27af499
--- /dev/null
+++ b/src/pkg/go/printer/testdata/parser.go
@@ -0,0 +1,2153 @@
+// Copyright 2009 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Package parser implements a parser for Go source files. Input may be
+// provided in a variety of forms (see the various Parse* functions); the
+// output is an abstract syntax tree (AST) representing the Go source. The
+// parser is invoked through one of the Parse* functions.
+//
+package parser
+
+import (
+ "fmt"
+ "go/ast"
+ "go/scanner"
+ "go/token"
+)
+
+// The mode parameter to the Parse* functions is a set of flags (or 0).
+// They control the amount of source code parsed and other optional
+// parser functionality.
+//
+const (
+ PackageClauseOnly uint = 1 << iota // parsing stops after package clause
+ ImportsOnly // parsing stops after import declarations
+ ParseComments // parse comments and add them to AST
+ Trace // print a trace of parsed productions
+ DeclarationErrors // report declaration errors
+)
+
+// The parser structure holds the parser's internal state.
+type parser struct {
+ file *token.File
+ scanner.ErrorVector
+ scanner scanner.Scanner
+
+ // Tracing/debugging
+ mode uint // parsing mode
+ trace bool // == (mode & Trace != 0)
+ indent uint // indentation used for tracing output
+
+ // Comments
+ comments []*ast.CommentGroup
+ leadComment *ast.CommentGroup // last lead comment
+ lineComment *ast.CommentGroup // last line comment
+
+ // Next token
+ pos token.Pos // token position
+ tok token.Token // one token look-ahead
+ lit string // token literal
+
+ // Non-syntactic parser control
+ exprLev int // < 0: in control clause, >= 0: in expression
+
+ // Ordinary identifer scopes
+ pkgScope *ast.Scope // pkgScope.Outer == nil
+ topScope *ast.Scope // top-most scope; may be pkgScope
+ unresolved []*ast.Ident // unresolved identifiers
+ imports []*ast.ImportSpec // list of imports
+
+ // Label scope
+ // (maintained by open/close LabelScope)
+ labelScope *ast.Scope // label scope for current function
+ targetStack [][]*ast.Ident // stack of unresolved labels
+}
+
+// scannerMode returns the scanner mode bits given the parser's mode bits.
+func scannerMode(mode uint) uint {
+ var m uint = scanner.InsertSemis
+ if mode&ParseComments != 0 {
+ m |= scanner.ScanComments
+ }
+ return m
+}
+
+func (p *parser) init(fset *token.FileSet, filename string, src []byte, mode uint) {
+ p.file = fset.AddFile(filename, fset.Base(), len(src))
+ p.scanner.Init(p.file, src, p, scannerMode(mode))
+
+ p.mode = mode
+ p.trace = mode&Trace != 0 // for convenience (p.trace is used frequently)
+
+ p.next()
+
+ // set up the pkgScope here (as opposed to in parseFile) because
+ // there are other parser entry points (ParseExpr, etc.)
+ p.openScope()
+ p.pkgScope = p.topScope
+
+ // for the same reason, set up a label scope
+ p.openLabelScope()
+}
+
+// ----------------------------------------------------------------------------
+// Scoping support
+
+func (p *parser) openScope() {
+ p.topScope = ast.NewScope(p.topScope)
+}
+
+func (p *parser) closeScope() {
+ p.topScope = p.topScope.Outer
+}
+
+func (p *parser) openLabelScope() {
+ p.labelScope = ast.NewScope(p.labelScope)
+ p.targetStack = append(p.targetStack, nil)
+}
+
+func (p *parser) closeLabelScope() {
+ // resolve labels
+ n := len(p.targetStack) - 1
+ scope := p.labelScope
+ for _, ident := range p.targetStack[n] {
+ ident.Obj = scope.Lookup(ident.Name)
+ if ident.Obj == nil && p.mode&DeclarationErrors != 0 {
+ p.error(ident.Pos(), fmt.Sprintf("label %s undefined", ident.Name))
+ }
+ }
+ // pop label scope
+ p.targetStack = p.targetStack[0:n]
+ p.labelScope = p.labelScope.Outer
+}
+
+func (p *parser) declare(decl interface{}, scope *ast.Scope, kind ast.ObjKind, idents ...*ast.Ident) {
+ for _, ident := range idents {
+ assert(ident.Obj == nil, "identifier already declared or resolved")
+ if ident.Name != "_" {
+ obj := ast.NewObj(kind, ident.Name)
+ // remember the corresponding declaration for redeclaration
+ // errors and global variable resolution/typechecking phase
+ obj.Decl = decl
+ if alt := scope.Insert(obj); alt != nil && p.mode&DeclarationErrors != 0 {
+ prevDecl := ""
+ if pos := alt.Pos(); pos.IsValid() {
+ prevDecl = fmt.Sprintf("\n\tprevious declaration at %s", p.file.Position(pos))
+ }
+ p.error(ident.Pos(), fmt.Sprintf("%s redeclared in this block%s", ident.Name, prevDecl))
+ }
+ ident.Obj = obj
+ }
+ }
+}
+
+func (p *parser) shortVarDecl(idents []*ast.Ident) {
+ // Go spec: A short variable declaration may redeclare variables
+ // provided they were originally declared in the same block with
+ // the same type, and at least one of the non-blank variables is new.
+ n := 0 // number of new variables
+ for _, ident := range idents {
+ assert(ident.Obj == nil, "identifier already declared or resolved")
+ if ident.Name != "_" {
+ obj := ast.NewObj(ast.Var, ident.Name)
+ // short var declarations cannot have redeclaration errors
+ // and are not global => no need to remember the respective
+ // declaration
+ alt := p.topScope.Insert(obj)
+ if alt == nil {
+ n++ // new declaration
+ alt = obj
+ }
+ ident.Obj = alt
+ }
+ }
+ if n == 0 && p.mode&DeclarationErrors != 0 {
+ p.error(idents[0].Pos(), "no new variables on left side of :=")
+ }
+}
+
+// The unresolved object is a sentinel to mark identifiers that have been added
+// to the list of unresolved identifiers. The sentinel is only used for verifying
+// internal consistency.
+var unresolved = new(ast.Object)
+
+func (p *parser) resolve(x ast.Expr) {
+ // nothing to do if x is not an identifier or the blank identifier
+ ident, _ := x.(*ast.Ident)
+ if ident == nil {
+ return
+ }
+ assert(ident.Obj == nil, "identifier already declared or resolved")
+ if ident.Name == "_" {
+ return
+ }
+ // try to resolve the identifier
+ for s := p.topScope; s != nil; s = s.Outer {
+ if obj := s.Lookup(ident.Name); obj != nil {
+ ident.Obj = obj
+ return
+ }
+ }
+ // all local scopes are known, so any unresolved identifier
+ // must be found either in the file scope, package scope
+ // (perhaps in another file), or universe scope --- collect
+ // them so that they can be resolved later
+ ident.Obj = unresolved
+ p.unresolved = append(p.unresolved, ident)
+}
+
+// ----------------------------------------------------------------------------
+// Parsing support
+
+func (p *parser) printTrace(a ...interface{}) {
+ const dots = ". . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . " +
+ ". . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . "
+ const n = uint(len(dots))
+ pos := p.file.Position(p.pos)
+ fmt.Printf("%5d:%3d: ", pos.Line, pos.Column)
+ i := 2 * p.indent
+ for ; i > n; i -= n {
+ fmt.Print(dots)
+ }
+ fmt.Print(dots[0:i])
+ fmt.Println(a...)
+}
+
+func trace(p *parser, msg string) *parser {
+ p.printTrace(msg, "(")
+ p.indent++
+ return p
+}
+
+// Usage pattern: defer un(trace(p, "..."));
+func un(p *parser) {
+ p.indent--
+ p.printTrace(")")
+}
+
+// Advance to the next token.
+func (p *parser) next0() {
+ // Because of one-token look-ahead, print the previous token
+ // when tracing as it provides a more readable output. The
+ // very first token (!p.pos.IsValid()) is not initialized
+ // (it is token.ILLEGAL), so don't print it .
+ if p.trace && p.pos.IsValid() {
+ s := p.tok.String()
+ switch {
+ case p.tok.IsLiteral():
+ p.printTrace(s, p.lit)
+ case p.tok.IsOperator(), p.tok.IsKeyword():
+ p.printTrace("\"" + s + "\"")
+ default:
+ p.printTrace(s)
+ }
+ }
+
+ p.pos, p.tok, p.lit = p.scanner.Scan()
+}
+
+// Consume a comment and return it and the line on which it ends.
+func (p *parser) consumeComment() (comment *ast.Comment, endline int) {
+ // /*-style comments may end on a different line than where they start.
+ // Scan the comment for '\n' chars and adjust endline accordingly.
+ endline = p.file.Line(p.pos)
+ if p.lit[1] == '*' {
+ // don't use range here - no need to decode Unicode code points
+ for i := 0; i < len(p.lit); i++ {
+ if p.lit[i] == '\n' {
+ endline++
+ }
+ }
+ }
+
+ comment = &ast.Comment{p.pos, p.lit}
+ p.next0()
+
+ return
+}
+
+// Consume a group of adjacent comments, add it to the parser's
+// comments list, and return it together with the line at which
+// the last comment in the group ends. An empty line or non-comment
+// token terminates a comment group.
+//
+func (p *parser) consumeCommentGroup() (comments *ast.CommentGroup, endline int) {
+ var list []*ast.Comment
+ endline = p.file.Line(p.pos)
+ for p.tok == token.COMMENT && endline+1 >= p.file.Line(p.pos) {
+ var comment *ast.Comment
+ comment, endline = p.consumeComment()
+ list = append(list, comment)
+ }
+
+ // add comment group to the comments list
+ comments = &ast.CommentGroup{list}
+ p.comments = append(p.comments, comments)
+
+ return
+}
+
+// Advance to the next non-comment token. In the process, collect
+// any comment groups encountered, and remember the last lead and
+// and line comments.
+//
+// A lead comment is a comment group that starts and ends in a
+// line without any other tokens and that is followed by a non-comment
+// token on the line immediately after the comment group.
+//
+// A line comment is a comment group that follows a non-comment
+// token on the same line, and that has no tokens after it on the line
+// where it ends.
+//
+// Lead and line comments may be considered documentation that is
+// stored in the AST.
+//
+func (p *parser) next() {
+ p.leadComment = nil
+ p.lineComment = nil
+ line := p.file.Line(p.pos) // current line
+ p.next0()
+
+ if p.tok == token.COMMENT {
+ var comment *ast.CommentGroup
+ var endline int
+
+ if p.file.Line(p.pos) == line {
+ // The comment is on same line as the previous token; it
+ // cannot be a lead comment but may be a line comment.
+ comment, endline = p.consumeCommentGroup()
+ if p.file.Line(p.pos) != endline {
+ // The next token is on a different line, thus
+ // the last comment group is a line comment.
+ p.lineComment = comment
+ }
+ }
+
+ // consume successor comments, if any
+ endline = -1
+ for p.tok == token.COMMENT {
+ comment, endline = p.consumeCommentGroup()
+ }
+
+ if endline+1 == p.file.Line(p.pos) {
+ // The next token is following on the line immediately after the
+ // comment group, thus the last comment group is a lead comment.
+ p.leadComment = comment
+ }
+ }
+}
+
+func (p *parser) error(pos token.Pos, msg string) {
+ p.Error(p.file.Position(pos), msg)
+}
+
+func (p *parser) errorExpected(pos token.Pos, msg string) {
+ msg = "expected " + msg
+ if pos == p.pos {
+ // the error happened at the current position;
+ // make the error message more specific
+ if p.tok == token.SEMICOLON && p.lit[0] == '\n' {
+ msg += ", found newline"
+ } else {
+ msg += ", found '" + p.tok.String() + "'"
+ if p.tok.IsLiteral() {
+ msg += " " + p.lit
+ }
+ }
+ }
+ p.error(pos, msg)
+}
+
+func (p *parser) expect(tok token.Token) token.Pos {
+ pos := p.pos
+ if p.tok != tok {
+ p.errorExpected(pos, "'"+tok.String()+"'")
+ }
+ p.next() // make progress
+ return pos
+}
+
+func (p *parser) expectSemi() {
+ if p.tok != token.RPAREN && p.tok != token.RBRACE {
+ p.expect(token.SEMICOLON)
+ }
+}
+
+func assert(cond bool, msg string) {
+ if !cond {
+ panic("go/parser internal error: " + msg)
+ }
+}
+
+// ----------------------------------------------------------------------------
+// Identifiers
+
+func (p *parser) parseIdent() *ast.Ident {
+ pos := p.pos
+ name := "_"
+ if p.tok == token.IDENT {
+ name = p.lit
+ p.next()
+ } else {
+ p.expect(token.IDENT) // use expect() error handling
+ }
+ return &ast.Ident{pos, name, nil}
+}
+
+func (p *parser) parseIdentList() (list []*ast.Ident) {
+ if p.trace {
+ defer un(trace(p, "IdentList"))
+ }
+
+ list = append(list, p.parseIdent())
+ for p.tok == token.COMMA {
+ p.next()
+ list = append(list, p.parseIdent())
+ }
+
+ return
+}
+
+// ----------------------------------------------------------------------------
+// Common productions
+
+// If lhs is set, result list elements which are identifiers are not resolved.
+func (p *parser) parseExprList(lhs bool) (list []ast.Expr) {
+ if p.trace {
+ defer un(trace(p, "ExpressionList"))
+ }
+
+ list = append(list, p.parseExpr(lhs))
+ for p.tok == token.COMMA {
+ p.next()
+ list = append(list, p.parseExpr(lhs))
+ }
+
+ return
+}
+
+func (p *parser) parseLhsList() []ast.Expr {
+ list := p.parseExprList(true)
+ switch p.tok {
+ case token.DEFINE:
+ // lhs of a short variable declaration
+ p.shortVarDecl(p.makeIdentList(list))
+ case token.COLON:
+ // lhs of a label declaration or a communication clause of a select
+ // statement (parseLhsList is not called when parsing the case clause
+ // of a switch statement):
+ // - labels are declared by the caller of parseLhsList
+ // - for communication clauses, if there is a stand-alone identifier
+ // followed by a colon, we have a syntax error; there is no need
+ // to resolve the identifier in that case
+ default:
+ // identifiers must be declared elsewhere
+ for _, x := range list {
+ p.resolve(x)
+ }
+ }
+ return list
+}
+
+func (p *parser) parseRhsList() []ast.Expr {
+ return p.parseExprList(false)
+}
+
+// ----------------------------------------------------------------------------
+// Types
+
+func (p *parser) parseType() ast.Expr {
+ if p.trace {
+ defer un(trace(p, "Type"))
+ }
+
+ typ := p.tryType()
+
+ if typ == nil {
+ pos := p.pos
+ p.errorExpected(pos, "type")
+ p.next() // make progress
+ return &ast.BadExpr{pos, p.pos}
+ }
+
+ return typ
+}
+
+// If the result is an identifier, it is not resolved.
+func (p *parser) parseTypeName() ast.Expr {
+ if p.trace {
+ defer un(trace(p, "TypeName"))
+ }
+
+ ident := p.parseIdent()
+ // don't resolve ident yet - it may be a parameter or field name
+
+ if p.tok == token.PERIOD {
+ // ident is a package name
+ p.next()
+ p.resolve(ident)
+ sel := p.parseIdent()
+ return &ast.SelectorExpr{ident, sel}
+ }
+
+ return ident
+}
+
+func (p *parser) parseArrayType(ellipsisOk bool) ast.Expr {
+ if p.trace {
+ defer un(trace(p, "ArrayType"))
+ }
+
+ lbrack := p.expect(token.LBRACK)
+ var len ast.Expr
+ if ellipsisOk && p.tok == token.ELLIPSIS {
+ len = &ast.Ellipsis{p.pos, nil}
+ p.next()
+ } else if p.tok != token.RBRACK {
+ len = p.parseRhs()
+ }
+ p.expect(token.RBRACK)
+ elt := p.parseType()
+
+ return &ast.ArrayType{lbrack, len, elt}
+}
+
+func (p *parser) makeIdentList(list []ast.Expr) []*ast.Ident {
+ idents := make([]*ast.Ident, len(list))
+ for i, x := range list {
+ ident, isIdent := x.(*ast.Ident)
+ if !isIdent {
+ pos := x.(ast.Expr).Pos()
+ p.errorExpected(pos, "identifier")
+ ident = &ast.Ident{pos, "_", nil}
+ }
+ idents[i] = ident
+ }
+ return idents
+}
+
+func (p *parser) parseFieldDecl(scope *ast.Scope) *ast.Field {
+ if p.trace {
+ defer un(trace(p, "FieldDecl"))
+ }
+
+ doc := p.leadComment
+
+ // fields
+ list, typ := p.parseVarList(false)
+
+ // optional tag
+ var tag *ast.BasicLit
+ if p.tok == token.STRING {
+ tag = &ast.BasicLit{p.pos, p.tok, p.lit}
+ p.next()
+ }
+
+ // analyze case
+ var idents []*ast.Ident
+ if typ != nil {
+ // IdentifierList Type
+ idents = p.makeIdentList(list)
+ } else {
+ // ["*"] TypeName (AnonymousField)
+ typ = list[0] // we always have at least one element
+ p.resolve(typ)
+ if n := len(list); n > 1 || !isTypeName(deref(typ)) {
+ pos := typ.Pos()
+ p.errorExpected(pos, "anonymous field")
+ typ = &ast.BadExpr{pos, list[n-1].End()}
+ }
+ }
+
+ p.expectSemi() // call before accessing p.linecomment
+
+ field := &ast.Field{doc, idents, typ, tag, p.lineComment}
+ p.declare(field, scope, ast.Var, idents...)
+
+ return field
+}
+
+func (p *parser) parseStructType() *ast.StructType {
+ if p.trace {
+ defer un(trace(p, "StructType"))
+ }
+
+ pos := p.expect(token.STRUCT)
+ lbrace := p.expect(token.LBRACE)
+ scope := ast.NewScope(nil) // struct scope
+ var list []*ast.Field
+ for p.tok == token.IDENT || p.tok == token.MUL || p.tok == token.LPAREN {
+ // a field declaration cannot start with a '(' but we accept
+ // it here for more robust parsing and better error messages
+ // (parseFieldDecl will check and complain if necessary)
+ list = append(list, p.parseFieldDecl(scope))
+ }
+ rbrace := p.expect(token.RBRACE)
+
+ // TODO(gri): store struct scope in AST
+ return &ast.StructType{pos, &ast.FieldList{lbrace, list, rbrace}, false}
+}
+
+func (p *parser) parsePointerType() *ast.StarExpr {
+ if p.trace {
+ defer un(trace(p, "PointerType"))
+ }
+
+ star := p.expect(token.MUL)
+ base := p.parseType()
+
+ return &ast.StarExpr{star, base}
+}
+
+func (p *parser) tryVarType(isParam bool) ast.Expr {
+ if isParam && p.tok == token.ELLIPSIS {
+ pos := p.pos
+ p.next()
+ typ := p.tryIdentOrType(isParam) // don't use parseType so we can provide better error message
+ if typ == nil {
+ p.error(pos, "'...' parameter is missing type")
+ typ = &ast.BadExpr{pos, p.pos}
+ }
+ if p.tok != token.RPAREN {
+ p.error(pos, "can use '...' with last parameter type only")
+ }
+ return &ast.Ellipsis{pos, typ}
+ }
+ return p.tryIdentOrType(false)
+}
+
+func (p *parser) parseVarType(isParam bool) ast.Expr {
+ typ := p.tryVarType(isParam)
+ if typ == nil {
+ pos := p.pos
+ p.errorExpected(pos, "type")
+ p.next() // make progress
+ typ = &ast.BadExpr{pos, p.pos}
+ }
+ return typ
+}
+
+func (p *parser) parseVarList(isParam bool) (list []ast.Expr, typ ast.Expr) {
+ if p.trace {
+ defer un(trace(p, "VarList"))
+ }
+
+ // a list of identifiers looks like a list of type names
+ for {
+ // parseVarType accepts any type (including parenthesized ones)
+ // even though the syntax does not permit them here: we
+ // accept them all for more robust parsing and complain
+ // afterwards
+ list = append(list, p.parseVarType(isParam))
+ if p.tok != token.COMMA {
+ break
+ }
+ p.next()
+ }
+
+ // if we had a list of identifiers, it must be followed by a type
+ typ = p.tryVarType(isParam)
+ if typ != nil {
+ p.resolve(typ)
+ }
+
+ return
+}
+
+func (p *parser) parseParameterList(scope *ast.Scope, ellipsisOk bool) (params []*ast.Field) {
+ if p.trace {
+ defer un(trace(p, "ParameterList"))
+ }
+
+ list, typ := p.parseVarList(ellipsisOk)
+ if typ != nil {
+ // IdentifierList Type
+ idents := p.makeIdentList(list)
+ field := &ast.Field{nil, idents, typ, nil, nil}
+ params = append(params, field)
+ // Go spec: The scope of an identifier denoting a function
+ // parameter or result variable is the function body.
+ p.declare(field, scope, ast.Var, idents...)
+ if p.tok == token.COMMA {
+ p.next()
+ }
+
+ for p.tok != token.RPAREN && p.tok != token.EOF {
+ idents := p.parseIdentList()
+ typ := p.parseVarType(ellipsisOk)
+ field := &ast.Field{nil, idents, typ, nil, nil}
+ params = append(params, field)
+ // Go spec: The scope of an identifier denoting a function
+ // parameter or result variable is the function body.
+ p.declare(field, scope, ast.Var, idents...)
+ if p.tok != token.COMMA {
+ break
+ }
+ p.next()
+ }
+
+ } else {
+ // Type { "," Type } (anonymous parameters)
+ params = make([]*ast.Field, len(list))
+ for i, x := range list {
+ p.resolve(x)
+ params[i] = &ast.Field{Type: x}
+ }
+ }
+
+ return
+}
+
+func (p *parser) parseParameters(scope *ast.Scope, ellipsisOk bool) *ast.FieldList {
+ if p.trace {
+ defer un(trace(p, "Parameters"))
+ }
+
+ var params []*ast.Field
+ lparen := p.expect(token.LPAREN)
+ if p.tok != token.RPAREN {
+ params = p.parseParameterList(scope, ellipsisOk)
+ }
+ rparen := p.expect(token.RPAREN)
+
+ return &ast.FieldList{lparen, params, rparen}
+}
+
+func (p *parser) parseResult(scope *ast.Scope) *ast.FieldList {
+ if p.trace {
+ defer un(trace(p, "Result"))
+ }
+
+ if p.tok == token.LPAREN {
+ return p.parseParameters(scope, false)
+ }
+
+ typ := p.tryType()
+ if typ != nil {
+ list := make([]*ast.Field, 1)
+ list[0] = &ast.Field{Type: typ}
+ return &ast.FieldList{List: list}
+ }
+
+ return nil
+}
+
+func (p *parser) parseSignature(scope *ast.Scope) (params, results *ast.FieldList) {
+ if p.trace {
+ defer un(trace(p, "Signature"))
+ }
+
+ params = p.parseParameters(scope, true)
+ results = p.parseResult(scope)
+
+ return
+}
+
+func (p *parser) parseFuncType() (*ast.FuncType, *ast.Scope) {
+ if p.trace {
+ defer un(trace(p, "FuncType"))
+ }
+
+ pos := p.expect(token.FUNC)
+ scope := ast.NewScope(p.topScope) // function scope
+ params, results := p.parseSignature(scope)
+
+ return &ast.FuncType{pos, params, results}, scope
+}
+
+func (p *parser) parseMethodSpec(scope *ast.Scope) *ast.Field {
+ if p.trace {
+ defer un(trace(p, "MethodSpec"))
+ }
+
+ doc := p.leadComment
+ var idents []*ast.Ident
+ var typ ast.Expr
+ x := p.parseTypeName()
+ if ident, isIdent := x.(*ast.Ident); isIdent && p.tok == token.LPAREN {
+ // method
+ idents = []*ast.Ident{ident}
+ scope := ast.NewScope(nil) // method scope
+ params, results := p.parseSignature(scope)
+ typ = &ast.FuncType{token.NoPos, params, results}
+ } else {
+ // embedded interface
+ typ = x
+ }
+ p.expectSemi() // call before accessing p.linecomment
+
+ spec := &ast.Field{doc, idents, typ, nil, p.lineComment}
+ p.declare(spec, scope, ast.Fun, idents...)
+
+ return spec
+}
+
+func (p *parser) parseInterfaceType() *ast.InterfaceType {
+ if p.trace {
+ defer un(trace(p, "InterfaceType"))
+ }
+
+ pos := p.expect(token.INTERFACE)
+ lbrace := p.expect(token.LBRACE)
+ scope := ast.NewScope(nil) // interface scope
+ var list []*ast.Field
+ for p.tok == token.IDENT {
+ list = append(list, p.parseMethodSpec(scope))
+ }
+ rbrace := p.expect(token.RBRACE)
+
+ // TODO(gri): store interface scope in AST
+ return &ast.InterfaceType{pos, &ast.FieldList{lbrace, list, rbrace}, false}
+}
+
+func (p *parser) parseMapType() *ast.MapType {
+ if p.trace {
+ defer un(trace(p, "MapType"))
+ }
+
+ pos := p.expect(token.MAP)
+ p.expect(token.LBRACK)
+ key := p.parseType()
+ p.expect(token.RBRACK)
+ value := p.parseType()
+
+ return &ast.MapType{pos, key, value}
+}
+
+func (p *parser) parseChanType() *ast.ChanType {
+ if p.trace {
+ defer un(trace(p, "ChanType"))
+ }
+
+ pos := p.pos
+ dir := ast.SEND | ast.RECV
+ if p.tok == token.CHAN {
+ p.next()
+ if p.tok == token.ARROW {
+ p.next()
+ dir = ast.SEND
+ }
+ } else {
+ p.expect(token.ARROW)
+ p.expect(token.CHAN)
+ dir = ast.RECV
+ }
+ value := p.parseType()
+
+ return &ast.ChanType{pos, dir, value}
+}
+
+// If the result is an identifier, it is not resolved.
+func (p *parser) tryIdentOrType(ellipsisOk bool) ast.Expr {
+ switch p.tok {
+ case token.IDENT:
+ return p.parseTypeName()
+ case token.LBRACK:
+ return p.parseArrayType(ellipsisOk)
+ case token.STRUCT:
+ return p.parseStructType()
+ case token.MUL:
+ return p.parsePointerType()
+ case token.FUNC:
+ typ, _ := p.parseFuncType()
+ return typ
+ case token.INTERFACE:
+ return p.parseInterfaceType()
+ case token.MAP:
+ return p.parseMapType()
+ case token.CHAN, token.ARROW:
+ return p.parseChanType()
+ case token.LPAREN:
+ lparen := p.pos
+ p.next()
+ typ := p.parseType()
+ rparen := p.expect(token.RPAREN)
+ return &ast.ParenExpr{lparen, typ, rparen}
+ }
+
+ // no type found
+ return nil
+}
+
+func (p *parser) tryType() ast.Expr {
+ typ := p.tryIdentOrType(false)
+ if typ != nil {
+ p.resolve(typ)
+ }
+ return typ
+}
+
+// ----------------------------------------------------------------------------
+// Blocks
+
+func (p *parser) parseStmtList() (list []ast.Stmt) {
+ if p.trace {
+ defer un(trace(p, "StatementList"))
+ }
+
+ for p.tok != token.CASE && p.tok != token.DEFAULT && p.tok != token.RBRACE && p.tok != token.EOF {
+ list = append(list, p.parseStmt())
+ }
+
+ return
+}
+
+func (p *parser) parseBody(scope *ast.Scope) *ast.BlockStmt {
+ if p.trace {
+ defer un(trace(p, "Body"))
+ }
+
+ lbrace := p.expect(token.LBRACE)
+ p.topScope = scope // open function scope
+ p.openLabelScope()
+ list := p.parseStmtList()
+ p.closeLabelScope()
+ p.closeScope()
+ rbrace := p.expect(token.RBRACE)
+
+ return &ast.BlockStmt{lbrace, list, rbrace}
+}
+
+func (p *parser) parseBlockStmt() *ast.BlockStmt {
+ if p.trace {
+ defer un(trace(p, "BlockStmt"))
+ }
+
+ lbrace := p.expect(token.LBRACE)
+ p.openScope()
+ list := p.parseStmtList()
+ p.closeScope()
+ rbrace := p.expect(token.RBRACE)
+
+ return &ast.BlockStmt{lbrace, list, rbrace}
+}
+
+// ----------------------------------------------------------------------------
+// Expressions
+
+func (p *parser) parseFuncTypeOrLit() ast.Expr {
+ if p.trace {
+ defer un(trace(p, "FuncTypeOrLit"))
+ }
+
+ typ, scope := p.parseFuncType()
+ if p.tok != token.LBRACE {
+ // function type only
+ return typ
+ }
+
+ p.exprLev++
+ body := p.parseBody(scope)
+ p.exprLev--
+
+ return &ast.FuncLit{typ, body}
+}
+
+// parseOperand may return an expression or a raw type (incl. array
+// types of the form [...]T. Callers must verify the result.
+// If lhs is set and the result is an identifier, it is not resolved.
+//
+func (p *parser) parseOperand(lhs bool) ast.Expr {
+ if p.trace {
+ defer un(trace(p, "Operand"))
+ }
+
+ switch p.tok {
+ case token.IDENT:
+ x := p.parseIdent()
+ if !lhs {
+ p.resolve(x)
+ }
+ return x
+
+ case token.INT, token.FLOAT, token.IMAG, token.CHAR, token.STRING:
+ x := &ast.BasicLit{p.pos, p.tok, p.lit}
+ p.next()
+ return x
+
+ case token.LPAREN:
+ lparen := p.pos
+ p.next()
+ p.exprLev++
+ x := p.parseRhs()
+ p.exprLev--
+ rparen := p.expect(token.RPAREN)
+ return &ast.ParenExpr{lparen, x, rparen}
+
+ case token.FUNC:
+ return p.parseFuncTypeOrLit()
+
+ default:
+ if typ := p.tryIdentOrType(true); typ != nil {
+ // could be type for composite literal or conversion
+ _, isIdent := typ.(*ast.Ident)
+ assert(!isIdent, "type cannot be identifier")
+ return typ
+ }
+ }
+
+ pos := p.pos
+ p.errorExpected(pos, "operand")
+ p.next() // make progress
+ return &ast.BadExpr{pos, p.pos}
+}
+
+func (p *parser) parseSelector(x ast.Expr) ast.Expr {
+ if p.trace {
+ defer un(trace(p, "Selector"))
+ }
+
+ sel := p.parseIdent()
+
+ return &ast.SelectorExpr{x, sel}
+}
+
+func (p *parser) parseTypeAssertion(x ast.Expr) ast.Expr {
+ if p.trace {
+ defer un(trace(p, "TypeAssertion"))
+ }
+
+ p.expect(token.LPAREN)
+ var typ ast.Expr
+ if p.tok == token.TYPE {
+ // type switch: typ == nil
+ p.next()
+ } else {
+ typ = p.parseType()
+ }
+ p.expect(token.RPAREN)
+
+ return &ast.TypeAssertExpr{x, typ}
+}
+
+func (p *parser) parseIndexOrSlice(x ast.Expr) ast.Expr {
+ if p.trace {
+ defer un(trace(p, "IndexOrSlice"))
+ }
+
+ lbrack := p.expect(token.LBRACK)
+ p.exprLev++
+ var low, high ast.Expr
+ isSlice := false
+ if p.tok != token.COLON {
+ low = p.parseRhs()
+ }
+ if p.tok == token.COLON {
+ isSlice = true
+ p.next()
+ if p.tok != token.RBRACK {
+ high = p.parseRhs()
+ }
+ }
+ p.exprLev--
+ rbrack := p.expect(token.RBRACK)
+
+ if isSlice {
+ return &ast.SliceExpr{x, lbrack, low, high, rbrack}
+ }
+ return &ast.IndexExpr{x, lbrack, low, rbrack}
+}
+
+func (p *parser) parseCallOrConversion(fun ast.Expr) *ast.CallExpr {
+ if p.trace {
+ defer un(trace(p, "CallOrConversion"))
+ }
+
+ lparen := p.expect(token.LPAREN)
+ p.exprLev++
+ var list []ast.Expr
+ var ellipsis token.Pos
+ for p.tok != token.RPAREN && p.tok != token.EOF && !ellipsis.IsValid() {
+ list = append(list, p.parseRhs())
+ if p.tok == token.ELLIPSIS {
+ ellipsis = p.pos
+ p.next()
+ }
+ if p.tok != token.COMMA {
+ break
+ }
+ p.next()
+ }
+ p.exprLev--
+ rparen := p.expect(token.RPAREN)
+
+ return &ast.CallExpr{fun, lparen, list, ellipsis, rparen}
+}
+
+func (p *parser) parseElement(keyOk bool) ast.Expr {
+ if p.trace {
+ defer un(trace(p, "Element"))
+ }
+
+ if p.tok == token.LBRACE {
+ return p.parseLiteralValue(nil)
+ }
+
+ x := p.parseExpr(keyOk) // don't resolve if map key
+ if keyOk {
+ if p.tok == token.COLON {
+ colon := p.pos
+ p.next()
+ return &ast.KeyValueExpr{x, colon, p.parseElement(false)}
+ }
+ p.resolve(x) // not a map key
+ }
+
+ return x
+}
+
+func (p *parser) parseElementList() (list []ast.Expr) {
+ if p.trace {
+ defer un(trace(p, "ElementList"))
+ }
+
+ for p.tok != token.RBRACE && p.tok != token.EOF {
+ list = append(list, p.parseElement(true))
+ if p.tok != token.COMMA {
+ break
+ }
+ p.next()
+ }
+
+ return
+}
+
+func (p *parser) parseLiteralValue(typ ast.Expr) ast.Expr {
+ if p.trace {
+ defer un(trace(p, "LiteralValue"))
+ }
+
+ lbrace := p.expect(token.LBRACE)
+ var elts []ast.Expr
+ p.exprLev++
+ if p.tok != token.RBRACE {
+ elts = p.parseElementList()
+ }
+ p.exprLev--
+ rbrace := p.expect(token.RBRACE)
+ return &ast.CompositeLit{typ, lbrace, elts, rbrace}
+}
+
+// checkExpr checks that x is an expression (and not a type).
+func (p *parser) checkExpr(x ast.Expr) ast.Expr {
+ switch t := unparen(x).(type) {
+ case *ast.BadExpr:
+ case *ast.Ident:
+ case *ast.BasicLit:
+ case *ast.FuncLit:
+ case *ast.CompositeLit:
+ case *ast.ParenExpr:
+ panic("unreachable")
+ case *ast.SelectorExpr:
+ case *ast.IndexExpr:
+ case *ast.SliceExpr:
+ case *ast.TypeAssertExpr:
+ if t.Type == nil {
+ // the form X.(type) is only allowed in type switch expressions
+ p.errorExpected(x.Pos(), "expression")
+ x = &ast.BadExpr{x.Pos(), x.End()}
+ }
+ case *ast.CallExpr:
+ case *ast.StarExpr:
+ case *ast.UnaryExpr:
+ if t.Op == token.RANGE {
+ // the range operator is only allowed at the top of a for statement
+ p.errorExpected(x.Pos(), "expression")
+ x = &ast.BadExpr{x.Pos(), x.End()}
+ }
+ case *ast.BinaryExpr:
+ default:
+ // all other nodes are not proper expressions
+ p.errorExpected(x.Pos(), "expression")
+ x = &ast.BadExpr{x.Pos(), x.End()}
+ }
+ return x
+}
+
+// isTypeName returns true iff x is a (qualified) TypeName.
+func isTypeName(x ast.Expr) bool {
+ switch t := x.(type) {
+ case *ast.BadExpr:
+ case *ast.Ident:
+ case *ast.SelectorExpr:
+ _, isIdent := t.X.(*ast.Ident)
+ return isIdent
+ default:
+ return false // all other nodes are not type names
+ }
+ return true
+}
+
+// isLiteralType returns true iff x is a legal composite literal type.
+func isLiteralType(x ast.Expr) bool {
+ switch t := x.(type) {
+ case *ast.BadExpr:
+ case *ast.Ident:
+ case *ast.SelectorExpr:
+ _, isIdent := t.X.(*ast.Ident)
+ return isIdent
+ case *ast.ArrayType:
+ case *ast.StructType:
+ case *ast.MapType:
+ default:
+ return false // all other nodes are not legal composite literal types
+ }
+ return true
+}
+
+// If x is of the form *T, deref returns T, otherwise it returns x.
+func deref(x ast.Expr) ast.Expr {
+ if p, isPtr := x.(*ast.StarExpr); isPtr {
+ x = p.X
+ }
+ return x
+}
+
+// If x is of the form (T), unparen returns unparen(T), otherwise it returns x.
+func unparen(x ast.Expr) ast.Expr {
+ if p, isParen := x.(*ast.ParenExpr); isParen {
+ x = unparen(p.X)
+ }
+ return x
+}
+
+// checkExprOrType checks that x is an expression or a type
+// (and not a raw type such as [...]T).
+//
+func (p *parser) checkExprOrType(x ast.Expr) ast.Expr {
+ switch t := unparen(x).(type) {
+ case *ast.ParenExpr:
+ panic("unreachable")
+ case *ast.UnaryExpr:
+ if t.Op == token.RANGE {
+ // the range operator is only allowed at the top of a for statement
+ p.errorExpected(x.Pos(), "expression")
+ x = &ast.BadExpr{x.Pos(), x.End()}
+ }
+ case *ast.ArrayType:
+ if len, isEllipsis := t.Len.(*ast.Ellipsis); isEllipsis {
+ p.error(len.Pos(), "expected array length, found '...'")
+ x = &ast.BadExpr{x.Pos(), x.End()}
+ }
+ }
+
+ // all other nodes are expressions or types
+ return x
+}
+
+// If lhs is set and the result is an identifier, it is not resolved.
+func (p *parser) parsePrimaryExpr(lhs bool) ast.Expr {
+ if p.trace {
+ defer un(trace(p, "PrimaryExpr"))
+ }
+
+ x := p.parseOperand(lhs)
+L:
+ for {
+ switch p.tok {
+ case token.PERIOD:
+ p.next()
+ if lhs {
+ p.resolve(x)
+ }
+ switch p.tok {
+ case token.IDENT:
+ x = p.parseSelector(p.checkExpr(x))
+ case token.LPAREN:
+ x = p.parseTypeAssertion(p.checkExpr(x))
+ default:
+ pos := p.pos
+ p.next() // make progress
+ p.errorExpected(pos, "selector or type assertion")
+ x = &ast.BadExpr{pos, p.pos}
+ }
+ case token.LBRACK:
+ if lhs {
+ p.resolve(x)
+ }
+ x = p.parseIndexOrSlice(p.checkExpr(x))
+ case token.LPAREN:
+ if lhs {
+ p.resolve(x)
+ }
+ x = p.parseCallOrConversion(p.checkExprOrType(x))
+ case token.LBRACE:
+ if isLiteralType(x) && (p.exprLev >= 0 || !isTypeName(x)) {
+ if lhs {
+ p.resolve(x)
+ }
+ x = p.parseLiteralValue(x)
+ } else {
+ break L
+ }
+ default:
+ break L
+ }
+ lhs = false // no need to try to resolve again
+ }
+
+ return x
+}
+
+// If lhs is set and the result is an identifier, it is not resolved.
+func (p *parser) parseUnaryExpr(lhs bool) ast.Expr {
+ if p.trace {
+ defer un(trace(p, "UnaryExpr"))
+ }
+
+ switch p.tok {
+ case token.ADD, token.SUB, token.NOT, token.XOR, token.AND, token.RANGE:
+ pos, op := p.pos, p.tok
+ p.next()
+ x := p.parseUnaryExpr(false)
+ return &ast.UnaryExpr{pos, op, p.checkExpr(x)}
+
+ case token.ARROW:
+ // channel type or receive expression
+ pos := p.pos
+ p.next()
+ if p.tok == token.CHAN {
+ p.next()
+ value := p.parseType()
+ return &ast.ChanType{pos, ast.RECV, value}
+ }
+
+ x := p.parseUnaryExpr(false)
+ return &ast.UnaryExpr{pos, token.ARROW, p.checkExpr(x)}
+
+ case token.MUL:
+ // pointer type or unary "*" expression
+ pos := p.pos
+ p.next()
+ x := p.parseUnaryExpr(false)
+ return &ast.StarExpr{pos, p.checkExprOrType(x)}
+ }
+
+ return p.parsePrimaryExpr(lhs)
+}
+
+// If lhs is set and the result is an identifier, it is not resolved.
+func (p *parser) parseBinaryExpr(lhs bool, prec1 int) ast.Expr {
+ if p.trace {
+ defer un(trace(p, "BinaryExpr"))
+ }
+
+ x := p.parseUnaryExpr(lhs)
+ for prec := p.tok.Precedence(); prec >= prec1; prec-- {
+ for p.tok.Precedence() == prec {
+ pos, op := p.pos, p.tok
+ p.next()
+ if lhs {
+ p.resolve(x)
+ lhs = false
+ }
+ y := p.parseBinaryExpr(false, prec+1)
+ x = &ast.BinaryExpr{p.checkExpr(x), pos, op, p.checkExpr(y)}
+ }
+ }
+
+ return x
+}
+
+// If lhs is set and the result is an identifier, it is not resolved.
+// TODO(gri): parseExpr may return a type or even a raw type ([..]int) -
+// should reject when a type/raw type is obviously not allowed
+func (p *parser) parseExpr(lhs bool) ast.Expr {
+ if p.trace {
+ defer un(trace(p, "Expression"))
+ }
+
+ return p.parseBinaryExpr(lhs, token.LowestPrec+1)
+}
+
+func (p *parser) parseRhs() ast.Expr {
+ return p.parseExpr(false)
+}
+
+// ----------------------------------------------------------------------------
+// Statements
+
+func (p *parser) parseSimpleStmt(labelOk bool) ast.Stmt {
+ if p.trace {
+ defer un(trace(p, "SimpleStmt"))
+ }
+
+ x := p.parseLhsList()
+
+ switch p.tok {
+ case
+ token.DEFINE, token.ASSIGN, token.ADD_ASSIGN,
+ token.SUB_ASSIGN, token.MUL_ASSIGN, token.QUO_ASSIGN,
+ token.REM_ASSIGN, token.AND_ASSIGN, token.OR_ASSIGN,
+ token.XOR_ASSIGN, token.SHL_ASSIGN, token.SHR_ASSIGN, token.AND_NOT_ASSIGN:
+ // assignment statement
+ pos, tok := p.pos, p.tok
+ p.next()
+ y := p.parseRhsList()
+ return &ast.AssignStmt{x, pos, tok, y}
+ }
+
+ if len(x) > 1 {
+ p.errorExpected(x[0].Pos(), "1 expression")
+ // continue with first expression
+ }
+
+ switch p.tok {
+ case token.COLON:
+ // labeled statement
+ colon := p.pos
+ p.next()
+ if label, isIdent := x[0].(*ast.Ident); labelOk && isIdent {
+ // Go spec: The scope of a label is the body of the function
+ // in which it is declared and excludes the body of any nested
+ // function.
+ stmt := &ast.LabeledStmt{label, colon, p.parseStmt()}
+ p.declare(stmt, p.labelScope, ast.Lbl, label)
+ return stmt
+ }
+ p.error(x[0].Pos(), "illegal label declaration")
+ return &ast.BadStmt{x[0].Pos(), colon + 1}
+
+ case token.ARROW:
+ // send statement
+ arrow := p.pos
+ p.next() // consume "<-"
+ y := p.parseRhs()
+ return &ast.SendStmt{x[0], arrow, y}
+
+ case token.INC, token.DEC:
+ // increment or decrement
+ s := &ast.IncDecStmt{x[0], p.pos, p.tok}
+ p.next() // consume "++" or "--"
+ return s
+ }
+
+ // expression
+ return &ast.ExprStmt{x[0]}
+}
+
+func (p *parser) parseCallExpr() *ast.CallExpr {
+ x := p.parseRhs()
+ if call, isCall := x.(*ast.CallExpr); isCall {
+ return call
+ }
+ p.errorExpected(x.Pos(), "function/method call")
+ return nil
+}
+
+func (p *parser) parseGoStmt() ast.Stmt {
+ if p.trace {
+ defer un(trace(p, "GoStmt"))
+ }
+
+ pos := p.expect(token.GO)
+ call := p.parseCallExpr()
+ p.expectSemi()
+ if call == nil {
+ return &ast.BadStmt{pos, pos + 2} // len("go")
+ }
+
+ return &ast.GoStmt{pos, call}
+}
+
+func (p *parser) parseDeferStmt() ast.Stmt {
+ if p.trace {
+ defer un(trace(p, "DeferStmt"))
+ }
+
+ pos := p.expect(token.DEFER)
+ call := p.parseCallExpr()
+ p.expectSemi()
+ if call == nil {
+ return &ast.BadStmt{pos, pos + 5} // len("defer")
+ }
+
+ return &ast.DeferStmt{pos, call}
+}
+
+func (p *parser) parseReturnStmt() *ast.ReturnStmt {
+ if p.trace {
+ defer un(trace(p, "ReturnStmt"))
+ }
+
+ pos := p.pos
+ p.expect(token.RETURN)
+ var x []ast.Expr
+ if p.tok != token.SEMICOLON && p.tok != token.RBRACE {
+ x = p.parseRhsList()
+ }
+ p.expectSemi()
+
+ return &ast.ReturnStmt{pos, x}
+}
+
+func (p *parser) parseBranchStmt(tok token.Token) *ast.BranchStmt {
+ if p.trace {
+ defer un(trace(p, "BranchStmt"))
+ }
+
+ pos := p.expect(tok)
+ var label *ast.Ident
+ if tok != token.FALLTHROUGH && p.tok == token.IDENT {
+ label = p.parseIdent()
+ // add to list of unresolved targets
+ n := len(p.targetStack) - 1
+ p.targetStack[n] = append(p.targetStack[n], label)
+ }
+ p.expectSemi()
+
+ return &ast.BranchStmt{pos, tok, label}
+}
+
+func (p *parser) makeExpr(s ast.Stmt) ast.Expr {
+ if s == nil {
+ return nil
+ }
+ if es, isExpr := s.(*ast.ExprStmt); isExpr {
+ return p.checkExpr(es.X)
+ }
+ p.error(s.Pos(), "expected condition, found simple statement")
+ return &ast.BadExpr{s.Pos(), s.End()}
+}
+
+func (p *parser) parseIfStmt() *ast.IfStmt {
+ if p.trace {
+ defer un(trace(p, "IfStmt"))
+ }
+
+ pos := p.expect(token.IF)
+ p.openScope()
+ defer p.closeScope()
+
+ var s ast.Stmt
+ var x ast.Expr
+ {
+ prevLev := p.exprLev
+ p.exprLev = -1
+ if p.tok == token.SEMICOLON {
+ p.next()
+ x = p.parseRhs()
+ } else {
+ s = p.parseSimpleStmt(false)
+ if p.tok == token.SEMICOLON {
+ p.next()
+ x = p.parseRhs()
+ } else {
+ x = p.makeExpr(s)
+ s = nil
+ }
+ }
+ p.exprLev = prevLev
+ }
+
+ body := p.parseBlockStmt()
+ var else_ ast.Stmt
+ if p.tok == token.ELSE {
+ p.next()
+ else_ = p.parseStmt()
+ } else {
+ p.expectSemi()
+ }
+
+ return &ast.IfStmt{pos, s, x, body, else_}
+}
+
+func (p *parser) parseTypeList() (list []ast.Expr) {
+ if p.trace {
+ defer un(trace(p, "TypeList"))
+ }
+
+ list = append(list, p.parseType())
+ for p.tok == token.COMMA {
+ p.next()
+ list = append(list, p.parseType())
+ }
+
+ return
+}
+
+func (p *parser) parseCaseClause(exprSwitch bool) *ast.CaseClause {
+ if p.trace {
+ defer un(trace(p, "CaseClause"))
+ }
+
+ pos := p.pos
+ var list []ast.Expr
+ if p.tok == token.CASE {
+ p.next()
+ if exprSwitch {
+ list = p.parseRhsList()
+ } else {
+ list = p.parseTypeList()
+ }
+ } else {
+ p.expect(token.DEFAULT)
+ }
+
+ colon := p.expect(token.COLON)
+ p.openScope()
+ body := p.parseStmtList()
+ p.closeScope()
+
+ return &ast.CaseClause{pos, list, colon, body}
+}
+
+func isExprSwitch(s ast.Stmt) bool {
+ if s == nil {
+ return true
+ }
+ if e, ok := s.(*ast.ExprStmt); ok {
+ if a, ok := e.X.(*ast.TypeAssertExpr); ok {
+ return a.Type != nil // regular type assertion
+ }
+ return true
+ }
+ return false
+}
+
+func (p *parser) parseSwitchStmt() ast.Stmt {
+ if p.trace {
+ defer un(trace(p, "SwitchStmt"))
+ }
+
+ pos := p.expect(token.SWITCH)
+ p.openScope()
+ defer p.closeScope()
+
+ var s1, s2 ast.Stmt
+ if p.tok != token.LBRACE {
+ prevLev := p.exprLev
+ p.exprLev = -1
+ if p.tok != token.SEMICOLON {
+ s2 = p.parseSimpleStmt(false)
+ }
+ if p.tok == token.SEMICOLON {
+ p.next()
+ s1 = s2
+ s2 = nil
+ if p.tok != token.LBRACE {
+ s2 = p.parseSimpleStmt(false)
+ }
+ }
+ p.exprLev = prevLev
+ }
+
+ exprSwitch := isExprSwitch(s2)
+ lbrace := p.expect(token.LBRACE)
+ var list []ast.Stmt
+ for p.tok == token.CASE || p.tok == token.DEFAULT {
+ list = append(list, p.parseCaseClause(exprSwitch))
+ }
+ rbrace := p.expect(token.RBRACE)
+ p.expectSemi()
+ body := &ast.BlockStmt{lbrace, list, rbrace}
+
+ if exprSwitch {
+ return &ast.SwitchStmt{pos, s1, p.makeExpr(s2), body}
+ }
+ // type switch
+ // TODO(gri): do all the checks!
+ return &ast.TypeSwitchStmt{pos, s1, s2, body}
+}
+
+func (p *parser) parseCommClause() *ast.CommClause {
+ if p.trace {
+ defer un(trace(p, "CommClause"))
+ }
+
+ p.openScope()
+ pos := p.pos
+ var comm ast.Stmt
+ if p.tok == token.CASE {
+ p.next()
+ lhs := p.parseLhsList()
+ if p.tok == token.ARROW {
+ // SendStmt
+ if len(lhs) > 1 {
+ p.errorExpected(lhs[0].Pos(), "1 expression")
+ // continue with first expression
+ }
+ arrow := p.pos
+ p.next()
+ rhs := p.parseRhs()
+ comm = &ast.SendStmt{lhs[0], arrow, rhs}
+ } else {
+ // RecvStmt
+ pos := p.pos
+ tok := p.tok
+ var rhs ast.Expr
+ if tok == token.ASSIGN || tok == token.DEFINE {
+ // RecvStmt with assignment
+ if len(lhs) > 2 {
+ p.errorExpected(lhs[0].Pos(), "1 or 2 expressions")
+ // continue with first two expressions
+ lhs = lhs[0:2]
+ }
+ p.next()
+ rhs = p.parseRhs()
+ } else {
+ // rhs must be single receive operation
+ if len(lhs) > 1 {
+ p.errorExpected(lhs[0].Pos(), "1 expression")
+ // continue with first expression
+ }
+ rhs = lhs[0]
+ lhs = nil // there is no lhs
+ }
+ if x, isUnary := rhs.(*ast.UnaryExpr); !isUnary || x.Op != token.ARROW {
+ p.errorExpected(rhs.Pos(), "send or receive operation")
+ rhs = &ast.BadExpr{rhs.Pos(), rhs.End()}
+ }
+ if lhs != nil {
+ comm = &ast.AssignStmt{lhs, pos, tok, []ast.Expr{rhs}}
+ } else {
+ comm = &ast.ExprStmt{rhs}
+ }
+ }
+ } else {
+ p.expect(token.DEFAULT)
+ }
+
+ colon := p.expect(token.COLON)
+ body := p.parseStmtList()
+ p.closeScope()
+
+ return &ast.CommClause{pos, comm, colon, body}
+}
+
+func (p *parser) parseSelectStmt() *ast.SelectStmt {
+ if p.trace {
+ defer un(trace(p, "SelectStmt"))
+ }
+
+ pos := p.expect(token.SELECT)
+ lbrace := p.expect(token.LBRACE)
+ var list []ast.Stmt
+ for p.tok == token.CASE || p.tok == token.DEFAULT {
+ list = append(list, p.parseCommClause())
+ }
+ rbrace := p.expect(token.RBRACE)
+ p.expectSemi()
+ body := &ast.BlockStmt{lbrace, list, rbrace}
+
+ return &ast.SelectStmt{pos, body}
+}
+
+func (p *parser) parseForStmt() ast.Stmt {
+ if p.trace {
+ defer un(trace(p, "ForStmt"))
+ }
+
+ pos := p.expect(token.FOR)
+ p.openScope()
+ defer p.closeScope()
+
+ var s1, s2, s3 ast.Stmt
+ if p.tok != token.LBRACE {
+ prevLev := p.exprLev
+ p.exprLev = -1
+ if p.tok != token.SEMICOLON {
+ s2 = p.parseSimpleStmt(false)
+ }
+ if p.tok == token.SEMICOLON {
+ p.next()
+ s1 = s2
+ s2 = nil
+ if p.tok != token.SEMICOLON {
+ s2 = p.parseSimpleStmt(false)
+ }
+ p.expectSemi()
+ if p.tok != token.LBRACE {
+ s3 = p.parseSimpleStmt(false)
+ }
+ }
+ p.exprLev = prevLev
+ }
+
+ body := p.parseBlockStmt()
+ p.expectSemi()
+
+ if as, isAssign := s2.(*ast.AssignStmt); isAssign {
+ // possibly a for statement with a range clause; check assignment operator
+ if as.Tok != token.ASSIGN && as.Tok != token.DEFINE {
+ p.errorExpected(as.TokPos, "'=' or ':='")
+ return &ast.BadStmt{pos, body.End()}
+ }
+ // check lhs
+ var key, value ast.Expr
+ switch len(as.Lhs) {
+ case 2:
+ key, value = as.Lhs[0], as.Lhs[1]
+ case 1:
+ key = as.Lhs[0]
+ default:
+ p.errorExpected(as.Lhs[0].Pos(), "1 or 2 expressions")
+ return &ast.BadStmt{pos, body.End()}
+ }
+ // check rhs
+ if len(as.Rhs) != 1 {
+ p.errorExpected(as.Rhs[0].Pos(), "1 expression")
+ return &ast.BadStmt{pos, body.End()}
+ }
+ if rhs, isUnary := as.Rhs[0].(*ast.UnaryExpr); isUnary && rhs.Op == token.RANGE {
+ // rhs is range expression
+ // (any short variable declaration was handled by parseSimpleStat above)
+ return &ast.RangeStmt{pos, key, value, as.TokPos, as.Tok, rhs.X, body}
+ }
+ p.errorExpected(s2.Pos(), "range clause")
+ return &ast.BadStmt{pos, body.End()}
+ }
+
+ // regular for statement
+ return &ast.ForStmt{pos, s1, p.makeExpr(s2), s3, body}
+}
+
+func (p *parser) parseStmt() (s ast.Stmt) {
+ if p.trace {
+ defer un(trace(p, "Statement"))
+ }
+
+ switch p.tok {
+ case token.CONST, token.TYPE, token.VAR:
+ s = &ast.DeclStmt{p.parseDecl()}
+ case
+ // tokens that may start a top-level expression
+ token.IDENT, token.INT, token.FLOAT, token.CHAR, token.STRING, token.FUNC, token.LPAREN, // operand
+ token.LBRACK, token.STRUCT, // composite type
+ token.MUL, token.AND, token.ARROW, token.ADD, token.SUB, token.XOR: // unary operators
+ s = p.parseSimpleStmt(true)
+ // because of the required look-ahead, labeled statements are
+ // parsed by parseSimpleStmt - don't expect a semicolon after
+ // them
+ if _, isLabeledStmt := s.(*ast.LabeledStmt); !isLabeledStmt {
+ p.expectSemi()
+ }
+ case token.GO:
+ s = p.parseGoStmt()
+ case token.DEFER:
+ s = p.parseDeferStmt()
+ case token.RETURN:
+ s = p.parseReturnStmt()
+ case token.BREAK, token.CONTINUE, token.GOTO, token.FALLTHROUGH:
+ s = p.parseBranchStmt(p.tok)
+ case token.LBRACE:
+ s = p.parseBlockStmt()
+ p.expectSemi()
+ case token.IF:
+ s = p.parseIfStmt()
+ case token.SWITCH:
+ s = p.parseSwitchStmt()
+ case token.SELECT:
+ s = p.parseSelectStmt()
+ case token.FOR:
+ s = p.parseForStmt()
+ case token.SEMICOLON:
+ s = &ast.EmptyStmt{p.pos}
+ p.next()
+ case token.RBRACE:
+ // a semicolon may be omitted before a closing "}"
+ s = &ast.EmptyStmt{p.pos}
+ default:
+ // no statement found
+ pos := p.pos
+ p.errorExpected(pos, "statement")
+ p.next() // make progress
+ s = &ast.BadStmt{pos, p.pos}
+ }
+
+ return
+}
+
+// ----------------------------------------------------------------------------
+// Declarations
+
+type parseSpecFunction func(p *parser, doc *ast.CommentGroup, iota int) ast.Spec
+
+func parseImportSpec(p *parser, doc *ast.CommentGroup, _ int) ast.Spec {
+ if p.trace {
+ defer un(trace(p, "ImportSpec"))
+ }
+
+ var ident *ast.Ident
+ switch p.tok {
+ case token.PERIOD:
+ ident = &ast.Ident{p.pos, ".", nil}
+ p.next()
+ case token.IDENT:
+ ident = p.parseIdent()
+ }
+
+ var path *ast.BasicLit
+ if p.tok == token.STRING {
+ path = &ast.BasicLit{p.pos, p.tok, p.lit}
+ p.next()
+ } else {
+ p.expect(token.STRING) // use expect() error handling
+ }
+ p.expectSemi() // call before accessing p.linecomment
+
+ // collect imports
+ spec := &ast.ImportSpec{doc, ident, path, p.lineComment}
+ p.imports = append(p.imports, spec)
+
+ return spec
+}
+
+func parseConstSpec(p *parser, doc *ast.CommentGroup, iota int) ast.Spec {
+ if p.trace {
+ defer un(trace(p, "ConstSpec"))
+ }
+
+ idents := p.parseIdentList()
+ typ := p.tryType()
+ var values []ast.Expr
+ if typ != nil || p.tok == token.ASSIGN || iota == 0 {
+ p.expect(token.ASSIGN)
+ values = p.parseRhsList()
+ }
+ p.expectSemi() // call before accessing p.linecomment
+
+ // Go spec: The scope of a constant or variable identifier declared inside
+ // a function begins at the end of the ConstSpec or VarSpec and ends at
+ // the end of the innermost containing block.
+ // (Global identifiers are resolved in a separate phase after parsing.)
+ spec := &ast.ValueSpec{doc, idents, typ, values, p.lineComment}
+ p.declare(spec, p.topScope, ast.Con, idents...)
+
+ return spec
+}
+
+func parseTypeSpec(p *parser, doc *ast.CommentGroup, _ int) ast.Spec {
+ if p.trace {
+ defer un(trace(p, "TypeSpec"))
+ }
+
+ ident := p.parseIdent()
+
+ // Go spec: The scope of a type identifier declared inside a function begins
+ // at the identifier in the TypeSpec and ends at the end of the innermost
+ // containing block.
+ // (Global identifiers are resolved in a separate phase after parsing.)
+ spec := &ast.TypeSpec{doc, ident, nil, nil}
+ p.declare(spec, p.topScope, ast.Typ, ident)
+
+ spec.Type = p.parseType()
+ p.expectSemi() // call before accessing p.linecomment
+ spec.Comment = p.lineComment
+
+ return spec
+}
+
+func parseVarSpec(p *parser, doc *ast.CommentGroup, _ int) ast.Spec {
+ if p.trace {
+ defer un(trace(p, "VarSpec"))
+ }
+
+ idents := p.parseIdentList()
+ typ := p.tryType()
+ var values []ast.Expr
+ if typ == nil || p.tok == token.ASSIGN {
+ p.expect(token.ASSIGN)
+ values = p.parseRhsList()
+ }
+ p.expectSemi() // call before accessing p.linecomment
+
+ // Go spec: The scope of a constant or variable identifier declared inside
+ // a function begins at the end of the ConstSpec or VarSpec and ends at
+ // the end of the innermost containing block.
+ // (Global identifiers are resolved in a separate phase after parsing.)
+ spec := &ast.ValueSpec{doc, idents, typ, values, p.lineComment}
+ p.declare(spec, p.topScope, ast.Var, idents...)
+
+ return spec
+}
+
+func (p *parser) parseGenDecl(keyword token.Token, f parseSpecFunction) *ast.GenDecl {
+ if p.trace {
+ defer un(trace(p, "GenDecl("+keyword.String()+")"))
+ }
+
+ doc := p.leadComment
+ pos := p.expect(keyword)
+ var lparen, rparen token.Pos
+ var list []ast.Spec
+ if p.tok == token.LPAREN {
+ lparen = p.pos
+ p.next()
+ for iota := 0; p.tok != token.RPAREN && p.tok != token.EOF; iota++ {
+ list = append(list, f(p, p.leadComment, iota))
+ }
+ rparen = p.expect(token.RPAREN)
+ p.expectSemi()
+ } else {
+ list = append(list, f(p, nil, 0))
+ }
+
+ return &ast.GenDecl{doc, pos, keyword, lparen, list, rparen}
+}
+
+func (p *parser) parseReceiver(scope *ast.Scope) *ast.FieldList {
+ if p.trace {
+ defer un(trace(p, "Receiver"))
+ }
+
+ pos := p.pos
+ par := p.parseParameters(scope, false)
+
+ // must have exactly one receiver
+ if par.NumFields() != 1 {
+ p.errorExpected(pos, "exactly one receiver")
+ // TODO determine a better range for BadExpr below
+ par.List = []*ast.Field{&ast.Field{Type: &ast.BadExpr{pos, pos}}}
+ return par
+ }
+
+ // recv type must be of the form ["*"] identifier
+ recv := par.List[0]
+ base := deref(recv.Type)
+ if _, isIdent := base.(*ast.Ident); !isIdent {
+ p.errorExpected(base.Pos(), "(unqualified) identifier")
+ par.List = []*ast.Field{&ast.Field{Type: &ast.BadExpr{recv.Pos(), recv.End()}}}
+ }
+
+ return par
+}
+
+func (p *parser) parseFuncDecl() *ast.FuncDecl {
+ if p.trace {
+ defer un(trace(p, "FunctionDecl"))
+ }
+
+ doc := p.leadComment
+ pos := p.expect(token.FUNC)
+ scope := ast.NewScope(p.topScope) // function scope
+
+ var recv *ast.FieldList
+ if p.tok == token.LPAREN {
+ recv = p.parseReceiver(scope)
+ }
+
+ ident := p.parseIdent()
+
+ params, results := p.parseSignature(scope)
+
+ var body *ast.BlockStmt
+ if p.tok == token.LBRACE {
+ body = p.parseBody(scope)
+ }
+ p.expectSemi()
+
+ decl := &ast.FuncDecl{doc, recv, ident, &ast.FuncType{pos, params, results}, body}
+ if recv == nil {
+ // Go spec: The scope of an identifier denoting a constant, type,
+ // variable, or function (but not method) declared at top level
+ // (outside any function) is the package block.
+ //
+ // init() functions cannot be referred to and there may
+ // be more than one - don't put them in the pkgScope
+ if ident.Name != "init" {
+ p.declare(decl, p.pkgScope, ast.Fun, ident)
+ }
+ }
+
+ return decl
+}
+
+func (p *parser) parseDecl() ast.Decl {
+ if p.trace {
+ defer un(trace(p, "Declaration"))
+ }
+
+ var f parseSpecFunction
+ switch p.tok {
+ case token.CONST:
+ f = parseConstSpec
+
+ case token.TYPE:
+ f = parseTypeSpec
+
+ case token.VAR:
+ f = parseVarSpec
+
+ case token.FUNC:
+ return p.parseFuncDecl()
+
+ default:
+ pos := p.pos
+ p.errorExpected(pos, "declaration")
+ p.next() // make progress
+ decl := &ast.BadDecl{pos, p.pos}
+ return decl
+ }
+
+ return p.parseGenDecl(p.tok, f)
+}
+
+func (p *parser) parseDeclList() (list []ast.Decl) {
+ if p.trace {
+ defer un(trace(p, "DeclList"))
+ }
+
+ for p.tok != token.EOF {
+ list = append(list, p.parseDecl())
+ }
+
+ return
+}
+
+// ----------------------------------------------------------------------------
+// Source files
+
+func (p *parser) parseFile() *ast.File {
+ if p.trace {
+ defer un(trace(p, "File"))
+ }
+
+ // package clause
+ doc := p.leadComment
+ pos := p.expect(token.PACKAGE)
+ // Go spec: The package clause is not a declaration;
+ // the package name does not appear in any scope.
+ ident := p.parseIdent()
+ if ident.Name == "_" {
+ p.error(p.pos, "invalid package name _")
+ }
+ p.expectSemi()
+
+ var decls []ast.Decl
+
+ // Don't bother parsing the rest if we had errors already.
+ // Likely not a Go source file at all.
+
+ if p.ErrorCount() == 0 && p.mode&PackageClauseOnly == 0 {
+ // import decls
+ for p.tok == token.IMPORT {
+ decls = append(decls, p.parseGenDecl(token.IMPORT, parseImportSpec))
+ }
+
+ if p.mode&ImportsOnly == 0 {
+ // rest of package body
+ for p.tok != token.EOF {
+ decls = append(decls, p.parseDecl())
+ }
+ }
+ }
+
+ assert(p.topScope == p.pkgScope, "imbalanced scopes")
+
+ // resolve global identifiers within the same file
+ i := 0
+ for _, ident := range p.unresolved {
+ // i <= index for current ident
+ assert(ident.Obj == unresolved, "object already resolved")
+ ident.Obj = p.pkgScope.Lookup(ident.Name) // also removes unresolved sentinel
+ if ident.Obj == nil {
+ p.unresolved[i] = ident
+ i++
+ }
+ }
+
+ // TODO(gri): store p.imports in AST
+ return &ast.File{doc, pos, ident, decls, p.pkgScope, p.imports, p.unresolved[0:i], p.comments}
+}
diff --git a/src/pkg/go/printer/testdata/slow.golden b/src/pkg/go/printer/testdata/slow.golden
new file mode 100644
index 000000000..43a15cb1d
--- /dev/null
+++ b/src/pkg/go/printer/testdata/slow.golden
@@ -0,0 +1,85 @@
+// Copyright 2011 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package deepequal_test
+
+import (
+ "testing"
+ "google3/spam/archer/frontend/deepequal"
+)
+
+func TestTwoNilValues(t *testing.T) {
+ if err := deepequal.Check(nil, nil); err != nil {
+ t.Errorf("expected nil, saw %v", err)
+ }
+}
+
+type Foo struct {
+ bar *Bar
+ bang *Bar
+}
+
+type Bar struct {
+ baz *Baz
+ foo []*Foo
+}
+
+type Baz struct {
+ entries map[int]interface{}
+ whatever string
+}
+
+func newFoo() *Foo {
+ return &Foo{bar: &Bar{baz: &Baz{
+ entries: map[int]interface{}{
+ 42: &Foo{},
+ 21: &Bar{},
+ 11: &Baz{whatever: "it's just a test"}}}},
+ bang: &Bar{foo: []*Foo{
+ &Foo{bar: &Bar{baz: &Baz{
+ entries: map[int]interface{}{
+ 43: &Foo{},
+ 22: &Bar{},
+ 13: &Baz{whatever: "this is nuts"}}}},
+ bang: &Bar{foo: []*Foo{
+ &Foo{bar: &Bar{baz: &Baz{
+ entries: map[int]interface{}{
+ 61: &Foo{},
+ 71: &Bar{},
+ 11: &Baz{whatever: "no, it's Go"}}}},
+ bang: &Bar{foo: []*Foo{
+ &Foo{bar: &Bar{baz: &Baz{
+ entries: map[int]interface{}{
+ 0: &Foo{},
+ -2: &Bar{},
+ -11: &Baz{whatever: "we need to go deeper"}}}},
+ bang: &Bar{foo: []*Foo{
+ &Foo{bar: &Bar{baz: &Baz{
+ entries: map[int]interface{}{
+ -2: &Foo{},
+ -5: &Bar{},
+ -7: &Baz{whatever: "are you serious?"}}}},
+ bang: &Bar{foo: []*Foo{}}},
+ &Foo{bar: &Bar{baz: &Baz{
+ entries: map[int]interface{}{
+ -100: &Foo{},
+ 50: &Bar{},
+ 20: &Baz{whatever: "na, not really ..."}}}},
+ bang: &Bar{foo: []*Foo{}}}}}}}}},
+ &Foo{bar: &Bar{baz: &Baz{
+ entries: map[int]interface{}{
+ 2: &Foo{},
+ 1: &Bar{},
+ -1: &Baz{whatever: "... it's just a test."}}}},
+ bang: &Bar{foo: []*Foo{}}}}}}}}}
+}
+
+func TestElaborate(t *testing.T) {
+ a := newFoo()
+ b := newFoo()
+
+ if err := deepequal.Check(a, b); err != nil {
+ t.Errorf("expected nil, saw %v", err)
+ }
+}
diff --git a/src/pkg/go/printer/testdata/slow.input b/src/pkg/go/printer/testdata/slow.input
new file mode 100644
index 000000000..0e5a23d88
--- /dev/null
+++ b/src/pkg/go/printer/testdata/slow.input
@@ -0,0 +1,85 @@
+// Copyright 2011 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package deepequal_test
+
+import (
+ "testing"
+ "google3/spam/archer/frontend/deepequal"
+)
+
+func TestTwoNilValues(t *testing.T) {
+ if err := deepequal.Check(nil, nil); err != nil {
+ t.Errorf("expected nil, saw %v", err)
+ }
+}
+
+type Foo struct {
+ bar *Bar
+ bang *Bar
+}
+
+type Bar struct {
+ baz *Baz
+ foo []*Foo
+}
+
+type Baz struct {
+ entries map[int]interface{}
+ whatever string
+}
+
+func newFoo() (*Foo) {
+return &Foo{bar: &Bar{ baz: &Baz{
+entries: map[int]interface{}{
+42: &Foo{},
+21: &Bar{},
+11: &Baz{ whatever: "it's just a test" }}}},
+ bang: &Bar{foo: []*Foo{
+&Foo{bar: &Bar{ baz: &Baz{
+entries: map[int]interface{}{
+43: &Foo{},
+22: &Bar{},
+13: &Baz{ whatever: "this is nuts" }}}},
+ bang: &Bar{foo: []*Foo{
+&Foo{bar: &Bar{ baz: &Baz{
+entries: map[int]interface{}{
+61: &Foo{},
+71: &Bar{},
+11: &Baz{ whatever: "no, it's Go" }}}},
+ bang: &Bar{foo: []*Foo{
+&Foo{bar: &Bar{ baz: &Baz{
+entries: map[int]interface{}{
+0: &Foo{},
+-2: &Bar{},
+-11: &Baz{ whatever: "we need to go deeper" }}}},
+ bang: &Bar{foo: []*Foo{
+&Foo{bar: &Bar{ baz: &Baz{
+entries: map[int]interface{}{
+-2: &Foo{},
+-5: &Bar{},
+-7: &Baz{ whatever: "are you serious?" }}}},
+ bang: &Bar{foo: []*Foo{}}},
+&Foo{bar: &Bar{ baz: &Baz{
+entries: map[int]interface{}{
+-100: &Foo{},
+50: &Bar{},
+20: &Baz{ whatever: "na, not really ..." }}}},
+ bang: &Bar{foo: []*Foo{}}}}}}}}},
+&Foo{bar: &Bar{ baz: &Baz{
+entries: map[int]interface{}{
+2: &Foo{},
+1: &Bar{},
+-1: &Baz{ whatever: "... it's just a test." }}}},
+ bang: &Bar{foo: []*Foo{}}}}}}}}}
+}
+
+func TestElaborate(t *testing.T) {
+ a := newFoo()
+ b := newFoo()
+
+ if err := deepequal.Check(a, b); err != nil {
+ t.Errorf("expected nil, saw %v", err)
+ }
+}
diff --git a/src/pkg/go/printer/testdata/statements.golden b/src/pkg/go/printer/testdata/statements.golden
new file mode 100644
index 000000000..a6d85107f
--- /dev/null
+++ b/src/pkg/go/printer/testdata/statements.golden
@@ -0,0 +1,412 @@
+// Copyright 2009 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package statements
+
+var expr bool
+
+func use(x interface{}) {}
+
+// Formatting of if-statement headers.
+func _() {
+ if true {
+ }
+ if true {
+ } // no semicolon printed
+ if expr {
+ }
+ if expr {
+ } // no semicolon printed
+ if expr {
+ } // no parens printed
+ if expr {
+ } // no semicolon and parens printed
+ if x := expr; true {
+ use(x)
+ }
+ if x := expr; expr {
+ use(x)
+ }
+}
+
+// Formatting of switch-statement headers.
+func _() {
+ switch {
+ }
+ switch {
+ } // no semicolon printed
+ switch expr {
+ }
+ switch expr {
+ } // no semicolon printed
+ switch expr {
+ } // no parens printed
+ switch expr {
+ } // no semicolon and parens printed
+ switch x := expr; {
+ default:
+ use(
+ x)
+ }
+ switch x := expr; expr {
+ default:
+ use(x)
+ }
+}
+
+// Formatting of switch statement bodies.
+func _() {
+ switch {
+ }
+
+ switch x := 0; x {
+ case 1:
+ use(x)
+ use(x) // followed by an empty line
+
+ case 2: // followed by an empty line
+
+ use(x) // followed by an empty line
+
+ case 3: // no empty lines
+ use(x)
+ use(x)
+ }
+
+ switch x {
+ case 0:
+ use(x)
+ case 1: // this comment should have no effect on the previous or next line
+ use(x)
+ }
+
+ switch x := 0; x {
+ case 1:
+ x = 0
+ // this comment should be indented
+ case 2:
+ x = 0
+ // this comment should not be indented, it is aligned with the next case
+ case 3:
+ x = 0
+ /* indented comment
+ aligned
+ aligned
+ */
+ // bla
+ /* and more */
+ case 4:
+ x = 0
+ /* not indented comment
+ aligned
+ aligned
+ */
+ // bla
+ /* and more */
+ case 5:
+ }
+}
+
+// Formatting of selected select statements.
+func _() {
+ select {}
+ select { /* this comment should not be tab-aligned because the closing } is on the same line */
+ }
+ select { /* this comment should be tab-aligned */
+ }
+ select { // this comment should be tab-aligned
+ }
+ select {
+ case <-c:
+ }
+}
+
+// Formatting of for-statement headers.
+func _() {
+ for {
+ }
+ for expr {
+ }
+ for expr {
+ } // no parens printed
+ for {
+ } // no semicolons printed
+ for x := expr; ; {
+ use(x)
+ }
+ for expr {
+ } // no semicolons printed
+ for expr {
+ } // no semicolons and parens printed
+ for ; ; expr = false {
+ }
+ for x := expr; expr; {
+ use(x)
+ }
+ for x := expr; ; expr = false {
+ use(x)
+ }
+ for ; expr; expr = false {
+ }
+ for x := expr; expr; expr = false {
+ use(x)
+ }
+ for x := range []int{} {
+ use(x)
+ }
+ for x := range []int{} {
+ use(x)
+ } // no parens printed
+}
+
+// Don't remove mandatory parentheses around composite literals in control clauses.
+func _() {
+ // strip parentheses - no composite literals or composite literals don't start with a type name
+ if x {
+ }
+ if x {
+ }
+ if []T{} {
+ }
+ if []T{} {
+ }
+ if []T{} {
+ }
+
+ for x {
+ }
+ for x {
+ }
+ for []T{} {
+ }
+ for []T{} {
+ }
+ for []T{} {
+ }
+
+ switch x {
+ }
+ switch x {
+ }
+ switch []T{} {
+ }
+ switch []T{} {
+ }
+
+ for _ = range []T{T{42}} {
+ }
+
+ // leave parentheses - composite literals start with a type name
+ if (T{}) {
+ }
+ if (T{}) {
+ }
+ if (T{}) {
+ }
+
+ for (T{}) {
+ }
+ for (T{}) {
+ }
+ for (T{}) {
+ }
+
+ switch (T{}) {
+ }
+ 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 {
+ }
+}
+
+// Extra empty lines inside functions. Do respect source code line
+// breaks between statement boundaries but print at most one empty
+// line at a time.
+func _() {
+
+ const _ = 0
+
+ const _ = 1
+ type _ int
+ type _ float
+
+ var _ = 0
+ 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)
+
+ if x < x {
+
+ use(x)
+
+ } else {
+
+ use(x)
+
+ }
+}
+
+// Formatting around labels.
+func _() {
+L:
+}
+
+func _() {
+ // this comment should be indented
+L: // no semicolon needed
+}
+
+func _() {
+ switch 0 {
+ case 0:
+ L0:
+ ; // semicolon required
+ case 1:
+ L1:
+ ; // semicolon required
+ default:
+ L2: // no semicolon needed
+ }
+}
+
+func _() {
+ f()
+L1:
+ f()
+L2:
+ ;
+L3:
+}
+
+func _() {
+ // this comment should be indented
+L:
+}
+
+func _() {
+L:
+ _ = 0
+}
+
+func _() {
+ // this comment should be indented
+L:
+ _ = 0
+}
+
+func _() {
+ for {
+ L1:
+ _ = 0
+ L2:
+ _ = 0
+ }
+}
+
+func _() {
+ // this comment should be indented
+ for {
+ L1:
+ _ = 0
+ L2:
+ _ = 0
+ }
+}
+
+func _() {
+ if true {
+ _ = 0
+ }
+ _ = 0 // the indentation here should not be affected by the long label name
+AnOverlongLabel:
+ _ = 0
+
+ if true {
+ _ = 0
+ }
+ _ = 0
+
+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
new file mode 100644
index 000000000..86a753c5a
--- /dev/null
+++ b/src/pkg/go/printer/testdata/statements.input
@@ -0,0 +1,351 @@
+// Copyright 2009 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package statements
+
+var expr bool
+
+func use(x interface{}) {}
+
+// Formatting of if-statement headers.
+func _() {
+ if true {}
+ if; true {} // no semicolon printed
+ if expr{}
+ if;expr{} // no semicolon printed
+ if (expr){} // no parens printed
+ if;((expr)){} // no semicolon and parens printed
+ if x:=expr;true{
+ use(x)}
+ if x:=expr; expr {use(x)}
+}
+
+
+// Formatting of switch-statement headers.
+func _() {
+ switch {}
+ switch;{} // no semicolon printed
+ switch expr {}
+ switch;expr{} // no semicolon printed
+ switch (expr) {} // no parens printed
+ switch;((expr)){} // no semicolon and parens printed
+ switch x := expr; { default:use(
+x)
+ }
+ switch x := expr; expr {default:use(x)}
+}
+
+
+// Formatting of switch statement bodies.
+func _() {
+ switch {
+ }
+
+ switch x := 0; x {
+ case 1:
+ use(x)
+ use(x) // followed by an empty line
+
+ case 2: // followed by an empty line
+
+ use(x) // followed by an empty line
+
+ case 3: // no empty lines
+ use(x)
+ use(x)
+ }
+
+ switch x {
+ case 0:
+ use(x)
+ case 1: // this comment should have no effect on the previous or next line
+ use(x)
+ }
+
+ switch x := 0; x {
+ case 1:
+ x = 0
+ // this comment should be indented
+ case 2:
+ x = 0
+ // this comment should not be indented, it is aligned with the next case
+ case 3:
+ x = 0
+ /* indented comment
+ aligned
+ aligned
+ */
+ // bla
+ /* and more */
+ case 4:
+ x = 0
+ /* not indented comment
+ aligned
+ aligned
+ */
+ // bla
+ /* and more */
+ case 5:
+ }
+}
+
+
+// Formatting of selected select statements.
+func _() {
+ select {
+ }
+ select { /* this comment should not be tab-aligned because the closing } is on the same line */ }
+ select { /* this comment should be tab-aligned */
+ }
+ select { // this comment should be tab-aligned
+ }
+ select { case <-c: }
+}
+
+
+// Formatting of for-statement headers.
+func _() {
+ for{}
+ for expr {}
+ for (expr) {} // no parens printed
+ for;;{} // no semicolons printed
+ for x :=expr;; {use( x)}
+ for; expr;{} // no semicolons printed
+ for; ((expr));{} // no semicolons and parens printed
+ for; ; expr = false {}
+ for x :=expr; expr; {use(x)}
+ for x := expr;; expr=false {use(x)}
+ for;expr;expr =false {
+ }
+ for x := expr;expr;expr = false { use(x) }
+ for x := range []int{} { use(x) }
+ for x := range (([]int{})) { use(x) } // no parens printed
+}
+
+
+// Don't remove mandatory parentheses around composite literals in control clauses.
+func _() {
+ // strip parentheses - no composite literals or composite literals don't start with a type name
+ if (x) {}
+ if (((x))) {}
+ if ([]T{}) {}
+ if (([]T{})) {}
+ if ; (((([]T{})))) {}
+
+ for (x) {}
+ for (((x))) {}
+ for ([]T{}) {}
+ for (([]T{})) {}
+ for ; (((([]T{})))) ; {}
+
+ switch (x) {}
+ switch (((x))) {}
+ switch ([]T{}) {}
+ switch ; (((([]T{})))) {}
+
+ for _ = range ((([]T{T{42}}))) {}
+
+ // leave parentheses - composite literals start with a type name
+ if (T{}) {}
+ if ((T{})) {}
+ if ; ((((T{})))) {}
+
+ for (T{}) {}
+ for ((T{})) {}
+ for ; ((((T{})))) ; {}
+
+ switch (T{}) {}
+ 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) {}
+}
+
+
+// Extra empty lines inside functions. Do respect source code line
+// breaks between statement boundaries but print at most one empty
+// line at a time.
+func _() {
+
+ const _ = 0
+
+ const _ = 1
+ type _ int
+ type _ float
+
+ var _ = 0
+ 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)
+
+ if x < x {
+
+ use(x)
+
+ } else {
+
+ use(x)
+
+ }
+}
+
+
+// Formatting around labels.
+func _() {
+ L:
+}
+
+
+func _() {
+ // this comment should be indented
+ L: ; // no semicolon needed
+}
+
+
+func _() {
+ switch 0 {
+ case 0:
+ L0: ; // semicolon required
+ case 1:
+ L1: ; // semicolon required
+ default:
+ L2: ; // no semicolon needed
+ }
+}
+
+
+func _() {
+ f()
+L1:
+ f()
+L2:
+ ;
+L3:
+}
+
+
+func _() {
+ // this comment should be indented
+ L:
+}
+
+
+func _() {
+ L: _ = 0
+}
+
+
+func _() {
+ // this comment should be indented
+ L: _ = 0
+}
+
+
+func _() {
+ for {
+ L1: _ = 0
+ L2:
+ _ = 0
+ }
+}
+
+
+func _() {
+ // this comment should be indented
+ for {
+ L1: _ = 0
+ L2:
+ _ = 0
+ }
+}
+
+
+func _() {
+ if true {
+ _ = 0
+ }
+ _ = 0 // the indentation here should not be affected by the long label name
+AnOverlongLabel:
+ _ = 0
+
+ if true {
+ _ = 0
+ }
+ _ = 0
+
+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()
+}