diff options
Diffstat (limited to 'src/lib/go')
| -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 +} + | 
