diff --git a/usr/gri/pretty/astprinter.go b/usr/gri/pretty/astprinter.go index 66fc3616f4..4e5fcba07b 100644 --- a/usr/gri/pretty/astprinter.go +++ b/usr/gri/pretty/astprinter.go @@ -5,22 +5,20 @@ package astPrinter import ( - "io"; - "vector"; - "tabwriter"; + "ast"; "flag"; "fmt"; + "io"; + "os"; "strings"; - "utf8"; - "unicode"; - - "utils"; + "tabwriter"; "token"; - "ast"; - "template"; - "symboltable"; + "unicode"; + "utf8"; + "vector"; ) + var ( debug = flag.Bool("ast_debug", false, "print debugging information"); @@ -75,9 +73,48 @@ func hasExportedNames(names []*ast.Ident) bool { } +// ---------------------------------------------------------------------------- +// TokenPrinter + +// TODO This is not yet used - should fix this. + +// An implementation of a TokenPrinter may be provided when +// initializing an AST Printer. It is used to print tokens. +// +type TokenPrinter interface { + PrintLit(w io.Write, tok token.Token, value []byte); + PrintIdent(w io.Write, value string); + PrintToken(w io.Write, token token.Token); + PrintComment(w io.Write, value []byte); +} + + +type defaultPrinter struct {} + +func (p defaultPrinter) PrintLit(w io.Write, tok token.Token, value []byte) { + w.Write(value); +} + + +func (p defaultPrinter) PrintIdent(w io.Write, value string) { + fmt.Fprint(w, value); +} + + +func (p defaultPrinter) PrintToken(w io.Write, token token.Token) { + fmt.Fprint(w, token.String()); +} + + +func (p defaultPrinter) PrintComment(w io.Write, value []byte) { + w.Write(value); +} + + // ---------------------------------------------------------------------------- // ASTPrinter + // Separators - printed in a delayed fashion, depending on context. const ( none = iota; @@ -101,14 +138,17 @@ type Printer struct { // output text io.Write; + // token printing + tprinter TokenPrinter; + // formatting control html bool; full bool; // if false, print interface only; print all otherwise // comments comments []*ast.Comment; // the list of unassociated comments - cindex int; // the current comment group index - cpos token.Position; // the position of the next comment group + cindex int; // the current comment index + cpos token.Position; // the position of the next comment // current state lastpos token.Position; // position after last string @@ -144,10 +184,17 @@ func (P *Printer) nextComments() { } -func (P *Printer) Init(text io.Write, comments []*ast.Comment, html bool) { +func (P *Printer) Init(text io.Write, tprinter TokenPrinter, comments []*ast.Comment, html bool) { // writers P.text = text; - + + // token printing + if tprinter != nil { + P.tprinter = tprinter; + } else { + P.tprinter = defaultPrinter{}; + } + // formatting control P.html = html; @@ -227,7 +274,7 @@ func (P *Printer) newline(n int) { func (P *Printer) TaggedString(pos token.Position, tag, s, endtag string) { // use estimate for pos if we don't have one offs := pos.Offset; - if offs == 0 { + if pos.Line == 0 { offs = P.lastpos.Offset; } @@ -401,6 +448,17 @@ func (P *Printer) Error(pos token.Position, tok token.Token, msg string) { } +// An astPrinter implements io.Write. +// TODO this is not yet used. +func (P *Printer) Write(p []byte) (n int, err *os.Error) { + // TODO + // - no string conversion every time + // - return proper results + P.String(noPos, string(p)); + return len(p), nil; +} + + // ---------------------------------------------------------------------------- // HTML support diff --git a/usr/gri/pretty/docprinter.go b/usr/gri/pretty/docprinter.go index 94fe501193..623c51614c 100644 --- a/usr/gri/pretty/docprinter.go +++ b/usr/gri/pretty/docprinter.go @@ -136,25 +136,27 @@ func (doc *PackageDoc) addFunc(fun *ast.FuncDecl) { typ.methods[name] = fdoc; } // if the type wasn't found, it wasn't exported + // TODO: a non-exported type may still have exported functions + // determine what to do in that case + return; + } - } else { - // 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] = fdoc; - 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] = fdoc; + return; } } - - // ordinary function - doc.funcs[name] = fdoc; } + + // ordinary function + doc.funcs[name] = fdoc; } @@ -279,18 +281,6 @@ func untabify(s []byte) []byte { } -func stripWhiteSpace(s []byte) []byte { - i, j := 0, len(s); - for i < len(s) && s[i] <= ' ' { - i++; - } - for j > i && s[j-1] <= ' ' { - j-- - } - return s[i : j]; -} - - func stripCommentDelimiters(s []byte) []byte { switch s[1] { case '/': return s[2 : len(s)-1]; @@ -308,8 +298,25 @@ const /* formatting mode */ ( ) func printLine(p *astPrinter.Printer, line []byte, mode int) int { - indented := len(line) > 0 && line[0] == '\t'; - line = stripWhiteSpace(line); + // If a line starts with " *" (as a result of a vertical /****/ comment), + // strip it away. For an example of such a comment, see src/lib/flag.go. + if len(line) >= 2 && line[0] == ' ' && line[1] == '*' { + line = line[2 : len(line)]; + } + + // The line is indented if it starts with a tab. + // In either case strip away a leading space or tab. + indented := false; + if len(line) > 0 { + switch line[0] { + case '\t': + indented = true; + fallthrough; + case ' ': + line = line[1 : len(line)]; + } + } + if len(line) == 0 { // empty line switch mode { @@ -426,7 +433,7 @@ func (t *typeDoc) print(p *astPrinter.Printer) { func (doc *PackageDoc) Print(writer io.Write) { var p astPrinter.Printer; - p.Init(writer, nil, true); + p.Init(writer, nil, nil, true); // program header fmt.Fprintf(writer, "
");
- offs := 0;
- for i, e := range errors {
- if 0 <= e.pos.Offset && e.pos.Offset <= len(src) {
- // TODO handle Write errors
- c.Write(src[offs : e.pos.Offset]);
- // TODO this should be done using a .css file
- fmt.Fprintf(c, "%s >>>", e.msg);
- offs = e.pos.Offset;
- } else {
- log.Stdoutf("error position %d out of bounds (len = %d)", e.pos.Offset, len(src));
- }
- }
- // TODO handle Write errors
- c.Write(src[offs : len(src)]);
- fmt.Fprintln(c, "");
- }
- });
-}
-
-
-func serveGoFile(c *http.Conn, dirname string, filenames []string) {
- // compute documentation
- var doc docPrinter.PackageDoc;
- for i, filename := range filenames {
- path := *root + "/" + dirname + "/" + filename;
- prog, errors := compile(path, parser.ParseComments);
- if len(errors) > 0 {
- c.SetHeader("content-type", "text/html; charset=utf-8");
- printErrors(c, filename, errors);
+ servePage(c, filename, func () {
+ // section title
+ fmt.Fprintf(c, "");
+ offs := 0;
+ for i, e := range errors {
+ if 0 <= e.pos.Offset && e.pos.Offset <= len(src) {
+ // TODO handle Write errors
+ c.Write(src[offs : e.pos.Offset]);
+ // TODO this should be done using a .css file
+ fmt.Fprintf(c, "%s >>>", e.msg);
+ offs = e.pos.Offset;
+ } else {
+ log.Stdoutf("error position %d out of bounds (len = %d)", e.pos.Offset, len(src));
+ }
}
+ // TODO handle Write errors
+ c.Write(src[offs : len(src)]);
+ fmt.Fprintln(c, "");
});
}
-func serveSrc(c *http.Conn, path string) {
+func serveGoSource(c *http.Conn, dirname string, filename string) {
+ path := dirname + "/" + filename;
+ prog, errors := compile(*root + "/" + path, parser.ParseComments);
+ if len(errors) > 0 {
+ serveCompilationErrors(c, filename, errors);
+ return;
+ }
+
+ servePage(c, path + " - Go source", func () {
+ fmt.Fprintln(c, ""); + var p astPrinter.Printer; + writer := makeTabwriter(c); // for nicely formatted output + p.Init(writer, nil, nil, true); + p.DoProgram(prog); + writer.Flush(); // ignore errors + fmt.Fprintln(c, ""); + }); +} + + +func serveHTMLFile(c *http.Conn, filename string) { + src, err1 := os.Open(filename, os.O_RDONLY, 0); + defer src.Close(); + if err1 != nil { + serveError(c, err1.String(), filename); + return + } + written, err2 := io.Copy(src, c); + if err2 != nil { + serveError(c, err2.String(), filename); + return + } +} + + +func serveFile(c *http.Conn, path string) { dir, err := os.Stat(*root + path); if err != nil { - c.WriteHeader(http.StatusNotFound); - fmt.Fprintf(c, "Error: %v (%s)\n", err, path); + serveError(c, err.String(), path); return; } @@ -383,10 +373,11 @@ func serveSrc(c *http.Conn, path string) { case dir.IsDirectory(): serveDir(c, path); case isGoFile(dir): - serveGoFile(c, "", []string{path}); + serveGoSource(c, "", path); + case isHTMLFile(dir): + serveHTMLFile(c, *root + path); default: - c.WriteHeader(http.StatusNotFound); - fmt.Fprintf(c, "Error: Not a directory or .go file (%s)\n", path); + serveError(c, "Not a directory or .go file", path); } } @@ -407,13 +398,12 @@ func (p pakArray) Less(i, j int) bool { return p[i].pakname < p[j].pakname; } func (p pakArray) Swap(i, j int) { p[i], p[j] = p[j], p[i]; } -var ( - pakMap map[string]*pakDesc; // dirname/pakname -> package descriptor - pakList pakArray; // sorted list of packages; in sync with pakMap -) +// The global list of packages (sorted) +// TODO should be accessed under a lock +var pakList pakArray; -func addFile(dirname string, filename string) { +func addFile(pmap map[string]*pakDesc, dirname string, filename string) { if hasSuffix(filename, "_test.go") { // ignore package tests return; @@ -431,11 +421,11 @@ func addFile(dirname string, filename string) { pakname := dirname + "/" + prog.Name.Value; // find package descriptor - pakdesc, found := pakMap[pakname]; + pakdesc, found := pmap[pakname]; if !found { // add a new descriptor pakdesc = &pakDesc{dirname, prog.Name.Value, make(map[string]bool)}; - pakMap[pakname] = pakdesc; + pmap[pakname] = pakdesc; } //fmt.Printf("pak = %s, file = %s\n", pakname, filename); @@ -448,11 +438,9 @@ func addFile(dirname string, filename string) { } -func addDirectory(dirname string) { +func addDirectory(pmap map[string]*pakDesc, dirname string) { // TODO should properly check device and inode to see if we have // traversed this directory already - //fmt.Printf("traversing %s\n", dirname); - fd, err1 := os.Open(*root + dirname, os.O_RDONLY, 0); if err1 != nil { log.Stdoutf("%s: %v", *root + dirname, err1); @@ -469,30 +457,33 @@ func addDirectory(dirname string) { switch { case entry.IsDirectory(): if entry.Name != "." && entry.Name != ".." { - addDirectory(dirname + "/" + entry.Name); + addDirectory(pmap, dirname + "/" + entry.Name); } case isGoFile(&entry): //fmt.Printf("found %s/%s\n", dirname, entry.Name); - addFile(dirname, entry.Name); + addFile(pmap, dirname, entry.Name); } } } func makePackageMap() { - // TODO shold do this under a lock, eventually + // TODO shold do this under a lock // populate package map - pakMap = make(map[string]*pakDesc); - addDirectory(""); + pmap := make(map[string]*pakDesc); + addDirectory(pmap, ""); // build sorted package list - pakList = make([]*pakDesc, len(pakMap)); + plist := make(pakArray, len(pmap)); i := 0; - for tmp, pakdesc := range pakMap { - pakList[i] = pakdesc; + for tmp, pakdesc := range pmap { + plist[i] = pakdesc; i++; } - sort.Sort(pakList); + sort.Sort(plist); + + // install package list (TODO should do this under a lock) + pakList = plist; if *verbose { log.Stdoutf("%d packages found under %s", i, *root); @@ -500,40 +491,46 @@ func makePackageMap() { } -func serveGoPackage(c *http.Conn, p *pakDesc) { +func servePackage(c *http.Conn, p *pakDesc) { // make a filename list - list := make([]string, len(p.filenames)); + filenames := make([]string, len(p.filenames)); i := 0; for filename, tmp := range p.filenames { - list[i] = filename; + filenames[i] = filename; i++; } - serveGoFile(c, p.dirname, list); + // compute documentation + var doc docPrinter.PackageDoc; + for i, filename := range filenames { + path := *root + "/" + p.dirname + "/" + filename; + prog, errors := compile(path, parser.ParseComments); + if len(errors) > 0 { + serveCompilationErrors(c, filename, errors); + return; + } + + if i == 0 { + // first package - initialize docPrinter + doc.Init(prog.Name.Value); + } + doc.AddProgram(prog); + } + + servePage(c, doc.PackageName() + " - Go package documentation", func () { + writer := makeTabwriter(c); // for nicely formatted output + doc.Print(writer); + writer.Flush(); // ignore errors + }); } func servePackageList(c *http.Conn, list *vector.Vector) { - godoc_template.Apply(c, "" : func() { - fmt.Fprint(c, "Packages"); - }, - - "HEADER-->" : func() { - fmt.Fprint(c, "Packages"); - }, - - "TIMESTAMP-->" : func() { - fmt.Fprint(c, time.UTC().String()); - }, - - "CONTENTS-->" : func () { - // TODO should do this under a lock, eventually - for i := 0; i < list.Len(); i++ { - p := list.At(i).(*pakDesc); - link := p.dirname + "/" + p.pakname; - fmt.Fprintf(c, "%s (%s)