Commit 2a982e8e authored by Robert Griesemer's avatar Robert Griesemer

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
parent a5e10edc
...@@ -900,7 +900,11 @@ func (p *printer) stmtList(list []ast.Stmt, nindent int, nextIsRBrace bool) { ...@@ -900,7 +900,11 @@ func (p *printer) stmtList(list []ast.Stmt, nindent int, nextIsRBrace bool) {
if _, isEmpty := s.(*ast.EmptyStmt); !isEmpty { if _, isEmpty := s.(*ast.EmptyStmt); !isEmpty {
// _indent == 0 only for lists of switch/select case clauses; // _indent == 0 only for lists of switch/select case clauses;
// in those cases each clause is a new section // in those cases each clause is a new section
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.linebreak(p.lineFor(s.Pos()), 1, ignore, i == 0 || nindent == 0 || multiLine)
}
p.stmt(s, nextIsRBrace && i == len(list)-1) p.stmt(s, nextIsRBrace && i == len(list)-1)
multiLine = p.isMultiLine(s) multiLine = p.isMultiLine(s)
i++ i++
...@@ -1523,31 +1527,35 @@ func declToken(decl ast.Decl) (tok token.Token) { ...@@ -1523,31 +1527,35 @@ func declToken(decl ast.Decl) (tok token.Token) {
return return
} }
func (p *printer) file(src *ast.File) { func (p *printer) declList(list []ast.Decl) {
p.setComment(src.Doc)
p.print(src.Pos(), token.PACKAGE, blank)
p.expr(src.Name)
if len(src.Decls) > 0 {
tok := token.ILLEGAL tok := token.ILLEGAL
for _, d := range src.Decls { for _, d := range list {
prev := tok prev := tok
tok = declToken(d) tok = declToken(d)
// if the declaration token changed (e.g., from CONST to TYPE) // If the declaration token changed (e.g., from CONST to TYPE)
// or the next declaration has documentation associated with it, // or the next declaration has documentation associated with it,
// print an empty line between top-level declarations // print an empty line between top-level declarations.
// (because p.linebreak is called with the position of d, which // (because p.linebreak is called with the position of d, which
// is past any documentation, the minimum requirement is satisfied // is past any documentation, the minimum requirement is satisfied
// even w/o the extra getDoc(d) nil-check - leave it in case the // even w/o the extra getDoc(d) nil-check - leave it in case the
// linebreak logic improves - there's already a TODO). // 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 min := 1
if prev != tok || getDoc(d) != nil { if prev != tok || getDoc(d) != nil {
min = 2 min = 2
} }
p.linebreak(p.lineFor(d.Pos()), min, ignore, false) 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) p.print(newline)
} }
...@@ -20,7 +20,7 @@ import ( ...@@ -20,7 +20,7 @@ import (
var testfile *ast.File var testfile *ast.File
func testprint(out io.Writer, file *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) log.Fatalf("print error: %s", err)
} }
} }
......
...@@ -165,15 +165,15 @@ func (p *printer) atLineBegin(pos token.Position) { ...@@ -165,15 +165,15 @@ func (p *printer) atLineBegin(pos token.Position) {
// write indentation // write indentation
// use "hard" htabs - indentation columns // use "hard" htabs - indentation columns
// must not be discarded by the tabwriter // 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') p.output = append(p.output, '\t')
} }
// update positions // update positions
i := p.indent p.pos.Offset += n
p.pos.Offset += i p.pos.Column += n
p.pos.Column += i p.out.Column += n
p.out.Column += i
} }
// writeByte writes ch n times to p.output and updates p.pos. // writeByte writes ch n times to p.output and updates p.pos.
...@@ -1032,9 +1032,9 @@ func (p *printer) printNode(node interface{}) error { ...@@ -1032,9 +1032,9 @@ func (p *printer) printNode(node interface{}) error {
case ast.Expr: case ast.Expr:
p.expr(n) p.expr(n)
case ast.Stmt: case ast.Stmt:
// A labeled statement will un-indent to position the // A labeled statement will un-indent to position the label.
// label. Set indent to 1 so we don't get indent "underflow". // Set p.indent to 1 so we don't get indent "underflow".
if _, labeledStmt := n.(*ast.LabeledStmt); labeledStmt { if _, ok := n.(*ast.LabeledStmt); ok {
p.indent = 1 p.indent = 1
} }
p.stmt(n, false) p.stmt(n, false)
...@@ -1042,6 +1042,17 @@ func (p *printer) printNode(node interface{}) error { ...@@ -1042,6 +1042,17 @@ func (p *printer) printNode(node interface{}) error {
p.decl(n) p.decl(n)
case ast.Spec: case ast.Spec:
p.spec(n, 1, false) 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: case *ast.File:
p.file(n) p.file(n)
default: default:
...@@ -1174,6 +1185,7 @@ const ( ...@@ -1174,6 +1185,7 @@ const (
type Config struct { type Config struct {
Mode Mode // default: 0 Mode Mode // default: 0
Tabwidth int // default: 8 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. // fprint implements Fprint and takes a nodesSizes map for setting up the printer state.
...@@ -1235,8 +1247,8 @@ type CommentedNode struct { ...@@ -1235,8 +1247,8 @@ type CommentedNode struct {
// Fprint "pretty-prints" an AST node to output for a given configuration cfg. // Fprint "pretty-prints" an AST node to output for a given configuration cfg.
// Position information is interpreted relative to the file set fset. // Position information is interpreted relative to the file set fset.
// The node type must be *ast.File, *CommentedNode, or assignment-compatible // The node type must be *ast.File, *CommentedNode, []ast.Decl, []ast.Stmt,
// to ast.Expr, ast.Decl, ast.Spec, or 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 { func (cfg *Config) Fprint(output io.Writer, fset *token.FileSet, node interface{}) error {
return cfg.fprint(output, fset, node, make(map[ast.Node]int)) return cfg.fprint(output, fset, node, make(map[ast.Node]int))
......
...@@ -434,6 +434,98 @@ func (t *t) foo(a, b, c int) int { ...@@ -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 // TestFuncType tests that an ast.FuncType with a nil Params field
// can be printed (per go/ast specification). Test case for issue 3870. // can be printed (per go/ast specification). Test case for issue 3870.
func TestFuncType(t *testing.T) { func TestFuncType(t *testing.T) {
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment