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) {
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)
}
......@@ -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)
}
}
......
......@@ -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))
......
......@@ -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) {
......
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