go/ast/filter.go:
- more orthogonal functionality of filter functions for better re-use

go/doc/doc.go:
- simplified interface
- collect filenames of packages so that they can be shown

godoc:
- removed TODO, show list of package (linked) files used to create documentation

R=rsc
DELTA=130  (68 added, 24 deleted, 38 changed)
OCL=32549
CL=32552
diff --git a/lib/godoc/package.html b/lib/godoc/package.html
index 0f1b045..4931035 100644
--- a/lib/godoc/package.html
+++ b/lib/godoc/package.html
@@ -1,15 +1,24 @@
 {.section Dirs}
 	<h2>Subdirectories</h2>
 	{.repeated section @}
-		<a href="{Name|html}/">{Name|html}</a><br />
+		<a href="{Name|html}">{Name|html}</a><br />
 	{.end}
 	<hr />
 {.end}
 {.section PDoc}
 	<h1>package {PackageName|html}</h1>
 	<p><code>import "{ImportPath|html}"</code></p>
-
 	{Doc|html-comment}
+	{.section Filenames}
+		<p>
+		<h4>Package files</h4>
+		<font size=-1>
+		{.repeated section @}
+			<a href="/{FilePath|html}/{@|html}">{@|html}</a>
+		{.end}
+		</font>
+		</p>
+	{.end}
 	{.section Consts}
 		<h2>Constants</h2>
 		{.repeated section @}
diff --git a/src/cmd/godoc/godoc.go b/src/cmd/godoc/godoc.go
index d1c1f15..4fe628f 100644
--- a/src/cmd/godoc/godoc.go
+++ b/src/cmd/godoc/godoc.go
@@ -444,12 +444,8 @@
 	// compute package documentation
 	var pdoc *doc.PackageDoc;
 	if pkg != nil {
-		// TODO(gri) Simplify DocReader interface: no need anymore to add
-		//           more than one file because of ast.PackageInterface.
-		var r doc.DocReader;
-		r.Init(pkg.Name, pathutil.Clean(path));  // no trailing '/' in importpath
-		r.AddFile(ast.PackageExports(pkg));
-		pdoc = r.Doc();
+		ast.PackageExports(pkg);
+		pdoc = doc.NewPackageDoc(pkg, pathutil.Clean(path));  // no trailing '/' in importpath
 	}
 
 	return PageInfo{pdoc, subdirs};
diff --git a/src/cmd/gofmt/gofmt.go b/src/cmd/gofmt/gofmt.go
index 9104583..9d27386 100644
--- a/src/cmd/gofmt/gofmt.go
+++ b/src/cmd/gofmt/gofmt.go
@@ -146,8 +146,8 @@
 	if !*silent {
 		w := makeTabwriter(os.Stdout);
 		if *exports {
-			src := ast.PackageExports(pkg);
-			printer.Fprint(w, src, printerMode());  // ignore errors
+			ast.PackageExports(pkg);
+			printer.Fprint(w, ast.MergePackageFiles(pkg), printerMode());  // ignore errors
 		} else {
 			for _, src := range pkg.Files {
 				printer.Fprint(w, src, printerMode());  // ignore errors
diff --git a/src/pkg/go/ast/filter.go b/src/pkg/go/ast/filter.go
index 28277af..94cd28e 100644
--- a/src/pkg/go/ast/filter.go
+++ b/src/pkg/go/ast/filter.go
@@ -168,16 +168,17 @@
 }
 
 
-// FilterExports trims an AST in place such that only exported nodes remain:
-// all top-level identifiers 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.
+// FileExports trims the AST for a Go source file in place such that only
+// exported nodes remain: all top-level identifiers 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.
+// The File.comments list is not changed.
 //
-// FilterExports returns true if there is an exported declaration; it returns
+// FileExports returns true if there is an exported declaration; it returns
 // false otherwise.
 //
-func FilterExports(src *File) bool {
+func FileExports(src *File) bool {
 	j := 0;
 	for _, d := range src.Decls {
 		if filterDecl(d) {
@@ -190,33 +191,44 @@
 }
 
 
+// PackageExports trims the AST for a Go package in place such that only
+// exported nodes remain. The pkg.Files list is not changed, so that file
+// names and top-level package comments don't get lost.
+//
+// PackageExports returns true if there is an exported declaration; it
+// returns false otherwise.
+//
+func PackageExports(pkg *Package) bool {
+	hasExports := false;
+	for _, f := range pkg.Files {
+		if FileExports(f) {
+			hasExports = true;
+		}
+	}
+	return hasExports;
+}
+
+
 // separator is an empty //-style comment that is interspersed between
 // different comment groups when they are concatenated into a single group
 //
 var separator = &Comment{noPos, []byte{'/', '/'}};
 
 
-// PackageExports returns an AST containing only the exported declarations
-// of the package pkg. PackageExports modifies the pkg AST.
+// MergePackageFiles creates a file AST by merging the ASTs of the
+// files belonging to a package.
 //
-func PackageExports(pkg *Package) *File {
-	// Collect all source files with exported declarations and count
-	// the number of package comments and declarations in all files.
-	files := make([]*File, len(pkg.Files));
+func MergePackageFiles(pkg *Package) *File {
+	// Count the number of package comments and declarations across
+	// all package files.
 	ncomments := 0;
 	ndecls := 0;
-	i := 0;
 	for _, f := range pkg.Files {
 		if f.Doc != nil {
 			ncomments += len(f.Doc.List) + 1;  // +1 for separator
 		}
-		if FilterExports(f) {
-			ndecls += len(f.Decls);
-			files[i] = f;
-			i++;
-		}
+		ndecls += len(f.Decls);
 	}
-	files = files[0 : i];
 
 	// Collect package comments from all package files into a single
 	// CommentGroup - the collected package documentation. The order
@@ -243,12 +255,12 @@
 		doc = &CommentGroup{list, nil};
 	}
 
-	// Collect exported declarations from all package files.
+	// Collect declarations from all package files.
 	var decls []Decl;
 	if ndecls > 0 {
 		decls = make([]Decl, ndecls);
 		i := 0;
-		for _, f := range files {
+		for _, f := range pkg.Files {
 			for _, d := range f.Decls {
 				decls[i] = d;
 				i++;
@@ -256,5 +268,8 @@
 		}
 	}
 
+	// TODO(gri) Should collect comments as well. For that the comment
+	//           list should be changed back into a []*CommentGroup,
+	//           otherwise need to modify the existing linked list.
 	return &File{doc, noPos, &Ident{noPos, pkg.Name}, decls, nil};
 }
diff --git a/src/pkg/go/doc/doc.go b/src/pkg/go/doc/doc.go
index 1675353..287677a 100644
--- a/src/pkg/go/doc/doc.go
+++ b/src/pkg/go/doc/doc.go
@@ -26,16 +26,14 @@
 }
 
 
-// DocReader accumulates documentation for a single package.
+// docReader accumulates documentation for a single package.
 // It modifies the AST: Comments (declaration documentation)
 // that have been collected by the DocReader are set to nil
 // in the respective AST nodes so that they are not printed
 // twice (once when printing the documentation and once when
 // printing the corresponding AST node).
 //
-type DocReader struct {
-	name string;  // package name
-	path string;  // import path
+type docReader struct {
 	doc *ast.CommentGroup;  // package documentation, if any
 	consts *vector.Vector;  // list of *ast.GenDecl
 	types map[string] *typeDoc;
@@ -45,12 +43,7 @@
 }
 
 
-// Init initializes a DocReader to collect package documentation
-// for the package with the given package name and import path.
-//
-func (doc *DocReader) Init(pkg, imp string) {
-	doc.name = pkg;
-	doc.path = imp;
+func (doc *docReader) init() {
 	doc.consts = vector.New(0);
 	doc.types = make(map[string] *typeDoc);
 	doc.vars = vector.New(0);
@@ -70,7 +63,7 @@
 }
 
 
-func (doc *DocReader) lookupTypeDoc(typ ast.Expr) *typeDoc {
+func (doc *docReader) lookupTypeDoc(typ ast.Expr) *typeDoc {
 	tdoc, found := doc.types[baseTypeName(typ)];
 	if found {
 		return tdoc;
@@ -79,7 +72,7 @@
 }
 
 
-func (doc *DocReader) addType(decl *ast.GenDecl) {
+func (doc *docReader) addType(decl *ast.GenDecl) {
 	typ := decl.Specs[0].(*ast.TypeSpec);
 	name := typ.Name.Value;
 	if _, found := doc.types[name]; !found {
@@ -91,7 +84,7 @@
 }
 
 
-func (doc *DocReader) addFunc(fun *ast.FuncDecl) {
+func (doc *docReader) addFunc(fun *ast.FuncDecl) {
 	name := fun.Name.Value;
 
 	// determine if it should be associated with a type
@@ -131,7 +124,7 @@
 }
 
 
-func (doc *DocReader) addDecl(decl ast.Decl) {
+func (doc *docReader) addDecl(decl ast.Decl) {
 	switch d := decl.(type) {
 	case *ast.GenDecl:
 		if len(d.Specs) > 0 {
@@ -186,22 +179,22 @@
 )
 
 
-// AddFile adds the AST for a source file to the DocReader.
+// addFile adds the AST for a source file to the docReader.
 // Adding the same AST multiple times is a no-op.
 //
-func (doc *DocReader) AddFile(src *ast.File) {
+func (doc *docReader) addFile(src *ast.File) {
 	if bug_markers == nil {
 		bug_markers = makeRex("^/[/*][ \t]*BUG\\(.*\\):[ \t]*");  // BUG(uid):
 		bug_content = makeRex("[^ \n\r\t]+");  // at least one non-whitespace char
 	}
 
-	if doc.name != src.Name.Value {
-		panic("package names don't match");
-	}
-
 	// add package documentation
-	// TODO(gri) what to do if there are multiple files?
 	if src.Doc != nil {
+		// TODO(gri) This won't do the right thing if there is more
+		//           than one file with package comments. Consider
+		//           using ast.MergePackageFiles which handles these
+		//           comments correctly (but currently looses BUG(...)
+		//           comments).
 		doc.doc = src.Doc;
 		src.Doc = nil;  // doc consumed - remove from ast.File node
 	}
@@ -228,6 +221,32 @@
 	src.Comments = nil;  // consumed unassociated comments - remove from ast.File node
 }
 
+
+type PackageDoc struct
+func (doc *docReader) newDoc(pkgname, importpath, filepath string, filenames []string) *PackageDoc
+
+func NewFileDoc(file *ast.File) *PackageDoc {
+	var r docReader;
+	r.init();
+	r.addFile(file);
+	return r.newDoc(file.Name.Value, "", "", nil);
+}
+
+
+func NewPackageDoc(pkg *ast.Package, importpath string) *PackageDoc {
+	var r docReader;
+	r.init();
+	filenames := make([]string, len(pkg.Files));
+	i := 0;
+	for filename, f := range pkg.Files {
+		r.addFile(f);
+		filenames[i] = filename;
+		i++;
+	}
+	return r.newDoc(pkg.Name, importpath, pkg.Path, filenames);
+}
+
+
 // ----------------------------------------------------------------------------
 // Conversion to external representation
 
@@ -402,6 +421,8 @@
 type PackageDoc struct {
 	PackageName string;
 	ImportPath string;
+	FilePath string;
+	Filenames []string;
 	Doc string;
 	Consts []*ValueDoc;
 	Types []*TypeDoc;
@@ -411,12 +432,15 @@
 }
 
 
-// Doc returns the accumulated documentation for the package.
+// newDoc returns the accumulated documentation for the package.
 //
-func (doc *DocReader) Doc() *PackageDoc {
+func (doc *docReader) newDoc(pkgname, importpath, filepath string, filenames []string) *PackageDoc {
 	p := new(PackageDoc);
-	p.PackageName = doc.name;
-	p.ImportPath = doc.path;
+	p.PackageName = pkgname;
+	p.ImportPath = importpath;
+	p.FilePath = filepath;
+	sort.SortStrings(filenames);
+	p.Filenames = filenames;
 	p.Doc = astComment(doc.doc);
 	p.Consts = makeValueDocs(doc.consts);
 	p.Vars = makeValueDocs(doc.vars);
diff --git a/src/pkg/go/printer/printer_test.go b/src/pkg/go/printer/printer_test.go
index 42996dc..8f047c9 100644
--- a/src/pkg/go/printer/printer_test.go
+++ b/src/pkg/go/printer/printer_test.go
@@ -48,7 +48,7 @@
 
 	// filter exports if necessary
 	if exports {
-		ast.FilterExports(prog);  // ignore result
+		ast.FileExports(prog);  // ignore result
 		prog.Comments = nil;  // don't print comments that are not in AST
 	}