From: Robert Griesemer Date: Mon, 26 Nov 2012 21:14:04 +0000 (-0800) Subject: go/printer: Permit declaration and statement lists as input. X-Git-Tag: go1.1rc2~1784 X-Git-Url: http://www.git.cypherpunks.su/?a=commitdiff_plain;h=2a982e8e25f35735268711d21c77aaaee75f8366;p=gostls13.git go/printer: Permit declaration and statement lists as input. Also: Can set base indentation in printer.Config: all code is going to be indented by at least that amount (except for raw string literals spanning multiple lines, since their values must not change). R=golang-dev, rsc CC=golang-dev https://golang.org/cl/6847086 --- diff --git a/src/pkg/go/printer/nodes.go b/src/pkg/go/printer/nodes.go index e99a2e36d4..a6945af5f7 100644 --- a/src/pkg/go/printer/nodes.go +++ b/src/pkg/go/printer/nodes.go @@ -900,7 +900,11 @@ func (p *printer) stmtList(list []ast.Stmt, nindent int, nextIsRBrace bool) { if _, isEmpty := s.(*ast.EmptyStmt); !isEmpty { // _indent == 0 only for lists of switch/select case clauses; // in those cases each clause is a new section - p.linebreak(p.lineFor(s.Pos()), 1, ignore, i == 0 || nindent == 0 || multiLine) + if len(p.output) > 0 { + // only print line break if we are not at the beginning of the output + // (i.e., we are not printing only a partial program) + p.linebreak(p.lineFor(s.Pos()), 1, ignore, i == 0 || nindent == 0 || multiLine) + } p.stmt(s, nextIsRBrace && i == len(list)-1) multiLine = p.isMultiLine(s) i++ @@ -1523,31 +1527,35 @@ func declToken(decl ast.Decl) (tok token.Token) { return } -func (p *printer) file(src *ast.File) { - p.setComment(src.Doc) - p.print(src.Pos(), token.PACKAGE, blank) - p.expr(src.Name) - - 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) - // or the next declaration has documentation associated with it, - // print an empty line between top-level declarations - // (because p.linebreak is called with the position of d, which - // is past any documentation, the minimum requirement is satisfied - // even w/o the extra getDoc(d) nil-check - leave it in case the - // linebreak logic improves - there's already a TODO). +func (p *printer) declList(list []ast.Decl) { + tok := token.ILLEGAL + for _, d := range list { + prev := tok + tok = declToken(d) + // If the declaration token changed (e.g., from CONST to TYPE) + // or the next declaration has documentation associated with it, + // print an empty line between top-level declarations. + // (because p.linebreak is called with the position of d, which + // is past any documentation, the minimum requirement is satisfied + // even w/o the extra getDoc(d) nil-check - leave it in case the + // linebreak logic improves - there's already a TODO). + if len(p.output) > 0 { + // only print line break if we are not at the beginning of the output + // (i.e., we are not printing only a partial program) min := 1 if prev != tok || getDoc(d) != nil { min = 2 } p.linebreak(p.lineFor(d.Pos()), min, ignore, false) - p.decl(d) } + p.decl(d) } +} +func (p *printer) file(src *ast.File) { + p.setComment(src.Doc) + p.print(src.Pos(), token.PACKAGE, blank) + p.expr(src.Name) + p.declList(src.Decls) p.print(newline) } diff --git a/src/pkg/go/printer/performance_test.go b/src/pkg/go/printer/performance_test.go index 31f5ef0883..5b29affcb7 100644 --- a/src/pkg/go/printer/performance_test.go +++ b/src/pkg/go/printer/performance_test.go @@ -20,7 +20,7 @@ import ( var testfile *ast.File func testprint(out io.Writer, file *ast.File) { - if err := (&Config{TabIndent | UseSpaces, 8}).Fprint(out, fset, file); err != nil { + if err := (&Config{TabIndent | UseSpaces, 8, 0}).Fprint(out, fset, file); err != nil { log.Fatalf("print error: %s", err) } } diff --git a/src/pkg/go/printer/printer.go b/src/pkg/go/printer/printer.go index 990655e716..5d75f09167 100644 --- a/src/pkg/go/printer/printer.go +++ b/src/pkg/go/printer/printer.go @@ -165,15 +165,15 @@ func (p *printer) atLineBegin(pos token.Position) { // write indentation // use "hard" htabs - indentation columns // must not be discarded by the tabwriter - for i := 0; i < p.indent; i++ { + n := p.Config.Indent + p.indent // include base indentation + for i := 0; i < n; i++ { p.output = append(p.output, '\t') } // update positions - i := p.indent - p.pos.Offset += i - p.pos.Column += i - p.out.Column += i + p.pos.Offset += n + p.pos.Column += n + p.out.Column += n } // writeByte writes ch n times to p.output and updates p.pos. @@ -1032,9 +1032,9 @@ func (p *printer) printNode(node interface{}) error { case ast.Expr: p.expr(n) case ast.Stmt: - // 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 { + // A labeled statement will un-indent to position the label. + // Set p.indent to 1 so we don't get indent "underflow". + if _, ok := n.(*ast.LabeledStmt); ok { p.indent = 1 } p.stmt(n, false) @@ -1042,6 +1042,17 @@ func (p *printer) printNode(node interface{}) error { p.decl(n) case ast.Spec: p.spec(n, 1, false) + case []ast.Stmt: + // A labeled statement will un-indent to position the label. + // Set p.indent to 1 so we don't get indent "underflow". + for _, s := range n { + if _, ok := s.(*ast.LabeledStmt); ok { + p.indent = 1 + } + } + p.stmtList(n, 0, false) + case []ast.Decl: + p.declList(n) case *ast.File: p.file(n) default: @@ -1174,6 +1185,7 @@ const ( type Config struct { Mode Mode // default: 0 Tabwidth int // default: 8 + Indent int // default: 0 (all code is indented at least by this much) } // fprint implements Fprint and takes a nodesSizes map for setting up the printer state. @@ -1235,8 +1247,8 @@ type CommentedNode struct { // Fprint "pretty-prints" an AST node to output for a given configuration cfg. // Position information is interpreted relative to the file set fset. -// The node type must be *ast.File, *CommentedNode, or assignment-compatible -// to ast.Expr, ast.Decl, ast.Spec, or ast.Stmt. +// The node type must be *ast.File, *CommentedNode, []ast.Decl, []ast.Stmt, +// 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{}) error { return cfg.fprint(output, fset, node, make(map[ast.Node]int)) diff --git a/src/pkg/go/printer/printer_test.go b/src/pkg/go/printer/printer_test.go index 36d1bf74d3..8454ac12b9 100644 --- a/src/pkg/go/printer/printer_test.go +++ b/src/pkg/go/printer/printer_test.go @@ -434,6 +434,98 @@ func (t *t) foo(a, b, c int) int { } } +var decls = []string{ + `import "fmt"`, + "const pi = 3.1415\nconst e = 2.71828\n\nvar x = pi", + "func sum(x, y int) int\t{ return x + y }", +} + +func TestDeclLists(t *testing.T) { + for _, src := range decls { + file, err := parser.ParseFile(fset, "", "package p;"+src, parser.ParseComments) + if err != nil { + panic(err) // error in test + } + + var buf bytes.Buffer + err = Fprint(&buf, fset, file.Decls) // only print declarations + if err != nil { + panic(err) // error in test + } + + out := buf.String() + if out != src { + t.Errorf("\ngot : %q\nwant: %q\n", out, src) + } + } +} + +var stmts = []string{ + "i := 0", + "select {}\nvar a, b = 1, 2\nreturn a + b", + "go f()\ndefer func() {}()", +} + +func TestStmtLists(t *testing.T) { + for _, src := range stmts { + file, err := parser.ParseFile(fset, "", "package p; func _() {"+src+"}", parser.ParseComments) + if err != nil { + panic(err) // error in test + } + + var buf bytes.Buffer + err = Fprint(&buf, fset, file.Decls[0].(*ast.FuncDecl).Body.List) // only print statements + if err != nil { + panic(err) // error in test + } + + out := buf.String() + if out != src { + t.Errorf("\ngot : %q\nwant: %q\n", out, src) + } + } +} + +func TestBaseIndent(t *testing.T) { + // The testfile must not contain multi-line raw strings since those + // are not indented (because their values must not change) and make + // this test fail. + const filename = "printer.go" + src, err := ioutil.ReadFile(filename) + if err != nil { + panic(err) // error in test + } + + file, err := parser.ParseFile(fset, filename, src, 0) + if err != nil { + panic(err) // error in test + } + + var buf bytes.Buffer + for indent := 0; indent < 4; indent++ { + buf.Reset() + (&Config{Tabwidth: tabwidth, Indent: indent}).Fprint(&buf, fset, file) + // all code must be indented by at least 'indent' tabs + lines := bytes.Split(buf.Bytes(), []byte{'\n'}) + for i, line := range lines { + if len(line) == 0 { + continue // empty lines don't have indentation + } + n := 0 + for j, b := range line { + if b != '\t' { + // end of indentation + n = j + break + } + } + if n < indent { + t.Errorf("line %d: got only %d tabs; want at least %d: %q", i, n, indent, line) + } + } + } +} + // TestFuncType tests that an ast.FuncType with a nil Params field // can be printed (per go/ast specification). Test case for issue 3870. func TestFuncType(t *testing.T) {