summary refs log tree commit diff stats
path: root/nim/rnimsyn.pas
diff options
context:
space:
mode:
authorAndreas Rumpf <andreas@andi>2008-06-22 16:14:11 +0200
committerAndreas Rumpf <andreas@andi>2008-06-22 16:14:11 +0200
commit405b86068e6a3d39970b9129ceec0a9108464b28 (patch)
treec0449946f54baae6ea88baf453157ddd7faa8f86 /nim/rnimsyn.pas
downloadNim-405b86068e6a3d39970b9129ceec0a9108464b28.tar.gz
Initial import
Diffstat (limited to 'nim/rnimsyn.pas')
-rwxr-xr-xnim/rnimsyn.pas1406
1 files changed, 1406 insertions, 0 deletions
diff --git a/nim/rnimsyn.pas b/nim/rnimsyn.pas
new file mode 100755
index 000000000..b4f97e82c
--- /dev/null
+++ b/nim/rnimsyn.pas
@@ -0,0 +1,1406 @@
+//
+//
+//           The Nimrod Compiler
+//        (c) Copyright 2008 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, lexbase, scanner, options, idents, strutils, ast, msgs, 
+  lists;
+
+type
+  TRenderFlag = (renderNone, renderNoBody, renderNoComments,
+                 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 begin
+    result := result +{&} toNimChar(s[i]);
+  end;
+  result := 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 = ' '+'';
+
+procedure gcom(var g: TSrcGen; n: PNode);
+var
+  ml: int;
+begin
+  assert(n <> nil);
+  if (n.comment <> snil) and not (renderNoComments in g.flags) 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
+  case n.base of
+    base10: result := toString(x);
+    base2:  result := '0b' + toBin(x, size*8);
+    base8:  result := '0o' + toOct(x, size*3);
+    base16: result := '0x' + toHex(x, size*2);
+    else begin
+      assert(false);
+      result := toString(x);
+    end
+  end
+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))) + '''';
+    nkRCharLit:     result := 'r''' + 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.base = base10 then result := toStringF(n.floatVal)
+      else result := litAux(n, ({@cast}PInt64(addr(n.floatVal)))^, 8);
+    end;
+    nkFloat32Lit:   begin
+      if n.base = base10 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.base = base10 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
+  len: int;
+begin
+  if n = nil then begin result := 0; exit end;
+  if n.comment <> snil then begin result := maxLineLen+1; exit end;
+  len := sonsLen(n);
+  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[0]);
+    end;
+    nkCast: begin
+      if sonsLen(n) = 2 then
+        result := lsub(n.sons[0])+lsub(n.sons[1])+length('cast[]()')
+      else
+        result := lsub(n.sons[0]) + length('cast()');
+    end;
+    nkAddr: result := lsub(n.sons[0])+length('addr()');
+    nkCommand: result := lsub(n.sons[0])+lcomma(n, 1)+1;
+    nkExprEqExpr, nkDefaultTypeParam, nkAsgn:
+      result := lsons(n)+3;
+    nkPar, nkRecordConstr, nkConstRecordConstr,
+    nkCurly, nkSetConstr, nkConstSetConstr,
+    nkBracket, nkArrayConstr, nkConstArrayConstr: result := lcomma(n)+2;
+    nkQualified, nkDotExpr: result := lsons(n)+1;
+    nkLambda: result := lsons(n)+length('lambda__=_');
+    nkConstDef, nkIdentDefs: begin
+      result := lcomma(n, 0, -3);
+      if n.sons[len-2] <> nil then
+        result := result + lsub(n.sons[len-2]) + 2;
+      if n.sons[len-1] <> nil then
+        result := result + lsub(n.sons[len-1]) + 3;
+    end;
+    nkExprColonExpr:  result := lsons(n) + 2;
+    nkInfix:          result := lsons(n) + 2;
+    nkPrefix:         result := lsons(n) + 1;
+    nkPostfix:        result := lsons(n);
+    nkPragmaExpr:     result := lsub(n.sons[0])+lcomma(n, 1);
+    nkRange:          result := lsons(n) + 2;
+    nkDerefExpr:      result := lsub(n.sons[0])+2;
+    nkImportAs:       result := lsons(n) + length('_as_');
+    nkAccQuoted:      result := lsub(n.sons[0]) + 2;
+    nkHeaderQuoted:   result := lsub(n.sons[0]) + lsub(n.sons[1]) + 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_');
+    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 len > 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(n.sons[len-1])
+                              + 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(n.sons[len-1])
+                              + 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
+    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 one(b: bool): int;
+begin
+  if b then result := 1 else result := 0
+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])+one(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
+  len, i: int;
+  a: TContext;
+begin
+  if n = nil then exit;
+  if n.comment <> snil then pushCom(g, n);
+  len := sonsLen(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));
+    nkRCharLit: put(g, tkRCharLit, 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;
+    nkHiddenStdConv, nkHiddenSubConv, nkHiddenCallConv: begin
+      gsub(g, n.sons[0]);
+    end;
+    nkCast: begin
+      put(g, tkCast, 'cast');
+      put(g, tkBracketLe, '['+'');
+      if sonsLen(n) = 2 then begin
+        gsub(g, n.sons[0]);
+        put(g, tkBracketRi, ']'+'');
+        put(g, tkParLe, '('+'');
+        gsub(g, n.sons[1]);
+      end
+      else
+        gsub(g, n.sons[0]);
+      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, nkDefaultTypeParam, nkAsgn: begin
+      gsub(g, n.sons[0]);
+      put(g, tkSpaces, Space);
+      putWithSpace(g, tkEquals, '='+'');
+      gsub(g, n.sons[1]);
+    end;
+    nkPar, nkRecordConstr, nkConstRecordConstr: begin
+      put(g, tkParLe, '('+'');
+      gcomma(g, n, c);
+      put(g, tkParRi, ')'+'');
+    end;
+    nkCurly, nkSetConstr, nkConstSetConstr: begin
+      put(g, tkCurlyLe, '{'+'');
+      gcomma(g, n, c);
+      put(g, tkCurlyRi, '}'+'');
+    end;
+    nkBracket, nkArrayConstr, nkConstArrayConstr: begin
+      put(g, tkBracketLe, '['+'');
+      gcomma(g, n, c);
+      put(g, tkBracketRi, ']'+'');
+    end;
+    nkQualified, nkDotExpr: begin
+      gsub(g, n.sons[0]);
+      put(g, tkDot, '.'+'');
+      gsub(g, n.sons[1]);
+    end;
+    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);
+      if n.sons[len-2] <> nil then begin
+        putWithSpace(g, tkColon, ':'+'');
+        gsub(g, n.sons[len-2])
+      end;
+      if n.sons[len-1] <> nil then begin
+        put(g, tkSpaces, Space);
+        putWithSpace(g, tkEquals, '='+'');
+        gsub(g, n.sons[len-1], c)
+      end;
+    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;
+    nkImportAs: begin
+      gsub(g, n.sons[0]);
+      put(g, tkSpaces, Space);
+      putWithSpace(g, tkAs, 'as');
+      gsub(g, n.sons[1]);
+    end;
+    nkAccQuoted: begin
+      put(g, tkAccent, '`'+'');
+      gsub(g, n.sons[0]);
+      put(g, tkAccent, '`'+'');
+    end;
+    nkHeaderQuoted: begin
+      put(g, tkAccent, '`'+'');
+      gsub(g, n.sons[0]);
+      gsub(g, n.sons[1]);
+      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;
+    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;
+    nkRecordTy: begin
+      putWithSpace(g, tkRecord, 'record');
+      gsub(g, n.sons[0]);
+      gsub(g, n.sons[1]);
+      gcoms(g);
+      gsub(g, n.sons[2]);
+    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 len-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);
+      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;
+    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
+      if len = 0 then exit;
+      putWithSpace(g, tkVar, 'var');
+      if len > 1 then begin
+        gcoms(g);
+        indentNL(g);
+        for i := 0 to len-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, n.sons[len-1], 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, n.sons[len-1], 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;
+    else 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.