Commit deb95477 authored by Robert Griesemer's avatar Robert Griesemer

- ast.FilterExports: strips all non-exported nodes from an AST

- use FilterExports instead of the various predicates in printer.go and doc.go
  which simplifies a lot of code and makes it easier to deal with complex cases

R=rsc
DELTA=445  (197 added, 190 deleted, 58 changed)
OCL=31110
CL=31196
parent cd4aab62
...@@ -198,9 +198,13 @@ func parse(path string, mode uint) (*ast.Program, *parseErrors) { ...@@ -198,9 +198,13 @@ func parse(path string, mode uint) (*ast.Program, *parseErrors) {
// Templates // Templates
// Return text for an AST node. // Return text for an AST node.
func nodeText(node interface{}, mode uint) []byte { func nodeText(node interface{}) []byte {
var buf bytes.Buffer; var buf bytes.Buffer;
tw := makeTabwriter(&buf); tw := makeTabwriter(&buf);
mode := uint(0);
if _, isProgram := node.(*ast.Program); isProgram {
mode = printer.DocComments;
}
printer.Fprint(tw, node, mode); printer.Fprint(tw, node, mode);
tw.Flush(); tw.Flush();
return buf.Data(); return buf.Data();
...@@ -219,9 +223,9 @@ func toText(x interface{}) []byte { ...@@ -219,9 +223,9 @@ func toText(x interface{}) []byte {
case String: case String:
return strings.Bytes(v.String()); return strings.Bytes(v.String());
case ast.Decl: case ast.Decl:
return nodeText(v, printer.ExportsOnly); return nodeText(v);
case ast.Expr: case ast.Expr:
return nodeText(v, printer.ExportsOnly); return nodeText(v);
} }
var buf bytes.Buffer; var buf bytes.Buffer;
fmt.Fprint(&buf, x); fmt.Fprint(&buf, x);
...@@ -331,7 +335,7 @@ func serveGoSource(c *http.Conn, name string) { ...@@ -331,7 +335,7 @@ func serveGoSource(c *http.Conn, name string) {
var buf bytes.Buffer; var buf bytes.Buffer;
fmt.Fprintln(&buf, "<pre>"); fmt.Fprintln(&buf, "<pre>");
template.HtmlEscape(&buf, nodeText(prog, printer.DocComments)); template.HtmlEscape(&buf, nodeText(prog));
fmt.Fprintln(&buf, "</pre>"); fmt.Fprintln(&buf, "</pre>");
servePage(c, name + " - Go source", buf.Data()); servePage(c, name + " - Go source", buf.Data());
...@@ -491,6 +495,7 @@ func (p *pakDesc) Doc() (*doc.PackageDoc, *parseErrors) { ...@@ -491,6 +495,7 @@ func (p *pakDesc) Doc() (*doc.PackageDoc, *parseErrors) {
r.Init(prog.Name.Value, p.importpath); r.Init(prog.Name.Value, p.importpath);
} }
i++; i++;
ast.FilterExports(prog); // we only care about exports
r.AddProgram(prog); r.AddProgram(prog);
} }
......
...@@ -7,6 +7,7 @@ package main ...@@ -7,6 +7,7 @@ package main
import ( import (
"flag"; "flag";
"fmt"; "fmt";
"go/ast";
"go/parser"; "go/parser";
"go/printer"; "go/printer";
"io"; "io";
...@@ -48,9 +49,6 @@ func parserMode() uint { ...@@ -48,9 +49,6 @@ func parserMode() uint {
func printerMode() uint { func printerMode() uint {
mode := uint(0); mode := uint(0);
if *exports {
mode |= printer.ExportsOnly;
}
if *optcommas { if *optcommas {
mode |= printer.OptCommas; mode |= printer.OptCommas;
} }
...@@ -100,6 +98,9 @@ func main() { ...@@ -100,6 +98,9 @@ func main() {
} }
if !*silent { if !*silent {
if *exports {
ast.FilterExports(prog); // ignore result
}
w := makeTabwriter(os.Stdout); w := makeTabwriter(os.Stdout);
printer.Fprint(w, prog, printerMode()); printer.Fprint(w, prog, printerMode());
w.Flush(); w.Flush();
......
...@@ -2,8 +2,9 @@ ...@@ -2,8 +2,9 @@
# Use of this source code is governed by a BSD-style # Use of this source code is governed by a BSD-style
# license that can be found in the LICENSE file. # license that can be found in the LICENSE file.
# DO NOT EDIT. Automatically generated by gobuild. # DO NOT EDIT. Automatically generated by gobuild.
# gobuild -m >Makefile # gobuild -m ast.go filter.go >Makefile
D=/go/ D=/go/
...@@ -20,7 +21,7 @@ test: packages ...@@ -20,7 +21,7 @@ test: packages
coverage: packages coverage: packages
gotest gotest
6cov -g `pwd` | grep -v '_test\.go:' 6cov -g $$(pwd) | grep -v '_test\.go:'
%.$O: %.go %.$O: %.go
$(GC) -I_obj $*.go $(GC) -I_obj $*.go
...@@ -34,14 +35,21 @@ coverage: packages ...@@ -34,14 +35,21 @@ coverage: packages
O1=\ O1=\
ast.$O\ ast.$O\
O2=\
filter.$O\
phases: a1 phases: a1 a2
_obj$D/ast.a: phases _obj$D/ast.a: phases
a1: $(O1) a1: $(O1)
$(AR) grc _obj$D/ast.a ast.$O $(AR) grc _obj$D/ast.a ast.$O
rm -f $(O1) rm -f $(O1)
a2: $(O2)
$(AR) grc _obj$D/ast.a filter.$O
rm -f $(O2)
newpkg: clean newpkg: clean
mkdir -p _obj$D mkdir -p _obj$D
...@@ -49,6 +57,7 @@ newpkg: clean ...@@ -49,6 +57,7 @@ newpkg: clean
$(O1): newpkg $(O1): newpkg
$(O2): a1 $(O2): a1
$(O3): a2
nuke: clean nuke: clean
rm -f $(GOROOT)/pkg/$(GOOS)_$(GOARCH)$D/ast.a rm -f $(GOROOT)/pkg/$(GOOS)_$(GOARCH)$D/ast.a
......
// 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 "go/ast"
func filterIdentList(list []*Ident) []*Ident {
j := 0;
for _, x := range list {
if x.IsExported() {
list[j] = x;
j++;
}
}
return list[0 : j];
}
func filterType(typ Expr)
func filterFieldList(list []*Field) []*Field {
j := 0;
for _, f := range list {
exported := false;
if len(f.Names) == 0 {
// anonymous field
// TODO(gri) check if the type is exported for anonymous field
exported = true;
} else {
f.Names = filterIdentList(f.Names);
exported = len(f.Names) > 0;
}
if exported {
filterType(f.Type);
list[j] = f;
j++;
}
}
return list[0 : j];
}
func filterType(typ Expr) {
switch t := typ.(type) {
case *ArrayType:
filterType(t.Elt);
case *StructType:
t.Fields = filterFieldList(t.Fields);
case *FuncType:
t.Params = filterFieldList(t.Params);
t.Results = filterFieldList(t.Results);
case *InterfaceType:
t.Methods = filterFieldList(t.Methods);
case *MapType:
filterType(t.Key);
filterType(t.Value);
case *ChanType:
filterType(t.Value);
}
}
func filterSpec(spec Spec) bool {
switch s := spec.(type) {
case *ValueSpec:
s.Names = filterIdentList(s.Names);
if len(s.Names) > 0 {
filterType(s.Type);
return true;
}
case *TypeSpec:
// TODO(gri) consider stripping forward declarations
// of structs, interfaces, functions, and methods
if s.Name.IsExported() {
filterType(s.Type);
return true;
}
}
return false;
}
func filterSpecList(list []Spec) []Spec {
j := 0;
for _, s := range list {
if filterSpec(s) {
list[j] = s;
j++;
}
}
return list[0 : j];
}
func filterDecl(decl Decl) bool {
switch d := decl.(type) {
case *GenDecl:
d.Specs = filterSpecList(d.Specs);
return len(d.Specs) > 0;
case *FuncDecl:
// TODO consider removing function declaration altogether if
// forward declaration (i.e., if d.Body == nil) because
// in that case the actual declaration will come later.
d.Body = nil; // strip body
return d.Name.IsExported();
}
return false;
}
// FilterExports trims an AST in place such that only exported nodes remain:
// all top-level identififiers which are not exported and their associated
// information (such as type, initial value, or function body) are removed.
// Non-exported fields and methods of exported types are stripped, and the
// function bodies of exported functions are set to nil.
//
// FilterExports returns true if there is an exported declaration; it returns
// false otherwise.
//
func FilterExports(prog *Program) bool {
j := 0;
for _, d := range prog.Decls {
if filterDecl(d) {
prog.Decls[j] = d;
j++;
}
}
prog.Decls = prog.Decls[0 : j];
prog.Comments = nil; // remove unassociated comments
return j > 0;
}
...@@ -17,28 +17,6 @@ import ( ...@@ -17,28 +17,6 @@ import (
) )
// ----------------------------------------------------------------------------
// Elementary support
func hasExportedNames(names []*ast.Ident) bool {
for i, name := range names {
if name.IsExported() {
return true;
}
}
return false;
}
func hasExportedSpecs(specs []ast.Spec) bool {
for i, s := range specs {
// only called for []astSpec lists of *ast.ValueSpec
return hasExportedNames(s.(*ast.ValueSpec).Names);
}
return false;
}
// ---------------------------------------------------------------------------- // ----------------------------------------------------------------------------
type typeDoc struct { type typeDoc struct {
...@@ -149,33 +127,25 @@ func (doc *DocReader) addDecl(decl ast.Decl) { ...@@ -149,33 +127,25 @@ func (doc *DocReader) addDecl(decl ast.Decl) {
// ignore // ignore
case token.CONST: case token.CONST:
// constants are always handled as a group // constants are always handled as a group
if hasExportedSpecs(d.Specs) { doc.consts.Push(d);
doc.consts.Push(d);
}
case token.TYPE: case token.TYPE:
// types are handled individually // types are handled individually
var noPos token.Position;
for i, spec := range d.Specs { for i, spec := range d.Specs {
// make a (fake) GenDecl node for this TypeSpec
// (we need to do this here - as opposed to just
// for printing - so we don't lose the GenDecl
// documentation)
s := spec.(*ast.TypeSpec); s := spec.(*ast.TypeSpec);
if s.Name.IsExported() { doc.addType(&ast.GenDecl{d.Doc, d.Pos(), token.TYPE, noPos, []ast.Spec{s}, noPos});
// make a (fake) GenDecl node for this TypeSpec
// (we need to do this here - as opposed to just
// for printing - so we don't loose the GenDecl
// documentation)
var noPos token.Position;
doc.addType(&ast.GenDecl{d.Doc, d.Pos(), token.TYPE, noPos, []ast.Spec{s}, noPos});
}
} }
case token.VAR: case token.VAR:
// variables are always handled as a group // variables are always handled as a group
if hasExportedSpecs(d.Specs) { doc.vars.Push(d);
doc.vars.Push(d);
}
} }
} }
case *ast.FuncDecl: case *ast.FuncDecl:
if d.Name.IsExported() { doc.addFunc(d);
doc.addFunc(d);
}
} }
} }
...@@ -194,7 +164,7 @@ func (doc *DocReader) AddProgram(prog *ast.Program) { ...@@ -194,7 +164,7 @@ func (doc *DocReader) AddProgram(prog *ast.Program) {
doc.doc = prog.Doc doc.doc = prog.Doc
} }
// add all exported declarations // add all declarations
for i, decl := range prog.Decls { for i, decl := range prog.Decls {
doc.addDecl(decl); doc.addDecl(decl);
} }
......
...@@ -20,8 +20,7 @@ import ( ...@@ -20,8 +20,7 @@ import (
// to Fprint via the mode parameter. // to Fprint via the mode parameter.
// //
const ( const (
ExportsOnly uint = 1 << iota; // print exported code only DocComments uint = 1 << iota; // print documentation comments
DocComments; // print documentation comments
OptCommas; // print optional commas OptCommas; // print optional commas
OptSemis; // print optional semicolons OptSemis; // print optional semicolons
) )
...@@ -181,97 +180,13 @@ func (p *printer) print(args ...) { ...@@ -181,97 +180,13 @@ func (p *printer) print(args ...) {
// ---------------------------------------------------------------------------- // ----------------------------------------------------------------------------
// Predicates // Printing of common AST nodes.
func (p *printer) optSemis() bool { func (p *printer) optSemis() bool {
return p.mode & OptSemis != 0; 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) { func (p *printer) comment(c *ast.Comment) {
if c != nil { if c != nil {
text := c.Text; text := c.Text;
...@@ -297,15 +212,11 @@ func (p *printer) doc(d ast.Comments) { ...@@ -297,15 +212,11 @@ func (p *printer) doc(d ast.Comments) {
func (p *printer) expr(x ast.Expr) bool func (p *printer) expr(x ast.Expr) bool
func (p *printer) identList(list []*ast.Ident) { func (p *printer) identList(list []*ast.Ident) {
needsComma := false;
for i, x := range list { for i, x := range list {
if p.isVisibleIdent(x) { if i > 0 {
if needsComma { p.print(token.COMMA, blank);
p.print(token.COMMA, blank);
}
p.expr(x);
needsComma = true;
} }
p.expr(x);
} }
} }
...@@ -362,83 +273,73 @@ func (p *printer) signature(params, result []*ast.Field) { ...@@ -362,83 +273,73 @@ func (p *printer) signature(params, result []*ast.Field) {
// Returns true if the field list ends in a closing brace. // 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 { func (p *printer) fieldList(lbrace token.Position, list []*ast.Field, rbrace token.Position, isInterface bool) bool {
hasBody := p.isVisibleFieldList(list); if !lbrace.IsValid() {
if !lbrace.IsValid() || p.exportsOnly() && !hasBody { // forward declaration without {}'s
// 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 return false; // no {}'s
} }
p.print(blank, lbrace, token.LBRACE); if len(list) == 0 {
p.print(blank, lbrace, token.LBRACE, rbrace, token.RBRACE);
return true; // empty list with {}'s
}
if hasBody { p.print(blank, lbrace, token.LBRACE, +1, newline);
p.print(+1, newline);
var needsSemi bool; var lastWasAnon bool; // true if the previous line was an anonymous field
var lastWasAnon bool; // true if the previous line was an anonymous field var lastComment *ast.Comment; // the comment from the previous line
var lastComment *ast.Comment; // the comment from the previous line for i, f := range list {
for _, f := range list { // at least one visible identifier or anonymous field
hasNames := p.isVisibleIdentList(f.Names); isAnon := len(f.Names) == 0;
isAnon := len(f.Names) == 0; if i > 0 {
p.print(token.SEMICOLON);
if hasNames || isAnon { p.comment(lastComment);
// at least one visible identifier or anonymous field if lastWasAnon == isAnon {
// TODO this is conservative - see isVisibleFieldList // previous and current line have same structure;
if needsSemi { // continue with existing columns
p.print(token.SEMICOLON); p.print(newline);
p.comment(lastComment); } else {
if lastWasAnon == isAnon { // previous and current line have different structure;
// previous and current line have same structure; // flush tabwriter and start new columns (the "type
// continue with existing columns // column" on a line with named fields may line up
p.print(newline); // with the "trailing comment column" on a line with
} else { // an anonymous field, leading to bad alignment)
// previous and current line have different structure; p.print(formfeed);
// 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 { p.doc(f.Doc);
if ftyp, isFtyp := f.Type.(*ast.FuncType); isFtyp { if !isAnon {
// methods p.identList(f.Names);
p.signature(ftyp.Params, ftyp.Results); p.print(tab);
} 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; if isInterface {
lastWasAnon = isAnon; if ftyp, isFtyp := f.Type.(*ast.FuncType); isFtyp {
lastComment = f.Comment; // methods
p.signature(ftyp.Params, ftyp.Results);
} else {
// embedded interface
p.expr(f.Type);
}
} else {
p.expr(f.Type);
if f.Tag != nil {
p.print(tab);
p.expr(&ast.StringList{f.Tag});
} }
} }
if p.optSemis() { lastWasAnon = isAnon;
p.print(token.SEMICOLON); lastComment = f.Comment;
} }
p.comment(lastComment); if p.optSemis() {
p.print(-1, newline); p.print(token.SEMICOLON);
} }
p.comment(lastComment);
p.print(-1, newline, rbrace, token.RBRACE);
p.print(rbrace, token.RBRACE);
return true; // field list with {}'s return true; // field list with {}'s
} }
...@@ -905,25 +806,17 @@ func (p *printer) decl(decl ast.Decl) (optSemi bool) { ...@@ -905,25 +806,17 @@ func (p *printer) decl(decl ast.Decl) (optSemi bool) {
if d.Lparen.IsValid() { if d.Lparen.IsValid() {
// group of parenthesized declarations // group of parenthesized declarations
p.print(d.Lparen, token.LPAREN); p.print(d.Lparen, token.LPAREN, +1, newline);
if p.isVisibleSpecList(d.Specs) { for i, s := range d.Specs {
p.print(+1, newline); if i > 0 {
semi := false; p.print(token.SEMICOLON, newline);
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.spec(s);
} }
p.print(d.Rparen, token.RPAREN); if p.optSemis() {
p.print(token.SEMICOLON);
}
p.print(-1, newline, d.Rparen, token.RPAREN);
optSemi = true; optSemi = true;
} else { } else {
...@@ -946,7 +839,7 @@ func (p *printer) decl(decl ast.Decl) (optSemi bool) { ...@@ -946,7 +839,7 @@ func (p *printer) decl(decl ast.Decl) (optSemi bool) {
} }
p.expr(d.Name); p.expr(d.Name);
p.signature(d.Type.Params, d.Type.Results); p.signature(d.Type.Params, d.Type.Results);
if !p.exportsOnly() && d.Body != nil { if d.Body != nil {
p.print(blank); p.print(blank);
p.level++; // adjust nesting level for function body p.level++; // adjust nesting level for function body
p.stmt(d.Body); p.stmt(d.Body);
...@@ -965,23 +858,19 @@ func (p *printer) decl(decl ast.Decl) (optSemi bool) { ...@@ -965,23 +858,19 @@ func (p *printer) decl(decl ast.Decl) (optSemi bool) {
// Programs // Programs
func (p *printer) program(prog *ast.Program) { func (p *printer) program(prog *ast.Program) {
// set unassociated comments if all code is printed // set unassociated comments
if !p.exportsOnly() { // TODO enable this once comments are properly interspersed
// TODO enable this once comments are properly interspersed // p.setComments(prog.Comments);
//p.setComments(prog.Comments);
}
p.doc(prog.Doc); p.doc(prog.Doc);
p.print(prog.Pos(), token.PACKAGE, blank); p.print(prog.Pos(), token.PACKAGE, blank);
p.expr(prog.Name); p.expr(prog.Name);
for _, d := range prog.Decls { for _, d := range prog.Decls {
if p.isVisibleDecl(d) { p.print(newline, newline);
p.print(newline, newline); p.decl(d);
p.decl(d); if p.optSemis() {
if p.optSemis() { p.print(token.SEMICOLON);
p.print(token.SEMICOLON);
}
} }
} }
......
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