diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/lib/go/Makefile | 9 | ||||
-rw-r--r-- | src/lib/go/doc.go | 562 |
2 files changed, 569 insertions, 2 deletions
diff --git a/src/lib/go/Makefile b/src/lib/go/Makefile index bfe1cede3..5e56c932e 100644 --- a/src/lib/go/Makefile +++ b/src/lib/go/Makefile @@ -47,11 +47,13 @@ O2=\ ast.$O\ O3=\ + doc.$O\ parser.$O\ phases: a1 a2 a3 _obj$D/ast.a: phases +_obj$D/doc.a: phases _obj$D/parser.a: phases _obj$D/scanner.a: phases _obj$D/token.a: phases @@ -66,6 +68,7 @@ a2: $(O2) rm -f $(O2) a3: $(O3) + $(AR) grc _obj$D/doc.a doc.$O $(AR) grc _obj$D/parser.a parser.$O rm -f $(O3) @@ -73,6 +76,7 @@ a3: $(O3) newpkg: clean mkdir -p _obj$D $(AR) grc _obj$D/ast.a + $(AR) grc _obj$D/doc.a $(AR) grc _obj$D/parser.a $(AR) grc _obj$D/scanner.a $(AR) grc _obj$D/token.a @@ -83,13 +87,14 @@ $(O3): a2 $(O4): a3 nuke: clean - rm -f $(GOROOT)/pkg$D/ast.a $(GOROOT)/pkg$D/parser.a $(GOROOT)/pkg$D/scanner.a $(GOROOT)/pkg$D/token.a + rm -f $(GOROOT)/pkg$D/ast.a $(GOROOT)/pkg$D/doc.a $(GOROOT)/pkg$D/parser.a $(GOROOT)/pkg$D/scanner.a $(GOROOT)/pkg$D/token.a -packages: _obj$D/ast.a _obj$D/parser.a _obj$D/scanner.a _obj$D/token.a +packages: _obj$D/ast.a _obj$D/doc.a _obj$D/parser.a _obj$D/scanner.a _obj$D/token.a install: packages test -d $(GOROOT)/pkg && mkdir -p $(GOROOT)/pkg$D cp _obj$D/ast.a $(GOROOT)/pkg$D/ast.a + cp _obj$D/doc.a $(GOROOT)/pkg$D/doc.a cp _obj$D/parser.a $(GOROOT)/pkg$D/parser.a cp _obj$D/scanner.a $(GOROOT)/pkg$D/scanner.a cp _obj$D/token.a $(GOROOT)/pkg$D/token.a diff --git a/src/lib/go/doc.go b/src/lib/go/doc.go new file mode 100644 index 000000000..e20db694f --- /dev/null +++ b/src/lib/go/doc.go @@ -0,0 +1,562 @@ +// 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 doc + +import ( + "container/vector"; + "fmt"; + "go/ast"; + "go/token"; + "io"; + "once"; + "regexp"; + "sort"; + "strings"; +) + + +// ---------------------------------------------------------------------------- +// 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 { + decl *ast.GenDecl; // len(decl.Specs) == 1, and the element type is *ast.TypeSpec + factories map[string] *ast.FuncDecl; + methods map[string] *ast.FuncDecl; +} + + +// DocReader accumulates documentation for a single package. +// +type DocReader struct { + name string; // package name + path string; // import path + doc ast.Comments; // package documentation, if any + consts *vector.Vector; // list of *ast.GenDecl + types map[string] *typeDoc; + vars *vector.Vector; // list of *ast.GenDecl + funcs map[string] *ast.FuncDecl; +} + + +// 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; + doc.consts = vector.New(0); + doc.types = make(map[string] *typeDoc); + doc.vars = vector.New(0); + doc.funcs = make(map[string] *ast.FuncDecl); +} + + +func baseTypeName(typ ast.Expr) string { + switch t := typ.(type) { + case *ast.Ident: + return string(t.Value); + case *ast.StarExpr: + return baseTypeName(t.X); + } + return ""; +} + + +func (doc *DocReader) lookupTypeDoc(typ ast.Expr) *typeDoc { + tdoc, found := doc.types[baseTypeName(typ)]; + if found { + return tdoc; + } + return nil; +} + + +func (doc *DocReader) addType(decl *ast.GenDecl) { + typ := decl.Specs[0].(*ast.TypeSpec); + name := typ.Name.Value; + tdoc := &typeDoc{decl, make(map[string] *ast.FuncDecl), make(map[string] *ast.FuncDecl)}; + doc.types[name] = tdoc; +} + + +func (doc *DocReader) addFunc(fun *ast.FuncDecl) { + name := fun.Name.Value; + + // determine if it should be associated with a type + var typ *typeDoc; + if fun.Recv != nil { + // method + // (all receiver types must be declared before they are used) + typ = doc.lookupTypeDoc(fun.Recv.Type); + if typ != nil { + // type found (i.e., exported) + typ.methods[name] = fun; + } + // if the type wasn't found, it wasn't exported + // TODO(gri): a non-exported type may still have exported functions + // determine what to do in that case + return; + } + + // perhaps a factory function + // determine result type, if any + if len(fun.Type.Results) >= 1 { + res := fun.Type.Results[0]; + if len(res.Names) <= 1 { + // exactly one (named or anonymous) result type + typ = doc.lookupTypeDoc(res.Type); + if typ != nil { + typ.factories[name] = fun; + return; + } + } + } + + // ordinary function + doc.funcs[name] = fun; +} + + +func (doc *DocReader) addDecl(decl ast.Decl) { + switch d := decl.(type) { + case *ast.GenDecl: + if len(d.Specs) > 0 { + switch d.Tok { + case token.IMPORT: + // ignore + case token.CONST: + // constants are always handled as a group + if hasExportedSpecs(d.Specs) { + doc.consts.Push(d); + } + case token.TYPE: + // types are handled individually + for i, spec := range d.Specs { + 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}); + } + } + case token.VAR: + // variables are always handled as a group + if hasExportedSpecs(d.Specs) { + doc.vars.Push(d); + } + } + } + case *ast.FuncDecl: + if d.Name.IsExported() { + doc.addFunc(d); + } + } +} + + +// AddProgram adds the AST for a source file to the DocReader. +// Adding the same AST multiple times is a no-op. +// +func (doc *DocReader) AddProgram(prog *ast.Program) { + if doc.name != prog.Name.Value { + panic("package names don't match"); + } + + // add package documentation + // TODO(gri) what to do if there are multiple files? + if prog.Doc != nil { + doc.doc = prog.Doc + } + + // add all exported declarations + for i, decl := range prog.Decls { + doc.addDecl(decl); + } +} + +// ---------------------------------------------------------------------------- +// Conversion to external representation + +func makeRex(s string) *regexp.Regexp { + re, err := regexp.Compile(s); + if err != nil { + panic("MakeRegexp ", s, " ", err.String()); + } + return re; +} + + +var ( + comment_markers *regexp.Regexp; + trailing_whitespace *regexp.Regexp; + comment_junk *regexp.Regexp; +) + +// TODO(rsc): Cannot use var initialization for regexps, +// because Regexp constructor needs threads. +func setupRegexps() { + comment_markers = makeRex("^[ \t]*(// ?| ?\\* ?)"); + trailing_whitespace = makeRex("[ \t\r]+$"); + comment_junk = makeRex("^[ \t]*(/\\*|\\*/)[ \t]*$"); +} + + +// Aggregate comment text, without comment markers. +func comment(comments ast.Comments) string { + once.Do(setupRegexps); + lines := make([]string, 0, 20); + for i, c := range comments { + // split on newlines + cl := strings.Split(string(c.Text), "\n"); + + // walk lines, stripping comment markers + w := 0; + for j, l := range cl { + // remove /* and */ lines + if comment_junk.Match(l) { + continue; + } + + // strip trailing white space + m := trailing_whitespace.Execute(l); + if len(m) > 0 { + l = l[0 : m[1]]; + } + + // strip leading comment markers + m = comment_markers.Execute(l); + if len(m) > 0 { + l = l[m[1] : len(l)]; + } + + // throw away leading blank lines + if w == 0 && l == "" { + continue; + } + + cl[w] = l; + w++; + } + + // throw away trailing blank lines + for w > 0 && cl[w-1] == "" { + w--; + } + cl = cl[0 : w]; + + // add this comment to total list + // TODO: maybe separate with a single blank line + // if there is already a comment and len(cl) > 0? + for j, l := range cl { + n := len(lines); + if n+1 >= cap(lines) { + newlines := make([]string, n, 2*cap(lines)); + for k := range newlines { + newlines[k] = lines[k]; + } + lines = newlines; + } + lines = lines[0 : n+1]; + lines[n] = l; + } + } + + // add final "" entry to get trailing newline. + // loop always leaves room for one more. + n := len(lines); + lines = lines[0 : n+1]; + + return strings.Join(lines, "\n"); +} + + +// ValueDoc is the documentation for a group of declared +// values, either vars or consts. +// +type ValueDoc struct { + Doc string; + Decl *ast.GenDecl; + order int; +} + +type sortValueDoc []*ValueDoc +func (p sortValueDoc) Len() int { return len(p); } +func (p sortValueDoc) Swap(i, j int) { p[i], p[j] = p[j], p[i]; } + + +func declName(d *ast.GenDecl) string { + if len(d.Specs) != 1 { + return "" + } + + switch v := d.Specs[0].(type) { + case *ast.ValueSpec: + return v.Names[0].Value; + case *ast.TypeSpec: + return v.Name.Value; + } + + return ""; +} + + +func (p sortValueDoc) Less(i, j int) bool { + // sort by name + // pull blocks (name = "") up to top + // in original order + if ni, nj := declName(p[i].Decl), declName(p[j].Decl); ni != nj { + return ni < nj; + } + return p[i].order < p[j].order; +} + + +func makeValueDocs(v *vector.Vector) []*ValueDoc { + d := make([]*ValueDoc, v.Len()); + for i := range d { + decl := v.At(i).(*ast.GenDecl); + d[i] = &ValueDoc{comment(decl.Doc), decl, i}; + } + sort.Sort(sortValueDoc(d)); + return d; +} + + +// FuncDoc is the documentation for a func declaration, +// either a top-level function or a method function. +// +type FuncDoc struct { + Doc string; + Recv ast.Expr; // TODO(rsc): Would like string here + Name string; + Decl *ast.FuncDecl; +} + +type sortFuncDoc []*FuncDoc +func (p sortFuncDoc) Len() int { return len(p); } +func (p sortFuncDoc) Swap(i, j int) { p[i], p[j] = p[j], p[i]; } +func (p sortFuncDoc) Less(i, j int) bool { return p[i].Name < p[j].Name; } + + +func makeFuncDocs(m map[string] *ast.FuncDecl) []*FuncDoc { + d := make([]*FuncDoc, len(m)); + i := 0; + for name, f := range m { + doc := new(FuncDoc); + doc.Doc = comment(f.Doc); + if f.Recv != nil { + doc.Recv = f.Recv.Type; + } + doc.Name = f.Name.Value; + doc.Decl = f; + d[i] = doc; + i++; + } + sort.Sort(sortFuncDoc(d)); + return d; +} + + +// TypeDoc is the documentation for a declared type. +// Factories is a sorted list of factory functions that return that type. +// Methods is a sorted list of method functions on that type. +type TypeDoc struct { + Doc string; + Type *ast.TypeSpec; + Factories []*FuncDoc; + Methods []*FuncDoc; + Decl *ast.GenDecl; + order int; +} + +type sortTypeDoc []*TypeDoc +func (p sortTypeDoc) Len() int { return len(p); } +func (p sortTypeDoc) Swap(i, j int) { p[i], p[j] = p[j], p[i]; } +func (p sortTypeDoc) Less(i, j int) bool { + // sort by name + // pull blocks (name = "") up to top + // in original order + if ni, nj := p[i].Type.Name.Value, p[j].Type.Name.Value; ni != nj { + return ni < nj; + } + return p[i].order < p[j].order; +} + + +// NOTE(rsc): This would appear not to be correct for type ( ) +// blocks, but the doc extractor above has split them into +// individual statements. +func makeTypeDocs(m map[string] *typeDoc) []*TypeDoc { + d := make([]*TypeDoc, len(m)); + i := 0; + for name, old := range m { + typespec := old.decl.Specs[0].(*ast.TypeSpec); + t := new(TypeDoc); + t.Doc = comment(typespec.Doc); + t.Type = typespec; + t.Factories = makeFuncDocs(old.factories); + t.Methods = makeFuncDocs(old.methods); + t.Decl = old.decl; + t.order = i; + d[i] = t; + i++; + } + sort.Sort(sortTypeDoc(d)); + return d; +} + + +// PackageDoc is the documentation for an entire package. +// +type PackageDoc struct { + PackageName string; + ImportPath string; + Doc string; + Consts []*ValueDoc; + Types []*TypeDoc; + Vars []*ValueDoc; + Funcs []*FuncDoc; +} + + +// Doc returns the accumulated documentation for the package. +// +func (doc *DocReader) Doc() *PackageDoc { + p := new(PackageDoc); + p.PackageName = doc.name; + p.ImportPath = doc.path; + p.Doc = comment(doc.doc); + p.Consts = makeValueDocs(doc.consts); + p.Vars = makeValueDocs(doc.vars); + p.Types = makeTypeDocs(doc.types); + p.Funcs = makeFuncDocs(doc.funcs); + return p; +} + + +// ---------------------------------------------------------------------------- +// Filtering by name + +// Does s look like a regular expression? +func isRegexp(s string) bool { + metachars := ".(|)*+?^$[]"; + for i, c := range s { + for j, m := range metachars { + if c == m { + return true + } + } + } + return false +} + + +func match(s string, a []string) bool { + for i, t := range a { + if isRegexp(t) { + if matched, err := regexp.Match(t, s); matched { + return true; + } + } + if s == t { + return true; + } + } + return false; +} + + +func matchDecl(d *ast.GenDecl, names []string) bool { + for i, d := range d.Specs { + switch v := d.(type) { + case *ast.ValueSpec: + for j, name := range v.Names { + if match(name.Value, names) { + return true; + } + } + case *ast.TypeSpec: + if match(v.Name.Value, names) { + return true; + } + } + } + return false; +} + + +func filterValueDocs(a []*ValueDoc, names []string) []*ValueDoc { + w := 0; + for i, vd := range a { + if matchDecl(vd.Decl, names) { + a[w] = vd; + w++; + } + } + return a[0 : w]; +} + + +func filterTypeDocs(a []*TypeDoc, names []string) []*TypeDoc { + w := 0; + for i, td := range a { + if matchDecl(td.Decl, names) { + a[w] = td; + w++; + } + } + return a[0 : w]; +} + + +func filterFuncDocs(a []*FuncDoc, names []string) []*FuncDoc { + w := 0; + for i, fd := range a { + if match(fd.Name, names) { + a[w] = fd; + w++; + } + } + return a[0 : w]; +} + + +// Filter eliminates information from d that is not +// about one of the given names. +// TODO: Recognize "Type.Method" as a name. +// TODO(r): maybe precompile the regexps. +// +func (p *PackageDoc) Filter(names []string) { + p.Consts = filterValueDocs(p.Consts, names); + p.Vars = filterValueDocs(p.Vars, names); + p.Types = filterTypeDocs(p.Types, names); + p.Funcs = filterFuncDocs(p.Funcs, names); + p.Doc = ""; // don't show top-level package doc +} + |