summary refs log blame commit diff stats
path: root/nim/docgen.pas
blob: 468dd1bc955ff6ec5880f870e47f5b42547eae6e (plain) (tree)
1
2
3
4

                                
                                          














                                                                         
                                                                              
                                                                              


                                                  
                                                 







                                        
                                                                           




                                                                          
                                                            








                                                        

                               


                                              
                 





                                      
                                      















                                         
                                              
                                                                            
                                                                          
                                             
                                                                

















                                                     
            



                                        
                        

                              
                          

                                      





                                                                           
                                                     







                                                                     
                                



                    
           


                                         



                                      











                                                                           
                   










































                                                                                
                                                                              

                                                


                             


                         



































                                                
                                                          
     




                                             
                                                 






                                  
                                                            

                        
               



                                         
                                                                              
                     
                              
          
                                               



                              
                                                                      
     
    


























                                                                              
 
                                                                            



                                
                                              
                                   






                                                                  
                                                                  


                         
                                 





                                                      
                                                                   
                               
                                                                  








                                                             

                                                                             








                                                 





                                               















                                                                 
                                    

                                   
                                                         
                
                                                           



                                                                    




































                                                                     
                                                                             


                                 
                

                                                        
                                    
                                                        
                                         
                   

                                                         
                               

                                                          
                

                                                        
                           

                                                          
                               

                                                            
               




                                                           
                                                                       
                                                                 
                                                            

                                                      



                                                                                
                                           
                                                                 
                                                                              








                                                       
                                              





                                         



                                                                        
     
      


                                                          







                                                     
                                         


                                                                   


                                                                     














                                                                           
                                                             











                                                         
                                                   
                                                                  


                                    
                                                         
                                                                  
        
                                     
                                                                  
                                    
                                                 


                                    
                                                                       



                                    
                                                                         

                                                                              
                                                                 

                                    
                                                 



                                            
                                                   




                                             
                                       




                                                             
                                                 






                                    
                                         
                      
                                                                  
                                                                    

                                             
                                                                          

                                    
                                             

                                    
                                             

                                    
                                             

                                    
                                        

                                    
                                          

                                    
                                        


                                    
                                          

                      
                                                    







                                                                      
                                                             
                     
                                                    





                                                                


                                                                    

















                                                                
                                                                    
    



                                          
                                                  
                   
     


                                                                                
                                  
                                                                              
                                 
                                                                            
                               
                                                                 
                                 




                                                                    







                                                      
                               
                         
                                                              



                                                             
                            





                                    
                                   
                                                     
                                                      
            




                                                              

                              


                                                                          
    


                                                      
                                         
                                       



































                                                                         
    
                                                     
   
         
                                                
                
































                                                                               
        






                                                                      
 
                 
                                                                      
                     



                                                                               
                   
                                                      
 


                                                                            
 
                                                                           
 

                                                                            
 


















                                                                               
 
                                                                             
 



                                                                          
                          













                                                                       
                     










                                                                            
                              

                                                                   
                    






                                                                        






                                         
                                                           
                                                             


                                                                
                                   
                                                          

                                   
                                                            

                                   
                                                           
























                                                                       
                                    






                                   

                 




                                                                            
                                                                      









                                                                                
                                                
                                                         


                                                                          
















                                                          
                                                 



                               
                                                          

                   
                                                        


                
              
     
                                       

                                                                 

                                                

                   









                                                  
    
//
//
//           The Nimrod Compiler
//        (c) Copyright 2009 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, nhashes, options, nversion, msgs,
  nos, ropes, idents, wordrecg, nmath, syntaxes, rnimsyn, scanner, rst, ntime,
  highlite;

procedure CommandDoc(const filename: string);
procedure CommandRst2Html(const filename: string);
procedure CommandRst2TeX(const filename: string);

implementation

type
  TTocEntry = record
    n: PRstNode;
    refname, header: PRope;
  end;
  TSections = array [TSymKind] of PRope;
  TMetaEnum = (metaNone, metaTitle, metaSubtitle, metaAuthor, metaVersion);
  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
    splitAfter: int;    // split too long entries in the TOC
    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;

var
  splitter: string = '<wbr />';

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 := addFileExt(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;
var
  s: string;
begin
  new(result);
{@ignore}
  fillChar(result^, sizeof(result^), 0);
{@emit
  result.tocPart := @[];
}
  result.filename := filename;
  result.id := 100;
  result.splitAfter := 20;
  s := getConfigVar('split.item.toc');
  if s <> '' then
    result.splitAfter := parseInt(s);
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, num: int;
  id: string;
begin
  i := strStart;
  L := length(frmt);
  result := nil;
  num := 0;
  while i <= L + StrStart - 1 do begin
    if frmt[i] = '$' then begin
      inc(i);                 // skip '$'
      case frmt[i] of
        '#': begin
          app(result, varvalues[num]);
          inc(num);
          inc(i);
        end;
        '$': 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');
          num := j;
          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;

// -------------------- dispatcher -------------------------------------------

procedure addXmlChar(var dest: string; c: Char);
begin
  case c of
    '&': add(dest, '&amp;');
    '<': add(dest, '&lt;');
    '>': add(dest, '&gt;');
    '"': add(dest, '&quot;');
    else addChar(dest, c)
  end
end;

procedure addRtfChar(var dest: string; c: Char);
begin
  case c of
    '{': add(dest, '\{');
    '}': add(dest, '\}');
    '\': add(dest, '\\');
    else addChar(dest, c)
  end
end;

procedure addTexChar(var dest: string; c: Char);
begin
  case c of
    '_': add(dest, '\_');
    '{': add(dest, '\symbol{123}');
    '}': add(dest, '\symbol{125}');
    '[': add(dest, '\symbol{91}');
    ']': add(dest, '\symbol{93}');
    '\': add(dest, '\symbol{92}');
    '$': add(dest, '\$');
    '&': add(dest, '\&');
    '#': add(dest, '\#');
    '%': add(dest, '\%');
    '~': add(dest, '\symbol{126}');
    '@': add(dest, '\symbol{64}');
    '^': add(dest, '\symbol{94}');
    '`': add(dest, '\symbol{96}');
    else addChar(dest, c)
  end
end;

procedure escChar(var dest: string; c: Char);
begin
  if gCmd <> cmdRst2Tex then addXmlChar(dest, c)
  else addTexChar(dest, c);
end;

function nextSplitPoint(const s: string; start: int): int;
begin
  result := start;
  while result < length(s)+strStart do begin
    case s[result] of
      '_': exit;
      'a'..'z': begin
        if result+1 < length(s)+strStart then
          if s[result+1] in ['A'..'Z'] then exit;
      end;
      else begin end;
    end;
    inc(result);
  end;
  dec(result); // last valid index
end;

function esc(const s: string; splitAfter: int = -1): string;
var
  i, j, k, partLen: int;
begin
  result := '';
  if splitAfter >= 0 then begin
    partLen := 0;
    j := strStart;
    while j < length(s)+strStart do begin
      k := nextSplitPoint(s, j);
      if (splitter <> ' '+'') or (partLen + k - j + 1 > splitAfter) then begin
        partLen := 0;
        add(result, splitter);
      end;
      for i := j to k do escChar(result, s[i]);
      inc(partLen, k - j + 1);
      j := k+1;
    end;
  end
  else begin
    for i := strStart to length(s)+strStart-1 do escChar(result, s[i])
  end
end;

function disp(const xml, tex: string): string;
begin  
  if gCmd <> cmdRst2Tex then
    result := xml
  else
    result := tex
end;

function dispF(const xml, tex: string; const args: array of PRope): PRope;
begin
  if gCmd <> cmdRst2Tex then
    result := ropef(xml, args)
  else
    result := ropef(tex, args)
end;

procedure dispA(var dest: PRope; const xml, tex: string;
                const args: array of PRope);
begin
  if gCmd <> cmdRst2Tex then
    appf(dest, xml, args)
  else
    appf(dest, tex, args)
end;

// ---------------------------------------------------------------------------

function renderRstToOut(d: PDoc; n: PRstNode): PRope; forward;

function renderAux(d: PDoc; n: PRstNode; const outer: string = '$1'): PRope;
var
  i: int;
begin
  result := nil;
  for i := 0 to rsonsLen(n)-1 do
    app(result, renderRstToOut(d, n.sons[i]));
  result := ropef(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 +{&} disp('#'+'', '')
                  +{&} 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 := dispF('<em id="$1">$2</em>',
                  '$2\label{$1}', [toRope(d.id), renderAux(d, n)]);
  h := newRstNode(rnHyperlink);
  a := newRstNode(rnLeaf, d.indexValFilename +{&} disp('#'+'', '')
                  +{&} 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 := renderRstToOut(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 begin
    if not (n.kind in [nkEmpty..nkNilLit]) then
      for i := 0 to sonsLen(n)-1 do begin
        result := genRecComment(d, n.sons[i]);
        if result <> nil then exit
      end
  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; splitAfter: int = -1): string;
begin
  case n.kind of
    nkPostfix: result := getName(n.sons[1], splitAfter);
    nkPragmaExpr: result := getName(n.sons[0], splitAfter);
    nkSym: result := esc(n.sym.name.s, splitAfter);
    nkIdent: result := esc(n.ident.s, splitAfter);
    nkAccQuoted:
      result := esc('`'+'') +{&} getName(n.sons[0], splitAfter) +{&}
                esc('`'+'');
    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, renderNoComments,
                             renderDocComments]);
  while true do begin
    getNextTok(r, kind, literal);
    case kind of
      tkEof: break;
      tkComment:
        dispA(result, '<span class="Comment">$1</span>',
                      '\spanComment{$1}',
                      [toRope(esc(literal))]);
      tokKeywordLow..tokKeywordHigh:
        dispA(result, '<span class="Keyword">$1</span>',
                      '\spanKeyword{$1}',
                      [toRope(literal)]);
      tkOpr, tkHat:
        dispA(result, '<span class="Operator">$1</span>',
                      '\spanOperator{$1}',
                      [toRope(esc(literal))]);
      tkStrLit..tkTripleStrLit:
        dispA(result, '<span class="StringLit">$1</span>',
                      '\spanStringLit{$1}',
                      [toRope(esc(literal))]);
      tkCharLit:
        dispA(result, '<span class="CharLit">$1</span>',
                      '\spanCharLit{$1}',
                      [toRope(esc(literal))]);
      tkIntLit..tkInt64Lit:
        dispA(result, '<span class="DecNumber">$1</span>',
                      '\spanDecNumber{$1}',
                      [toRope(esc(literal))]);
      tkFloatLit..tkFloat64Lit:
        dispA(result, '<span class="FloatNumber">$1</span>',
                      '\spanFloatNumber{$1}',
                      [toRope(esc(literal))]);
      tkSymbol:
        dispA(result, '<span class="Identifier">$1</span>',
                      '\spanIdentifier{$1}',
                      [toRope(esc(literal))]);
      tkInd, tkSad, tkDed, tkSpaces: begin
        app(result, literal)
      end;
      tkParLe, tkParRi, tkBracketLe, tkBracketRi, tkCurlyLe, tkCurlyRi,
      tkBracketDotLe, tkBracketDotRi, tkCurlyDotLe, tkCurlyDotRi,
      tkParDotLe, tkParDotRi, tkComma, tkSemiColon, tkColon,
      tkEquals, tkDot, tkDotDot, tkAccent:
        dispA(result, '<span class="Other">$1</span>',
                      '\spanOther{$1}',
                      [toRope(esc(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'],
      [toRope(getName(nameNode, d.splitAfter)), 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, renderRstToOut(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 := dispF(
      '<h$1><a class="toc-backref" id="$2" href="#$2_toc">$3</a></h$1>',
      '\rsth$4{$3}\label{$2}$n',
      [toRope(n.level), d.tocPart[len].refname, result,
       toRope(chr(n.level-1+ord('A'))+'')]);
  end
  else
    result := dispF('<h$1 id="$2">$3</h$1>',
                    '\rsth$4{$3}\label{$2}$n',
                    [toRope(n.level), refname, result,
                     toRope(chr(n.level-1+ord('A'))+'')]);
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, renderRstToOut(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 := dispF('<h$1 id="$2"><center>$3</center></h$1>',
                    '\rstov$4{$3}\label{$2}$n',
                    [toRope(n.level), toRope(rstnodeToRefname(n)), t,
                     toRope(chr(n.level-1+ord('A'))+'')]);
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 := ropef('$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 := ropef('$n$1$3$n$1$2$n$1$3', [ind, result,
                      toRope(repeatChar(L, lvlToChar[n.level]))]);
    end;
    rnTransition:
      result := ropef('$n$n$1$2$n$n',
                     [ind, toRope(repeatChar(78-d.indent, '-'))]);
    rnParagraph: begin
      result := renderRstSons(d, n);
      result := ropef('$n$n$1$2', [ind, result]);
    end;
    rnBulletItem: begin
      inc(d.indent, 2);
      result := renderRstSons(d, n);
      if result <> nil then result := ropef('$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 := ropef('$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 := ropef('$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 := ropef('$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 := ropef('$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 := ropef('$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 := ropef('`$1`_', [result]);
    end;
    rnHyperlink: begin
      result := ropef('`$1 <$2>`_', [renderRstToRst(d, n.sons[0]),
                                     renderRstToRst(d, n.sons[1])]);
    end;
    rnGeneralRole: begin
      result := renderRstToRst(d, n.sons[0]);
      result := ropef('`$1`:$2:', [result, renderRstToRst(d, n.sons[1])]);
    end;
    rnSub: begin
      result := renderRstSons(d, n);
      result := ropef('`$1`:sub:', [result]);
    end;
    rnSup: begin
      result := renderRstSons(d, n);
      result := ropef('`$1`:sup:', [result]);
    end;
    rnIdx: begin
      result := renderRstSons(d, n);
      result := ropef('`$1`:idx:', [result]);
    end;
    rnEmphasis: begin
      result := renderRstSons(d, n);
      result := ropef('*$1*', [result]);
    end;
    rnStrongEmphasis: begin
      result := renderRstSons(d, n);
      result := ropef('**$1**', [result]);
    end;
    rnInterpretedText: begin
      result := renderRstSons(d, n);
      result := ropef('`$1`', [result]);
    end;
    rnInlineLiteral: begin
      inc(d.verbatim);
      result := renderRstSons(d, n);
      result := ropef('``$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 := ropef('$n$n$1.. index::$n$2', [ind, result]);
    end;
    rnContents: begin
      result := ropef('$n$n$1.. contents::', [ind]);
    end;
    else rawMessage(errCannotRenderX, rstnodeKindToStr[n.kind]);
  end;
end;

function renderTocEntry(d: PDoc; const e: TTocEntry): PRope;
begin
  result := dispF(
    '<li><a class="reference" id="$1_toc" href="#$1">$2</a></li>$n',
    '\item\label{$1_toc} $2\ref{$1}$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 := dispF('<ul class="simple">$1</ul>',
                    '\begin{enumerate}$1\end{enumerate}', [result]);
end;

function fieldAux(const s: string): PRope;
begin
  result := toRope(strip(s))
end;

function renderImage(d: PDoc; n: PRstNode): PRope;
var
  s, scale: string;
  options: PRope;
begin
  options := nil;
  s := getFieldValue(n, 'scale');
  if s <> '' then dispA(options, ' scale="$1"', ' scale=$1', [fieldAux(scale)]);
  
  s := getFieldValue(n, 'height');
  if s <> '' then dispA(options, ' height="$1"', ' height=$1', [fieldAux(s)]);
  
  s := getFieldValue(n, 'width');
  if s <> '' then dispA(options, ' width="$1"', ' width=$1', [fieldAux(s)]);
  
  s := getFieldValue(n, 'alt');
  if s <> '' then dispA(options, ' alt="$1"', '', [fieldAux(s)]);
  s := getFieldValue(n, 'align');
  if s <> '' then dispA(options, ' align="$1"', '', [fieldAux(s)]);
    
  if options <> nil then options := dispF('$1', '[$1]', [options]);
  result := dispF('<img src="$1"$2 />',
    '\includegraphics$2{$1}', [toRope(getArgument(n)), options]);
  if rsonsLen(n) >= 3 then app(result, renderRstToOut(d, n.sons[2]))
end;

function renderCodeBlock(d: PDoc; n: PRstNode): PRope;
var
  m: PRstNode;
  g: TGeneralTokenizer;
  langstr: string;
  lang: TSourceLanguage;
begin
  result := nil;
  if n.sons[2] = nil then exit;
  m := n.sons[2].sons[0];
  if (m.kind <> rnLeaf) then InternalError('renderCodeBlock');
  langstr := strip(getArgument(n));
  if langstr = '' then lang := langNimrod // default language
  else lang := getSourceLanguage(langstr);
  if lang = langNone then begin
    rawMessage(warnLanguageXNotSupported, langstr);
    result := 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: begin
          app(result, ncopy(m.text, g.start+strStart,
                            g.len+g.start-1+strStart))
        end
        else
          dispA(result,
                '<span class="$2">$1</span>',
                '\span$2{$1}',
                [toRope(esc(ncopy(m.text, g.start+strStart,
                                  g.len+g.start-1+strStart))),
                 toRope(tokenClassToStr[g.kind])]);
      end;
    end;
    deinitGeneralTokenizer(g);
  end;
  if result <> nil then
    result := dispF('<pre>$1</pre>', '\begin{rstpre}$n$1$n\end{rstpre}$n',
                    [result])
end;

function renderContainer(d: PDoc; n: PRstNode): PRope;
var
  arg: PRope;
begin
  result := renderRstToOut(d, n.sons[2]);
  arg := toRope(strip(getArgument(n)));
  if arg = nil then result := dispF('<div>$1</div>', '$1', [result])
  else result := dispF('<div class="$1">$2</div>', '$2', [arg, result])  
end;

function texColumns(n: PRstNode): string;
var
  i: int;
begin
  result := '';
  for i := 1 to rsonsLen(n) do add(result, '|X');
end;

function renderField(d: PDoc; n: PRstNode): PRope;
var
  fieldname: string;
  fieldval: PRope;
  b: bool;
begin
  b := false;
  if gCmd = cmdRst2Tex then begin
    fieldname := addNodes(n.sons[0]);
    fieldval := toRope(esc(strip(addNodes(n.sons[1]))));
    if cmpIgnoreStyle(fieldname, 'author') = 0 then begin
      if d.meta[metaAuthor] = nil then begin
        d.meta[metaAuthor] := fieldval;
        b := true
      end
    end
    else if cmpIgnoreStyle(fieldName, 'version') = 0 then begin
      if d.meta[metaVersion] = nil then begin
        d.meta[metaVersion] := fieldval;
        b := true
      end
    end
  end;
  if b then result := nil
  else result := renderAux(d, n, disp('<tr>$1</tr>$n', '$1'));
end;

function renderRstToOut(d: PDoc; n: PRstNode): PRope;
var
  i: int;
begin
  if n = nil then begin result := nil; exit end;
  case n.kind of
    rnInner: result := renderAux(d, n);
    rnHeadline: result := renderHeadline(d, n);
    rnOverline: result := renderOverline(d, n);
    rnTransition:
      result := renderAux(d, n, disp('<hr />'+nl, '\hrule'+nl));
    rnParagraph:
      result := renderAux(d, n, disp('<p>$1</p>'+nl, '$1$n$n'));
    rnBulletList:
      result := renderAux(d, n, disp('<ul class="simple">$1</ul>'+nl,
                                '\begin{itemize}$1\end{itemize}'+nl));
    rnBulletItem, rnEnumItem:
      result := renderAux(d, n, disp('<li>$1</li>'+nl, '\item $1'+nl));
    rnEnumList:
      result := renderAux(d, n, disp('<ol class="simple">$1</ol>'+nl,
                                     '\begin{enumerate}$1\end{enumerate}'+nl));
    rnDefList: 
      result := renderAux(d, n, disp('<dl class="docutils">$1</dl>'+nl,
                        '\begin{description}$1\end{description}'+nl));
    rnDefItem: 
      result := renderAux(d, n);
    rnDefName:
      result := renderAux(d, n, disp('<dt>$1</dt>'+nl, '\item[$1] '));
    rnDefBody:
      result := renderAux(d, n, disp('<dd>$1</dd>'+nl, '$1'+nl));
    rnFieldList: begin
      result := nil;
      for i := 0 to rsonsLen(n)-1 do app(result, renderRstToOut(d, n.sons[i]));
      if result <> nil then
        result := dispf('<table class="docinfo" frame="void" rules="none">' +
                        '<col class="docinfo-name" />' +
                        '<col class="docinfo-content" />' +
                        '<tbody valign="top">$1' +
                        '</tbody></table>',
                        '\begin{description}$1\end{description}'+nl, [result]);
    end;
    rnField: result := renderField(d, n);
    rnFieldName:
      result := renderAux(d, n, disp(
                  '<th class="docinfo-name">$1:</th>', '\item[$1:]'));
    rnFieldBody:
      result := renderAux(d, n, disp('<td>$1</td>', ' $1$n'));
    rnIndex:
      result := renderRstToOut(d, n.sons[2]);

    rnOptionList:
      result := renderAux(d, n, disp('<table frame="void">$1</table>',
                    '\begin{description}$n$1\end{description}'+nl));
    rnOptionListItem:
      result := renderAux(d, n, disp('<tr>$1</tr>$n', '$1'));
    rnOptionGroup:
      result := renderAux(d, n, disp('<th align="left">$1</th>', '\item[$1]'));
    rnDescription:
      result := renderAux(d, n, disp('<td align="left">$1</td>$n', ' $1$n'));
    rnOption,
    rnOptionString,
    rnOptionArgument: InternalError('renderRstToOut');

    rnLiteralBlock: 
      result := renderAux(d, n, disp('<pre>$1</pre>$n',
                                     '\begin{rstpre}$n$1$n\end{rstpre}$n'));
    rnQuotedLiteralBlock: InternalError('renderRstToOut');

    rnLineBlock: result := renderAux(d, n, disp('<p>$1</p>', '$1$n$n'));
    rnLineBlockItem: result := renderAux(d, n, disp('$1<br />', '$1\\$n'));

    rnBlockQuote:
      result := renderAux(d, n, disp('<blockquote><p>$1</p></blockquote>$n',
                                     '\begin{quote}$1\end{quote}$n'));

    rnTable, rnGridTable: begin
      result := renderAux(d, n,
                          disp('<table border="1" class="docutils">$1</table>',
                               '\begin{table}\begin{rsttab}{' +{&}
                               texColumns(n) +{&}
                               '|}$n\hline$n$1\end{rsttab}\end{table}'));
    end;
    rnTableRow: begin
      if rsonsLen(n) >= 1 then begin
        result := renderRstToOut(d, n.sons[0]);
        for i := 1 to rsonsLen(n)-1 do
          dispa(result, '$1', ' & $1', [renderRstToOut(d, n.sons[i])]);
        result := dispf('<tr>$1</tr>$n', '$1\\$n\hline$n', [result]);
      end
      else
        result := nil;
    end;
    rnTableDataCell: result := renderAux(d, n, disp('<td>$1</td>', '$1'));
    rnTableHeaderCell:
      result := renderAux(d, n, disp('<th>$1</th>', '\textbf{$1}'));

    rnLabel: InternalError('renderRstToOut'); // used for footnotes and other
    rnFootnote: InternalError('renderRstToOut'); // a footnote

    rnCitation: InternalError('renderRstToOut');    // similar to footnote
    rnRef: 
      result := dispF('<a class="reference external" href="#$2">$1</a>',
                      '$1\ref{$2}',
                      [renderAux(d, n), toRope(rstnodeToRefname(n))]);
    rnStandaloneHyperlink:
      result := renderAux(d, n, disp(
                    '<a class="reference external" href="$1">$1</a>',
                    '\href{$1}{$1}'));
    rnHyperlink: 
      result := dispF('<a class="reference external" href="$2">$1</a>',
                      '\href{$2}{$1}',
                      [renderRstToOut(d, n.sons[0]),
                       renderRstToOut(d, n.sons[1])]);
    rnDirArg, rnRaw: result := renderAux(d, n);
    rnImage, rnFigure: result := renderImage(d, n);
    rnCodeBlock: result := renderCodeBlock(d, n);
    rnContainer: result := renderContainer(d, n);
    rnSubstitutionReferences, rnSubstitutionDef:
      result := renderAux(d, n, disp('|$1|', '|$1|'));
    rnDirective: result := renderAux(d, n, '');

    // Inline markup:
    rnGeneralRole: 
      result := dispF('<span class="$2">$1</span>',
                      '\span$2{$1}',
                      [renderRstToOut(d, n.sons[0]),
                       renderRstToOut(d, n.sons[1])]);
    rnSub: result := renderAux(d, n, disp('<sub>$1</sub>', '\rstsub{$1}'));
    rnSup: result := renderAux(d, n, disp('<sup>$1</sup>', '\rstsup{$1}'));
    rnEmphasis: result := renderAux(d, n, disp('<em>$1</em>', '\emph{$1}'));
    rnStrongEmphasis:
      result := renderAux(d, n, disp('<strong>$1</strong>', '\textbf{$1}'));
    rnInterpretedText:
      result := renderAux(d, n, disp('<cite>$1</cite>', '\emph{$1}'));
    rnIdx: begin
      if d.theIndex = nil then
        result := renderAux(d, n, disp('<em>$1</em>', '\emph{$1}'))
      else
        result := renderIndexTerm(d, n);
    end;
    rnInlineLiteral:
      result := renderAux(d, n, disp(
        '<tt class="docutils literal"><span class="pre">$1</span></tt>',
        '\texttt{$1}'));
    rnLeaf: result := toRope(esc(n.text));
    rnContents: d.hasToc := true;
    rnTitle: d.meta[metaTitle] := renderRstToOut(d, n.sons[0]);
    else InternalError('renderRstToOut');
  end
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);
    nkMethodDef:    genItem(d, n, n.sons[namePos], skMethod);
    nkIteratorDef:  genItem(d, n, n.sons[namePos], skIterator);
    nkMacroDef:     genItem(d, n, n.sons[namePos], skMacro);
    nkTemplateDef:  genItem(d, n, n.sons[namePos], skTemplate);
    nkConverterDef: genItem(d, n, n.sons[namePos], skConverter);
    nkVarSection: begin
      for i := 0 to sonsLen(n)-1 do
        if n.sons[i].kind <> nkCommentStmt then
          genItem(d, n.sons[i], n.sons[i].sons[0], skVar);
    end;
    nkConstSection: begin
      for i := 0 to sonsLen(n)-1 do
        if n.sons[i].kind <> nkCommentStmt then
          genItem(d, n.sons[i], n.sons[i].sons[0], skConst);
    end;
    nkTypeSection: begin
      for i := 0 to sonsLen(n)-1 do
        if n.sons[i].kind <> nkCommentStmt then
          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 genOutFile(d: PDoc): PRope;
var
  code, toc, title, content: PRope;
  bodyname: string;
  i: TSymKind;
  j: int;
begin
  j := 0;
  toc := renderTocEntries(d, j, 1);
  code := nil;
  content := nil;
  title := nil;
  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]);
  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', 'author', 'version'],
      [title, toc, d.modDesc, toRope(getDateStr()), toRope(getClockStr()),
       content, d.meta[metaAuthor], d.meta[metaVersion]])
  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(addFileExt(filename, nimExt));
  if ast = nil then exit;
  d := newDocumentor(filename);
  initIndexFile(d);
  d.hasToc := true;
  generateDoc(d, ast);
  writeRope(genOutFile(d), getOutFile(filename, HtmlExt));
  generateIndex(d);
end;

procedure CommandRstAux(const filename, outExt: string);
var
  filen: string;
  d: PDoc;
  rst: PRstNode;
  code: PRope;
begin
  filen := addFileExt(filename, 'txt');
  d := newDocumentor(filen);
  initIndexFile(d);
  rst := rstParse(readFile(filen), false, filen, 0, 1, d.hasToc);
  d.modDesc := renderRstToOut(d, rst);
  code := genOutFile(d);
  writeRope(code, getOutFile(filename, outExt));
  generateIndex(d);
end;

procedure CommandRst2Html(const filename: string);
begin
  CommandRstAux(filename, HtmlExt);
end;

procedure CommandRst2TeX(const filename: string);
begin
  splitter := '\-';
  CommandRstAux(filename, TexExt);
end;

end.