view 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 source

/++
  Author: Aziz Köksal
  License: GPL3
+/
module cmd.DDoc;

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;
import dil.semantic.Symbols;
import dil.Information;
import dil.File;
import common;

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)
{
  // Parse macro files.
  MacroTable mtable;
  MacroParser mparser;
  foreach (macroPath; macroPaths)
  {
    auto macros = mparser.parse(loadFile(macroPath));
    mtable = new MacroTable(mtable);
    mtable.insert(macros);
  }

//   foreach (k, v; mtable.table)
//     Stdout(k)("=")(v.text);

  foreach (filePath; filePaths)
  {
    auto mod = new Module(filePath, infoMan);
    // Parse the file.
    mod.parse();
    if (mod.hasErrors)
      continue;

    // Start semantic analysis.
    auto pass1 = new SemanticPass1(mod);
    pass1.start();

    // Generate documentation.
    auto dest = new FilePath(destDir);
    generateDocumentation(dest, mod, mtable, incUndoc);
  }
}

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("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("DATETIME", time_str.dup);
  mtable.insert("YEAR", time_str[20..24].dup);

  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; }
}