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) {
// Templates
// Return text for an AST node.
func nodeText(node interface{}, mode uint) []byte {
func nodeText(node interface{}) []byte {
var buf bytes.Buffer;
tw := makeTabwriter(&buf);
mode := uint(0);
if _, isProgram := node.(*ast.Program); isProgram {
mode = printer.DocComments;
}
printer.Fprint(tw, node, mode);
tw.Flush();
return buf.Data();
......@@ -219,9 +223,9 @@ func toText(x interface{}) []byte {
case String:
return strings.Bytes(v.String());
case ast.Decl:
return nodeText(v, printer.ExportsOnly);
return nodeText(v);
case ast.Expr:
return nodeText(v, printer.ExportsOnly);
return nodeText(v);
}
var buf bytes.Buffer;
fmt.Fprint(&buf, x);
......@@ -331,7 +335,7 @@ func serveGoSource(c *http.Conn, name string) {
var buf bytes.Buffer;
fmt.Fprintln(&buf, "<pre>");
template.HtmlEscape(&buf, nodeText(prog, printer.DocComments));
template.HtmlEscape(&buf, nodeText(prog));
fmt.Fprintln(&buf, "</pre>");
servePage(c, name + " - Go source", buf.Data());
......@@ -491,6 +495,7 @@ func (p *pakDesc) Doc() (*doc.PackageDoc, *parseErrors) {
r.Init(prog.Name.Value, p.importpath);
}
i++;
ast.FilterExports(prog); // we only care about exports
r.AddProgram(prog);
}
......
......@@ -7,6 +7,7 @@ package main
import (
"flag";
"fmt";
"go/ast";
"go/parser";
"go/printer";
"io";
......@@ -48,9 +49,6 @@ func parserMode() uint {
func printerMode() uint {
mode := uint(0);
if *exports {
mode |= printer.ExportsOnly;
}
if *optcommas {
mode |= printer.OptCommas;
}
......@@ -100,6 +98,9 @@ func main() {
}
if !*silent {
if *exports {
ast.FilterExports(prog); // ignore result
}
w := makeTabwriter(os.Stdout);
printer.Fprint(w, prog, printerMode());
w.Flush();
......
......@@ -2,8 +2,9 @@
# 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
# gobuild -m ast.go filter.go >Makefile
D=/go/
......@@ -20,7 +21,7 @@ test: packages
coverage: packages
gotest
6cov -g `pwd` | grep -v '_test\.go:'
6cov -g $$(pwd) | grep -v '_test\.go:'
%.$O: %.go
$(GC) -I_obj $*.go
......@@ -34,14 +35,21 @@ coverage: packages
O1=\
ast.$O\
O2=\
filter.$O\
phases: a1
phases: a1 a2
_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 filter.$O
rm -f $(O2)
newpkg: clean
mkdir -p _obj$D
......@@ -49,6 +57,7 @@ newpkg: clean
$(O1): newpkg
$(O2): a1
$(O3): a2
nuke: clean
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 (
)
// ----------------------------------------------------------------------------
// 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 {
......@@ -149,33 +127,25 @@ func (doc *DocReader) addDecl(decl ast.Decl) {
// ignore
case token.CONST:
// constants are always handled as a group
if hasExportedSpecs(d.Specs) {
doc.consts.Push(d);
}
doc.consts.Push(d);
case token.TYPE:
// types are handled individually
var noPos token.Position;
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);
if s.Name.IsExported() {
// 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});
}
doc.addType(&ast.GenDecl{d.Doc, d.Pos(), token.TYPE, noPos, []ast.Spec{s}, noPos});
}
case token.VAR:
// variables are always handled as a group
if hasExportedSpecs(d.Specs) {
doc.vars.Push(d);
}
doc.vars.Push(d);
}
}
case *ast.FuncDecl:
if d.Name.IsExported() {
doc.addFunc(d);
}
doc.addFunc(d);
}
}
......@@ -194,7 +164,7 @@ func (doc *DocReader) AddProgram(prog *ast.Program) {
doc.doc = prog.Doc
}
// add all exported declarations
// add all declarations
for i, decl := range prog.Decls {
doc.addDecl(decl);
}
......
......@@ -20,8 +20,7 @@ import (
// to Fprint via the mode parameter.
//
const (
ExportsOnly uint = 1 << iota; // print exported code only
DocComments; // print documentation comments
DocComments uint = 1 << iota; // print documentation comments
OptCommas; // print optional commas
OptSemis; // print optional semicolons
)
......@@ -181,97 +180,13 @@ func (p *printer) print(args ...) {
// ----------------------------------------------------------------------------
// Predicates
// Printing of common AST nodes.
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;
......@@ -297,15 +212,11 @@ func (p *printer) doc(d ast.Comments) {
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;
if i > 0 {
p.print(token.COMMA, blank);
}
p.expr(x);
}
}
......@@ -362,83 +273,73 @@ func (p *printer) signature(params, result []*ast.Field) {
// 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)
if !lbrace.IsValid() {
// forward declaration without {}'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(+1, newline);
p.print(blank, lbrace, token.LBRACE, +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);
}
var lastWasAnon bool; // true if the previous line was an anonymous field
var lastComment *ast.Comment; // the comment from the previous line
for i, f := range list {
// at least one visible identifier or anonymous field
isAnon := len(f.Names) == 0;
if i > 0 {
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);
}
}
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});
}
}
p.doc(f.Doc);
if !isAnon {
p.identList(f.Names);
p.print(tab);
}
needsSemi = true;
lastWasAnon = isAnon;
lastComment = f.Comment;
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.print(tab);
p.expr(&ast.StringList{f.Tag});
}
}
if p.optSemis() {
p.print(token.SEMICOLON);
}
lastWasAnon = isAnon;
lastComment = f.Comment;
}
p.comment(lastComment);
p.print(-1, newline);
if p.optSemis() {
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
}
......@@ -905,25 +806,17 @@ func (p *printer) decl(decl ast.Decl) (optSemi bool) {
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(d.Lparen, token.LPAREN, +1, newline);
for i, s := range d.Specs {
if i > 0 {
p.print(token.SEMICOLON, newline);
}
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;
} else {
......@@ -946,7 +839,7 @@ func (p *printer) decl(decl ast.Decl) (optSemi bool) {
}
p.expr(d.Name);
p.signature(d.Type.Params, d.Type.Results);
if !p.exportsOnly() && d.Body != nil {
if d.Body != nil {
p.print(blank);
p.level++; // adjust nesting level for function body
p.stmt(d.Body);
......@@ -965,23 +858,19 @@ func (p *printer) decl(decl ast.Decl) (optSemi bool) {
// 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);
}
// set unassociated comments
// 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);
}
p.print(newline, newline);
p.decl(d);
if p.optSemis() {
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