"regexp";
        "vector";
 
+       "astprinter";
        "compilation";  // TODO removing this causes link errors - why?
        "docprinter";
 )
 // 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/";
 )
 
 
 
 
 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");
 )
 
 
 }
 
 
+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);
 }
 
 
 }
 
 
+// ----------------------------------------------------------------------------
+// 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
 
 
        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);
                        }
                }
        });
 // ----------------------------------------------------------------------------
 // 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;
        }
 
        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);
        }
 }
 
 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;
        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);
 }
 
 
-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);
                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);
 }
 
 
-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);
                }
        });
 }
        // 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
                }
        }
 
-       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);
        }
 }
 // ----------------------------------------------------------------------------
 // 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 {
        }
 
        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)
                }
        }
 }
-