summary refs log tree commit diff stats
path: root/nim/docgen.pas
diff options
context:
space:
mode:
Diffstat (limited to 'nim/docgen.pas')
-rwxr-xr-xnim/docgen.pas934
1 files changed, 934 insertions, 0 deletions
diff --git a/nim/docgen.pas b/nim/docgen.pas
new file mode 100755
index 000000000..9615dad35
--- /dev/null
+++ b/nim/docgen.pas
@@ -0,0 +1,934 @@
+//
+//
+//           The Nimrod Compiler
+//        (c) Copyright 2008 Andreas Rumpf
+//
+//    See the file "copying.txt", included in this
+//    distribution, for details about the copyright.
+//
+
+unit docgen;
+
+// This is the documentation generator. It is currently pretty simple: No
+// semantic checking is done for the code. Cross-references are generated
+// by knowing how the anchors are going to be named.
+
+interface
+
+{$include 'config.inc'}
+
+uses
+  nsystem, charsets, ast, astalgo, strutils, hashes, options, nversion, msgs,
+  nos, ropes, idents, wordrecg, nmath, pnimsyn, rnimsyn, scanner, rst, ntime,
+  highlite;
+
+procedure CommandDoc(const filename: string);
+procedure CommandRst2Html(const filename: string);
+
+implementation
+
+type
+  TTocEntry = record
+    n: PRstNode;
+    refname, header: PRope;
+  end;
+  TSections = array [TSymKind] of PRope;
+  TMetaEnum = (metaNone, metaTitle, metaSubtitle);
+  TDocumentor = record  // contains a module's documentation
+    filename: string;   // filename of the source file; without extension
+    basedir: string;    // base directory (where to put the documentation)
+    modDesc: PRope;     // module description
+    dependsOn: PRope;   // dependencies
+    id: int;            // for generating IDs
+    tocPart: array of TTocEntry;
+    hasToc: bool;
+    toc, section: TSections;
+    indexFile, theIndex: PRstNode;
+    indexValFilename: string;
+    indent, verbatim: int;        // for code generation
+    meta: array [TMetaEnum] of PRope;
+  end;
+  PDoc = ^TDocumentor;
+
+function findIndexNode(n: PRstNode): PRstNode;
+var
+  i: int;
+begin
+  if n = nil then 
+    result := nil
+  else if n.kind = rnIndex then begin
+    result := n.sons[2];
+    if result = nil then begin
+      result := newRstNode(rnDefList);
+      n.sons[2] := result
+    end
+    else if result.kind = rnInner then 
+      result := result.sons[0]
+  end
+  else begin
+    result := nil;
+    for i := 0 to rsonsLen(n)-1 do begin
+      result := findIndexNode(n.sons[i]);
+      if result <> nil then exit
+    end
+  end
+end;
+
+procedure initIndexFile(d: PDoc);
+var
+  h: PRstNode;
+  dummyHasToc: bool;
+begin
+  if gIndexFile = '' then exit;
+  gIndexFile := appendFileExt(gIndexFile, 'txt');
+  d.indexValFilename := changeFileExt(extractFilename(d.filename), HtmlExt);
+  if ExistsFile(gIndexFile) then begin
+    d.indexFile := rstParse(readFile(gIndexFile), false, gIndexFile, 0, 1, 
+                            dummyHasToc);
+    d.theIndex := findIndexNode(d.indexFile);
+    if (d.theIndex = nil) or (d.theIndex.kind <> rnDefList) then 
+      rawMessage(errXisNoValidIndexFile, gIndexFile);
+    clearIndex(d.theIndex, d.indexValFilename);
+  end
+  else begin
+    d.indexFile := newRstNode(rnInner);
+    h := newRstNode(rnOverline);
+    h.level := 1;
+    addSon(h, newRstNode(rnLeaf, 'Index'));
+    addSon(d.indexFile, h);
+    h := newRstNode(rnIndex);
+    addSon(h, nil); // no argument
+    addSon(h, nil); // no options
+    d.theIndex := newRstNode(rnDefList);
+    addSon(h, d.theIndex);
+    addSon(d.indexFile, h);
+  end
+end;
+
+function newDocumentor(const filename: string): PDoc;
+begin
+  new(result);
+{@ignore}
+  fillChar(result^, sizeof(result^), 0);
+{@emit
+  result.tocPart := [];
+}
+  result.filename := filename;
+  result.id := 100;
+end;
+
+function getVarIdx(const varnames: array of string; const id: string): int;
+var
+  i: int;
+begin
+  for i := 0 to high(varnames) do
+    if cmpIgnoreStyle(varnames[i], id) = 0 then begin 
+      result := i; exit
+    end;
+  result := -1
+end;
+
+function ropeFormatNamedVars(const frmt: TFormatStr;
+                             const varnames: array of string;
+                             const varvalues: array of PRope): PRope;
+var
+  i, j, L, start, idx: int;
+  id: string;
+begin
+  i := strStart;
+  L := length(frmt);
+  result := nil;
+  while i <= L + StrStart - 1 do begin
+    if frmt[i] = '$' then begin
+      inc(i);                 // skip '$'
+      case frmt[i] of
+        '$': begin
+          app(result, '$'+'');
+          inc(i)
+        end;
+        '0'..'9': begin
+          j := 0;
+          while true do begin
+            j := (j * 10) + Ord(frmt[i]) - ord('0');
+            inc(i);
+            if (i > L+StrStart-1) or not (frmt[i] in ['0'..'9']) then break
+          end;
+          if j > high(varvalues) + 1 then
+            internalError('ropeFormatNamedVars');
+          app(result, varvalues[j - 1])
+        end;
+        'A'..'Z', 'a'..'z', #128..#255: begin
+          id := '';
+          while true do begin
+            addChar(id, frmt[i]);
+            inc(i);
+            if not (frmt[i] in ['A'..'Z', '_', 'a'..'z', #128..#255]) then break
+          end;
+          // search for the variable:
+          idx := getVarIdx(varnames, id);
+          if idx >= 0 then app(result, varvalues[idx])
+          else rawMessage(errUnkownSubstitionVar, id)
+        end;
+        '{': begin
+          id := '';
+          inc(i);
+          while frmt[i] <> '}' do begin
+            if frmt[i] = #0 then rawMessage(errTokenExpected, '}'+'');
+            addChar(id, frmt[i]);
+            inc(i);
+          end;
+          inc(i); // skip }
+          // search for the variable:
+          idx := getVarIdx(varnames, id);
+          if idx >= 0 then app(result, varvalues[idx])
+          else rawMessage(errUnkownSubstitionVar, id)
+        end
+        else
+          InternalError('ropeFormatNamedVars')
+      end
+    end;
+    start := i;
+    while (i <= L + StrStart - 1) do begin
+      if (frmt[i] <> '$') then
+        inc(i)
+      else
+        break
+    end;
+    if i - 1 >= start then
+      app(result, ncopy(frmt, start, i - 1))
+  end
+end;
+
+procedure addXmlChar(var dest: string; c: Char);
+begin
+  case c of
+    '&': dest := dest + '&amp;';
+    '<': dest := dest + '&lt;';
+    '>': dest := dest + '&gt;';
+    else addChar(dest, c)
+  end
+end;
+
+function toXml(const s: string): string;
+var
+  i: int;
+begin
+  result := '';
+  for i := strStart to length(s)+strStart-1 do addXmlChar(result, s[i])
+end;
+
+function renderRstToHtml(d: PDoc; n: PRstNode): PRope; forward;
+
+function renderAux(d: PDoc; n: PRstNode; const outer: string = '$1';
+                   const inner: string = '$1'): PRope;
+var
+  i: int;
+begin
+  result := nil;
+  for i := 0 to rsonsLen(n)-1 do
+    appRopeFormat(result, inner, [renderRstToHtml(d, n.sons[i])]);
+  result := ropeFormat(outer, [result]);
+end;
+
+procedure setIndexForSourceTerm(d: PDoc; name: PRstNode; id: int);
+var
+  a, h: PRstNode;
+begin
+  if d.theIndex = nil then exit;
+  h := newRstNode(rnHyperlink);
+  a := newRstNode(rnLeaf, d.indexValFilename +{&} '#' +{&} toString(id));
+  addSon(h, a);
+  addSon(h, a);
+  a := newRstNode(rnIdx);
+  addSon(a, name);
+  setIndexPair(d.theIndex, a, h);  
+end;
+
+function renderIndexTerm(d: PDoc; n: PRstNode): PRope;
+var
+  a, h: PRstNode;
+begin
+  inc(d.id);
+  result := ropeFormat('<em id="$1">$2</em>', 
+                       [toRope(d.id), renderAux(d, n)]);
+  h := newRstNode(rnHyperlink);
+  a := newRstNode(rnLeaf, d.indexValFilename +{&} '#' +{&} toString(d.id));
+  addSon(h, a);
+  addSon(h, a);
+  setIndexPair(d.theIndex, n, h);
+end;
+
+function genComment(d: PDoc; n: PNode): PRope;
+var
+  dummyHasToc: bool;
+begin
+  if (n.comment <> snil) and startsWith(n.comment, '##') then
+    result := renderRstToHtml(d, rstParse(n.comment, true,
+                        toFilename(n.info),
+                        toLineNumber(n.info), toColumn(n.info),
+                        dummyHasToc))
+  else
+    result := nil;
+end;
+
+function genRecComment(d: PDoc; n: PNode): PRope;
+var
+  i: int;
+begin
+  if n = nil then begin result := nil; exit end;
+  result := genComment(d, n);
+  if result = nil then
+    for i := 0 to sonsLen(n)-1 do begin
+      result := genRecComment(d, n.sons[i]);
+      if result <> nil then exit
+    end
+  else
+    n.comment := snil
+end;
+
+function isVisible(n: PNode): bool;
+var
+  v: PIdent;
+begin
+  result := false;
+  if n.kind = nkPostfix then begin
+    if (sonsLen(n) = 2) and (n.sons[0].kind = nkIdent) then begin
+      v := n.sons[0].ident;
+      result := (v.id = ord(wStar)) or (v.id = ord(wMinus));
+    end
+  end
+  else if n.kind = nkSym then
+    result := sfInInterface in n.sym.flags
+  else if n.kind = nkPragmaExpr then 
+    result := isVisible(n.sons[0]);
+end;
+
+function getName(n: PNode): string;
+begin
+  case n.kind of
+    nkPostfix: result := getName(n.sons[1]);
+    nkPragmaExpr: result := getName(n.sons[0]);
+    nkSym: result := toXML(n.sym.name.s);
+    nkIdent: result := toXML(n.ident.s);
+    nkAccQuoted: result := '`' +{&} getName(n.sons[0]) +{&} '`';
+    else begin
+      internalError(n.info, 'getName()');
+      result := ''
+    end
+  end
+end;
+
+function getRstName(n: PNode): PRstNode;
+begin
+  case n.kind of
+    nkPostfix: result := getRstName(n.sons[1]);
+    nkPragmaExpr: result := getRstName(n.sons[0]);
+    nkSym: result := newRstNode(rnLeaf, n.sym.name.s);
+    nkIdent: result := newRstNode(rnLeaf, n.ident.s);
+    nkAccQuoted: result := getRstName(n.sons[0]);
+    else begin
+      internalError(n.info, 'getRstName()');
+      result := nil
+    end
+  end
+end;
+
+procedure genItem(d: PDoc; n, nameNode: PNode; k: TSymKind);
+var
+  r: TSrcGen;
+  kind: TTokType;
+  literal: string;
+  name, result, comm: PRope;
+begin
+  if not isVisible(nameNode) then exit;
+  name := toRope(getName(nameNode));
+  result := nil;
+  literal := '';
+  kind := tkEof;
+{@ignore}
+  fillChar(r, sizeof(r), 0);
+{@emit}
+  comm := genRecComment(d, n); // call this here for the side-effect!
+  initTokRender(r, n, {@set}[renderNoPragmas, renderNoBody]);
+  while true do begin
+    getNextTok(r, kind, literal);
+    case kind of
+      tkEof: break;
+      tkComment: 
+        appRopeFormat(result, '<span class="Comment">$1</span>', 
+                      [toRope(toXml(literal))]);
+      tokKeywordLow..tokKeywordHigh: 
+        appRopeFormat(result, '<span class="Keyword">$1</span>', 
+                      [toRope(literal)]);
+      tkOpr, tkHat: 
+        appRopeFormat(result, '<span class="Operator">$1</span>', 
+                      [toRope(toXml(literal))]);
+      tkStrLit..tkTripleStrLit: 
+        appRopeFormat(result, '<span class="StringLit">$1</span>', 
+                      [toRope(toXml(literal))]);
+      tkCharLit, tkRCharLit:
+        appRopeFormat(result, '<span class="CharLit">$1</span>', 
+                      [toRope(toXml(literal))]);
+      tkIntLit..tkInt64Lit:
+        appRopeFormat(result, '<span class="DecNumber">$1</span>', 
+                      [toRope(literal)]);
+      tkFloatLit..tkFloat64Lit: 
+        appRopeFormat(result, '<span class="FloatNumber">$1</span>', 
+                      [toRope(literal)]);
+      tkSymbol:
+        appRopeFormat(result, '<span class="Identifier">$1</span>', 
+                      [toRope(literal)]);
+      tkInd, tkSad, tkDed, tkSpaces: 
+        app(result, literal);
+        //appRopeFormat(result, '<span class="Whitespace">$1</span>', 
+        //              [toRope(literal)]);
+      tkParLe, tkParRi, tkBracketLe, tkBracketRi, tkCurlyLe, tkCurlyRi,
+      tkBracketDotLe, tkBracketDotRi, tkCurlyDotLe, tkCurlyDotRi, 
+      tkParDotLe, tkParDotRi, tkComma, tkSemiColon, tkColon,
+      tkEquals, tkDot, tkDotDot, tkAccent:
+        appRopeFormat(result, '<span class="Other">$1</span>', 
+                      [toRope(literal)]);
+      else InternalError(n.info, 'docgen.genThing(' + toktypeToStr[kind] + ')');
+    end
+  end;
+  inc(d.id);
+  app(d.section[k], ropeFormatNamedVars(getConfigVar('doc.item'),
+                ['name', 'header', 'desc', 'itemID'],
+                [name, result, comm, toRope(d.id)]));
+  app(d.toc[k], ropeFormatNamedVars(getConfigVar('doc.item.toc'),
+                ['name', 'header', 'desc', 'itemID'],
+                [name, result, comm, toRope(d.id)]));
+  setIndexForSourceTerm(d, getRstName(nameNode), d.id);
+end;
+
+function renderHeadline(d: PDoc; n: PRstNode): PRope;
+var
+  i, len: int;
+  refname: PRope;
+begin
+  result := nil;
+  for i := 0 to rsonsLen(n)-1 do
+    app(result, renderRstToHtml(d, n.sons[i]));
+  refname := toRope(rstnodeToRefname(n));
+  if d.hasToc then begin
+    len := length(d.tocPart);
+    setLength(d.tocPart, len+1);
+    d.tocPart[len].refname := refname;
+    d.tocPart[len].n := n;
+    d.tocPart[len].header := result;
+    result := ropeFormat('<h$1><a class="toc-backref" id="$2" href="#$2_toc">$3'+
+                         '</a></h$1>',
+                         [toRope(n.level), d.tocPart[len].refname, result]);
+  end
+  else 
+    result := ropeFormat('<h$1 id="$2">$3</h$1>', 
+                         [toRope(n.level), refname, result]);
+end;
+
+function renderOverline(d: PDoc; n: PRstNode): PRope;
+var
+  i: int;
+  t: PRope;
+begin
+  t := nil;
+  for i := 0 to rsonsLen(n)-1 do
+    app(t, renderRstToHtml(d, n.sons[i]));
+  result := nil;
+  if d.meta[metaTitle] = nil then d.meta[metaTitle] := t
+  else if d.meta[metaSubtitle] = nil then d.meta[metaSubtitle] := t
+  else
+    result := ropeFormat('<h$1 id="$2"><center>$3</center></h$1>',
+                         [toRope(n.level), toRope(rstnodeToRefname(n)), t]);  
+end;
+
+function renderRstToRst(d: PDoc; n: PRstNode): PRope; forward;
+
+function renderRstSons(d: PDoc; n: PRstNode): PRope;
+var
+  i: int;
+begin
+  result := nil;
+  for i := 0 to rsonsLen(n)-1 do app(result, renderRstToRst(d, n.sons[i]));
+end;
+
+function renderRstToRst(d: PDoc; n: PRstNode): PRope;
+// this is needed for the index generation; it may also be useful for
+// debugging, but most code is already debugged...
+const
+  lvlToChar: array [0..8] of char = ('!', '=', '-', '~', '`', 
+                                     '<', '*', '|', '+');
+var
+  L: int;
+  ind: PRope;
+begin
+  result := nil;
+  if n = nil then exit;
+  ind := toRope(repeatChar(d.indent));
+  case n.kind of
+    rnInner: result := renderRstSons(d, n);
+    rnHeadline: begin
+      result := renderRstSons(d, n);
+      L := ropeLen(result);
+      result := ropeFormat('$n$1$2$n$1$3', [ind, result, 
+                           toRope(repeatChar(L, lvlToChar[n.level]))]);
+    end;
+    rnOverline: begin
+      result := renderRstSons(d, n);
+      L := ropeLen(result);
+      result := ropeFormat('$n$1$3$n$1$2$n$1$3', [ind, result, 
+                           toRope(repeatChar(L, lvlToChar[n.level]))]);
+    end;
+    rnTransition: 
+      result := ropeFormat('$n$n$1$2$n$n', 
+                          [ind, toRope(repeatChar(78-d.indent, '-'))]);
+    rnParagraph: begin
+      result := renderRstSons(d, n);
+      result := ropeFormat('$n$n$1$2', [ind, result]);
+    end;
+    rnBulletItem: begin
+      inc(d.indent, 2);
+      result := renderRstSons(d, n);
+      if result <> nil then result := ropeFormat('$n$1* $2', [ind, result]);
+      dec(d.indent, 2);
+    end;
+    rnEnumItem: begin
+      inc(d.indent, 4);
+      result := renderRstSons(d, n);
+      if result <> nil then result := ropeFormat('$n$1(#) $2', [ind, result]);
+      dec(d.indent, 4);
+    end;
+    rnOptionList, rnFieldList, rnDefList, rnDefItem, rnLineBlock, rnFieldName,
+    rnFieldBody, rnStandaloneHyperlink, rnBulletList, rnEnumList: 
+      result := renderRstSons(d, n);
+    rnDefName: begin
+      result := renderRstSons(d, n);
+      result := ropeFormat('$n$n$1$2', [ind, result]);
+    end;
+    rnDefBody: begin
+      inc(d.indent, 2);
+      result := renderRstSons(d, n);
+      if n.sons[0].kind <> rnBulletList then
+        result := ropeFormat('$n$1  $2', [ind, result]);
+      dec(d.indent, 2);
+    end;
+    rnField: begin
+      result := renderRstToRst(d, n.sons[0]);
+      L := max(ropeLen(result)+3, 30);
+      inc(d.indent, L);
+      result := ropeFormat('$n$1:$2:$3$4', [
+        ind, result, toRope(repeatChar(L-ropeLen(result)-2)),
+        renderRstToRst(d, n.sons[1])]);
+      dec(d.indent, L);
+    end;
+    rnLineBlockItem: begin
+      result := renderRstSons(d, n);
+      result := ropeFormat('$n$1| $2', [ind, result]);
+    end;
+    rnBlockQuote: begin
+      inc(d.indent, 2);
+      result := renderRstSons(d, n);
+      dec(d.indent, 2);
+    end;
+    rnRef: begin
+      result := renderRstSons(d, n);
+      result := ropeFormat('`$1`_', [result]);
+    end;
+    rnHyperlink: begin
+      result := ropeFormat('`$1 <$2>`_', [renderRstToRst(d, n.sons[0]), 
+                                          renderRstToRst(d, n.sons[1])]);
+    end;
+    rnGeneralRole: begin
+      result := renderRstToRst(d, n.sons[0]);
+      result := ropeFormat('`$1`:$2:', [result, renderRstToRst(d, n.sons[1])]);    
+    end;
+    rnSub: begin
+      result := renderRstSons(d, n);
+      result := ropeFormat('`$1`:sub:', [result]);
+    end;
+    rnSup: begin
+      result := renderRstSons(d, n);
+      result := ropeFormat('`$1`:sup:', [result]);
+    end;
+    rnIdx: begin
+      result := renderRstSons(d, n);
+      result := ropeFormat('`$1`:idx:', [result]);
+    end;
+    rnEmphasis: begin
+      result := renderRstSons(d, n);
+      result := ropeFormat('*$1*', [result]);
+    end;
+    rnStrongEmphasis: begin
+      result := renderRstSons(d, n);
+      result := ropeFormat('**$1**', [result]);
+    end;
+    rnInterpretedText: begin
+      result := renderRstSons(d, n);
+      result := ropeFormat('`$1`', [result]);
+    end;
+    rnInlineLiteral: begin
+      inc(d.verbatim);
+      result := renderRstSons(d, n);
+      result := ropeFormat('``$1``', [result]);
+      dec(d.verbatim);
+    end;
+    rnLeaf: begin
+      if (d.verbatim = 0) and (n.text = '\'+'') then 
+        result := toRope('\\') // XXX: escape more special characters!
+      else
+        result := toRope(n.text);
+    end;
+    rnIndex: begin
+      inc(d.indent, 3);
+      if n.sons[2] <> nil then
+        result := renderRstSons(d, n.sons[2]);
+      dec(d.indent, 3);
+      result := ropeFormat('$n$n$1.. index::$n$2', [ind, result]);
+    end;
+    rnContents: begin
+      result := ropeFormat('$n$n$1.. contents::', [ind]);
+    end;
+    else rawMessage(errCannotRenderX, rstnodeKindToStr[n.kind]);
+  end;
+end;
+
+function renderTocEntry(d: PDoc; const e: TTocEntry): PRope;
+begin
+  result := ropeFormat('<li><a class="reference" id="$1_toc" href="#$1">$2' +
+                       '</a></li>$n', [e.refname, e.header]);
+end;
+
+function renderTocEntries(d: PDoc; var j: int; lvl: int): PRope;
+var
+  a: int;
+begin
+  result := nil;
+  while (j <= high(d.tocPart)) do begin
+    a := abs(d.tocPart[j].n.level);
+    if (a = lvl) then begin
+      app(result, renderTocEntry(d, d.tocPart[j]));
+      inc(j);
+    end
+    else if (a > lvl) then
+      app(result, renderTocEntries(d, j, a))
+    else
+      break
+  end;
+  if lvl > 1 then
+    result := ropeFormat('<ul class="simple">$1</ul>', [result]);
+end;
+
+function renderImage(d: PDoc; n: PRstNode): PRope;
+var
+  s: string;
+begin
+  result := ropeFormat('<img src="$1"', [toRope(getArgument(n))]);
+  s := getFieldValue(n, 'height');
+  if s <> '' then appRopeFormat(result, ' height="$1"', [toRope(s)]);
+  s := getFieldValue(n, 'width');
+  if s <> '' then appRopeFormat(result, ' width="$1"', [toRope(s)]);
+  s := getFieldValue(n, 'scale');
+  if s <> '' then appRopeFormat(result, ' scale="$1"', [toRope(s)]);
+  s := getFieldValue(n, 'alt');
+  if s <> '' then appRopeFormat(result, ' alt="$1"', [toRope(s)]);
+  s := getFieldValue(n, 'align');
+  if s <> '' then appRopeFormat(result, ' align="$1"', [toRope(s)]);
+  app(result, ' />');
+  if rsonsLen(n) >= 3 then app(result, renderRstToHtml(d, n.sons[2]))
+end;
+
+function renderCodeBlock(d: PDoc; n: PRstNode): PRope;
+var
+  m: PRstNode;
+  g: TGeneralTokenizer;
+  langstr: string;
+  lang: TSourceLanguage;
+begin
+  m := n.sons[2].sons[0];
+  assert(m.kind = rnLeaf);
+  result := nil;
+  langstr := strip(getArgument(n));
+  if langstr = '' then lang := langNimrod // default language
+  else lang := getSourceLanguage(langstr);
+  if lang = langNone then begin
+    rawMessage(warnLanguageXNotSupported, langstr);
+    result := ropeFormat('<pre>$1</pre>', [toRope(m.text)])
+  end
+  else begin
+    initGeneralTokenizer(g, m.text);
+    while true do begin
+      getNextToken(g, lang);
+      case g.kind of
+        gtEof: break;
+        gtNone, gtWhitespace: 
+          app(result, ncopy(m.text, g.start+strStart, g.len+g.start));
+        else 
+          appRopeFormat(result, '<span class="$2">$1</span>',
+            [toRope(ncopy(m.text, g.start+strStart, g.len+g.start)),
+             toRope(tokenClassToStr[g.kind])]);
+      end;
+    end;
+    deinitGeneralTokenizer(g);
+    if result <> nil then result := ropeFormat('<pre>$1</pre>', [result]);
+  end
+end;
+
+function renderRstToHtml(d: PDoc; n: PRstNode): PRope;
+var
+  outer, inner: string;
+begin
+  if n = nil then begin result := nil; exit end;
+  outer := '$1';
+  inner := '$1';
+  case n.kind of
+    rnInner: begin end;
+    rnHeadline: begin
+      result := renderHeadline(d, n); exit;
+    end;
+    rnOverline: begin
+      result := renderOverline(d, n);
+      exit;
+    end;
+    rnTransition: outer := '<hr />'+nl;
+    rnParagraph: outer := '<p>$1</p>'+nl;
+    rnBulletList: outer := '<ul class="simple">$1</ul>'+nl;
+    rnBulletItem, rnEnumItem: outer := '<li>$1</li>'+nl;
+    rnEnumList: outer := '<ol class="simple">$1</ol>'+nl;
+    rnDefList: outer := '<dl class="docutils">$1</dl>'+nl;
+    rnDefItem: begin end;
+    rnDefName: outer := '<dt>$1</dt>'+nl;
+    rnDefBody: outer := '<dd>$1</dd>'+nl;
+    rnFieldList: 
+      outer := '<table class="docinfo" frame="void" rules="none">' +
+               '<col class="docinfo-name" />' +
+               '<col class="docinfo-content" />' +    
+               '<tbody valign="top">$1' +
+               '</tbody></table>';
+    rnField: outer := '<tr>$1</tr>$n';
+    rnFieldName: outer := '<th class="docinfo-name">$1:</th>';
+    rnFieldBody: outer := '<td>$1</td>';
+    rnIndex: begin
+      result := renderRstToHtml(d, n.sons[2]);
+      exit
+    end;
+
+    rnOptionList: 
+      outer := '<table frame="void">$1</table>';
+    rnOptionListItem:
+      outer := '<tr>$1</tr>$n';
+    rnOptionGroup: outer := '<th align="left">$1</th>';
+    rnDescription: outer := '<td align="left">$1</td>$n';
+    rnOption,
+    rnOptionString,
+    rnOptionArgument: assert(false);
+
+    rnLiteralBlock: outer := '<pre>$1</pre>'+nl;
+    rnQuotedLiteralBlock: assert(false);
+
+    rnLineBlock: outer := '<p>$1</p>';
+    rnLineBlockItem: outer := '$1<br />';
+
+    rnBlockQuote: outer := '<blockquote>$1</blockquote>$n';
+
+    rnTable, rnGridTable: 
+      outer := '<table border="1" class="docutils">$1</table>';
+    rnTableRow: outer := '<tr>$1</tr>$n';
+    rnTableDataCell: outer := '<td>$1</td>';
+    rnTableHeaderCell: outer := '<th>$1</th>';
+
+    rnLabel: assert(false);       // used for footnotes and other things
+    rnFootnote: assert(false);    // a footnote
+
+    rnCitation: assert(false);    // similar to footnote
+    rnRef: begin
+      result := ropeFormat('<a class="reference external" href="#$2">$1</a>',
+                           [renderAux(d, n), toRope(rstnodeToRefname(n))]);
+      exit
+    end;
+    rnStandaloneHyperlink:
+      outer := '<a class="reference external" href="$1">$1</a>';
+    rnHyperlink: begin
+      result := ropeFormat('<a class="reference external" href="$2">$1</a>',
+                           [renderRstToHtml(d, n.sons[0]), 
+                            renderRstToHtml(d, n.sons[1])]);
+      exit
+    end;
+    rnDirArg, rnRaw: begin end;
+    rnImage, rnFigure: begin
+      result := renderImage(d, n);
+      exit
+    end;
+    rnCodeBlock: begin
+      result := renderCodeBlock(d, n);
+      exit
+    end;
+    rnSubstitutionReferences, rnSubstitutionDef: outer := '|$1|';
+    rnDirective: outer := '';
+
+    // Inline markup:
+    rnGeneralRole: begin
+      result := ropeFormat('<span class="$2">$1</span>',
+                           [renderRstToHtml(d, n.sons[0]), 
+                            renderRstToHtml(d, n.sons[1])]);
+      exit
+    end;
+    rnSub: outer := '<sub>$1</sub>';
+    rnSup: outer := '<sup>$1</sup>';
+    rnEmphasis: outer := '<em>$1</em>';
+    rnStrongEmphasis: outer := '<strong>$1</strong>';
+    rnInterpretedText: outer := '<cite>$1</cite>';
+    rnIdx: begin 
+      if d.theIndex = nil then 
+        outer := '<em>$1</em>'
+      else begin
+        result := renderIndexTerm(d, n); exit
+      end
+    end;
+    rnInlineLiteral:
+      outer := '<tt class="docutils literal"><span class="pre">'
+             +{&} '$1</span></tt>';
+    rnLeaf: begin
+      result := toRope(toXml(n.text));
+      exit
+    end;
+    rnContents: begin
+      d.hasToc := true;
+      exit;
+    end;
+    rnTitle: begin
+      d.meta[metaTitle] := renderRstToHtml(d, n.sons[0]);
+      exit
+    end;
+    else assert(false);
+  end;
+  result := renderAux(d, n, outer, inner);
+end;
+
+procedure generateDoc(d: PDoc; n: PNode);
+var
+  i: int;
+begin
+  if n = nil then exit;
+  case n.kind of
+    nkCommentStmt: app(d.modDesc, genComment(d, n));
+    nkProcDef:     genItem(d, n, n.sons[namePos], skProc);
+    nkIteratorDef: genItem(d, n, n.sons[namePos], skIterator);
+    nkMacroDef:    genItem(d, n, n.sons[namePos], skMacro);
+    nkTemplateDef: genItem(d, n, n.sons[namePos], skTemplate);
+    nkVarSection: begin
+      for i := 0 to sonsLen(n)-1 do
+        genItem(d, n.sons[i], n.sons[i].sons[0], skVar);
+    end;
+    nkConstSection: begin
+      for i := 0 to sonsLen(n)-1 do
+        genItem(d, n.sons[i], n.sons[i].sons[0], skConst);
+    end;
+    nkTypeSection: begin
+      for i := 0 to sonsLen(n)-1 do
+        genItem(d, n.sons[i], n.sons[i].sons[0], skType);
+    end;
+    nkStmtList: begin
+      for i := 0 to sonsLen(n)-1 do generateDoc(d, n.sons[i]);
+    end;
+    nkWhenStmt: begin
+      // generate documentation for the first branch only:
+      generateDoc(d, lastSon(n.sons[0]));
+    end
+    else begin end
+  end
+end;
+
+procedure genSection(d: PDoc; kind: TSymKind);
+var
+  title: PRope;
+begin
+  if d.section[kind] = nil then exit;
+  title := toRope(ncopy(symKindToStr[kind], strStart+2) + 's');
+  d.section[kind] := ropeFormatNamedVars(getConfigVar('doc.section'),
+    ['sectionid', 'sectionTitle', 'sectionTitleID', 'content'],
+    [toRope(ord(kind)), title, toRope(ord(kind)+50), d.section[kind]]);
+  d.toc[kind] := ropeFormatNamedVars(getConfigVar('doc.section.toc'),
+    ['sectionid', 'sectionTitle', 'sectionTitleID', 'content'],
+    [toRope(ord(kind)), title, toRope(ord(kind)+50), d.toc[kind]]);
+end;
+
+function genHtmlFile(d: PDoc): PRope;
+var
+  code, toc, title, content: PRope;
+  bodyname: string;
+  i: TSymKind;
+  j: int;
+begin
+  j := 0;
+  toc := renderTocEntries(d, j, 1);
+  for i := low(TSymKind) to high(TSymKind) do begin
+    genSection(d, i);
+    app(toc, d.toc[i]);
+  end;
+  if toc <> nil then
+    toc := ropeFormatNamedVars(getConfigVar('doc.toc'), ['content'], [toc]);
+  code := nil;
+  for i := low(TSymKind) to high(TSymKind) do 
+    app(code, d.section[i]);
+  if d.meta[metaTitle] <> nil then
+    title := d.meta[metaTitle]
+  else
+    title := toRope('Module ' + extractFilename(changeFileExt(d.filename, '')));
+  if d.hasToc then
+    bodyname := 'doc.body_toc'
+  else
+    bodyname := 'doc.body_no_toc';
+  content := ropeFormatNamedVars(getConfigVar(bodyname),
+    ['title', 'tableofcontents', 'moduledesc', 'date', 'time', 'content'],
+    [title, toc, d.modDesc, toRope(getDateStr()), toRope(getClockStr()), code]);
+  if not (optCompileOnly in gGlobalOptions) then 
+    code := ropeFormatNamedVars(getConfigVar('doc.file'),
+      ['title', 'tableofcontents', 'moduledesc', 'date', 'time', 'content'],
+      [title, toc, d.modDesc,
+       toRope(getDateStr()), toRope(getClockStr()), content])
+  else
+    code := content;
+  result := code;
+end;
+
+procedure generateIndex(d: PDoc);
+begin
+  if d.theIndex <> nil then begin
+    sortIndex(d.theIndex);
+    writeRope(renderRstToRst(d, d.indexFile), gIndexFile);
+  end
+end;
+
+procedure CommandDoc(const filename: string);
+var
+  ast: PNode;
+  d: PDoc;
+begin
+  ast := parseFile(appendFileExt(filename, nimExt));
+  if ast = nil then exit;
+  d := newDocumentor(filename);
+  initIndexFile(d);
+  d.hasToc := true;
+  generateDoc(d, ast);
+  writeRope(genHtmlFile(d), getOutFile(filename, HtmlExt));
+  generateIndex(d);
+end;
+
+procedure CommandRst2Html(const filename: string);
+var
+  filen: string;
+  d: PDoc;
+  rst: PRstNode;
+begin
+  filen := appendFileExt(filename, 'txt');
+  d := newDocumentor(filen);
+  initIndexFile(d);
+  rst := rstParse(readFile(filen), false, filen, 0, 1, d.hasToc);
+  d.modDesc := renderRstToHtml(d, rst);
+  writeRope(genHtmlFile(d), getOutFile(filename, HtmlExt));
+  generateIndex(d);
+end;
+
+// #FFD700
+// #9f9b75
+
+end.