diff options
| author | Robert Griesemer <gri@golang.org> | 2009-06-15 16:23:16 -0700 |
|---|---|---|
| committer | Robert Griesemer <gri@golang.org> | 2009-06-15 16:23:16 -0700 |
| commit | a893db8767e0857fffe3d8ae64d2b4b2dd2c22fe (patch) | |
| tree | 2b1c45e73309b6dd4032ddc4d22df0842ac4ef00 /src | |
| parent | c2faeac8c4be9fa116be01c975518ffa299bf89a (diff) | |
| download | go-a893db8767e0857fffe3d8ae64d2b4b2dd2c22fe.tar.xz | |
gofmt (final resting place TBD):
- replacement for pretty; app to format a single .go file
printer.go (pkg/go/printer):
- replacement for astprinter.go; implements AST printing
- also replaces pkg/go/ast/format.go for now
cleanups:
- removed/saved away old code
R=r,rsc,iant
DELTA=2833 (1183 added, 1628 deleted, 22 changed)
OCL=30226
CL=30306
Diffstat (limited to 'src')
| -rw-r--r-- | src/pkg/Make.deps | 3 | ||||
| -rw-r--r-- | src/pkg/Makefile | 1 | ||||
| -rw-r--r-- | src/pkg/go/ast/Makefile | 10 | ||||
| -rw-r--r-- | src/pkg/go/ast/format.go | 123 | ||||
| -rw-r--r-- | src/pkg/go/printer/Makefile | 60 | ||||
| -rw-r--r-- | src/pkg/go/printer/printer.go | 1019 |
6 files changed, 1083 insertions, 133 deletions
diff --git a/src/pkg/Make.deps b/src/pkg/Make.deps index dd83e8b1cb..d710c59e35 100644 --- a/src/pkg/Make.deps +++ b/src/pkg/Make.deps @@ -16,9 +16,10 @@ exec.install: os.install strings.install exvar.install: fmt.install http.install io.install log.install strconv.install sync.install flag.install: fmt.install os.install strconv.install fmt.install: io.install os.install reflect.install strconv.install utf8.install -go/ast.install: datafmt.install go/token.install io.install os.install unicode.install utf8.install +go/ast.install: go/token.install unicode.install utf8.install go/doc.install: container/vector.install fmt.install go/ast.install go/token.install io.install once.install regexp.install sort.install strings.install template.install go/parser.install: container/vector.install fmt.install go/ast.install go/scanner.install go/token.install io.install os.install +go/printer.install: fmt.install go/ast.install go/token.install io.install os.install reflect.install go/scanner.install: go/token.install strconv.install unicode.install utf8.install go/token.install: strconv.install hash.install: io.install diff --git a/src/pkg/Makefile b/src/pkg/Makefile index 036a82e38b..3339a9d369 100644 --- a/src/pkg/Makefile +++ b/src/pkg/Makefile @@ -33,6 +33,7 @@ DIRS=\ go/ast\ go/doc\ go/parser\ + go/printer\ go/scanner\ go/token\ hash\ diff --git a/src/pkg/go/ast/Makefile b/src/pkg/go/ast/Makefile index 1fd22ae71f..ead35dead1 100644 --- a/src/pkg/go/ast/Makefile +++ b/src/pkg/go/ast/Makefile @@ -34,21 +34,14 @@ coverage: packages O1=\ ast.$O\ -O2=\ - format.$O\ - -phases: a1 a2 +phases: a1 _obj$D/ast.a: phases a1: $(O1) $(AR) grc _obj$D/ast.a ast.$O rm -f $(O1) -a2: $(O2) - $(AR) grc _obj$D/ast.a format.$O - rm -f $(O2) - newpkg: clean mkdir -p _obj$D @@ -56,7 +49,6 @@ newpkg: clean $(O1): newpkg $(O2): a1 -$(O3): a2 nuke: clean rm -f $(GOROOT)/pkg/$(GOOS)_$(GOARCH)$D/ast.a diff --git a/src/pkg/go/ast/format.go b/src/pkg/go/ast/format.go deleted file mode 100644 index caeca19aa6..0000000000 --- a/src/pkg/go/ast/format.go +++ /dev/null @@ -1,123 +0,0 @@ -// 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 ast - -import ( - "datafmt"; - "go/ast"; - "go/token"; - "io"; - "os"; -) - - -// Format is a customized datafmt.Format for printing of ASTs. -type Format datafmt.Format; - - -// ---------------------------------------------------------------------------- -// Custom formatters - -// The AST-specific formatting state is maintained by a state variable. -type state struct { - // for now we have very little state - // TODO maintain list of unassociated comments - optSemi bool -} - - -func (s *state) Copy() datafmt.Environment { - copy := *s; - return © -} - - -func isValidPos(s *datafmt.State, value interface{}, ruleName string) bool { - pos := value.(token.Position); - return pos.IsValid(); -} - - -func isSend(s *datafmt.State, value interface{}, ruleName string) bool { - return value.(ast.ChanDir) & ast.SEND != 0; -} - - -func isRecv(s *datafmt.State, value interface{}, ruleName string) bool { - return value.(ast.ChanDir) & ast.RECV != 0; -} - - -func isMultiLineComment(s *datafmt.State, value interface{}, ruleName string) bool { - return value.([]byte)[1] == '*'; -} - - -func clearOptSemi(s *datafmt.State, value interface{}, ruleName string) bool { - s.Env().(*state).optSemi = false; - return true; -} - - -func setOptSemi(s *datafmt.State, value interface{}, ruleName string) bool { - s.Env().(*state).optSemi = true; - return true; -} - - -func optSemi(s *datafmt.State, value interface{}, ruleName string) bool { - if !s.Env().(*state).optSemi { - s.Write([]byte{';'}); - } - return true; -} - - -var fmap = datafmt.FormatterMap { - "isValidPos": isValidPos, - "isSend": isSend, - "isRecv": isRecv, - "isMultiLineComment": isMultiLineComment, - "/": clearOptSemi, - "clearOptSemi": clearOptSemi, - "setOptSemi": setOptSemi, - "optSemi": optSemi, -} - - -// ---------------------------------------------------------------------------- -// Printing - -// NewFormat parses a datafmt format specification from a file -// and adds AST-specific custom formatter rules. The result is -// the customized format or an os.Error, if any. -// -func NewFormat(filename string) (Format, os.Error) { - src, err := io.ReadFile(filename); - if err != nil { - return nil, err; - } - f, err := datafmt.Parse(src, fmap); - return Format(f), err; -} - - -// Fprint formats each AST node provided as argument according to the -// format f and writes to standard output. The result is the total number -// of bytes written and an os.Error, if any. -// -func (f Format) Fprint(w io.Writer, nodes ...) (int, os.Error) { - var s state; - return datafmt.Format(f).Fprint(w, &s, nodes); -} - - -// Fprint formats each AST node provided as argument according to the -// format f and writes to w. The result is the total number of bytes -// written and an os.Error, if any. -// -func (f Format) Print(nodes ...) (int, os.Error) { - return f.Fprint(os.Stdout, nodes); -} diff --git a/src/pkg/go/printer/Makefile b/src/pkg/go/printer/Makefile new file mode 100644 index 0000000000..0b0b5a77c6 --- /dev/null +++ b/src/pkg/go/printer/Makefile @@ -0,0 +1,60 @@ +# 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. + +# DO NOT EDIT. Automatically generated by gobuild. +# gobuild -m >Makefile + +D=/go/ + +include $(GOROOT)/src/Make.$(GOARCH) +AR=gopack + +default: packages + +clean: + rm -rf *.[$(OS)] *.a [$(OS)].out _obj + +test: packages + gotest + +coverage: packages + gotest + 6cov -g `pwd` | grep -v '_test\.go:' + +%.$O: %.go + $(GC) -I_obj $*.go + +%.$O: %.c + $(CC) $*.c + +%.$O: %.s + $(AS) $*.s + +O1=\ + printer.$O\ + + +phases: a1 +_obj$D/printer.a: phases + +a1: $(O1) + $(AR) grc _obj$D/printer.a printer.$O + rm -f $(O1) + + +newpkg: clean + mkdir -p _obj$D + $(AR) grc _obj$D/printer.a + +$(O1): newpkg +$(O2): a1 + +nuke: clean + rm -f $(GOROOT)/pkg/$(GOOS)_$(GOARCH)$D/printer.a + +packages: _obj$D/printer.a + +install: packages + test -d $(GOROOT)/pkg && mkdir -p $(GOROOT)/pkg/$(GOOS)_$(GOARCH)$D + cp _obj$D/printer.a $(GOROOT)/pkg/$(GOOS)_$(GOARCH)$D/printer.a diff --git a/src/pkg/go/printer/printer.go b/src/pkg/go/printer/printer.go new file mode 100644 index 0000000000..74318b0cd0 --- /dev/null +++ b/src/pkg/go/printer/printer.go @@ -0,0 +1,1019 @@ +// 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. + +// The printer package implements printing of AST nodes. +package printer + +import ( + "fmt"; + "go/ast"; + "go/token"; + "io"; + "os"; + "reflect"; +) + + +// Printing is controlled with these flags supplied +// to Fprint via the mode parameter. +// +const ( + ExportsOnly uint = 1 << iota; // print exported code only + DocComments; // print documentation comments + OptCommas; // print optional commas + OptSemis; // print optional semicolons +) + + +type printer struct { + // configuration (does not change after initialization) + output io.Writer; + mode uint; + errors chan os.Error; + comments ast.Comments; // list of unassociated comments; or nil + + // current state (changes during printing) + written int; // number of bytes written + level int; // function nesting level; 0 = package scope, 1 = top-level function scope, etc. + indent int; // indent level + pos token.Position; // output position (possibly estimated) in "AST space" + + // comments + cindex int; // the current comment index + cpos token.Position; // the position of the next comment +} + + +func (p *printer) hasComment(pos token.Position) bool { + return p.cpos.Offset < pos.Offset; +} + + +func (p *printer) nextComment() { + p.cindex++; + if p.comments != nil && p.cindex < len(p.comments) && p.comments[p.cindex] != nil { + p.cpos = p.comments[p.cindex].Pos(); + } else { + p.cpos = token.Position{1<<30, 1<<30, 1}; // infinite + } +} + + +func (p *printer) setComments(comments ast.Comments) { + p.comments = comments; + p.cindex = -1; + p.nextComment(); +} + + +func (p *printer) init(output io.Writer, mode uint) { + p.output = output; + p.mode = mode; + p.errors = make(chan os.Error); + p.setComments(nil); +} + + +var ( + blank = []byte{' '}; + tab = []byte{'\t'}; + newline = []byte{'\n'}; + formfeed = []byte{'\f'}; +) + + +// Writing to p.output is done with write0 which also handles errors. +// It should only be called by write. +// +func (p *printer) write0(data []byte) { + n, err := p.output.Write(data); + p.written += n; + if err != nil { + p.errors <- err; + } +} + + +func (p *printer) write(data []byte) { + i0 := 0; + for i, b := range data { + if b == '\n' || b == '\f' { + // write segment ending in a newline/formfeed followed by indentation + // TODO should convert '\f' into '\n' if the output is not going through + // tabwriter + p.write0(data[i0 : i+1]); + for j := p.indent; j > 0; j-- { + p.write0(tab); + } + i0 = i+1; + + // update p.pos + p.pos.Offset += i+1 - i0 + p.indent; + p.pos.Line++; + p.pos.Column = p.indent + 1; + } + } + + // write remaining segment + p.write0(data[i0 : len(data)]); + + // update p.pos + n := len(data) - i0; + p.pos.Offset += n; + p.pos.Column += n; +} + + +// Reduce contiguous sequences of '\t' in a []byte to a single '\t'. +func untabify(src []byte) []byte { + dst := make([]byte, len(src)); + j := 0; + for i, c := range src { + if c != '\t' || i == 0 || src[i-1] != '\t' { + dst[j] = c; + j++; + } + } + return dst[0 : j]; +} + + +func (p *printer) adjustSpacingAndMergeComments() { + for ; p.hasComment(p.pos); p.nextComment() { + // we have a comment that comes before the current position + comment := p.comments[p.cindex]; + p.write(untabify(comment.Text)); + // TODO + // - classify comment and provide better formatting + // - add extra newlines if so indicated by source positions + } +} + + +func (p *printer) print(args ...) { + v := reflect.NewValue(args).(reflect.StructValue); + for i := 0; i < v.Len(); i++ { + p.adjustSpacingAndMergeComments(); + f := v.Field(i); + switch x := f.Interface().(type) { + case int: + // indentation delta + p.indent += x; + if p.indent < 0 { + panic("print: negative indentation"); + } + case []byte: + p.write(x); + case string: + p.write(io.StringBytes(x)); + case token.Token: + p.write(io.StringBytes(x.String())); + case token.Position: + // set current position + p.pos = x; + default: + panicln("print: unsupported argument type", f.Type().String()); + } + } +} + + +// ---------------------------------------------------------------------------- +// Predicates + +func (p *printer) optSemis() bool { + return p.mode & OptSemis != 0; +} + + +func (p *printer) exportsOnly() bool { + return p.mode & ExportsOnly != 0; +} + + +// The isVisibleX predicates return true if X should produce any output +// given the printing mode and depending on whether X contains exported +// names. + +func (p *printer) isVisibleIdent(x *ast.Ident) bool { + // identifiers in local scopes (p.level > 0) are always visible + // if the surrounding code is printed in the first place + return !p.exportsOnly() || x.IsExported() || p.level > 0; +} + + +func (p *printer) isVisibleIdentList(list []*ast.Ident) bool { + for _, x := range list { + if p.isVisibleIdent(x) { + return true; + } + } + return false; +} + + +func (p *printer) isVisibleFieldList(list []*ast.Field) bool { + for _, f := range list { + if len(f.Names) == 0 { + // anonymous field + // TODO should only return true if the anonymous field + // type is visible (for now be conservative and + // print it so that the generated code is valid) + return true; + } + if p.isVisibleIdentList(f.Names) { + return true; + } + } + return false; +} + + +func (p *printer) isVisibleSpec(spec ast.Spec) bool { + switch s := spec.(type) { + case *ast.ImportSpec: + return !p.exportsOnly(); + case *ast.ValueSpec: + return p.isVisibleIdentList(s.Names); + case *ast.TypeSpec: + return p.isVisibleIdent(s.Name); + } + panic("unreachable"); + return false; +} + + +func (p *printer) isVisibleSpecList(list []ast.Spec) bool { + for _, s := range list { + if p.isVisibleSpec(s) { + return true; + } + } + return false; +} + + +func (p *printer) isVisibleDecl(decl ast.Decl) bool { + switch d := decl.(type) { + case *ast.BadDecl: + return false; + case *ast.GenDecl: + return p.isVisibleSpecList(d.Specs); + case *ast.FuncDecl: + return p.isVisibleIdent(d.Name); + } + panic("unreachable"); + return false; +} + + +// ---------------------------------------------------------------------------- +// Printing of common AST nodes. + +func (p *printer) comment(c *ast.Comment) { + if c != nil { + text := c.Text; + if text[1] == '/' { + // //-style comment - dont print the '\n' + // TODO scanner should probably not include the '\n' in this case + text = text[0 : len(text)-1]; + } + p.print(tab, c.Pos(), text); // tab-separated trailing comment + } +} + + +func (p *printer) doc(d ast.Comments) { + if p.mode & DocComments != 0 { + for _, c := range d { + p.print(c.Pos(), c.Text); + } + } +} + + +func (p *printer) expr(x ast.Expr) bool + +func (p *printer) identList(list []*ast.Ident) { + needsComma := false; + for i, x := range list { + if p.isVisibleIdent(x) { + if needsComma { + p.print(token.COMMA, blank); + } + p.expr(x); + needsComma = true; + } + } +} + + +func (p *printer) exprList(list []ast.Expr) { + for i, x := range list { + if i > 0 { + p.print(token.COMMA, blank); + } + p.expr(x); + } +} + + +func (p *printer) parameters(list []*ast.Field) { + p.print(token.LPAREN); + if len(list) > 0 { + for i, par := range list { + if i > 0 { + p.print(token.COMMA, blank); + } + p.identList(par.Names); // p.level > 0; all identifiers will be printed + if len(par.Names) > 0 { + // at least one identifier + p.print(blank); + }; + p.expr(par.Type); + } + } + p.print(token.RPAREN); +} + + +func (p *printer) signature(params, result []*ast.Field) { + p.parameters(params); + if result != nil { + p.print(blank); + + if len(result) == 1 && result[0].Names == nil { + // single anonymous result; no ()'s unless it's a function type + f := result[0]; + if _, isFtyp := f.Type.(*ast.FuncType); !isFtyp { + p.expr(f.Type); + return; + } + } + + p.parameters(result); + } +} + + +// Returns true if the field list ends in a closing brace. +func (p *printer) fieldList(lbrace token.Position, list []*ast.Field, rbrace token.Position, isInterface bool) bool { + hasBody := p.isVisibleFieldList(list); + if !lbrace.IsValid() || p.exportsOnly() && !hasBody { + // forward declaration without {}'s or no visible exported fields + // (in all other cases, the {}'s must be printed even if there are + // no fields, otherwise the type is incorrect) + return false; // no {}'s + } + + p.print(blank, lbrace, token.LBRACE); + + if hasBody { + p.print(+1, newline); + + var needsSemi bool; + var lastWasAnon bool; // true if the previous line was an anonymous field + var lastComment *ast.Comment; // the comment from the previous line + for _, f := range list { + hasNames := p.isVisibleIdentList(f.Names); + isAnon := len(f.Names) == 0; + + if hasNames || isAnon { + // at least one visible identifier or anonymous field + // TODO this is conservative - see isVisibleFieldList + if needsSemi { + p.print(token.SEMICOLON); + p.comment(lastComment); + if lastWasAnon == isAnon { + // previous and current line have same structure; + // continue with existing columns + p.print(newline); + } else { + // previous and current line have different structure; + // flush tabwriter and start new columns (the "type + // column" on a line with named fields may line up + // with the "trailing comment column" on a line with + // an anonymous field, leading to bad alignment) + p.print(formfeed); + } + } + + p.doc(f.Doc); + if hasNames { + p.identList(f.Names); + p.print(tab); + } + + if isInterface { + if ftyp, isFtyp := f.Type.(*ast.FuncType); isFtyp { + // methods + p.signature(ftyp.Params, ftyp.Results); + } else { + // embedded interface + p.expr(f.Type); + } + } else { + p.expr(f.Type); + if f.Tag != nil && !p.exportsOnly() { + p.print(tab); + p.expr(&ast.StringList{f.Tag}); + } + } + + needsSemi = true; + lastWasAnon = isAnon; + lastComment = f.Comment; + } + } + + if p.optSemis() { + p.print(token.SEMICOLON); + } + + p.comment(lastComment); + p.print(-1, newline); + } + + p.print(rbrace, token.RBRACE); + return true; // field list with {}'s +} + + +// ---------------------------------------------------------------------------- +// Expressions + +func (p *printer) stmt(s ast.Stmt) (optSemi bool) + +// Returns true if a separating semicolon is optional. +func (p *printer) expr1(expr ast.Expr, prec1 int) (optSemi bool) { + p.print(expr.Pos()); + + switch x := expr.(type) { + case *ast.BadExpr: + p.print("BadExpr"); + + case *ast.Ident: + p.print(x.Value); + + case *ast.BinaryExpr: + prec := x.Op.Precedence(); + if prec < prec1 { + p.print(token.LPAREN); + } + p.expr1(x.X, prec); + p.print(blank, x.OpPos, x.Op, blank); + p.expr1(x.Y, prec); + if prec < prec1 { + p.print(token.RPAREN); + } + + case *ast.KeyValueExpr: + p.expr(x.Key); + p.print(blank, x.Colon, token.COLON, blank); + p.expr(x.Value); + + case *ast.StarExpr: + p.print(token.MUL); + p.expr(x.X); + + case *ast.UnaryExpr: + prec := token.UnaryPrec; + if prec < prec1 { + p.print(token.LPAREN); + } + p.print(x.Op); + if x.Op == token.RANGE { + p.print(blank); + } + p.expr1(x.X, prec); + if prec < prec1 { + p.print(token.RPAREN); + } + + case *ast.IntLit: + p.print(x.Value); + + case *ast.FloatLit: + p.print(x.Value); + + case *ast.CharLit: + p.print(x.Value); + + case *ast.StringLit: + p.print(x.Value); + + case *ast.StringList: + for i, x := range x.Strings { + if i > 0 { + p.print(blank); + } + p.expr(x); + } + + case *ast.FuncLit: + p.level++; + p.expr(x.Type); + p.print(blank); + p.stmt(x.Body); + p.level--; + + case *ast.ParenExpr: + p.print(token.LPAREN); + p.expr(x.X); + p.print(x.Rparen, token.RPAREN); + + case *ast.SelectorExpr: + p.expr1(x.X, token.HighestPrec); + p.print(token.PERIOD); + p.expr1(x.Sel, token.HighestPrec); + + case *ast.TypeAssertExpr: + p.expr1(x.X, token.HighestPrec); + p.print(token.PERIOD, token.LPAREN); + p.expr(x.Type); + p.print(token.RPAREN); + + case *ast.IndexExpr: + p.expr1(x.X, token.HighestPrec); + p.print(token.LBRACK); + p.expr(x.Index); + if x.End != nil { + p.print(blank, token.COLON, blank); + p.expr(x.End); + } + p.print(token.RBRACK); + + case *ast.CallExpr: + p.expr1(x.Fun, token.HighestPrec); + p.print(x.Lparen, token.LPAREN); + p.exprList(x.Args); + p.print(x.Rparen, token.RPAREN); + + case *ast.CompositeLit: + p.expr1(x.Type, token.HighestPrec); + p.print(x.Lbrace, token.LBRACE); + p.exprList(x.Elts); + if p.mode & OptCommas != 0 { + p.print(token.COMMA); + } + p.print(x.Rbrace, token.RBRACE); + + case *ast.Ellipsis: + p.print(token.ELLIPSIS); + + case *ast.ArrayType: + p.print(token.LBRACK); + if x.Len != nil { + p.expr(x.Len); + } + p.print(token.RBRACK); + p.expr(x.Elt); + + case *ast.StructType: + p.print(token.STRUCT); + optSemi = p.fieldList(x.Lbrace, x.Fields, x.Rbrace, false); + + case *ast.FuncType: + p.print(token.FUNC); + p.signature(x.Params, x.Results); + + case *ast.InterfaceType: + p.print(token.INTERFACE); + optSemi = p.fieldList(x.Lbrace, x.Methods, x.Rbrace, true); + + case *ast.MapType: + p.print(token.MAP, blank, token.LBRACK); + p.expr(x.Key); + p.print(token.RBRACK); + p.expr(x.Value); + + 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, blank, token.ARROW); + } + p.print(blank); + p.expr(x.Value); + + default: + panic("unreachable"); + } + + return optSemi; +} + + +// Returns true if a separating semicolon is optional. +func (p *printer) expr(x ast.Expr) bool { + return p.expr1(x, token.LowestPrec); +} + + +// ---------------------------------------------------------------------------- +// Statements + +func (p *printer) decl(decl ast.Decl) (optSemi bool) + +// Print the statement list indented, but without a newline after the last statement. +func (p *printer) stmtList(list []ast.Stmt) { + if len(list) > 0 { + p.print(+1, newline); + optSemi := false; + for i, s := range list { + if i > 0 { + if !optSemi || p.optSemis() { + // semicolon is required + p.print(token.SEMICOLON); + } + p.print(newline); + } + optSemi = p.stmt(s); + } + if p.optSemis() { + p.print(token.SEMICOLON); + } + p.print(-1); + } +} + + +func (p *printer) block(s *ast.BlockStmt) { + p.print(s.Pos(), token.LBRACE); + if len(s.List) > 0 { + p.stmtList(s.List); + p.print(newline); + } + p.print(s.Rbrace, token.RBRACE); +} + + +func (p *printer) switchBlock(s *ast.BlockStmt) { + p.print(s.Pos(), token.LBRACE); + if len(s.List) > 0 { + for i, s := range s.List { + // s is one of *ast.CaseClause, *ast.TypeCaseClause, *ast.CommClause; + p.print(newline); + p.stmt(s); + } + p.print(newline); + } + p.print(s.Rbrace, token.RBRACE); +} + + +func (p *printer) controlClause(isForStmt bool, init ast.Stmt, expr ast.Expr, post ast.Stmt) { + if init == nil && post == nil { + // no semicolons required + if expr != nil { + p.print(blank); + p.expr(expr); + } + } else { + // all semicolons required + // (they are not separators, print them explicitly) + p.print(blank); + if init != nil { + p.stmt(init); + } + p.print(token.SEMICOLON, blank); + if expr != nil { + p.expr(expr); + } + if isForStmt { + p.print(token.SEMICOLON, blank); + if post != nil { + p.stmt(post); + } + } + } +} + + +// Returns true if a separating semicolon is optional. +func (p *printer) stmt(stmt ast.Stmt) (optSemi bool) { + p.print(stmt.Pos()); + + switch s := stmt.(type) { + case *ast.BadStmt: + p.print("BadStmt"); + + case *ast.DeclStmt: + optSemi = p.decl(s.Decl); + + case *ast.EmptyStmt: + // nothing to do + + case *ast.LabeledStmt: + p.print(-1, newline); + p.expr(s.Label); + p.print(token.COLON, tab, +1); + optSemi = p.stmt(s.Stmt); + + case *ast.ExprStmt: + p.expr(s.X); + + case *ast.IncDecStmt: + p.expr(s.X); + p.print(s.Tok); + + case *ast.AssignStmt: + p.exprList(s.Lhs); + p.print(blank, s.TokPos, s.Tok, blank); + p.exprList(s.Rhs); + + case *ast.GoStmt: + p.print(token.GO, blank); + p.expr(s.Call); + + case *ast.DeferStmt: + p.print(token.DEFER, blank); + p.expr(s.Call); + + case *ast.ReturnStmt: + p.print(token.RETURN); + if s.Results != nil { + p.print(blank); + p.exprList(s.Results); + } + + case *ast.BranchStmt: + p.print(s.Tok); + if s.Label != nil { + p.print(blank); + p.expr(s.Label); + } + + case *ast.BlockStmt: + p.block(s); + optSemi = true; + + case *ast.IfStmt: + p.print(token.IF); + p.controlClause(false, s.Init, s.Cond, nil); + p.print(blank); + p.block(s.Body); + optSemi = true; + if s.Else != nil { + p.print(blank, token.ELSE, blank); + optSemi = p.stmt(s.Else); + } + + case *ast.CaseClause: + if s.Values != nil { + p.print(token.CASE, blank); + p.exprList(s.Values); + } else { + p.print(token.DEFAULT); + } + p.print(s.Colon, token.COLON); + p.stmtList(s.Body); + + case *ast.SwitchStmt: + p.print(token.SWITCH); + p.controlClause(false, s.Init, s.Tag, nil); + p.print(blank); + p.switchBlock(s.Body); + optSemi = true; + + case *ast.TypeCaseClause: + if s.Type != nil { + p.print(token.CASE, blank); + p.expr(s.Type); + } else { + p.print(token.DEFAULT); + } + p.print(s.Colon, token.COLON); + p.stmtList(s.Body); + + case *ast.TypeSwitchStmt: + p.print(token.SWITCH); + if s.Init != nil { + p.print(blank); + p.stmt(s.Init); + p.print(token.SEMICOLON); + } + p.print(blank); + p.stmt(s.Assign); + p.print(blank); + p.switchBlock(s.Body); + optSemi = true; + + case *ast.CommClause: + if s.Rhs != nil { + p.print(token.CASE, blank); + if s.Lhs != nil { + p.expr(s.Lhs); + p.print(blank, s.Tok, blank); + } + p.expr(s.Rhs); + } else { + p.print(token.DEFAULT); + } + p.print(s.Colon, token.COLON); + p.stmtList(s.Body); + + case *ast.SelectStmt: + p.print(token.SELECT, blank); + p.switchBlock(s.Body); + optSemi = true; + + case *ast.ForStmt: + p.print(token.FOR); + p.controlClause(true, s.Init, s.Cond, s.Post); + p.print(blank); + p.block(s.Body); + optSemi = true; + + case *ast.RangeStmt: + p.print(token.FOR, blank); + p.expr(s.Key); + if s.Value != nil { + p.print(token.COMMA, blank); + p.expr(s.Value); + } + p.print(blank, s.TokPos, s.Tok, blank, token.RANGE, blank); + p.expr(s.X); + p.print(blank); + p.block(s.Body); + optSemi = true; + + default: + panic("unreachable"); + } + + return optSemi; +} + + +// ---------------------------------------------------------------------------- +// Declarations + +// Returns true if a separating semicolon is optional. +func (p *printer) spec(spec ast.Spec) (optSemi bool) { + switch s := spec.(type) { + case *ast.ImportSpec: + p.doc(s.Doc); + if s.Name != nil { + p.expr(s.Name); + } + // TODO fix for longer package names + p.print(tab, s.Path[0].Pos(), s.Path[0].Value); + + case *ast.ValueSpec: + p.doc(s.Doc); + p.identList(s.Names); + if s.Type != nil { + p.print(blank); // TODO switch to tab? (indent problem with structs) + p.expr(s.Type); + } + if s.Values != nil { + p.print(tab, token.ASSIGN, blank); + p.exprList(s.Values); + } + + case *ast.TypeSpec: + p.doc(s.Doc); + p.expr(s.Name); + p.print(blank); // TODO switch to tab? (but indent problem with structs) + optSemi = p.expr(s.Type); + + default: + panic("unreachable"); + } + + return optSemi; +} + + +// Returns true if a separating semicolon is optional. +func (p *printer) decl(decl ast.Decl) (optSemi bool) { + switch d := decl.(type) { + case *ast.BadDecl: + p.print(d.Pos(), "BadDecl"); + + case *ast.GenDecl: + p.doc(d.Doc); + p.print(d.Pos(), d.Tok, blank); + + if d.Lparen.IsValid() { + // group of parenthesized declarations + p.print(d.Lparen, token.LPAREN); + if p.isVisibleSpecList(d.Specs) { + p.print(+1, newline); + semi := false; + for _, s := range d.Specs { + if p.isVisibleSpec(s) { + if semi { + p.print(token.SEMICOLON, newline); + } + p.spec(s); + semi = true; + } + } + if p.optSemis() { + p.print(token.SEMICOLON); + } + p.print(-1, newline); + } + p.print(d.Rparen, token.RPAREN); + optSemi = true; + + } else { + // single declaration + optSemi = p.spec(d.Specs[0]); + } + + case *ast.FuncDecl: + p.level++; + p.doc(d.Doc); + p.print(d.Pos(), token.FUNC, blank); + if recv := d.Recv; recv != nil { + // method: print receiver + p.print(token.LPAREN); + if len(recv.Names) > 0 { + p.expr(recv.Names[0]); + p.print(blank); + } + p.expr(recv.Type); + p.print(token.RPAREN, blank); + } + p.expr(d.Name); + p.signature(d.Type.Params, d.Type.Results); + if !p.exportsOnly() && d.Body != nil { + p.print(blank); + p.stmt(d.Body); + } + p.level--; + + default: + panic("unreachable"); + } + + return optSemi; +} + + +// ---------------------------------------------------------------------------- +// Programs + +func (p *printer) program(prog *ast.Program) { + // set unassociated comments if all code is printed + if !p.exportsOnly() { + // TODO enable this once comments are properly interspersed + //p.setComments(prog.Comments); + } + + p.doc(prog.Doc); + p.print(prog.Pos(), token.PACKAGE, blank); + p.expr(prog.Name); + + for _, d := range prog.Decls { + if p.isVisibleDecl(d) { + p.print(newline, newline); + p.decl(d); + if p.optSemis() { + p.print(token.SEMICOLON); + } + } + } +} + + +// ---------------------------------------------------------------------------- +// Public interface + +// Fprint "pretty-prints" an AST node to output and returns the number of +// bytes written, and an error, if any. The node type must be *ast.Program, +// or assignment-compatible to ast.Expr, ast.Decl, or ast.Stmt. Printing is +// controlled by the mode parameter. For best results, the output should be +// a tabwriter.Writer. +// +func Fprint(output io.Writer, node interface{}, mode uint) (int, os.Error) { + var p printer; + p.init(output, mode); + + go func() { + switch n := node.(type) { + case ast.Expr: + p.expr(n); + case ast.Stmt: + p.stmt(n); + case ast.Decl: + p.decl(n); + case *ast.Program: + p.program(n); + default: + p.errors <- os.NewError("unsupported node type"); + } + p.print(newline); + p.errors <- nil; // no errors + }(); + err := <-p.errors; // wait for completion of goroutine + + return p.written, err; +} |
