diff trunk/src/cmd/DDoc.d @ 737:f88b5285b86b

Implemented DDocEmitter. Fixed quite a few bugs.
author Aziz K?ksal <aziz.koeksal@gmail.com>
date Sat, 09 Feb 2008 02:00:20 +0100
parents ca7607226caa
children 49fe21aa387c
line wrap: on
line diff
--- a/trunk/src/cmd/DDoc.d	Mon Feb 04 21:55:44 2008 +0200
+++ b/trunk/src/cmd/DDoc.d	Sat Feb 09 02:00:20 2008 +0100
@@ -7,7 +7,14 @@
 import dil.doc.Parser;
 import dil.doc.Macro;
 import dil.doc.Doc;
+import dil.ast.Node;
+import dil.ast.Declarations,
+       dil.ast.Statements,
+       dil.ast.Expression,
+       dil.ast.Parameters,
+       dil.ast.Types;
 import dil.ast.DefaultVisitor;
+import dil.lexer.Token;
 import dil.semantic.Module;
 import dil.semantic.Pass1;
 import dil.semantic.Symbol;
@@ -18,6 +25,9 @@
 
 import tango.stdc.time : time_t, time, ctime;
 import tango.stdc.string : strlen;
+import tango.text.Ascii : toUpper;
+import tango.io.File;
+import tango.io.FilePath;
 
 void execute(string[] filePaths, string destDir, string[] macroPaths,
              bool incUndoc, InfoManager infoMan)
@@ -35,11 +45,9 @@
 //   foreach (k, v; mtable.table)
 //     Stdout(k)("=")(v.text);
 
-  Module[] modules;
   foreach (filePath; filePaths)
   {
     auto mod = new Module(filePath, infoMan);
-    modules ~= mod;
     // Parse the file.
     mod.parse();
     if (mod.hasErrors)
@@ -48,47 +56,470 @@
     // Start semantic analysis.
     auto pass1 = new SemanticPass1(mod);
     pass1.start();
+
+    // Generate documentation.
+    auto dest = new FilePath(destDir);
+    generateDocumentation(dest, mod, mtable, incUndoc);
   }
-
-  foreach (mod; modules)
-    generateDocumentation(mod, mtable);
 }
 
-void generateDocumentation(Module mod, MacroTable mtable)
+void generateDocumentation(FilePath dest, Module mod, MacroTable mtable, bool incUndoc)
 {
   // Create a macro environment for this module.
   mtable = new MacroTable(mtable);
   // Define runtime macros.
-  mtable.insert(new Macro("TITLE", mod.getFQN()));
-  mtable.insert(new Macro("DOCFILENAME", mod.getFQN()));
+  mtable.insert("TITLE", mod.getFQN());
+  mtable.insert("DOCFILENAME", mod.getFQN());
 
   time_t time_val;
   time(&time_val);
   char* str = ctime(&time_val);
   char[] time_str = str[0 .. strlen(str)];
-  mtable.insert(new Macro("DATETIME", time_str.dup));
-  mtable.insert(new Macro("YEAR", time_str[20..24].dup));
+  mtable.insert("DATETIME", time_str.dup);
+  mtable.insert("YEAR", time_str[20..24].dup);
 
-  if (mod.moduleDecl)
-  {
-    auto ddocComment = getDDocComment(mod.moduleDecl);
-    if (auto copyright = ddocComment.getCopyright())
-      mtable.insert(new Macro("COPYRIGHT", copyright.text));
-  }
-
-  auto docEmitter = new DDocEmitter();
-  docEmitter.emit(mod);
-
-  mtable.insert(new Macro("BODY", docEmitter.text));
-  expandMacros(mtable, "$(DDOC)");
+  auto doc = new DDocEmitter(mtable, incUndoc);
+  doc.emit(mod);
+  // Set BODY macro to the text produced by the DDocEmitter.
+  mtable.insert("BODY", doc.text);
+  // Do the macro expansion pass.
+  auto fileText = expandMacros(mtable, "$(DDOC)");
+  // Finally write the file out to the harddisk.
+  dest.append(mod.getFQN() ~ ".html");
+  auto file = new File(dest);
+  file.write(fileText);
 }
 
+/// Traverses the syntax tree and writes DDoc macros to a string buffer.
 class DDocEmitter : DefaultVisitor
 {
   char[] text;
+  bool includeUndocumented;
+  MacroTable mtable;
 
+  this(MacroTable mtable, bool includeUndocumented)
+  {
+    this.mtable = mtable;
+    this.includeUndocumented = includeUndocumented;
+  }
+
+  /// Entry method.
   char[] emit(Module mod)
   {
+    if (auto d = mod.moduleDecl)
+    {
+      if (ddoc(d))
+      {
+        if (auto copyright = cmnt.takeCopyright())
+          mtable.insert(new Macro("COPYRIGHT", copyright.text));
+        DESC({ writeComment(); });
+      }
+    }
+    MEMBERS("MODULE", { visitD(mod.root); });
+    write(\n);
     return text;
   }
+
+  char[] textSpan(Token* left, Token* right)
+  {
+    //assert(left && right && (left.end <= right.start || left is right));
+    //char[] result;
+    //TODO: filter out whitespace tokens.
+    return Token.textSpan(left, right);
+  }
+
+  bool isTemplatized; /// True if an aggregate declaration is templatized.
+  TemplateParameters tparams; /// The template parameters of the declaration.
+
+  DDocComment cmnt; /// Current comment.
+  DDocComment prevCmnt; /// Previous comment in scope.
+  /// An empty comment. Used for undocumented symbols.
+  static const DDocComment emptyCmnt;
+
+  static this()
+  {
+    this.emptyCmnt = new DDocComment(null, null, null);
+  }
+
+  /// Keeps track of previous comments in each scope.
+  scope class Scope
+  {
+    DDocComment old_prevCmnt;
+    this()
+    { // Save the previous comment of the parent scope.
+      old_prevCmnt = this.outer.prevCmnt;
+      // Entering a new scope. Set to null.
+      this.outer.prevCmnt = null;
+    }
+
+    ~this()
+    { // Restore the previous comment of the parent scope.
+      this.outer.prevCmnt = old_prevCmnt;
+    }
+  }
+
+  DDocComment ddoc(Node node)
+  {
+    auto c = getDDocComment(node);
+    this.cmnt = null;
+    if (c)
+    {
+      if (c.isDitto)
+        this.cmnt = this.prevCmnt;
+      else
+      {
+        this.cmnt = c;
+        this.prevCmnt = c;
+      }
+    }
+    else if (includeUndocumented)
+      this.cmnt = this.emptyCmnt;
+    return this.cmnt;
+  }
+
+  static char[][char[]] specialSections;
+  static this()
+  {
+    foreach (name; ["AUTHORS", "BUGS", "COPYRIGHT", "DATE", "DEPRECATED",
+                    "EXAMPLES", "HISTORY", "LICENSE", "RETURNS", "SEE_ALSO",
+                    "STANDARDS", "THROWS", "VERSION"])
+      specialSections[name] = name;
+  }
+
+  void writeComment()
+  {
+    auto c = this.cmnt;
+    assert(c !is null);
+    if (c.sections.length == 0)
+      return;
+    write("$(DDOC_SECTIONS ");
+      foreach (s; c.sections)
+      {
+        if (s is c.summary)
+          write("\n$(DDOC_SUMMARY ");
+        else if (s is c.description)
+          write("\n$(DDOC_DESCRIPTION ");
+        else if (auto name = toUpper(s.name.dup) in specialSections)
+          write("\n$(DDOC_" ~ *name ~ " ");
+        else if (s.Is("params"))
+        { // Process parameters section.
+          auto ps = new ParamsSection(s.name, s.text);
+          write("\n$(DDOC_PARAMS ");
+          foreach (i, paramName; ps.paramNames)
+            write("$(DDOC_PARAM_ROW ",
+                    "$(DDOC_PARAM_ID ", paramName, ")",
+                    "$(DDOC_PARAM_DESC ", ps.paramDescs[i], ")",
+                  ")");
+          write(")");
+          continue;
+        }
+        else if (s.Is("macros"))
+        { // Declare the macros in this section.
+          auto ms = new MacrosSection(s.name, s.text);
+          mtable.insert(ms.macroNames, ms.macroTexts);
+          continue;
+        }
+        else
+          write("\n$(DDOC_SECTION $(DDOC_SECTION_H " ~ s.name ~ ":)");
+        write(s.text, ")");
+      }
+    write(")");
+  }
+
+  void writeTemplateParams()
+  {
+    if (!isTemplatized)
+      return;
+    text ~= "(" ~ (tparams ? textSpan(tparams.begin, tparams.end) : "") ~ ")";
+    isTemplatized = false;
+    tparams = null;
+  }
+
+  void writeInheritanceList(BaseClassType[] bases)
+  {
+    if (bases.length == 0)
+      return;
+    text ~= " : " ~ textSpan(bases[0].begin, bases[$-1].end);
+  }
+
+  void writeFuncHeader(Declaration d, FuncBodyStatement s)
+  {
+    auto begin = d.begin;
+    auto end = d.end.prev;
+    if (!s.isEmpty)
+      end = s.begin.prev;
+    text ~= textSpan(begin, end);
+  }
+
+  void write(char[][] strings...)
+  {
+    foreach (s; strings)
+      text ~= s;
+  }
+
+  void SYMBOL(char[][] strings...)
+  {
+    write("$(DDOC_PSYMBOL ");
+    write(strings);
+    write(")");
+  }
+
+  void DECL(void delegate() dg)
+  {
+    write("\n$(DDOC_DECL ");
+    dg();
+    write(")");
+  }
+
+  void DESC(void delegate() dg)
+  {
+    write("\n$(DDOC_DECL_DD ");
+    dg();
+    write(")");
+  }
+
+  void MEMBERS(char[] kind, void delegate() dg)
+  {
+    write("\n$(DDOC_"~kind~"_MEMBERS ");
+    dg();
+    write(")");
+  }
+
+  void writeClassOrInterface(T)(T d)
+  {
+    if (!ddoc(d))
+      return d;
+    scope s = new Scope();
+    DECL({
+      write(d.begin.srcText, " ");
+      SYMBOL(d.name.str);
+      writeTemplateParams();
+      writeInheritanceList(d.bases);
+    });
+    DESC({
+      writeComment();
+      MEMBERS(is(T == ClassDeclaration) ? "CLASS" : "INTERFACE", {
+        d.decls && super.visit(d.decls);
+      });
+    });
+  }
+
+  void writeStructOrUnion(T)(T d)
+  {
+    if (!ddoc(d))
+      return d;
+    scope s = new Scope();
+    DECL({
+      write(d.begin.srcText, d.name ? " " : "");
+      if (d.name)
+        SYMBOL(d.name.str);
+      writeTemplateParams();
+    });
+    DESC({
+      writeComment();
+      MEMBERS(is(T == StructDeclaration) ? "STRUCT" : "UNION", {
+        d.decls && super.visit(d.decls);
+      });
+    });
+  }
+
+  alias Declaration D;
+
+override:
+//   D visit(ModuleDeclaration d)
+//   { return d; }
+
+  D visit(AliasDeclaration d)
+  {
+    if (!ddoc(d))
+      return d;
+    DECL({ write(textSpan(d.begin, d.end)); });
+    DESC({ writeComment(); });
+    return d;
+  }
+
+  D visit(TypedefDeclaration d)
+  {
+    if (!ddoc(d))
+      return d;
+    DECL({ write(textSpan(d.begin, d.end)); });
+    DESC({ writeComment(); });
+    return d;
+  }
+
+  D visit(EnumDeclaration d)
+  {
+    if (!ddoc(d))
+      return d;
+    scope s = new Scope();
+    DECL({
+      write("enum", d.name ? " " : "");
+      d.name && SYMBOL(d.name.str);
+    });
+    DESC({
+      writeComment();
+      MEMBERS("ENUM", { super.visit(d); });
+    });
+    return d;
+  }
+
+  D visit(EnumMemberDeclaration d)
+  {
+    if (!ddoc(d))
+      return d;
+    DECL({ SYMBOL(d.name.str); });
+    DESC({ writeComment(); });
+    return d;
+  }
+
+  D visit(TemplateDeclaration d)
+  {
+    if (d.begin.kind != TOK.Template)
+    { // This is a templatized class/interface/struct/union.
+      this.isTemplatized = true;
+      this.tparams = d.tparams;
+      return super.visit(d.decls);
+    }
+    if (!ddoc(d))
+      return d;
+    scope s = new Scope();
+    DECL({
+      write("template ");
+      SYMBOL(d.name.str);
+      write(textSpan(d.begin.next.next, d.decls.begin.prev));
+    });
+    DESC({
+      writeComment();
+      MEMBERS("TEMPLATE", {
+        super.visit(d.decls);
+      });
+    });
+    return d;
+  }
+
+  D visit(ClassDeclaration d)
+  {
+    writeClassOrInterface(d);
+    return d;
+  }
+
+  D visit(InterfaceDeclaration d)
+  {
+    writeClassOrInterface(d);
+    return d;
+  }
+
+  D visit(StructDeclaration d)
+  {
+    writeStructOrUnion(d);
+    return d;
+  }
+
+  D visit(UnionDeclaration d)
+  {
+    writeStructOrUnion(d);
+    return d;
+  }
+
+  D visit(ConstructorDeclaration d)
+  {
+    if (!ddoc(d))
+      return d;
+    DECL({ writeFuncHeader(d, d.funcBody); });
+    DESC({ writeComment(); });
+    return d;
+  }
+
+  D visit(StaticConstructorDeclaration d)
+  {
+    if (!ddoc(d))
+      return d;
+    DECL({ writeFuncHeader(d, d.funcBody); });
+    DESC({ writeComment(); });
+    return d;
+  }
+
+  D visit(DestructorDeclaration d)
+  {
+    if (!ddoc(d))
+      return d;
+    DECL({ writeFuncHeader(d, d.funcBody); });
+    DESC({ writeComment(); });
+    return d;
+  }
+
+  D visit(StaticDestructorDeclaration d)
+  {
+    if (!ddoc(d))
+      return d;
+    DECL({ writeFuncHeader(d, d.funcBody); });
+    DESC({ writeComment(); });
+    return d;
+  }
+
+  D visit(FunctionDeclaration d)
+  {
+    if (!ddoc(d))
+      return d;
+    DECL({ writeFuncHeader(d, d.funcBody); });
+    DESC({ writeComment(); });
+    return d;
+  }
+
+  D visit(NewDeclaration d)
+  {
+    if (!ddoc(d))
+      return d;
+    DECL({ writeFuncHeader(d, d.funcBody); });
+    DESC({ writeComment(); });
+    return d;
+  }
+
+  D visit(DeleteDeclaration d)
+  {
+    if (!ddoc(d))
+      return d;
+    DECL({ writeFuncHeader(d, d.funcBody); });
+    DESC({ writeComment(); });
+    return d;
+  }
+
+  D visit(VariablesDeclaration d)
+  {
+    if (!ddoc(d))
+      return d;
+    char[] type = "auto";
+    if (d.typeNode)
+      type = textSpan(d.typeNode.baseType.begin, d.typeNode.end);
+    foreach (name; d.names)
+    {
+      DECL({ write(type, " "); SYMBOL(name.str); });
+      DESC({ writeComment(); });
+    }
+    return d;
+  }
+
+  D visit(InvariantDeclaration d)
+  {
+    if (!ddoc(d))
+      return d;
+    DECL({ write("invariant"); });
+    DESC({ writeComment(); });
+    return d;
+  }
+
+  D visit(UnittestDeclaration d)
+  {
+    if (!ddoc(d))
+      return d;
+    DECL({ write("unittest"); });
+    DESC({ writeComment(); });
+    return d;
+  }
+
+  D visit(DebugDeclaration d)
+  { return d; }
+
+  D visit(VersionDeclaration d)
+  { return d; }
 }