]> Cypherpunks repositories - gostls13.git/commitdiff
go/printer: Permit declaration and statement lists as input.
authorRobert Griesemer <gri@golang.org>
Mon, 26 Nov 2012 21:14:04 +0000 (13:14 -0800)
committerRobert Griesemer <gri@golang.org>
Mon, 26 Nov 2012 21:14:04 +0000 (13:14 -0800)
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

src/pkg/go/printer/nodes.go
src/pkg/go/printer/performance_test.go
src/pkg/go/printer/printer.go
src/pkg/go/printer/printer_test.go

index e99a2e36d4fd8cb0d56093a98a98589118a3e329..a6945af5f74283707d7c3a928c7cbccd2ef8ccac 100644 (file)
@@ -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)
 }
index 31f5ef0883c1b64e1f48983438fb3bd091dec16a..5b29affcb7db1aef9dac200d177e6373a11296de 100644 (file)
@@ -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)
        }
 }
index 990655e716044896ecaa1ab57e1522f261618164..5d75f09167d6d29d0902861de6e1727107710ae8 100644 (file)
@@ -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))
index 36d1bf74d3ffe748f42b3ebaebd3e390aa456a93..8454ac12b9e7e45afa45d52ef0b25dd782d5d83e 100644 (file)
@@ -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) {