diff options
-rw-r--r-- | usr/gri/pretty/astprinter.go | 88 | ||||
-rw-r--r-- | usr/gri/pretty/docprinter.go | 67 | ||||
-rw-r--r-- | usr/gri/pretty/godoc.go | 369 | ||||
-rw-r--r-- | usr/gri/pretty/pretty.go | 2 |
4 files changed, 301 insertions, 225 deletions
diff --git a/usr/gri/pretty/astprinter.go b/usr/gri/pretty/astprinter.go index 66fc3616f..4e5fcba07 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"); @@ -76,8 +74,47 @@ 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 94fe50119..623c51614 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, "<h1>package %s</h1>\n", doc.name); diff --git a/usr/gri/pretty/godoc.go b/usr/gri/pretty/godoc.go index 8b428d38f..afedd7949 100644 --- a/usr/gri/pretty/godoc.go +++ b/usr/gri/pretty/godoc.go @@ -25,6 +25,7 @@ import ( "regexp"; "vector"; + "astprinter"; "compilation"; // TODO removing this causes link errors - why? "docprinter"; ) @@ -33,11 +34,12 @@ import ( // TODO // - uniform use of path, filename, dirname, pakname, etc. // - fix weirdness with double-/'s in paths +// - cleanup uses of *root, GOROOT, etc. (quite a mess at the moment) const ( docPrefix = "/doc/"; - srcPrefix = "/src/"; + filePrefix = "/file/"; ) @@ -48,19 +50,16 @@ func getenv(varname string) string { var ( - GOROOT string; + GOROOT = getenv("GOROOT"); // server control verbose = flag.Bool("v", false, "verbose mode"); port = flag.String("port", "6060", "server port"); - root = flag.String("root", getenv("GOROOT"), "go root directory"); + root = flag.String("root", GOROOT, "root directory"); // layout control tabwidth = flag.Int("tabwidth", 4, "tab width"); usetabs = flag.Bool("usetabs", false, "align with tabs instead of blanks"); - - // html template - godoc_template = template.NewTemplateOrDie("godoc.html"); ) @@ -111,8 +110,13 @@ func isGoFile(dir *os.Dir) bool { } +func isHTMLFile(dir *os.Dir) bool { + return dir.IsRegular() && hasSuffix(dir.Name, ".html"); +} + + func printLink(c *http.Conn, path, name string) { - fmt.Fprintf(c, "<a href=\"%s\">%s</a><br />\n", srcPrefix + path + name, name); + fmt.Fprintf(c, "<a href=\"%s\">%s</a><br />\n", filePrefix + path + name, name); } @@ -189,6 +193,33 @@ func compile(path string, mode uint) (*ast.Program, errorList) { // ---------------------------------------------------------------------------- +// Templates + +// html template +// TODO initialize only if needed (i.e. if run as a server) +var godoc_html = template.NewTemplateOrDie("godoc.html"); + +func servePage(c *http.Conn, title string, contents func()) { + c.SetHeader("content-type", "text/html; charset=utf-8"); + + // TODO handle Apply errors + godoc_html.Apply(c, "<!--", template.Substitution { + "TITLE-->" : func() { fmt.Fprint(c, title); }, + "HEADER-->" : func() { fmt.Fprint(c, title); }, + "TIMESTAMP-->" : func() { fmt.Fprint(c, time.UTC().String()); }, + "CONTENTS-->" : contents + }); +} + + +func serveError(c *http.Conn, err, arg string) { + servePage(c, "Error", func () { + fmt.Fprintf(c, "%v (%s)\n", err, arg); + }); +} + + +// ---------------------------------------------------------------------------- // Directories type dirArray []os.Dir @@ -214,45 +245,28 @@ func serveDir(c *http.Conn, dirname string) { sort.Sort(dirArray(list)); - c.SetHeader("content-type", "text/html; charset=utf-8"); path := dirname + "/"; // Print contents in 3 sections: directories, go files, everything else - - // TODO handle Apply errors - godoc_template.Apply(c, "<!--", template.Substitution { - "TITLE-->" : func() { - fmt.Fprint(c, dirname); - }, - - "HEADER-->" : func() { - fmt.Fprint(c, dirname); - }, - - "TIMESTAMP-->" : func() { - fmt.Fprint(c, time.UTC().String()); - }, - - "CONTENTS-->" : func () { - fmt.Fprintln(c, "<h2>Directories</h2>"); - for i, entry := range list { - if entry.IsDirectory() { - printLink(c, path, entry.Name); - } + servePage(c, dirname + " - Contents", func () { + fmt.Fprintln(c, "<h2>Directories</h2>"); + for i, entry := range list { + if entry.IsDirectory() { + printLink(c, path, entry.Name); } + } - fmt.Fprintln(c, "<h2>Go files</h2>"); - for i, entry := range list { - if isGoFile(&entry) { - printLink(c, path, entry.Name); - } + fmt.Fprintln(c, "<h2>Go files</h2>"); + for i, entry := range list { + if isGoFile(&entry) { + printLink(c, path, entry.Name); } + } - fmt.Fprintln(c, "<h2>Other files</h2>"); - for i, entry := range list { - if !entry.IsDirectory() && !isGoFile(&entry) { - fmt.Fprintf(c, "%s<br />\n", entry.Name); - } + fmt.Fprintln(c, "<h2>Other files</h2>"); + for i, entry := range list { + if !entry.IsDirectory() && !isGoFile(&entry) { + fmt.Fprintf(c, "%s<br />\n", entry.Name); } } }); @@ -262,120 +276,96 @@ func serveDir(c *http.Conn, dirname string) { // ---------------------------------------------------------------------------- // Files -func printErrors(c *http.Conn, filename string, errors errorList) { +func serveCompilationErrors(c *http.Conn, filename string, errors errorList) { // open file path := *root + filename; fd, err1 := os.Open(path, os.O_RDONLY, 0); defer fd.Close(); if err1 != nil { - // TODO better error handling - log.Stdoutf("%s: %v", path, err1); + serveError(c, err1.String(), path); + return; } // read source var buf io.ByteBuffer; n, err2 := io.Copy(fd, &buf); if err2 != nil { - // TODO better error handling - log.Stdoutf("%s: %v", path, err2); + serveError(c, err2.String(), path); + return; } src := buf.Data(); // TODO handle Apply errors - godoc_template.Apply(c, "<!--", template.Substitution { - "TITLE-->" : func() { - fmt.Fprint(c, filename); - }, - - "HEADER-->" : func() { - fmt.Fprint(c, filename); - }, - - "TIMESTAMP-->" : func() { - fmt.Fprint(c, time.UTC().String()); - }, - - "CONTENTS-->" : func () { - // section title - fmt.Fprintf(c, "<h1>Compilation errors in %s</h1>\n", filename); - - // handle read errors - if err1 != nil || err2 != nil /* 6g bug139 */ { - fmt.Fprintf(c, "could not read file %s\n", filename); - return; - } - - // write source with error messages interspersed - fmt.Fprintln(c, "<pre>"); - 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, "<b><font color=red>%s >>></font></b>", e.msg); - offs = e.pos.Offset; - } else { - log.Stdoutf("error position %d out of bounds (len = %d)", e.pos.Offset, len(src)); - } + servePage(c, filename, func () { + // section title + fmt.Fprintf(c, "<h1>Compilation errors in %s</h1>\n", filename); + + // handle read errors + if err1 != nil || err2 != nil /* 6g bug139 */ { + fmt.Fprintf(c, "could not read file %s\n", filename); + return; + } + + // write source with error messages interspersed + fmt.Fprintln(c, "<pre>"); + 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, "<b><font color=red>%s >>></font></b>", 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, "</pre>"); } + // TODO handle Write errors + c.Write(src[offs : len(src)]); + fmt.Fprintln(c, "</pre>"); }); } -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); - return; - } - - if i == 0 { - // first package - initialize docPrinter - doc.Init(prog.Name.Value); - } - doc.AddProgram(prog); +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; } - c.SetHeader("content-type", "text/html; charset=utf-8"); - - godoc_template.Apply(c, "<!--", template.Substitution { - "TITLE-->" : func() { - fmt.Fprintf(c, "%s - Go package documentation", doc.PackageName()); - }, - - "HEADER-->" : func() { - fmt.Fprintf(c, "%s - Go package documentation", doc.PackageName()); - }, - - "TIMESTAMP-->" : func() { - fmt.Fprint(c, time.UTC().String()); - }, - - "CONTENTS-->" : func () { - // write documentation - writer := makeTabwriter(c); // for nicely formatted output - doc.Print(writer); - writer.Flush(); // ignore errors - } + servePage(c, path + " - Go source", func () { + fmt.Fprintln(c, "<pre>"); + 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, "</pre>"); }); } -func serveSrc(c *http.Conn, path string) { +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, "<!--", template.Substitution { - "TITLE-->" : 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, "<a href=\"%s\">%s</a> <font color=grey>(%s)</font><br />\n", docPrefix + link, p.pakname, link); - } + servePage(c, "Packages", func () { + for i := 0; i < list.Len(); i++ { + p := list.At(i).(*pakDesc); + link := p.dirname + "/" + p.pakname; + fmt.Fprintf(c, "<a href=\"%s\">%s</a> <font color=grey>(%s)</font><br />\n", docPrefix + link, p.pakname, link); } }); } @@ -543,8 +540,8 @@ func serveDoc(c *http.Conn, path string) { // make regexp for package matching rex, err := regexp.Compile(path); if err != nil { - // TODO report this via an error page - log.Stdoutf("failed to compile regexp: %s", path); + serveError(c, err.String(), path); + return; } // build list of matching packages @@ -555,9 +552,12 @@ func serveDoc(c *http.Conn, path string) { } } - if list.Len() == 1 { - serveGoPackage(c, list.At(0).(*pakDesc)); - } else { + switch list.Len() { + case 0: + serveError(c, "No packages found", path); + case 1: + servePackage(c, list.At(0).(*pakDesc)); + default: servePackageList(c, list); } } @@ -566,8 +566,17 @@ func serveDoc(c *http.Conn, path string) { // ---------------------------------------------------------------------------- // Server +func makeFixedFileServer(filename string) (func(c *http.Conn, path string)) { + return func(c *http.Conn, path string) { + // ignore path and always serve the same file + // TODO this should be serveFile but there are some issues with *root + serveHTMLFile(c, filename); + }; +} + + func installHandler(prefix string, handler func(c *http.Conn, path string)) { - // customized handler with prefix + // create a handler customized with prefix f := func(c *http.Conn, req *http.Request) { path := req.Url.Path; if *verbose { @@ -604,13 +613,15 @@ func main() { } makePackageMap(); - + + installHandler("/mem", makeFixedFileServer(GOROOT + "/doc/go_mem.html")); + installHandler("/spec", makeFixedFileServer(GOROOT + "/doc/go_spec.html")); installHandler(docPrefix, serveDoc); - installHandler(srcPrefix, serveSrc); + installHandler(filePrefix, serveFile); + { err := http.ListenAndServe(":" + *port, nil); if err != nil { log.Exitf("ListenAndServe: %v", err) } } } - diff --git a/usr/gri/pretty/pretty.go b/usr/gri/pretty/pretty.go index fe2a03ecb..1c0f2445d 100644 --- a/usr/gri/pretty/pretty.go +++ b/usr/gri/pretty/pretty.go @@ -50,7 +50,7 @@ func print(prog *ast.Program) { // initialize printer var printer astPrinter.Printer; - printer.Init(writer, prog.Comments, *html); + printer.Init(writer, nil, prog.Comments, *html); printer.DoProgram(prog); |