summary refs log blame commit diff stats
path: root/nim/rnimsyn.pas
blob: ec1e9571e30a4ea965043d774f8ca554b9f1e894 (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 rnimsyn;

// This module implements the renderer of the standard Nimrod representation.

{$include config.inc}

interface

uses
  nsystem, charsets, scanner, options, idents, strutils, ast, msgs,
  lists;

type
  TRenderFlag = (renderNone, renderNoBody, renderNoComments,
                 renderDocComments, renderNoPragmas, renderIds);
  TRenderFlags = set of TRenderFlag;

  TRenderTok = record
    kind: TTokType;
    len: int16;
  end;
  TRenderTokSeq = array of TRenderTok;

  TSrcGen = record
    indent: int;
    lineLen: int;
    pos: int;       // current position for iteration over the buffer
    idx: int;       // current token index for iteration over the buffer
    tokens: TRenderTokSeq;
    buf: string;
    pendingNL: int; // negative if not active; else contains the
                    // indentation value
    comStack: array of PNode;  // comment stack
    flags: TRenderFlags;
  end;

procedure renderModule(n: PNode; const filename: string;
                       renderFlags: TRenderFlags = {@set}[]);

function renderTree(n: PNode; renderFlags: TRenderFlags = {@set}[]): string;

procedure initTokRender(var r: TSrcGen; n: PNode;
                        renderFlags: TRenderFlags = {@set}[]);
procedure getNextTok(var r: TSrcGen; var kind: TTokType; var literal: string);

implementation

// We render the source code in a two phases: The first
// determines how long the subtree will likely be, the second
// phase appends to a buffer that will be the output.

const
  IndentWidth = 2;
  longIndentWid = 4;
  MaxLineLen = 80;
  LineCommentColumn = 30;

procedure InitSrcGen(out g: TSrcGen; renderFlags: TRenderFlags);
begin
{@ignore}
  fillChar(g, sizeof(g), 0);
  g.comStack := nil;
  g.tokens := nil;
{@emit
  g.comStack := @[];}
{@emit
  g.tokens := @[];}
  g.indent := 0;
  g.lineLen := 0;
  g.pos := 0;
  g.idx := 0;
  g.buf := '';
  g.flags := renderFlags;
  g.pendingNL := -1;
end;

{@ignore}
procedure add(var dest: string; const src: string);
begin
  dest := dest +{&} src;
end;
{@emit}

procedure addTok(var g: TSrcGen; kind: TTokType; const s: string);
var
  len: int;
begin
  len := length(g.tokens);
  setLength(g.tokens, len+1);
  g.tokens[len].kind := kind;
  g.tokens[len].len := int16(length(s));
  add(g.buf, s);
end;

procedure addPendingNL(var g: TSrcGen);
begin
  if g.pendingNL >= 0 then begin
    addTok(g, tkInd, NL+{&}repeatChar(g.pendingNL));
    g.lineLen := g.pendingNL;
    g.pendingNL := -1;
  end
end;

procedure putNL(var g: TSrcGen; indent: int); overload;
begin
  if g.pendingNL >= 0 then
    addPendingNL(g)
  else
    addTok(g, tkInd, NL);
  g.pendingNL := indent;
  g.lineLen := indent;
end;

procedure putNL(var g: TSrcGen); overload;
begin
  putNL(g, g.indent);
end;

procedure optNL(var g: TSrcGen; indent: int); overload;
begin
  g.pendingNL := indent;
  g.lineLen := indent; // BUGFIX
end;

procedure optNL(var g: TSrcGen); overload;
begin
  optNL(g, g.indent)
end;

procedure indentNL(var g: TSrcGen);
begin
  inc(g.indent, indentWidth);
  g.pendingNL := g.indent;
  g.lineLen := g.indent;
end;

procedure Dedent(var g: TSrcGen);
begin
  dec(g.indent, indentWidth);
  assert(g.indent >= 0);
  if g.pendingNL > indentWidth then begin
    Dec(g.pendingNL, indentWidth);
    Dec(g.lineLen, indentWidth)
  end
end;

procedure put(var g: TSrcGen; const kind: TTokType; const s: string);
begin
  addPendingNL(g);
  if length(s) > 0 then begin
    addTok(g, kind, s);
    inc(g.lineLen, length(s));
  end
end;

procedure putLong(var g: TSrcGen; const kind: TTokType; const s: string;
                  lineLen: int);
// use this for tokens over multiple lines.
begin
  addPendingNL(g);
  addTok(g, kind, s);
  g.lineLen := lineLen;
end;

// ----------------------- helpers --------------------------------------------

function toNimChar(c: Char): string;
begin
  case c of
    #0: result := '\0';
    #1..#31, #128..#255: result := '\x' + strutils.toHex(ord(c), 2);
    '''', '"', '\': result := '\' + c;
    else result := c + ''
  end;
end;

function makeNimString(const s: string): string;
var
  i: int;
begin
  result := '"' + '';
  for i := strStart to length(s)+strStart-1 do add(result, toNimChar(s[i]));
  addChar(result, '"');
end;

procedure putComment(var g: TSrcGen; s: string);
var
  i, j, ind, comIndent: int;
  isCode: bool;
  com: string;
begin
  {@ignore} s := s + #0; {@emit}
  i := strStart;
  comIndent := 1;
  isCode := (length(s) >= 2) and (s[strStart+1] <> ' ');
  ind := g.lineLen;
  com := '';
  while true do begin
    case s[i] of
      #0: break;
      #13: begin
        put(g, tkComment, com);
        com := '';
        inc(i);
        if s[i] = #10 then inc(i);
        optNL(g, ind);
      end;
      #10: begin
        put(g, tkComment, com);
        com := '';
        inc(i);
        optNL(g, ind);
      end;
      '#': begin
        addChar(com, s[i]);
        inc(i);
        comIndent := 0;
        while s[i] = ' ' do begin
          addChar(com, s[i]);
          inc(i); inc(comIndent);
        end
      end;
      ' ', #9: begin
        addChar(com, s[i]);
        inc(i);
      end
      else begin
        // we may break the comment into a multi-line comment if the line
        // gets too long:

        // compute length of the following word:
        j := i;
        while s[j] > ' ' do inc(j);
        if not isCode and (g.lineLen + (j-i) > MaxLineLen) then begin
          put(g, tkComment, com);
          com := '';
          optNL(g, ind);
          com := com +{&} '#' +{&} repeatChar(comIndent);
        end;
        while s[i] > ' ' do begin
          addChar(com, s[i]);
          inc(i);
        end
      end
    end
  end;
  put(g, tkComment, com);
  optNL(g);
end;

function maxLineLength(s: string): int;
var
  i, linelen: int;
begin
  {@ignore} s := s + #0; {@emit}
  result := 0;
  i := strStart;
  lineLen := 0;
  while true do begin
    case s[i] of
      #0: break;
      #13: begin
        inc(i);
        if s[i] = #10 then inc(i);
        result := max(result, lineLen);
        lineLen := 0;
      end;
      #10: begin
        inc(i);
        result := max(result, lineLen);
        lineLen := 0;
      end;
      else begin
        inc(lineLen); inc(i);
      end
    end
  end
end;

procedure putRawStr(var g: TSrcGen; kind: TTokType; const s: string);
var
  i, hi: int;
  str: string;
begin
  i := strStart;
  hi := length(s)+strStart-1;
  str := '';
  while i <= hi do begin
    case s[i] of
      #13: begin
        put(g, kind, str);
        str := '';
        inc(i);
        if (i <= hi) and (s[i] = #10) then inc(i);
        optNL(g, 0);
      end;
      #10: begin
        put(g, kind, str);
        str := '';
        inc(i);
        optNL(g, 0);
      end;
      else begin
        addChar(str, s[i]);
        inc(i)
      end
    end
  end;
  put(g, kind, str);
end;

function containsNL(const s: string): bool;
var
  i: int;
begin
  for i := strStart to length(s)+strStart-1 do
    case s[i] of
      #13, #10: begin result := true; exit end;
      else begin end
    end;
  result := false
end;

procedure pushCom(var g: TSrcGen; n: PNode);
var
  len: int;
begin
  len := length(g.comStack);
  setLength(g.comStack, len+1);
  g.comStack[len] := n;
end;

procedure popAllComs(var g: TSrcGen);
begin
  setLength(g.comStack, 0);
end;

procedure popCom(var g: TSrcGen);
begin
  setLength(g.comStack, length(g.comStack)-1);
end;

const
  Space = ' '+'';

function shouldRenderComment(var g: TSrcGen; n: PNode): bool;
begin
  result := false;
  if n.comment <> snil then
    result := not (renderNoComments in g.flags) or
      (renderDocComments in g.flags) and startsWith(n.comment, '##');
end;

procedure gcom(var g: TSrcGen; n: PNode);
var
  ml: int;
begin
  assert(n <> nil);
  if shouldRenderComment(g, n) then begin
    if (g.pendingNL < 0) and (length(g.buf) > 0)
    and (g.buf[length(g.buf)] <> ' ') then
      put(g, tkSpaces, Space);
    // Before long comments we cannot make sure that a newline is generated,
    // because this might be wrong. But it is no problem in practice.
    if (g.pendingNL < 0) and (length(g.buf) > 0)
        and (g.lineLen < LineCommentColumn) then begin
      ml := maxLineLength(n.comment);
      if ml+LineCommentColumn <= maxLineLen then
        put(g, tkSpaces, repeatChar(LineCommentColumn - g.lineLen));
    end;
    putComment(g, n.comment);
    //assert(g.comStack[high(g.comStack)] = n);
  end
end;

procedure gcoms(var g: TSrcGen);
var
  i: int;
begin
  for i := 0 to high(g.comStack) do gcom(g, g.comStack[i]);
  popAllComs(g);
end;

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

function lsub(n: PNode): int; forward;

function litAux(n: PNode; x: biggestInt; size: int): string;
begin
  if nfBase2 in n.flags then result := '0b' + toBin(x, size*8)
  else if nfBase8 in n.flags then result := '0o' + toOct(x, size*3)
  else if nfBase16 in n.flags then result := '0x' + toHex(x, size*2)
  else result := toString(x)
end;

function atom(n: PNode): string;
var
  f: float32;
begin
  case n.kind of
    nkEmpty:        result := '';
    nkIdent:        result := n.ident.s;
    nkSym:          result := n.sym.name.s;
    nkStrLit:       result := makeNimString(n.strVal);
    nkRStrLit:      result := 'r"' + n.strVal + '"';
    nkTripleStrLit: result := '"""' + n.strVal + '"""';
    nkCharLit:      result := '''' + toNimChar(chr(int(n.intVal))) + '''';
    nkIntLit:       result := litAux(n, n.intVal, 4);
    nkInt8Lit:      result := litAux(n, n.intVal, 1) + '''i8';
    nkInt16Lit:     result := litAux(n, n.intVal, 2) + '''i16';
    nkInt32Lit:     result := litAux(n, n.intVal, 4) + '''i32';
    nkInt64Lit:     result := litAux(n, n.intVal, 8) + '''i64';
    nkFloatLit:     begin
      if n.flags * [nfBase2, nfBase8, nfBase16] = [] then
        result := toStringF(n.floatVal)
      else
        result := litAux(n, ({@cast}PInt64(addr(n.floatVal)))^, 8);
    end;
    nkFloat32Lit:   begin
      if n.flags * [nfBase2, nfBase8, nfBase16] = [] then
        result := toStringF(n.floatVal) + '''f32'
      else begin
        f := n.floatVal;
        result := litAux(n, ({@cast}PInt32(addr(f)))^, 4) + '''f32'
      end;
    end;
    nkFloat64Lit:   begin
      if n.flags * [nfBase2, nfBase8, nfBase16] = [] then
        result := toStringF(n.floatVal) + '''f64'
      else
        result := litAux(n, ({@cast}PInt64(addr(n.floatVal)))^, 8) + '''f64';
    end;
    nkNilLit: result := 'nil';
    nkType: begin
      if (n.typ <> nil) and (n.typ.sym <> nil) then result := n.typ.sym.name.s
      else result := '[type node]';
    end;
    else InternalError('rnimsyn.atom ' + nodeKindToStr[n.kind]);
  end
end;

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

function lcomma(n: PNode; start: int = 0; theEnd: int = -1): int;
var
  i: int;
begin
  assert(theEnd < 0);
  result := 0;
  for i := start to sonsLen(n)+theEnd do begin
    inc(result, lsub(n.sons[i]));
    inc(result, 2); // for ``, ``
  end;
  if result > 0 then dec(result, 2); // last does not get a comma!
end;

function lsons(n: PNode; start: int = 0; theEnd: int = -1): int;
var
  i: int;
begin
  assert(theEnd < 0);
  result := 0;
  for i := start to sonsLen(n)+theEnd do inc(result, lsub(n.sons[i]));
end;

function lsub(n: PNode): int;
// computes the length of a tree
var
  L: int;
begin
  if n = nil then begin result := 0; exit end;
  if n.comment <> snil then begin result := maxLineLen+1; exit end;
  case n.kind of
    nkTripleStrLit: begin
      if containsNL(n.strVal) then result := maxLineLen+1
      else result := length(atom(n));
    end;
    nkEmpty..pred(nkTripleStrLit), succ(nkTripleStrLit)..nkNilLit:
      result := length(atom(n));
    nkCall, nkBracketExpr, nkConv: result := lsub(n.sons[0])+lcomma(n, 1)+2;
    nkHiddenStdConv, nkHiddenSubConv, nkHiddenCallConv: begin
      result := lsub(n.sons[1]);
    end;
    nkCast: result := lsub(n.sons[0])+lsub(n.sons[1])+length('cast[]()');
    nkAddr: result := lsub(n.sons[0])+length('addr()');
    nkHiddenAddr, nkHiddenDeref: result := lsub(n.sons[0]);
    nkCommand: result := lsub(n.sons[0])+lcomma(n, 1)+1;
    nkExprEqExpr, nkAsgn, nkFastAsgn: result := lsons(n)+3;
    nkPar, nkCurly, nkBracket: result := lcomma(n)+2;
    nkSymChoice: result := lsons(n) + length('()') + sonsLen(n)-1;
    nkTupleTy: result := lcomma(n)+length('tuple[]');
    nkDotExpr: result := lsons(n)+1;
    nkBind: result := lsons(n)+length('bind_');
    nkCheckedFieldExpr: result := lsub(n.sons[0]);
    nkLambda: result := lsons(n)+length('lambda__=_');
    nkConstDef, nkIdentDefs: begin
      result := lcomma(n, 0, -3);
      L := sonsLen(n);
      if n.sons[L-2] <> nil then
        result := result + lsub(n.sons[L-2]) + 2;
      if n.sons[L-1] <> nil then
        result := result + lsub(n.sons[L-1]) + 3;
    end;
    nkVarTuple: result := lcomma(n, 0, -3) + length('() = ') + lsub(lastSon(n));
    nkChckRangeF: result := length('chckRangeF') + 2 + lcomma(n);
    nkChckRange64: result := length('chckRange64') + 2 + lcomma(n);
    nkChckRange: result := length('chckRange') + 2 + lcomma(n);

    nkObjDownConv, nkObjUpConv,
    nkStringToCString, nkCStringToString, nkPassAsOpenArray: begin
      result := 2;
      if sonsLen(n) >= 1 then
        result := result + lsub(n.sons[0]);
      result := result + lcomma(n, 1);
    end;
    nkExprColonExpr:  result := lsons(n) + 2;
    nkInfix:          result := lsons(n) + 2;
    nkPrefix:         result := lsons(n) + 1;
    nkPostfix:        result := lsons(n);
    nkCallStrLit:     result := lsons(n);
    nkPragmaExpr:     result := lsub(n.sons[0])+lcomma(n, 1);
    nkRange:          result := lsons(n) + 2;
    nkDerefExpr:      result := lsub(n.sons[0])+2;
    nkAccQuoted:      result := lsub(n.sons[0]) + 2;

    nkIfExpr:         result := lsub(n.sons[0].sons[0])+lsub(n.sons[0].sons[1])
                              + lsons(n, 1) + length('if_:_');
    nkElifExpr:       result := lsons(n) + length('_elif_:_');
    nkElseExpr:       result := lsub(n.sons[0])+ length('_else:_');

    // type descriptions
    nkTypeOfExpr:     result := lsub(n.sons[0])+length('type_');
    nkRefTy:          result := lsub(n.sons[0])+length('ref_');
    nkPtrTy:          result := lsub(n.sons[0])+length('ptr_');
    nkVarTy:          result := lsub(n.sons[0])+length('var_');
    nkDistinctTy:     result := lsub(n.sons[0])+length('Distinct_');
    nkTypeDef:        result := lsons(n)+3;
    nkOfInherit:      result := lsub(n.sons[0])+length('of_');
    nkProcTy:         result := lsons(n)+length('proc_');
    nkEnumTy:         result := lsub(n.sons[0])+lcomma(n,1)+length('enum_');
    nkEnumFieldDef:   result := lsons(n)+3;

    nkVarSection:     if sonsLen(n) > 1 then result := maxLineLen+1
                      else result := lsons(n) + length('var_');
    nkReturnStmt:     result := lsub(n.sons[0])+length('return_');
    nkRaiseStmt:      result := lsub(n.sons[0])+length('raise_');
    nkYieldStmt:      result := lsub(n.sons[0])+length('yield_');
    nkDiscardStmt:    result := lsub(n.sons[0])+length('discard_');
    nkBreakStmt:      result := lsub(n.sons[0])+length('break_');
    nkContinueStmt:   result := lsub(n.sons[0])+length('continue_');
    nkPragma:         result := lcomma(n) + 4;
    nkCommentStmt:    result := length(n.comment);

    nkOfBranch:       result := lcomma(n, 0, -2) + lsub(lastSon(n))
                              + length('of_:_');
    nkElifBranch:     result := lsons(n)+length('elif_:_');
    nkElse:           result := lsub(n.sons[0]) + length('else:_');
    nkFinally:        result := lsub(n.sons[0]) + length('finally:_');
    nkGenericParams:  result := lcomma(n) + 2;
    nkFormalParams:   begin
      result := lcomma(n, 1) + 2;
      if n.sons[0] <> nil then result := result + lsub(n.sons[0]) + 2
    end;
    nkExceptBranch:   result := lcomma(n, 0, -2) + lsub(lastSon(n))
                              + length('except_:_');
    else result := maxLineLen+1
  end
end;

function fits(const g: TSrcGen; x: int): bool;
begin
  result := x + g.lineLen <= maxLineLen
end;

// ------------------------- render part --------------------------------------

type
  TSubFlag = (rfLongMode, rfNoIndent, rfInConstExpr);
  TSubFlags = set of TSubFlag;
  TContext = record{@tuple}
    spacing: int;
    flags: TSubFlags;
  end;

const
  emptyContext: TContext = (spacing: 0; flags: {@set}[]);

procedure initContext(out c: TContext);
begin
  c.spacing := 0;
  c.flags := {@set}[];
end;

procedure gsub(var g: TSrcGen; n: PNode; const c: TContext); overload; forward;

procedure gsub(var g: TSrcGen; n: PNode); overload;
var
  c: TContext;
begin
  initContext(c);
  gsub(g, n, c);
end;

function hasCom(n: PNode): bool;
var
  i: int;
begin
  result := false;
  if n = nil then exit;
  if n.comment <> snil then begin result := true; exit end;
  case n.kind of
    nkEmpty..nkNilLit: begin end;
    else begin
      for i := 0 to sonsLen(n)-1 do
        if hasCom(n.sons[i]) then begin
          result := true; exit
        end
    end
  end
end;

procedure putWithSpace(var g: TSrcGen; kind: TTokType; const s: string);
begin
  put(g, kind, s);
  put(g, tkSpaces, Space);
end;

procedure gcommaAux(var g: TSrcGen; n: PNode; ind: int;
                    start: int = 0; theEnd: int = -1);
var
  i, sublen: int;
  c: bool;
begin
  for i := start to sonsLen(n)+theEnd do begin
    c := i < sonsLen(n)+theEnd;
    sublen := lsub(n.sons[i])+ord(c);
    if not fits(g, sublen) and (ind+sublen < maxLineLen) then optNL(g, ind);
    gsub(g, n.sons[i]);
    if c then begin
      putWithSpace(g, tkComma, ','+'');
      if hasCom(n.sons[i]) then begin
        gcoms(g);
        optNL(g, ind);
      end
    end
  end
end;

procedure gcomma(var g: TSrcGen; n: PNode; const c: TContext;
                 start: int = 0; theEnd: int = -1); overload;
var
  ind: int;
begin
  if rfInConstExpr in c.flags then
    ind := g.indent + indentWidth
  else begin
    ind := g.lineLen;
    if ind > maxLineLen div 2 then ind := g.indent + longIndentWid
  end;
  gcommaAux(g, n, ind, start, theEnd);
end;

procedure gcomma(var g: TSrcGen; n: PNode;
                 start: int = 0; theEnd: int = -1); overload;
var
  ind: int;
begin
  ind := g.lineLen;
  if ind > maxLineLen div 2 then ind := g.indent + longIndentWid;
  gcommaAux(g, n, ind, start, theEnd);
end;

procedure gsons(var g: TSrcGen; n: PNode; const c: TContext;
                start: int = 0; theEnd: int = -1);
var
  i: int;
begin
  for i := start to sonsLen(n)+theEnd do begin
    gsub(g, n.sons[i], c);
  end
end;

procedure gsection(var g: TSrcGen; n: PNode; const c: TContext; kind: TTokType;
                   const k: string);
var
  i: int;
begin
  if sonsLen(n) = 0 then exit; // empty var sections are possible
  putWithSpace(g, kind, k);
  gcoms(g);
  indentNL(g);
  for i := 0 to sonsLen(n)-1 do begin
    optNL(g);
    gsub(g, n.sons[i], c);
    gcoms(g);
  end;
  dedent(g);
end;


function longMode(n: PNode; start: int = 0; theEnd: int = -1): bool;
var
  i: int;
begin
  result := n.comment <> snil;
  if not result then begin
    // check further
    for i := start to sonsLen(n)+theEnd do begin
      if (lsub(n.sons[i]) > maxLineLen) then begin
        result := true; break end;
    end
  end
end;

procedure gstmts(var g: TSrcGen; n: PNode; const c: TContext);
var
  i: int;
begin
  if n = nil then exit;
  if (n.kind = nkStmtList) or (n.kind = nkStmtListExpr) then begin
    indentNL(g);
    for i := 0 to sonsLen(n)-1 do begin
      optNL(g);
      gsub(g, n.sons[i]);
      gcoms(g);
    end;
    dedent(g);
  end
  else begin
    if rfLongMode in c.flags then indentNL(g);
    gsub(g, n);
    gcoms(g);
    optNL(g);
    if rfLongMode in c.flags then dedent(g);
  end
end;

procedure gif(var g: TSrcGen; n: PNode);
var
  c: TContext;
  i, len: int;
begin
  gsub(g, n.sons[0].sons[0]);
  initContext(c);
  putWithSpace(g, tkColon, ':'+'');
  if longMode(n) or (lsub(n.sons[0].sons[1])+g.lineLen > maxLineLen) then
    include(c.flags, rfLongMode);
  gcoms(g); // a good place for comments
  gstmts(g, n.sons[0].sons[1], c);
  len := sonsLen(n);
  for i := 1 to len-1 do begin
    optNL(g);
    gsub(g, n.sons[i], c)
  end;
end;

procedure gwhile(var g: TSrcGen; n: PNode);
var
  c: TContext;
begin
  putWithSpace(g, tkWhile, 'while');
  gsub(g, n.sons[0]);
  putWithSpace(g, tkColon, ':'+'');
  initContext(c);
  if longMode(n) or (lsub(n.sons[1])+g.lineLen > maxLineLen) then
    include(c.flags, rfLongMode);
  gcoms(g); // a good place for comments
  gstmts(g, n.sons[1], c);
end;

procedure gtry(var g: TSrcGen; n: PNode);
var
  c: TContext;
begin
  put(g, tkTry, 'try');
  putWithSpace(g, tkColon, ':'+'');
  initContext(c);
  if longMode(n) or (lsub(n.sons[0])+g.lineLen > maxLineLen) then
    include(c.flags, rfLongMode);
  gcoms(g); // a good place for comments
  gstmts(g, n.sons[0], c);
  gsons(g, n, c, 1);
end;

procedure gfor(var g: TSrcGen; n: PNode);
var
  c: TContext;
  len: int;
begin
  len := sonsLen(n);
  putWithSpace(g, tkFor, 'for');
  initContext(c);
  if longMode(n)
      or (lsub(n.sons[len-1])
        + lsub(n.sons[len-2]) + 6 + g.lineLen > maxLineLen) then
    include(c.flags, rfLongMode);
  gcomma(g, n, c, 0, -3);
  put(g, tkSpaces, Space);
  putWithSpace(g, tkIn, 'in');
  gsub(g, n.sons[len-2], c);
  putWithSpace(g, tkColon, ':'+'');
  gcoms(g);
  gstmts(g, n.sons[len-1], c);
end;

procedure gmacro(var g: TSrcGen; n: PNode);
var
  c: TContext;
begin
  initContext(c);
  gsub(g, n.sons[0]);
  putWithSpace(g, tkColon, ':'+'');
  if longMode(n) or (lsub(n.sons[1])+g.lineLen > maxLineLen) then
    include(c.flags, rfLongMode);
  gcoms(g);
  gsons(g, n, c, 1);
end;

procedure gcase(var g: TSrcGen; n: PNode);
var
  c: TContext;
  len, last: int;
begin
  initContext(c);
  len := sonsLen(n);
  if n.sons[len-1].kind = nkElse then last := -2
  else last := -1;
  if longMode(n, 0, last) then include(c.flags, rfLongMode);
  putWithSpace(g, tkCase, 'case');
  gsub(g, n.sons[0]);
  gcoms(g);
  optNL(g);
  gsons(g, n, c, 1, last);
  if last = -2 then begin
    initContext(c);
    if longMode(n.sons[len-1]) then include(c.flags, rfLongMode);
    gsub(g, n.sons[len-1], c);
  end
end;

procedure gproc(var g: TSrcGen; n: PNode);
var
  c: TContext;
begin
  gsub(g, n.sons[0]);
  gsub(g, n.sons[1]);
  gsub(g, n.sons[2]);
  gsub(g, n.sons[3]);
  if not (renderNoBody in g.flags) then begin
    if n.sons[4] <> nil then begin
      put(g, tkSpaces, Space);
      putWithSpace(g, tkEquals, '='+'');
      indentNL(g);
      gcoms(g);
      dedent(g);
      initContext(c);
      gstmts(g, n.sons[4], c);
      putNL(g);
    end
    else begin
      indentNL(g);
      gcoms(g);
      dedent(g);
    end
  end;
end;

procedure gblock(var g: TSrcGen; n: PNode);
var
  c: TContext;
begin
  initContext(c);
  putWithSpace(g, tkBlock, 'block');
  gsub(g, n.sons[0]);
  putWithSpace(g, tkColon, ':'+'');
  if longMode(n) or (lsub(n.sons[1])+g.lineLen > maxLineLen) then
    include(c.flags, rfLongMode);
  gcoms(g);
  gstmts(g, n.sons[1], c);
end;

procedure gasm(var g: TSrcGen; n: PNode);
begin
  putWithSpace(g, tkAsm, 'asm');
  gsub(g, n.sons[0]);
  gcoms(g);
  gsub(g, n.sons[1]);
end;

procedure gident(var g: TSrcGen; n: PNode);
var
  s: string;
  t: TTokType;
begin
  s := atom(n);
  if (s[strStart] in scanner.SymChars) then begin
    if (n.kind = nkIdent) then begin
      if (n.ident.id < ord(tokKeywordLow)-ord(tkSymbol)) or
         (n.ident.id > ord(tokKeywordHigh)-ord(tkSymbol)) then
        t := tkSymbol
      else
        t := TTokType(n.ident.id+ord(tkSymbol))
    end
    else
      t := tkSymbol;
  end
  else
    t := tkOpr;
  put(g, t, s);
  if (n.kind = nkSym) and (renderIds in g.flags) then
    put(g, tkIntLit, toString(n.sym.id));
end;

procedure gsub(var g: TSrcGen; n: PNode; const c: TContext);
var
  L, i: int;
  a: TContext;
begin
  if n = nil then exit;
  if n.comment <> snil then pushCom(g, n);
  case n.kind of
    // atoms:
    nkTripleStrLit: putRawStr(g, tkTripleStrLit, n.strVal);
    nkEmpty, nkType: put(g, tkInvalid, atom(n));
    nkSym, nkIdent: gident(g, n);
    nkIntLit: put(g, tkIntLit, atom(n));
    nkInt8Lit: put(g, tkInt8Lit, atom(n));
    nkInt16Lit: put(g, tkInt16Lit, atom(n));
    nkInt32Lit: put(g, tkInt32Lit, atom(n));
    nkInt64Lit: put(g, tkInt64Lit, atom(n));
    nkFloatLit: put(g, tkFloatLit, atom(n));
    nkFloat32Lit: put(g, tkFloat32Lit, atom(n));
    nkFloat64Lit: put(g, tkFloat64Lit, atom(n));
    nkStrLit: put(g, tkStrLit, atom(n));
    nkRStrLit: put(g, tkRStrLit, atom(n));
    nkCharLit: put(g, tkCharLit, atom(n));
    nkNilLit: put(g, tkNil, atom(n));
    // complex expressions
    nkCall, nkConv, nkDotCall: begin
      if sonsLen(n) >= 1 then
        gsub(g, n.sons[0]);
      put(g, tkParLe, '('+'');
      gcomma(g, n, 1);
      put(g, tkParRi, ')'+'');
    end;
    nkCallStrLit: begin
      gsub(g, n.sons[0]);
      if n.sons[1].kind = nkRStrLit then
        put(g, tkRStrLit, '"' + n.sons[1].strVal + '"')
      else
        gsub(g, n.sons[0]);
    end;
    nkHiddenStdConv, nkHiddenSubConv, nkHiddenCallConv: begin
      gsub(g, n.sons[0]);
    end;
    nkCast: begin
      put(g, tkCast, 'cast');
      put(g, tkBracketLe, '['+'');
      gsub(g, n.sons[0]);
      put(g, tkBracketRi, ']'+'');
      put(g, tkParLe, '('+'');
      gsub(g, n.sons[1]);
      put(g, tkParRi, ')'+'');
    end;
    nkAddr: begin
      put(g, tkAddr, 'addr');
      put(g, tkParLe, '('+'');
      gsub(g, n.sons[0]);
      put(g, tkParRi, ')'+'');
    end;
    nkBracketExpr: begin
      gsub(g, n.sons[0]);
      put(g, tkBracketLe, '['+'');
      gcomma(g, n, 1);
      put(g, tkBracketRi, ']'+'');
    end;
    nkPragmaExpr: begin
      gsub(g, n.sons[0]);
      gcomma(g, n, 1);
    end;
    nkCommand: begin
      gsub(g, n.sons[0]);
      put(g, tkSpaces, space);
      gcomma(g, n, 1);
    end;
    nkExprEqExpr, nkAsgn, nkFastAsgn: begin
      gsub(g, n.sons[0]);
      put(g, tkSpaces, Space);
      putWithSpace(g, tkEquals, '='+'');
      gsub(g, n.sons[1]);
    end;
    nkChckRangeF: begin
      put(g, tkSymbol, 'chckRangeF');
      put(g, tkParLe, '('+'');
      gcomma(g, n);
      put(g, tkParRi, ')'+'');
    end;
    nkChckRange64: begin
      put(g, tkSymbol, 'chckRange64');
      put(g, tkParLe, '('+'');
      gcomma(g, n);
      put(g, tkParRi, ')'+'');
    end;
    nkChckRange: begin
      put(g, tkSymbol, 'chckRange');
      put(g, tkParLe, '('+'');
      gcomma(g, n);
      put(g, tkParRi, ')'+'');
    end;
    nkObjDownConv, nkObjUpConv,
    nkStringToCString, nkCStringToString, nkPassAsOpenArray: begin
      if sonsLen(n) >= 1 then
        gsub(g, n.sons[0]);
      put(g, tkParLe, '('+'');
      gcomma(g, n, 1);
      put(g, tkParRi, ')'+'');
    end;
    nkSymChoice: begin
      put(g, tkParLe, '('+'');
      for i := 0 to sonsLen(n)-1 do begin
        if i > 0 then put(g, tkOpr, '|'+'');
        gsub(g, n.sons[i], c);
      end;
      put(g, tkParRi, ')'+'');      
    end;
    nkPar: begin
      put(g, tkParLe, '('+'');
      gcomma(g, n, c);
      put(g, tkParRi, ')'+'');
    end;
    nkCurly: begin
      put(g, tkCurlyLe, '{'+'');
      gcomma(g, n, c);
      put(g, tkCurlyRi, '}'+'');
    end;
    nkBracket: begin
      put(g, tkBracketLe, '['+'');
      gcomma(g, n, c);
      put(g, tkBracketRi, ']'+'');
    end;
    nkDotExpr: begin
      gsub(g, n.sons[0]);
      put(g, tkDot, '.'+'');
      gsub(g, n.sons[1]);
    end;
    nkBind: begin
      putWithSpace(g, tkBind, 'bind');
      gsub(g, n.sons[0]);
    end;
    nkCheckedFieldExpr, nkHiddenAddr, nkHiddenDeref: gsub(g, n.sons[0]);
    nkLambda: begin
      assert(n.sons[genericParamsPos] = nil);
      putWithSpace(g, tkLambda, 'lambda');
      gsub(g, n.sons[paramsPos]);
      gsub(g, n.sons[pragmasPos]);
      put(g, tkSpaces, Space);
      putWithSpace(g, tkEquals, '='+'');
      gsub(g, n.sons[codePos]);
    end;
    nkConstDef, nkIdentDefs: begin
      gcomma(g, n, 0, -3);
      L := sonsLen(n);
      if n.sons[L-2] <> nil then begin
        putWithSpace(g, tkColon, ':'+'');
        gsub(g, n.sons[L-2])
      end;
      if n.sons[L-1] <> nil then begin
        put(g, tkSpaces, Space);
        putWithSpace(g, tkEquals, '='+'');
        gsub(g, n.sons[L-1], c)
      end;
    end;
    nkVarTuple: begin
      put(g, tkParLe, '('+'');
      gcomma(g, n, 0, -3);
      put(g, tkParRi, ')'+'');
      put(g, tkSpaces, Space);
      putWithSpace(g, tkEquals, '='+'');
      gsub(g, lastSon(n), c);
    end;
    nkExprColonExpr: begin
      gsub(g, n.sons[0]);
      putWithSpace(g, tkColon, ':'+'');
      gsub(g, n.sons[1]);
    end;
    nkInfix: begin
      gsub(g, n.sons[1]);
      put(g, tkSpaces, Space);
      gsub(g, n.sons[0]); // binary operator
      if not fits(g, lsub(n.sons[2])+ lsub(n.sons[0]) + 1) then
        optNL(g, g.indent+longIndentWid)
      else put(g, tkSpaces, Space);
      gsub(g, n.sons[2]);
    end;
    nkPrefix: begin
      gsub(g, n.sons[0]);
      put(g, tkSpaces, space);
      gsub(g, n.sons[1]);
    end;
    nkPostfix: begin
      gsub(g, n.sons[1]);
      gsub(g, n.sons[0]);
    end;
    nkRange: begin
      gsub(g, n.sons[0]);
      put(g, tkDotDot, '..');
      gsub(g, n.sons[1]);
    end;
    nkDerefExpr: begin
      gsub(g, n.sons[0]);
      putWithSpace(g, tkHat, '^'+'');
      // unfortunately this requires a space, because ^. would be
      // only one operator
    end;
    nkAccQuoted: begin
      put(g, tkAccent, '`'+'');
      gsub(g, n.sons[0]);
      put(g, tkAccent, '`'+'');
    end;
    nkIfExpr: begin
      putWithSpace(g, tkIf, 'if');
      gsub(g, n.sons[0].sons[0]);
      putWithSpace(g, tkColon, ':'+'');
      gsub(g, n.sons[0].sons[1]);
      gsons(g, n, emptyContext, 1);
    end;
    nkElifExpr: begin
      putWithSpace(g, tkElif, ' elif');
      gsub(g, n.sons[0]);
      putWithSpace(g, tkColon, ':'+'');
      gsub(g, n.sons[1]);
    end;
    nkElseExpr: begin
      put(g, tkElse, ' else');
      putWithSpace(g, tkColon, ':'+'');
      gsub(g, n.sons[0]);
    end;

    nkTypeOfExpr: begin
      putWithSpace(g, tkType, 'type');
      gsub(g, n.sons[0]);
    end;
    nkRefTy: begin
      putWithSpace(g, tkRef, 'ref');
      gsub(g, n.sons[0]);
    end;
    nkPtrTy: begin
      putWithSpace(g, tkPtr, 'ptr');
      gsub(g, n.sons[0]);
    end;
    nkVarTy: begin
      putWithSpace(g, tkVar, 'var');
      gsub(g, n.sons[0]);
    end;
    nkDistinctTy: begin
      putWithSpace(g, tkDistinct, 'distinct');
      gsub(g, n.sons[0]);
    end;
    nkTypeDef: begin
      gsub(g, n.sons[0]);
      gsub(g, n.sons[1]);
      put(g, tkSpaces, Space);
      if n.sons[2] <> nil then begin
        putWithSpace(g, tkEquals, '='+'');
        gsub(g, n.sons[2]);
      end
    end;
    nkObjectTy: begin
      putWithSpace(g, tkObject, 'object');
      gsub(g, n.sons[0]);
      gsub(g, n.sons[1]);
      gcoms(g);
      gsub(g, n.sons[2]);
    end;
    nkRecList: begin
      indentNL(g);
      for i := 0 to sonsLen(n)-1 do begin
        optNL(g);
        gsub(g, n.sons[i], c);
        gcoms(g);
      end;
      dedent(g);
      putNL(g);
    end;
    nkOfInherit: begin
      putWithSpace(g, tkOf, 'of');
      gsub(g, n.sons[0]);
    end;
    nkProcTy: begin
      putWithSpace(g, tkProc, 'proc');
      gsub(g, n.sons[0]);
      gsub(g, n.sons[1]);
    end;
    nkEnumTy: begin
      putWithSpace(g, tkEnum, 'enum');
      gsub(g, n.sons[0]);
      gcoms(g);
      indentNL(g);
      gcommaAux(g, n, g.indent, 1);
      gcoms(g); // BUGFIX: comment for the last enum field
      dedent(g);
    end;
    nkEnumFieldDef: begin
      gsub(g, n.sons[0]);
      put(g, tkSpaces, Space);
      putWithSpace(g, tkEquals, '='+'');
      gsub(g, n.sons[1]);
    end;
    nkStmtList, nkStmtListExpr: gstmts(g, n, emptyContext);
    nkIfStmt: begin
      putWithSpace(g, tkIf, 'if');
      gif(g, n);
    end;
    nkWhenStmt, nkRecWhen: begin
      putWithSpace(g, tkWhen, 'when');
      gif(g, n);
    end;
    nkWhileStmt: gwhile(g, n);
    nkCaseStmt, nkRecCase: gcase(g, n);
    nkMacroStmt: gmacro(g, n);
    nkTryStmt: gtry(g, n);
    nkForStmt: gfor(g, n);
    nkBlockStmt, nkBlockExpr: gblock(g, n);
    nkAsmStmt: gasm(g, n);
    nkProcDef: begin
      putWithSpace(g, tkProc, 'proc');
      gproc(g, n);
    end;
    nkMethodDef: begin
      putWithSpace(g, tkMethod, 'method');
      gproc(g, n);
    end;
    nkIteratorDef: begin
      putWithSpace(g, tkIterator, 'iterator');
      gproc(g, n);
    end;
    nkMacroDef: begin
      putWithSpace(g, tkMacro, 'macro');
      gproc(g, n);
    end;
    nkTemplateDef: begin
      putWithSpace(g, tkTemplate, 'template');
      gproc(g, n);
    end;
    nkTypeSection: gsection(g, n, emptyContext, tkType, 'type');
    nkConstSection: begin
      initContext(a);
      include(a.flags, rfInConstExpr);
      gsection(g, n, a, tkConst, 'const')
    end;
    nkVarSection: begin
      L := sonsLen(n);
      if L = 0 then exit;
      putWithSpace(g, tkVar, 'var');
      if L > 1 then begin
        gcoms(g);
        indentNL(g);
        for i := 0 to L-1 do begin
          optNL(g);
          gsub(g, n.sons[i]);
          gcoms(g);
        end;
        dedent(g);
      end
      else
        gsub(g, n.sons[0]);
    end;
    nkReturnStmt: begin
      putWithSpace(g, tkReturn, 'return');
      gsub(g, n.sons[0]);
    end;
    nkRaiseStmt: begin
      putWithSpace(g, tkRaise, 'raise');
      gsub(g, n.sons[0]);
    end;
    nkYieldStmt: begin
      putWithSpace(g, tkYield, 'yield');
      gsub(g, n.sons[0]);
    end;
    nkDiscardStmt: begin
      putWithSpace(g, tkDiscard, 'discard');
      gsub(g, n.sons[0]);
    end;
    nkBreakStmt: begin
      putWithSpace(g, tkBreak, 'break');
      gsub(g, n.sons[0]);
    end;
    nkContinueStmt: begin
      putWithSpace(g, tkContinue, 'continue');
      gsub(g, n.sons[0]);
    end;
    nkPragma: begin
      if not (renderNoPragmas in g.flags) then begin
        put(g, tkCurlyDotLe, '{.');
        gcomma(g, n, emptyContext);
        put(g, tkCurlyDotRi, '.}')
      end;
    end;
    nkImportStmt: begin
      putWithSpace(g, tkImport, 'import');
      gcoms(g);
      indentNL(g);
      gcommaAux(g, n, g.indent);
      dedent(g);
      putNL(g);
    end;
    nkFromStmt: begin
      putWithSpace(g, tkFrom, 'from');
      gsub(g, n.sons[0]);
      put(g, tkSpaces, Space);
      putWithSpace(g, tkImport, 'import');
      gcomma(g, n, emptyContext, 1);
      putNL(g);
    end;
    nkIncludeStmt: begin
      putWithSpace(g, tkInclude, 'include');
      gcoms(g);
      indentNL(g);
      gcommaAux(g, n, g.indent);
      dedent(g);
      putNL(g);
    end;
    nkCommentStmt: begin
      gcoms(g);
      optNL(g);
    end;
    nkOfBranch: begin
      optNL(g);
      putWithSpace(g, tkOf, 'of');
      gcomma(g, n, c, 0, -2);
      putWithSpace(g, tkColon, ':'+'');
      gcoms(g);
      gstmts(g, lastSon(n), c);
    end;
    nkElifBranch: begin
      optNL(g);
      putWithSpace(g, tkElif, 'elif');
      gsub(g, n.sons[0]);
      putWithSpace(g, tkColon, ':'+'');
      gcoms(g);
      gstmts(g, n.sons[1], c)
    end;
    nkElse: begin
      optNL(g);
      put(g, tkElse, 'else');
      putWithSpace(g, tkColon, ':'+'');
      gcoms(g);
      gstmts(g, n.sons[0], c)
    end;
    nkFinally: begin
      optNL(g);
      put(g, tkFinally, 'finally');
      putWithSpace(g, tkColon, ':'+'');
      gcoms(g);
      gstmts(g, n.sons[0], c)
    end;
    nkExceptBranch: begin
      optNL(g);
      putWithSpace(g, tkExcept, 'except');
      gcomma(g, n, 0, -2);
      putWithSpace(g, tkColon, ':'+'');
      gcoms(g);
      gstmts(g, lastSon(n), c)
    end;
    nkGenericParams: begin
      put(g, tkBracketLe, '['+'');
      gcomma(g, n);
      put(g, tkBracketRi, ']'+'');
    end;
    nkFormalParams: begin
      put(g, tkParLe, '('+'');
      gcomma(g, n, 1);
      put(g, tkParRi, ')'+'');
      if n.sons[0] <> nil then begin
        putWithSpace(g, tkColon, ':'+'');
        gsub(g, n.sons[0]);
      end;
      // XXX: gcomma(g, n, 1, -2);
    end;
    nkTupleTy: begin
      put(g, tkTuple, 'tuple');
      put(g, tkBracketLe, '['+'');
      gcomma(g, n);
      put(g, tkBracketRi, ']'+'');
    end;
    else begin
      //nkNone, nkMetaNode, nkTableConstr, nkExplicitTypeListCall: begin
      InternalError(n.info, 'rnimsyn.gsub(' +{&} nodeKindToStr[n.kind] +{&} ')')
    end
  end
end;

function renderTree(n: PNode; renderFlags: TRenderFlags = {@set}[]): string;
var
  g: TSrcGen;
begin
  initSrcGen(g, renderFlags);
  gsub(g, n);
  result := g.buf
end;

procedure renderModule(n: PNode; const filename: string;
                       renderFlags: TRenderFlags = {@set}[]);
var
  i: int;
  f: tTextFile;
  g: TSrcGen;
begin
  initSrcGen(g, renderFlags);
  for i := 0 to sonsLen(n)-1 do begin
    gsub(g, n.sons[i]);
    optNL(g);
    if n.sons[i] <> nil then
      case n.sons[i].kind of
        nkTypeSection, nkConstSection, nkVarSection, nkCommentStmt:
          putNL(g);
        else begin end
      end
  end;
  gcoms(g);
  if OpenFile(f, filename, fmWrite) then begin
    nimWrite(f, g.buf);
    nimCloseFile(f);
  end;
end;

procedure initTokRender(var r: TSrcGen; n: PNode;
                        renderFlags: TRenderFlags = {@set}[]);
begin
  initSrcGen(r, renderFlags);
  gsub(r, n);
end;

procedure getNextTok(var r: TSrcGen; var kind: TTokType; var literal: string);
var
  len: int;
begin
  if r.idx < length(r.tokens) then begin
    kind := r.tokens[r.idx].kind;
    len := r.tokens[r.idx].len;
    literal := ncopy(r.buf, r.pos+strStart, r.pos+strStart+len-1);
    inc(r.pos, len);
    inc(r.idx);
  end
  else
    kind := tkEof;
end;

end.