diff options
Diffstat (limited to 'compiler/renderer.nim')
-rw-r--r-- | compiler/renderer.nim | 1883 |
1 files changed, 1883 insertions, 0 deletions
diff --git a/compiler/renderer.nim b/compiler/renderer.nim new file mode 100644 index 000000000..cc07c0c2d --- /dev/null +++ b/compiler/renderer.nim @@ -0,0 +1,1883 @@ +# +# +# The Nim Compiler +# (c) Copyright 2013 Andreas Rumpf +# +# See the file "copying.txt", included in this +# distribution, for details about the copyright. +# + +# This module implements the renderer of the standard Nim representation. + +# 'import renderer' is so useful for debugging +# that Nim shouldn't produce a warning for that: +{.used.} + +import + lexer, options, idents, ast, msgs, lineinfos, wordrecg + +import std/[strutils] + +when defined(nimPreviewSlimSystem): + import std/[syncio, assertions, formatfloat] + +type + TRenderFlag* = enum + renderNone, renderNoBody, renderNoComments, renderDocComments, + renderNoPragmas, renderIds, renderNoProcDefs, renderSyms, renderRunnableExamples, + renderIr, renderNonExportedFields, renderExpandUsing, renderNoPostfix + + TRenderFlags* = set[TRenderFlag] + TRenderTok* = object + kind*: TokType + length*: int16 + sym*: PSym + + Section = enum + GenericParams + ObjectDef + + TRenderTokSeq* = seq[TRenderTok] + TSrcGen* = object + indent*: int + lineLen*: int + col: 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 + pendingWhitespace: int + comStack*: seq[PNode] # comment stack + flags*: TRenderFlags + inside: set[Section] # Keeps track of contexts we are in + checkAnon: bool # we're in a context that can contain sfAnon + inPragma: int + when defined(nimpretty): + pendingNewlineCount: int + fid*: FileIndex + config*: ConfigRef + mangler: seq[PSym] + +proc renderTree*(n: PNode, renderFlags: TRenderFlags = {}): string + +# 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. + +proc disamb(g: var TSrcGen; s: PSym): int = + # we group by 's.name.s' to compute the stable name ID. + result = 0 + for i in 0 ..< g.mangler.len: + if s == g.mangler[i]: return result + if s.name.s == g.mangler[i].name.s: inc result + g.mangler.add s + +proc isKeyword*(i: PIdent): bool = + if (i.id >= ord(tokKeywordLow) - ord(tkSymbol)) and + (i.id <= ord(tokKeywordHigh) - ord(tkSymbol)): + result = true + else: + result = false + +proc isExported(n: PNode): bool = + ## Checks if an ident is exported. + ## This is meant to be used with idents in nkIdentDefs. + case n.kind + of nkPostfix: + n[0].ident.s == "*" and n[1].kind == nkIdent + of nkPragmaExpr: + n[0].isExported() + else: false + +proc renderDefinitionName*(s: PSym, noQuotes = false): string = + ## Returns the definition name of the symbol. + ## + ## If noQuotes is false the symbol may be returned in backticks. This will + ## happen if the name happens to be a keyword or the first character is not + ## part of the SymStartChars set. + let x = s.name.s + if noQuotes or (x[0] in SymStartChars and not renderer.isKeyword(s.name)): + result = x + else: + result = '`' & x & '`' + +template inside(g: var TSrcGen, section: Section, body: untyped) = + ## Runs `body` with `section` included in `g.inside`. + ## Removes it at the end of the body if `g` wasn't inside it + ## before the template. + let wasntInSection = section notin g.inside + g.inside.incl section + body + if wasntInSection: + g.inside.excl section + +template outside(g: var TSrcGen, section: Section, body: untyped) = + ## Temporarily removes `section` from `g.inside`. Adds it back + ## at the end of the body if `g` was inside it before the template + let wasInSection = section in g.inside + g.inside.excl section + body + if wasInSection: + g.inside.incl section + +const + IndentWidth = 2 + longIndentWid = IndentWidth * 2 + MaxLineLen = 80 + LineCommentColumn = 30 + +when defined(nimpretty): + proc minmaxLine(n: PNode): (int, int) = + case n.kind + of nkTripleStrLit: + result = (n.info.line.int, n.info.line.int + countLines(n.strVal)) + of nkCommentStmt: + result = (n.info.line.int, n.info.line.int + countLines(n.comment)) + else: + result = (n.info.line.int, n.info.line.int) + for i in 0..<n.safeLen: + let (currMin, currMax) = minmaxLine(n[i]) + if currMin < result[0]: result[0] = currMin + if currMax > result[1]: result[1] = currMax + + proc lineDiff(a, b: PNode): int = + result = minmaxLine(b)[0] - minmaxLine(a)[1] + +proc initSrcGen(renderFlags: TRenderFlags; config: ConfigRef): TSrcGen = + result = TSrcGen(comStack: @[], tokens: @[], indent: 0, + lineLen: 0, pos: 0, idx: 0, buf: "", + flags: renderFlags, pendingNL: -1, + pendingWhitespace: -1, inside: {}, + config: config + ) + +proc addTok(g: var TSrcGen, kind: TokType, s: string; sym: PSym = nil) = + g.tokens.add TRenderTok(kind: kind, length: int16(s.len), sym: sym) + g.buf.add(s) + if kind != tkSpaces: + inc g.col, s.len + +proc addPendingNL(g: var TSrcGen) = + if g.pendingNL >= 0: + when defined(nimpretty): + let newlines = repeat("\n", clamp(g.pendingNewlineCount, 1, 3)) + else: + const newlines = "\n" + addTok(g, tkSpaces, newlines & spaces(g.pendingNL)) + g.lineLen = g.pendingNL + g.col = g.pendingNL + g.pendingNL = - 1 + g.pendingWhitespace = -1 + elif g.pendingWhitespace >= 0: + addTok(g, tkSpaces, spaces(g.pendingWhitespace)) + g.pendingWhitespace = -1 + +proc putNL(g: var TSrcGen, indent: int) = + if g.pendingNL >= 0: addPendingNL(g) + else: + addTok(g, tkSpaces, "\n") + g.col = 0 + + g.pendingNL = indent + g.lineLen = indent + g.pendingWhitespace = -1 + +proc previousNL(g: TSrcGen): bool = + result = g.pendingNL >= 0 or (g.tokens.len > 0 and + g.tokens[^1].kind == tkSpaces) + +proc putNL(g: var TSrcGen) = + putNL(g, g.indent) + +proc optNL(g: var TSrcGen, indent: int) = + g.pendingNL = indent + g.lineLen = indent + g.col = g.indent + when defined(nimpretty): g.pendingNewlineCount = 0 + +proc optNL(g: var TSrcGen) = + optNL(g, g.indent) + +proc optNL(g: var TSrcGen; a, b: PNode) = + g.pendingNL = g.indent + g.lineLen = g.indent + g.col = g.indent + when defined(nimpretty): g.pendingNewlineCount = lineDiff(a, b) + +proc indentNL(g: var TSrcGen) = + inc(g.indent, IndentWidth) + g.pendingNL = g.indent + g.lineLen = g.indent + +proc dedent(g: var TSrcGen) = + dec(g.indent, IndentWidth) + assert(g.indent >= 0) + if g.pendingNL > IndentWidth: + dec(g.pendingNL, IndentWidth) + dec(g.lineLen, IndentWidth) + +proc put(g: var TSrcGen, kind: TokType, s: string; sym: PSym = nil) = + if kind != tkSpaces: + addPendingNL(g) + if s.len > 0 or kind in {tkHideableStart, tkHideableEnd}: + addTok(g, kind, s, sym) + else: + g.pendingWhitespace = s.len + inc g.col, s.len + inc(g.lineLen, s.len) + +proc putComment(g: var TSrcGen, s: string) = + if s.len == 0: return + var i = 0 + let hi = s.len - 1 + let isCode = (s.len >= 2) and (s[1] != ' ') + let ind = g.col + var com = "## " + while i <= hi: + case s[i] + of '\0': + break + of '\r': + put(g, tkComment, com) + com = "## " + inc(i) + if i <= hi and s[i] == '\n': inc(i) + optNL(g, ind) + of '\n': + put(g, tkComment, com) + com = "## " + inc(i) + optNL(g, ind) + of ' ', '\t': + com.add(s[i]) + inc(i) + else: + # we may break the comment into a multi-line comment if the line + # gets too long: + # compute length of the following word: + var j = i + while j <= hi and s[j] > ' ': inc(j) + if not isCode and (g.col + (j - i) > MaxLineLen): + put(g, tkComment, com) + optNL(g, ind) + com = "## " + while i <= hi and s[i] > ' ': + com.add(s[i]) + inc(i) + put(g, tkComment, com) + optNL(g) + +proc maxLineLength(s: string): int = + result = 0 + if s.len == 0: return 0 + var i = 0 + let hi = s.len - 1 + var lineLen = 0 + while i <= hi: + case s[i] + of '\0': + break + of '\r': + inc(i) + if i <= hi and s[i] == '\n': inc(i) + result = max(result, lineLen) + lineLen = 0 + of '\n': + inc(i) + result = max(result, lineLen) + lineLen = 0 + else: + inc(lineLen) + inc(i) + +proc putRawStr(g: var TSrcGen, kind: TokType, s: string) = + var i = 0 + let hi = s.len - 1 + var str = "" + while i <= hi: + case s[i] + of '\r': + put(g, kind, str) + str = "" + inc(i) + if i <= hi and s[i] == '\n': inc(i) + optNL(g, 0) + of '\n': + put(g, kind, str) + str = "" + inc(i) + optNL(g, 0) + else: + str.add(s[i]) + inc(i) + put(g, kind, str) + +proc containsNL(s: string): bool = + for i in 0..<s.len: + case s[i] + of '\r', '\n': + return true + else: + discard + result = false + +proc pushCom(g: var TSrcGen, n: PNode) = + setLen(g.comStack, g.comStack.len + 1) + g.comStack[^1] = n + +proc popAllComs(g: var TSrcGen) = + setLen(g.comStack, 0) + +const + Space = " " + +proc shouldRenderComment(g: TSrcGen): bool {.inline.} = + (renderNoComments notin g.flags or renderDocComments in g.flags) + +proc shouldRenderComment(g: TSrcGen, n: PNode): bool {.inline.} = + shouldRenderComment(g) and n.comment.len > 0 + +proc gcom(g: var TSrcGen, n: PNode) = + assert(n != nil) + if shouldRenderComment(g, n): + var oneSpaceAdded = 0 + if (g.pendingNL < 0) and (g.buf.len > 0) and (g.buf[^1] != ' '): + put(g, tkSpaces, Space) + oneSpaceAdded = 1 + # 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 (g.buf.len > 0) and + (g.col < LineCommentColumn): + var ml = maxLineLength(n.comment) + if ml + LineCommentColumn <= MaxLineLen: + put(g, tkSpaces, spaces(LineCommentColumn - g.col)) + dec g.col, oneSpaceAdded + putComment(g, n.comment) #assert(g.comStack[high(g.comStack)] = n); + +proc gcoms(g: var TSrcGen) = + for i in 0..high(g.comStack): gcom(g, g.comStack[i]) + popAllComs(g) + +proc lsub(g: TSrcGen; n: PNode): int +proc litAux(g: TSrcGen; n: PNode, x: BiggestInt, size: int): string = + proc skip(t: PType): PType = + result = t + while result != nil and result.kind in {tyGenericInst, tyRange, tyVar, + tyLent, tyDistinct, tyOrdinal, tyAlias, tySink}: + result = skipModifier(result) + + result = "" + let typ = n.typ.skip + if typ != nil and typ.kind in {tyBool, tyEnum}: + if sfPure in typ.sym.flags: + result = typ.sym.name.s & '.' + let enumfields = typ.n + # we need a slow linear search because of enums with holes: + for e in items(enumfields): + if e.sym.position == x: + result &= e.sym.name.s + return + + if nfBase2 in n.flags: result = "0b" & toBin(x, size * 8) + elif nfBase8 in n.flags: + var y = if size < sizeof(BiggestInt): x and ((1.BiggestInt shl (size*8)) - 1) + else: x + result = "0o" & toOct(y, size * 3) + elif nfBase16 in n.flags: result = "0x" & toHex(x, size * 2) + else: result = $x + +proc ulitAux(g: TSrcGen; n: PNode, x: BiggestInt, size: int): string = + if nfBase2 in n.flags: result = "0b" & toBin(x, size * 8) + elif nfBase8 in n.flags: result = "0o" & toOct(x, size * 3) + elif nfBase16 in n.flags: result = "0x" & toHex(x, size * 2) + else: result = $cast[BiggestUInt](x) + +proc atom(g: TSrcGen; n: PNode): string = + when defined(nimpretty): + doAssert g.config != nil, "g.config not initialized!" + let comment = if n.info.commentOffsetA < n.info.commentOffsetB: + " " & fileSection(g.config, g.fid, n.info.commentOffsetA, n.info.commentOffsetB) + else: + "" + if n.info.offsetA <= n.info.offsetB: + # for some constructed tokens this can not be the case and we're better + # off to not mess with the offset then. + return fileSection(g.config, g.fid, n.info.offsetA, n.info.offsetB) & comment + var f: float32 + case n.kind + of nkEmpty: result = "" + of nkIdent: result = n.ident.s + of nkSym: result = n.sym.name.s + of nkClosedSymChoice, nkOpenSymChoice: result = n[0].sym.name.s + of nkStrLit: result = ""; result.addQuoted(n.strVal) + of nkRStrLit: result = "r\"" & replace(n.strVal, "\"", "\"\"") & '\"' + of nkTripleStrLit: result = "\"\"\"" & n.strVal & "\"\"\"" + of nkCharLit: + result = "\'" + result.addEscapedChar(chr(int(n.intVal))); + result.add '\'' + of nkIntLit: result = litAux(g, n, n.intVal, 4) + of nkInt8Lit: result = litAux(g, n, n.intVal, 1) & "\'i8" + of nkInt16Lit: result = litAux(g, n, n.intVal, 2) & "\'i16" + of nkInt32Lit: result = litAux(g, n, n.intVal, 4) & "\'i32" + of nkInt64Lit: result = litAux(g, n, n.intVal, 8) & "\'i64" + of nkUIntLit: result = ulitAux(g, n, n.intVal, 4) & "\'u" + of nkUInt8Lit: result = ulitAux(g, n, n.intVal, 1) & "\'u8" + of nkUInt16Lit: result = ulitAux(g, n, n.intVal, 2) & "\'u16" + of nkUInt32Lit: result = ulitAux(g, n, n.intVal, 4) & "\'u32" + of nkUInt64Lit: result = ulitAux(g, n, n.intVal, 8) & "\'u64" + of nkFloatLit: + if n.flags * {nfBase2, nfBase8, nfBase16} == {}: result = $(n.floatVal) + else: result = litAux(g, n, (cast[ptr int64](addr(n.floatVal)))[] , 8) + of nkFloat32Lit: + if n.flags * {nfBase2, nfBase8, nfBase16} == {}: + result = $n.floatVal & "\'f32" + else: + f = n.floatVal.float32 + result = litAux(g, n, (cast[ptr int32](addr(f)))[], 4) & "\'f32" + of nkFloat64Lit: + if n.flags * {nfBase2, nfBase8, nfBase16} == {}: + result = $n.floatVal & "\'f64" + else: + result = litAux(g, n, (cast[ptr int64](addr(n.floatVal)))[], 8) & "\'f64" + of nkFloat128Lit: + if n.flags * {nfBase2, nfBase8, nfBase16} == {}: + result = $n.floatVal & "\'f128" + else: + result = litAux(g, n, (cast[ptr int64](addr(n.floatVal)))[], 8) & "\'f128" + of nkNilLit: result = "nil" + of nkType: + if (n.typ != nil) and (n.typ.sym != nil): result = n.typ.sym.name.s + else: result = "[type node]" + else: + internalError(g.config, "renderer.atom " & $n.kind) + result = "" + +proc lcomma(g: TSrcGen; n: PNode, start: int = 0, theEnd: int = - 1): int = + assert(theEnd < 0) + result = 0 + for i in start..n.len + theEnd: + let param = n[i] + if nfDefaultParam notin param.flags: + inc(result, lsub(g, param)) + inc(result, 2) # for ``, `` + if result > 0: + dec(result, 2) # last does not get a comma! + +proc lsons(g: TSrcGen; n: PNode, start: int = 0, theEnd: int = - 1): int = + assert(theEnd < 0) + result = 0 + for i in start..n.len + theEnd: inc(result, lsub(g, n[i])) + +proc origUsingType(n: PNode): PSym {.inline.} = + ## Returns the type that a parameter references. Check with referencesUsing first + ## to check `n` is actually referencing a using node + # If the node is untyped the typ field will be nil + if n[0].sym.typ != nil: + n[0].sym.typ.sym + else: nil + +proc referencesUsing(n: PNode): bool = + ## Returns true if n references a using statement. + ## e.g. proc foo(x) # x doesn't have type or def value so it references a using + result = n.kind == nkIdentDefs and + # Sometimes the node might not have been semmed (e.g. doc0) and will be nkIdent instead + n[0].kind == nkSym and + # Templates/macros can have parameters with no type (But their orig type will be nil) + n.origUsingType != nil and + n[1].kind == nkEmpty and n[2].kind == nkEmpty + +proc lsub(g: TSrcGen; n: PNode): int = + # computes the length of a tree + result = 0 + if isNil(n): return 0 + if shouldRenderComment(g, n): return MaxLineLen + 1 + case n.kind + of nkEmpty: result = 0 + of nkTripleStrLit: + if containsNL(n.strVal): result = MaxLineLen + 1 + else: result = atom(g, n).len + of succ(nkEmpty)..pred(nkTripleStrLit), succ(nkTripleStrLit)..nkNilLit: + result = atom(g, n).len + of nkCall, nkBracketExpr, nkCurlyExpr, nkConv, nkPattern, nkObjConstr: + result = lsub(g, n[0]) + lcomma(g, n, 1) + 2 + of nkHiddenStdConv, nkHiddenSubConv, nkHiddenCallConv: result = lsub(g, n[1]) + of nkCast: result = lsub(g, n[0]) + lsub(g, n[1]) + len("cast[]()") + of nkAddr: result = (if n.len>0: lsub(g, n[0]) + len("addr()") else: 4) + of nkStaticExpr: result = lsub(g, n[0]) + len("static_") + of nkHiddenAddr, nkHiddenDeref, nkStringToCString, nkCStringToString: result = lsub(g, n[0]) + of nkCommand: result = lsub(g, n[0]) + lcomma(g, n, 1) + 1 + of nkExprEqExpr, nkAsgn, nkFastAsgn: result = lsons(g, n) + 3 + of nkPar, nkCurly, nkBracket, nkClosure: result = lcomma(g, n) + 2 + of nkTupleConstr: + # assume the trailing comma: + result = lcomma(g, n) + 3 + of nkArgList: result = lcomma(g, n) + of nkTableConstr: + result = if n.len > 0: lcomma(g, n) + 2 else: len("{:}") + of nkClosedSymChoice, nkOpenSymChoice: + if n.len > 0: result += lsub(g, n[0]) + of nkOpenSym: result = lsub(g, n[0]) + of nkTupleTy: result = lcomma(g, n) + len("tuple[]") + of nkTupleClassTy: result = len("tuple") + of nkDotExpr: result = lsons(g, n) + 1 + of nkBind: result = lsons(g, n) + len("bind_") + of nkBindStmt: result = lcomma(g, n) + len("bind_") + of nkMixinStmt: result = lcomma(g, n) + len("mixin_") + of nkCheckedFieldExpr: result = lsub(g, n[0]) + of nkLambda: result = lsons(g, n) + len("proc__=_") + of nkDo: result = lsons(g, n) + len("do__:_") + of nkConstDef, nkIdentDefs: + result = lcomma(g, n, 0, - 3) + if n.referencesUsing: + result += lsub(g, newSymNode(n.origUsingType)) + 2 + else: + if n[^2].kind != nkEmpty: result += lsub(g, n[^2]) + 2 + if n[^1].kind != nkEmpty: result += lsub(g, n[^1]) + 3 + of nkVarTuple: + if n[^1].kind == nkEmpty: + result = lcomma(g, n, 0, - 2) + len("()") + else: + result = lcomma(g, n, 0, - 3) + len("() = ") + lsub(g, lastSon(n)) + of nkChckRangeF: result = len("chckRangeF") + 2 + lcomma(g, n) + of nkChckRange64: result = len("chckRange64") + 2 + lcomma(g, n) + of nkChckRange: result = len("chckRange") + 2 + lcomma(g, n) + of nkObjDownConv, nkObjUpConv: + result = 2 + if n.len >= 1: result += lsub(g, n[0]) + result += lcomma(g, n, 1) + of nkExprColonExpr: result = lsons(g, n) + 2 + of nkInfix: result = lsons(g, n) + 2 + of nkPrefix: + result = lsons(g, n)+1+(if n.len > 0 and n[1].kind == nkInfix: 2 else: 0) + of nkPostfix: + if renderNoPostfix notin g.flags: + result = lsons(g, n) + else: + result = lsub(g, n[1]) + of nkCallStrLit: result = lsons(g, n) + of nkPragmaExpr: result = lsub(g, n[0]) + lcomma(g, n, 1) + of nkRange: result = lsons(g, n) + 2 + of nkDerefExpr: result = lsub(g, n[0]) + 2 + of nkAccQuoted: result = lsons(g, n) + 2 + of nkIfExpr: + result = lsub(g, n[0][0]) + lsub(g, n[0][1]) + lsons(g, n, 1) + + len("if_:_") + of nkElifExpr: result = lsons(g, n) + len("_elif_:_") + of nkElseExpr: result = lsub(g, n[0]) + len("_else:_") # type descriptions + of nkTypeOfExpr: result = (if n.len > 0: lsub(g, n[0]) else: 0)+len("typeof()") + of nkRefTy: result = (if n.len > 0: lsub(g, n[0])+1 else: 0) + len("ref") + of nkPtrTy: result = (if n.len > 0: lsub(g, n[0])+1 else: 0) + len("ptr") + of nkVarTy, nkOutTy: result = (if n.len > 0: lsub(g, n[0])+1 else: 0) + len("var") + of nkDistinctTy: + result = len("distinct") + (if n.len > 0: lsub(g, n[0])+1 else: 0) + if n.len > 1: + result += (if n[1].kind == nkWith: len("_with_") else: len("_without_")) + result += lcomma(g, n[1]) + of nkStaticTy: result = (if n.len > 0: lsub(g, n[0]) else: 0) + + len("static[]") + of nkTypeDef: result = lsons(g, n) + 3 + of nkOfInherit: result = lsub(g, n[0]) + len("of_") + of nkProcTy: result = lsons(g, n) + len("proc_") + of nkIteratorTy: result = lsons(g, n) + len("iterator_") + of nkSinkAsgn: result = lsons(g, n) + len("`=sink`(, )") + of nkEnumTy: + if n.len > 0: + result = lsub(g, n[0]) + lcomma(g, n, 1) + len("enum_") + else: + result = len("enum") + of nkEnumFieldDef: result = lsons(g, n) + 3 + of nkVarSection, nkLetSection: + if n.len > 1: result = MaxLineLen + 1 + else: result = lsons(g, n) + len("var_") + of nkUsingStmt: + if n.len > 1: result = MaxLineLen + 1 + else: result = lsons(g, n) + len("using_") + of nkReturnStmt: + if n.len > 0 and n[0].kind == nkAsgn and renderIr notin g.flags: + result = len("return_") + lsub(g, n[0][1]) + else: + result = len("return_") + lsub(g, n[0]) + of nkRaiseStmt: result = lsub(g, n[0]) + len("raise_") + of nkYieldStmt: result = lsub(g, n[0]) + len("yield_") + of nkDiscardStmt: result = lsub(g, n[0]) + len("discard_") + of nkBreakStmt: result = lsub(g, n[0]) + len("break_") + of nkContinueStmt: result = lsub(g, n[0]) + len("continue_") + of nkPragma: result = lcomma(g, n) + 4 + of nkCommentStmt: result = n.comment.len + of nkOfBranch: result = lcomma(g, n, 0, - 2) + lsub(g, lastSon(n)) + len("of_:_") + of nkImportAs: result = lsub(g, n[0]) + len("_as_") + lsub(g, n[1]) + of nkElifBranch: result = lsons(g, n) + len("elif_:_") + of nkElse: result = lsub(g, n[0]) + len("else:_") + of nkFinally: result = lsub(g, n[0]) + len("finally:_") + of nkGenericParams: result = lcomma(g, n) + 2 + of nkFormalParams: + result = lcomma(g, n, 1) + 2 + if n[0].kind != nkEmpty: result += lsub(g, n[0]) + 2 + of nkExceptBranch: + result = lcomma(g, n, 0, -2) + lsub(g, lastSon(n)) + len("except_:_") + of nkObjectTy: + result = len("object_") + else: result = MaxLineLen + 1 + +proc fits(g: TSrcGen, x: int): bool = + result = x <= MaxLineLen + +type + TSubFlag = enum + rfLongMode, rfInConstExpr + TSubFlags = set[TSubFlag] + TContext = tuple[spacing: int, flags: TSubFlags] + +const + emptyContext: TContext = (spacing: 0, flags: {}) + +proc initContext(): TContext = + result = (spacing: 0, flags: {}) + +proc gsub(g: var TSrcGen, n: PNode, c: TContext, fromStmtList = false) +proc gsub(g: var TSrcGen, n: PNode, fromStmtList = false) = + var c: TContext = initContext() + gsub(g, n, c, fromStmtList = fromStmtList) + +proc hasCom(n: PNode): bool = + result = false + if n.isNil: return false + if n.comment.len > 0: return true + case n.kind + of nkEmpty..nkNilLit: discard + else: + for i in 0..<n.len: + if hasCom(n[i]): return true + +proc putWithSpace(g: var TSrcGen, kind: TokType, s: string) = + put(g, kind, s) + put(g, tkSpaces, Space) + +proc isHideable(config: ConfigRef, n: PNode): bool = + # xxx compare `ident` directly with `getIdent(cache, wRaises)`, but + # this requires a `cache`. + case n.kind + of nkExprColonExpr: + result = n[0].kind == nkIdent and + n[0].ident.s.nimIdentNormalize in ["raises", "tags", "extern", "deprecated", "forbids", "stacktrace"] + of nkIdent: result = n.ident.s in ["gcsafe", "deprecated"] + else: result = false + +proc gcommaAux(g: var TSrcGen, n: PNode, ind: int, start: int = 0, + theEnd: int = - 1, separator = tkComma) = + let inPragma = g.inPragma == 1 # just the top-level + var inHideable = false + for i in start..n.len + theEnd: + let c = i < n.len + theEnd + let sublen = lsub(g, n[i]) + ord(c) + if not fits(g, g.lineLen + sublen) and (ind + sublen < MaxLineLen): optNL(g, ind) + let oldLen = g.tokens.len + if inPragma: + if not inHideable and isHideable(g.config, n[i]): + inHideable = true + put(g, tkHideableStart, "") + elif inHideable and not isHideable(g.config, n[i]): + inHideable = false + put(g, tkHideableEnd, "") + gsub(g, n[i]) + if c: + if g.tokens.len > oldLen: + putWithSpace(g, separator, $separator) + if shouldRenderComment(g) and hasCom(n[i]): + gcoms(g) + optNL(g, ind) + if inHideable: + put(g, tkHideableEnd, "") + inHideable = false + +proc gcomma(g: var TSrcGen, n: PNode, c: TContext, start: int = 0, + theEnd: int = -1) = + var ind: int + if rfInConstExpr in c.flags: + ind = g.indent + IndentWidth + else: + ind = g.lineLen + if ind > MaxLineLen div 2: ind = g.indent + longIndentWid + gcommaAux(g, n, ind, start, theEnd) + +proc gcomma(g: var TSrcGen, n: PNode, start: int = 0, theEnd: int = - 1) = + var ind = g.lineLen + if ind > MaxLineLen div 2: ind = g.indent + longIndentWid + gcommaAux(g, n, ind, start, theEnd) + +proc gsemicolon(g: var TSrcGen, n: PNode, start: int = 0, theEnd: int = - 1) = + var ind = g.lineLen + if ind > MaxLineLen div 2: ind = g.indent + longIndentWid + gcommaAux(g, n, ind, start, theEnd, tkSemiColon) + +proc gsons(g: var TSrcGen, n: PNode, c: TContext, start: int = 0, + theEnd: int = - 1) = + for i in start..n.len + theEnd: gsub(g, n[i], c) + +proc gsection(g: var TSrcGen, n: PNode, c: TContext, kind: TokType, + k: string) = + if n.len == 0: return # empty var sections are possible + putWithSpace(g, kind, k) + gcoms(g) + indentNL(g) + for i in 0..<n.len: + optNL(g) + gsub(g, n[i], c) + gcoms(g) + dedent(g) + +proc longMode(g: TSrcGen; n: PNode, start: int = 0, theEnd: int = - 1): bool = + result = shouldRenderComment(g, n) + if not result: + # check further + for i in start..n.len + theEnd: + if (lsub(g, n[i]) > MaxLineLen): + result = true + break + +proc gstmts(g: var TSrcGen, n: PNode, c: TContext, doIndent=true) = + if n.kind == nkEmpty: return + if n.kind in {nkStmtList, nkStmtListExpr, nkStmtListType}: + if doIndent: indentNL(g) + for i in 0..<n.len: + if i > 0: + optNL(g, n[i-1], n[i]) + else: + optNL(g) + if n[i].kind in {nkStmtList, nkStmtListExpr, nkStmtListType}: + gstmts(g, n[i], c, doIndent=false) + else: + gsub(g, n[i], fromStmtList = true) + gcoms(g) + if doIndent: dedent(g) + else: + indentNL(g) + gsub(g, n) + gcoms(g) + dedent(g) + optNL(g) + + +proc gcond(g: var TSrcGen, n: PNode) = + if n.kind == nkStmtListExpr: + put(g, tkParLe, "(") + gsub(g, n) + if n.kind == nkStmtListExpr: + put(g, tkParRi, ")") + +proc gif(g: var TSrcGen, n: PNode) = + var c: TContext = initContext() + gcond(g, n[0][0]) + putWithSpace(g, tkColon, ":") + if longMode(g, n) or (lsub(g, n[0][1]) + g.lineLen > MaxLineLen): + incl(c.flags, rfLongMode) + gcoms(g) # a good place for comments + gstmts(g, n[0][1], c) + for i in 1..<n.len: + optNL(g) + gsub(g, n[i], c) + +proc gwhile(g: var TSrcGen, n: PNode) = + var c: TContext = initContext() + putWithSpace(g, tkWhile, "while") + gcond(g, n[0]) + putWithSpace(g, tkColon, ":") + if longMode(g, n) or (lsub(g, n[1]) + g.lineLen > MaxLineLen): + incl(c.flags, rfLongMode) + gcoms(g) # a good place for comments + gstmts(g, n[1], c) + +proc gpattern(g: var TSrcGen, n: PNode) = + var c: TContext = initContext() + put(g, tkCurlyLe, "{") + if longMode(g, n) or (lsub(g, n[0]) + g.lineLen > MaxLineLen): + incl(c.flags, rfLongMode) + gcoms(g) # a good place for comments + gstmts(g, n, c) + put(g, tkCurlyRi, "}") + +proc gpragmaBlock(g: var TSrcGen, n: PNode) = + var c: TContext = initContext() + gsub(g, n[0]) + putWithSpace(g, tkColon, ":") + if longMode(g, n) or (lsub(g, n[1]) + g.lineLen > MaxLineLen): + incl(c.flags, rfLongMode) + gcoms(g) # a good place for comments + gstmts(g, n[1], c) + +proc gtry(g: var TSrcGen, n: PNode) = + var c: TContext = initContext() + put(g, tkTry, "try") + putWithSpace(g, tkColon, ":") + if longMode(g, n) or (lsub(g, n[0]) + g.lineLen > MaxLineLen): + incl(c.flags, rfLongMode) + gcoms(g) # a good place for comments + gstmts(g, n[0], c) + gsons(g, n, c, 1) + +proc gfor(g: var TSrcGen, n: PNode) = + var c: TContext = initContext() + putWithSpace(g, tkFor, "for") + if longMode(g, n) or + (lsub(g, n[^1]) + lsub(g, n[^2]) + 6 + g.lineLen > MaxLineLen): + incl(c.flags, rfLongMode) + gcomma(g, n, c, 0, - 3) + put(g, tkSpaces, Space) + putWithSpace(g, tkIn, "in") + gsub(g, n[^2], c) + putWithSpace(g, tkColon, ":") + gcoms(g) + gstmts(g, n[^1], c) + +proc gcase(g: var TSrcGen, n: PNode) = + var c: TContext = initContext() + if n.len == 0: return + var last = if n[^1].kind == nkElse: -2 else: -1 + if longMode(g, n, 0, last): incl(c.flags, rfLongMode) + putWithSpace(g, tkCase, "case") + gcond(g, n[0]) + gcoms(g) + optNL(g) + gsons(g, n, c, 1, last) + if last == - 2: + c = initContext() + if longMode(g, n[^1]): incl(c.flags, rfLongMode) + gsub(g, n[^1], c) + +proc genSymSuffix(result: var string, s: PSym) {.inline.} = + if sfGenSym in s.flags and s.name.id != ord(wUnderscore): + result.add '_' + result.addInt s.id + +proc gproc(g: var TSrcGen, n: PNode) = + var c: TContext = initContext() + if n[namePos].kind == nkSym: + let s = n[namePos].sym + var ret = renderDefinitionName(s) + ret.genSymSuffix(s) + put(g, tkSymbol, ret) + else: + gsub(g, n[namePos]) + + if n[patternPos].kind != nkEmpty: + gpattern(g, n[patternPos]) + g.inside(GenericParams): + if renderNoBody in g.flags and n[miscPos].kind != nkEmpty and + n[miscPos][1].kind != nkEmpty: + gsub(g, n[miscPos][1]) + else: + gsub(g, n[genericParamsPos]) + gsub(g, n[paramsPos]) + if renderNoPragmas notin g.flags: + gsub(g, n[pragmasPos]) + if renderNoBody notin g.flags: + if n.len > bodyPos and n[bodyPos].kind != nkEmpty: + put(g, tkSpaces, Space) + putWithSpace(g, tkEquals, "=") + indentNL(g) + gcoms(g) + dedent(g) + c = initContext() + gstmts(g, n[bodyPos], c) + putNL(g) + else: + indentNL(g) + gcoms(g) + dedent(g) + +proc gTypeClassTy(g: var TSrcGen, n: PNode) = + var c: TContext = initContext() + putWithSpace(g, tkConcept, "concept") + gsons(g, n[0], c) # arglist + gsub(g, n[1]) # pragmas + gsub(g, n[2]) # of + gcoms(g) + indentNL(g) + gcoms(g) + gstmts(g, n[3], c) + dedent(g) + +proc gblock(g: var TSrcGen, n: PNode) = + # you shouldn't simplify it to `n.len < 2` + # because the following codes should be executed + # even when block stmt has only one child for getting + # better error messages. + if n.len == 0: + return + + var c: TContext = initContext() + + if n[0].kind != nkEmpty: + putWithSpace(g, tkBlock, "block") + gsub(g, n[0]) + else: + put(g, tkBlock, "block") + + # block stmt should have two children + if n.len == 1: + return + + putWithSpace(g, tkColon, ":") + + if longMode(g, n) or (lsub(g, n[1]) + g.lineLen > MaxLineLen): + incl(c.flags, rfLongMode) + gcoms(g) + gstmts(g, n[1], c) + +proc gstaticStmt(g: var TSrcGen, n: PNode) = + var c: TContext = initContext() + putWithSpace(g, tkStatic, "static") + putWithSpace(g, tkColon, ":") + if longMode(g, n) or (lsub(g, n[0]) + g.lineLen > MaxLineLen): + incl(c.flags, rfLongMode) + gcoms(g) # a good place for comments + gstmts(g, n[0], c) + +proc gasm(g: var TSrcGen, n: PNode) = + putWithSpace(g, tkAsm, "asm") + gsub(g, n[0]) + gcoms(g) + if n.len > 1: + gsub(g, n[1]) + +proc gident(g: var TSrcGen, n: PNode) = + if GenericParams in g.inside and n.kind == nkSym: + if sfAnon in n.sym.flags or + (n.typ != nil and tfImplicitTypeParam in n.typ.flags): return + + var t: TokType + var s = atom(g, n) + if s.len > 0 and s[0] in lexer.SymChars: + if n.kind == nkIdent: + if (n.ident.id < ord(tokKeywordLow) - ord(tkSymbol)) or + (n.ident.id > ord(tokKeywordHigh) - ord(tkSymbol)): + t = tkSymbol + else: + t = TokType(n.ident.id + ord(tkSymbol)) + else: + t = tkSymbol + else: + t = tkOpr + if renderIr in g.flags and n.kind == nkSym: + let localId = disamb(g, n.sym) + if localId != 0 and n.sym.magic == mNone: + s.add '_' + s.addInt localId + if sfCursor in n.sym.flags: + s.add "_cursor" + elif n.kind == nkSym and (renderIds in g.flags or + (sfGenSym in n.sym.flags and n.sym.name.id != ord(wUnderscore)) or + n.sym.kind == skTemp): + s.add '_' + s.addInt n.sym.id + when defined(debugMagics): + s.add '_' + s.add $n.sym.magic + put(g, t, s, if n.kind == nkSym and renderSyms in g.flags: n.sym else: nil) + +proc doParamsAux(g: var TSrcGen, params: PNode) = + if params.len > 1: + put(g, tkParLe, "(") + gsemicolon(g, params, 1) + put(g, tkParRi, ")") + + if params.len > 0 and params[0].kind != nkEmpty: + put(g, tkSpaces, Space) + putWithSpace(g, tkOpr, "->") + gsub(g, params[0]) + +proc gsub(g: var TSrcGen; n: PNode; i: int) = + if i < n.len: + gsub(g, n[i]) + else: + put(g, tkOpr, "<<" & $i & "th child missing for " & $n.kind & " >>") + +type + BracketKind = enum + bkNone, bkBracket, bkBracketAsgn, bkCurly, bkCurlyAsgn + +proc bracketKind*(g: TSrcGen, n: PNode): BracketKind = + if renderIds notin g.flags: + case n.kind + of nkClosedSymChoice, nkOpenSymChoice: + if n.len > 0: result = bracketKind(g, n[0]) + else: result = bkNone + of nkSym: + result = case n.sym.name.s + of "[]": bkBracket + of "[]=": bkBracketAsgn + of "{}": bkCurly + of "{}=": bkCurlyAsgn + else: bkNone + else: result = bkNone + else: + result = bkNone + +proc skipHiddenNodes(n: PNode): PNode = + result = n + while result != nil: + if result.kind in {nkHiddenStdConv, nkHiddenSubConv, nkHiddenCallConv, nkOpenSym} and result.len > 1: + result = result[1] + elif result.kind in {nkCheckedFieldExpr, nkHiddenAddr, nkHiddenDeref, nkStringToCString, nkCStringToString} and + result.len > 0: + result = result[0] + else: break + +proc accentedName(g: var TSrcGen, n: PNode) = + # This is for cases where ident should've really been a `nkAccQuoted`, e.g. `:tmp` + # or if user writes a macro with `ident":foo"`. It's unclear whether these should be legal. + const backticksNeeded = OpChars + {'[', '{', '\''} + if n == nil: return + let ident = n.getPIdent + if ident != nil and ident.s[0] in backticksNeeded: + put(g, tkAccent, "`") + gident(g, n) + put(g, tkAccent, "`") + else: + gsub(g, n) + +proc infixArgument(g: var TSrcGen, n: PNode, i: int) = + if i < 1 or i > 2: return + var needsParenthesis = false + let nNext = n[i].skipHiddenNodes + if nNext.kind == nkInfix: + if nNext[0].kind in {nkSym, nkIdent} and n[0].kind in {nkSym, nkIdent}: + let nextId = if nNext[0].kind == nkSym: nNext[0].sym.name else: nNext[0].ident + let nnId = if n[0].kind == nkSym: n[0].sym.name else: n[0].ident + if i == 1: + if getPrecedence(nextId) < getPrecedence(nnId): + needsParenthesis = true + elif i == 2: + if getPrecedence(nextId) <= getPrecedence(nnId): + needsParenthesis = true + if needsParenthesis: + put(g, tkParLe, "(") + gsub(g, n, i) + if needsParenthesis: + put(g, tkParRi, ")") + +const postExprBlocks = {nkStmtList, nkStmtListExpr, + nkOfBranch, nkElifBranch, nkElse, + nkExceptBranch, nkFinally, nkDo} + +proc postStatements(g: var TSrcGen, n: PNode, i: int, fromStmtList: bool) = + var i = i + if n[i].kind in {nkStmtList, nkStmtListExpr}: + if fromStmtList: + put(g, tkColon, ":") + else: + put(g, tkSpaces, Space) + put(g, tkDo, "do") + put(g, tkColon, ":") + gsub(g, n, i) + i.inc + for j in i ..< n.len: + if n[j].kind == nkDo: + optNL(g) + elif n[j].kind in {nkStmtList, nkStmtListExpr}: + optNL(g) + put(g, tkDo, "do") + put(g, tkColon, ":") + gsub(g, n, j) + +proc isCustomLit(n: PNode): bool = + if n.len == 2 and n[0].kind == nkRStrLit: + let ident = n[1].getPIdent + result = ident != nil and ident.s.startsWith('\'') + else: + result = false + +proc gsub(g: var TSrcGen, n: PNode, c: TContext, fromStmtList = false) = + if isNil(n): return + var + a: TContext = default(TContext) + if shouldRenderComment(g, n): pushCom(g, n) + case n.kind # atoms: + of nkTripleStrLit: put(g, tkTripleStrLit, atom(g, n)) + of nkEmpty: discard + of nkType: put(g, tkInvalid, atom(g, n)) + of nkSym, nkIdent: gident(g, n) + of nkIntLit: put(g, tkIntLit, atom(g, n)) + of nkInt8Lit: put(g, tkInt8Lit, atom(g, n)) + of nkInt16Lit: put(g, tkInt16Lit, atom(g, n)) + of nkInt32Lit: put(g, tkInt32Lit, atom(g, n)) + of nkInt64Lit: put(g, tkInt64Lit, atom(g, n)) + of nkUIntLit: put(g, tkUIntLit, atom(g, n)) + of nkUInt8Lit: put(g, tkUInt8Lit, atom(g, n)) + of nkUInt16Lit: put(g, tkUInt16Lit, atom(g, n)) + of nkUInt32Lit: put(g, tkUInt32Lit, atom(g, n)) + of nkUInt64Lit: put(g, tkUInt64Lit, atom(g, n)) + of nkFloatLit: put(g, tkFloatLit, atom(g, n)) + of nkFloat32Lit: put(g, tkFloat32Lit, atom(g, n)) + of nkFloat64Lit: put(g, tkFloat64Lit, atom(g, n)) + of nkFloat128Lit: put(g, tkFloat128Lit, atom(g, n)) + of nkStrLit: put(g, tkStrLit, atom(g, n)) + of nkRStrLit: put(g, tkRStrLit, atom(g, n)) + of nkCharLit: put(g, tkCharLit, atom(g, n)) + of nkNilLit: put(g, tkNil, atom(g, n)) # complex expressions + of nkCall, nkConv, nkDotCall, nkPattern, nkObjConstr: + if n.len > 1 and n.lastSon.kind in postExprBlocks: + accentedName(g, n[0]) + var i = 1 + while i < n.len and n[i].kind notin postExprBlocks: i.inc + if i > 1: + put(g, tkParLe, "(") + gcomma(g, n, 1, i - 1 - n.len) + put(g, tkParRi, ")") + postStatements(g, n, i, fromStmtList) + elif n.len >= 1: + case bracketKind(g, n[0]) + of bkBracket: + gsub(g, n, 1) + put(g, tkBracketLe, "[") + gcomma(g, n, 2) + put(g, tkBracketRi, "]") + of bkBracketAsgn: + gsub(g, n, 1) + put(g, tkBracketLe, "[") + gcomma(g, n, 2, -2) + put(g, tkBracketRi, "]") + put(g, tkSpaces, Space) + putWithSpace(g, tkEquals, "=") + gsub(g, n, n.len - 1) + of bkCurly: + gsub(g, n, 1) + put(g, tkCurlyLe, "{") + gcomma(g, n, 2) + put(g, tkCurlyRi, "}") + of bkCurlyAsgn: + gsub(g, n, 1) + put(g, tkCurlyLe, "{") + gcomma(g, n, 2, -2) + put(g, tkCurlyRi, "}") + put(g, tkSpaces, Space) + putWithSpace(g, tkEquals, "=") + gsub(g, n, n.len - 1) + of bkNone: + accentedName(g, n[0]) + put(g, tkParLe, "(") + gcomma(g, n, 1) + put(g, tkParRi, ")") + else: + put(g, tkParLe, "(") + put(g, tkParRi, ")") + of nkCallStrLit: + if n.len > 0: accentedName(g, n[0]) + if n.len > 1 and n[1].kind == nkRStrLit: + put(g, tkRStrLit, '\"' & replace(n[1].strVal, "\"", "\"\"") & '\"') + else: + gsub(g, n, 1) + of nkHiddenStdConv, nkHiddenSubConv: + if n.len >= 2: + when false: + # if {renderIds, renderIr} * g.flags != {}: + put(g, tkSymbol, "(conv)") + put(g, tkParLe, "(") + gsub(g, n[1]) + put(g, tkParRi, ")") + else: + gsub(g, n[1]) + else: + put(g, tkSymbol, "(wrong conv)") + of nkHiddenCallConv: + if {renderIds, renderIr} * g.flags != {}: + accentedName(g, n[0]) + put(g, tkParLe, "(") + gcomma(g, n, 1) + put(g, tkParRi, ")") + elif n.len >= 2: + gsub(g, n[1]) + else: + put(g, tkSymbol, "(wrong conv)") + of nkCast: + put(g, tkCast, "cast") + if n.len > 0 and n[0].kind != nkEmpty: + put(g, tkBracketLe, "[") + gsub(g, n, 0) + put(g, tkBracketRi, "]") + put(g, tkParLe, "(") + gsub(g, n, 1) + put(g, tkParRi, ")") + of nkAddr: + put(g, tkAddr, "addr") + if n.len > 0: + put(g, tkParLe, "(") + gsub(g, n[0]) + put(g, tkParRi, ")") + of nkStaticExpr: + put(g, tkStatic, "static") + put(g, tkSpaces, Space) + gsub(g, n, 0) + of nkBracketExpr: + gsub(g, n, 0) + put(g, tkBracketLe, "[") + gcomma(g, n, 1) + put(g, tkBracketRi, "]") + of nkCurlyExpr: + gsub(g, n, 0) + put(g, tkCurlyLe, "{") + gcomma(g, n, 1) + put(g, tkCurlyRi, "}") + of nkPragmaExpr: + gsub(g, n, 0) + gcomma(g, n, 1) + of nkCommand: + accentedName(g, n[0]) + put(g, tkSpaces, Space) + if n.len > 1 and n.lastSon.kind in postExprBlocks: + var i = 1 + while i < n.len and n[i].kind notin postExprBlocks: i.inc + if i > 1: + gcomma(g, n, 1, i - 1 - n.len) + postStatements(g, n, i, fromStmtList) + else: + gcomma(g, n, 1) + of nkExprEqExpr, nkAsgn, nkFastAsgn: + gsub(g, n, 0) + put(g, tkSpaces, Space) + putWithSpace(g, tkEquals, "=") + gsub(g, n, 1) + of nkSinkAsgn: + put(g, tkSymbol, "`=sink`") + put(g, tkParLe, "(") + gcomma(g, n) + put(g, tkParRi, ")") + of nkChckRangeF: + put(g, tkSymbol, "chckRangeF") + put(g, tkParLe, "(") + gcomma(g, n) + put(g, tkParRi, ")") + of nkChckRange64: + put(g, tkSymbol, "chckRange64") + put(g, tkParLe, "(") + gcomma(g, n) + put(g, tkParRi, ")") + of nkChckRange: + put(g, tkSymbol, "chckRange") + put(g, tkParLe, "(") + gcomma(g, n) + put(g, tkParRi, ")") + of nkObjDownConv, nkObjUpConv: + let typ = if (n.typ != nil) and (n.typ.sym != nil): n.typ.sym.name.s else: "" + put(g, tkParLe, typ & "(") + if n.len >= 1: gsub(g, n[0]) + put(g, tkParRi, ")") + of nkClosedSymChoice, nkOpenSymChoice: + if renderIds in g.flags: + put(g, tkParLe, "(") + for i in 0..<n.len: + if i > 0: put(g, tkOpr, "|") + if n[i].kind == nkSym: + let s = n[i].sym + if s.owner != nil: + put g, tkSymbol, n[i].sym.owner.name.s + put g, tkOpr, "." + put g, tkSymbol, n[i].sym.name.s + else: + gsub(g, n[i], c) + put(g, tkParRi, if n.kind == nkOpenSymChoice: "|...)" else: ")") + else: + gsub(g, n, 0) + of nkOpenSym: gsub(g, n, 0) + of nkPar, nkClosure: + put(g, tkParLe, "(") + gcomma(g, n, c) + put(g, tkParRi, ")") + of nkTupleConstr: + put(g, tkParLe, "(") + gcomma(g, n, c) + if n.len == 1 and n[0].kind != nkExprColonExpr: put(g, tkComma, ",") + put(g, tkParRi, ")") + of nkCurly: + put(g, tkCurlyLe, "{") + gcomma(g, n, c) + put(g, tkCurlyRi, "}") + of nkArgList: + gcomma(g, n, c) + of nkTableConstr: + put(g, tkCurlyLe, "{") + if n.len > 0: gcomma(g, n, c) + else: put(g, tkColon, ":") + put(g, tkCurlyRi, "}") + of nkBracket: + put(g, tkBracketLe, "[") + gcomma(g, n, c) + put(g, tkBracketRi, "]") + of nkDotExpr: + if isCustomLit(n): + put(g, tkCustomLit, n[0].strVal) + gsub(g, n, 1) + else: + gsub(g, n, 0) + put(g, tkDot, ".") + assert n.len == 2, $n.len + accentedName(g, n[1]) + of nkBind: + putWithSpace(g, tkBind, "bind") + gsub(g, n, 0) + of nkCheckedFieldExpr, nkHiddenAddr, nkHiddenDeref, nkStringToCString, nkCStringToString: + if renderIds in g.flags: + put(g, tkAddr, $n.kind) + put(g, tkParLe, "(") + gsub(g, n, 0) + if renderIds in g.flags: + put(g, tkParRi, ")") + + of nkLambda: + putWithSpace(g, tkProc, "proc") + gsub(g, n, paramsPos) + gsub(g, n, pragmasPos) + put(g, tkSpaces, Space) + putWithSpace(g, tkEquals, "=") + gsub(g, n, bodyPos) + of nkDo: + putWithSpace(g, tkDo, "do") + if paramsPos < n.len: + doParamsAux(g, n[paramsPos]) + gsub(g, n, pragmasPos) + put(g, tkColon, ":") + gsub(g, n, bodyPos) + of nkIdentDefs: + var exclFlags: TRenderFlags = {} + if ObjectDef in g.inside: + if not n[0].isExported() and renderNonExportedFields notin g.flags: + # Skip if this is a property in a type and its not exported + # (While also not allowing rendering of non exported fields) + return + # render postfix for object fields: + exclFlags = g.flags * {renderNoPostfix} + # We render the identDef without being inside the section incase we render something like + # y: proc (x: string) # (We wouldn't want to check if x is exported) + g.outside(ObjectDef): + g.flags.excl(exclFlags) + gcomma(g, n, 0, -3) + g.flags.incl(exclFlags) + if n.len >= 2 and n[^2].kind != nkEmpty: + putWithSpace(g, tkColon, ":") + gsub(g, n[^2], c) + elif n.referencesUsing and renderExpandUsing in g.flags: + putWithSpace(g, tkColon, ":") + gsub(g, newSymNode(n.origUsingType), c) + + if n.len >= 1 and n[^1].kind != nkEmpty: + put(g, tkSpaces, Space) + putWithSpace(g, tkEquals, "=") + gsub(g, n[^1], c) + of nkConstDef: + gcomma(g, n, 0, -3) + if n.len >= 2 and n[^2].kind != nkEmpty: + putWithSpace(g, tkColon, ":") + gsub(g, n[^2], c) + + if n.len >= 1 and n[^1].kind != nkEmpty: + put(g, tkSpaces, Space) + putWithSpace(g, tkEquals, "=") + gsub(g, n[^1], c) + of nkVarTuple: + if n[^1].kind == nkEmpty: + put(g, tkParLe, "(") + gcomma(g, n, 0, -2) + put(g, tkParRi, ")") + else: + put(g, tkParLe, "(") + gcomma(g, n, 0, -3) + put(g, tkParRi, ")") + put(g, tkSpaces, Space) + putWithSpace(g, tkEquals, "=") + gsub(g, lastSon(n), c) + of nkExprColonExpr: + gsub(g, n, 0) + putWithSpace(g, tkColon, ":") + gsub(g, n, 1) + of nkInfix: + if n.len < 3: + var i = 0 + put(g, tkOpr, "Too few children for nkInfix") + return + let oldLineLen = g.lineLen # we cache this because lineLen gets updated below + infixArgument(g, n, 1) + put(g, tkSpaces, Space) + gsub(g, n, 0) # binary operator + # e.g.: `n1 == n2` decompses as following sum: + if n.len == 3 and not fits(g, oldLineLen + lsub(g, n[1]) + lsub(g, n[2]) + lsub(g, n[0]) + len(" ")): + optNL(g, g.indent + longIndentWid) + else: + put(g, tkSpaces, Space) + infixArgument(g, n, 2) + if n.len > 3 and n.lastSon.kind in postExprBlocks: + var i = 3 + while i < n.len and n[i].kind notin postExprBlocks: i.inc + postStatements(g, n, i, fromStmtList) + of nkPrefix: + gsub(g, n, 0) + if n.len > 1: + let opr = if n[0].kind == nkIdent: n[0].ident + elif n[0].kind == nkSym: n[0].sym.name + elif n[0].kind in {nkOpenSymChoice, nkClosedSymChoice}: n[0][0].sym.name + else: nil + let nNext = skipHiddenNodes(n[1]) + if nNext.kind == nkPrefix or (opr != nil and renderer.isKeyword(opr)): + put(g, tkSpaces, Space) + if nNext.kind == nkInfix: + put(g, tkParLe, "(") + gsub(g, n[1]) + put(g, tkParRi, ")") + else: + gsub(g, n[1]) + if n.len > 2 and n.lastSon.kind in postExprBlocks: + var i = 2 + while i < n.len and n[i].kind notin postExprBlocks: i.inc + postStatements(g, n, i, fromStmtList) + of nkPostfix: + gsub(g, n, 1) + if renderNoPostfix notin g.flags: + gsub(g, n, 0) + of nkRange: + gsub(g, n, 0) + put(g, tkDotDot, "..") + gsub(g, n, 1) + of nkDerefExpr: + gsub(g, n, 0) + put(g, tkOpr, "[]") + of nkAccQuoted: + put(g, tkAccent, "`") + for i in 0..<n.len: + proc isAlpha(n: PNode): bool = + if n.kind in {nkIdent, nkSym}: + let tmp = n.getPIdent.s + result = tmp.len > 0 and tmp[0] in {'a'..'z', 'A'..'Z'} + else: + result = false + var useSpace = false + if i == 1 and n[0].kind == nkIdent and n[0].ident.s in ["=", "'"]: + if not n[1].isAlpha: # handle `=destroy`, `'big' + useSpace = true + elif i == 1 and n[1].kind == nkIdent and n[1].ident.s == "=": + if not n[0].isAlpha: # handle setters, e.g. `foo=` + useSpace = true + elif i > 0: useSpace = true + if useSpace: put(g, tkSpaces, Space) + gsub(g, n[i]) + put(g, tkAccent, "`") + of nkIfExpr: + putWithSpace(g, tkIf, "if") + if n.len > 0: gcond(g, n[0][0]) + putWithSpace(g, tkColon, ":") + if n.len > 0: gsub(g, n[0], 1) + gsons(g, n, emptyContext, 1) + of nkElifExpr: + putWithSpace(g, tkElif, " elif") + gcond(g, n[0]) + putWithSpace(g, tkColon, ":") + gsub(g, n, 1) + of nkElseExpr: + put(g, tkElse, " else") + putWithSpace(g, tkColon, ":") + gsub(g, n, 0) + of nkTypeOfExpr: + put(g, tkType, "typeof") + put(g, tkParLe, "(") + if n.len > 0: gsub(g, n[0]) + put(g, tkParRi, ")") + of nkRefTy: + if n.len > 0: + putWithSpace(g, tkRef, "ref") + gsub(g, n[0]) + else: + put(g, tkRef, "ref") + of nkPtrTy: + if n.len > 0: + putWithSpace(g, tkPtr, "ptr") + gsub(g, n[0]) + else: + put(g, tkPtr, "ptr") + of nkVarTy: + if n.len > 0: + putWithSpace(g, tkVar, "var") + gsub(g, n[0]) + else: + put(g, tkVar, "var") + of nkOutTy: + if n.len > 0: + putWithSpace(g, tkOut, "out") + gsub(g, n[0]) + else: + put(g, tkOut, "out") + of nkDistinctTy: + if n.len > 0: + putWithSpace(g, tkDistinct, "distinct") + gsub(g, n[0]) + if n.len > 1: + if n[1].kind == nkWith: + putWithSpace(g, tkSymbol, " with") + else: + putWithSpace(g, tkSymbol, " without") + gcomma(g, n[1]) + else: + put(g, tkDistinct, "distinct") + of nkTypeDef: + if n[0].kind == nkPragmaExpr: + # generate pragma after generic + gsub(g, n[0], 0) + gsub(g, n, 1) + gsub(g, n[0], 1) + else: + gsub(g, n, 0) + gsub(g, n, 1) + put(g, tkSpaces, Space) + if n.len > 2 and n[2].kind != nkEmpty: + putWithSpace(g, tkEquals, "=") + gsub(g, n[2]) + of nkObjectTy: + if n.len > 0: + putWithSpace(g, tkObject, "object") + g.inside(ObjectDef): + gsub(g, n[0]) + gsub(g, n[1]) + gcoms(g) + indentNL(g) + gsub(g, n[2]) + dedent(g) + else: + put(g, tkObject, "object") + of nkRecList: + for i in 0..<n.len: + optNL(g) + gsub(g, n[i], c) + gcoms(g) + of nkOfInherit: + putWithSpace(g, tkOf, "of") + gsub(g, n, 0) + of nkProcTy: + if n.len > 0: + putWithSpace(g, tkProc, "proc") + gsub(g, n, 0) + gsub(g, n, 1) + else: + put(g, tkProc, "proc") + of nkIteratorTy: + if n.len > 0: + putWithSpace(g, tkIterator, "iterator") + gsub(g, n, 0) + gsub(g, n, 1) + else: + put(g, tkIterator, "iterator") + of nkStaticTy: + put(g, tkStatic, "static") + put(g, tkBracketLe, "[") + if n.len > 0: + gsub(g, n[0]) + put(g, tkBracketRi, "]") + of nkEnumTy: + if n.len > 0: + putWithSpace(g, tkEnum, "enum") + gsub(g, n[0]) + gcoms(g) + indentNL(g) + gcommaAux(g, n, g.indent, 1) + gcoms(g) # BUGFIX: comment for the last enum field + dedent(g) + else: + put(g, tkEnum, "enum") + of nkEnumFieldDef: + gsub(g, n, 0) + put(g, tkSpaces, Space) + putWithSpace(g, tkEquals, "=") + gsub(g, n, 1) + of nkStmtList, nkStmtListExpr, nkStmtListType: + if n.len == 1 and n[0].kind == nkDiscardStmt: + put(g, tkParLe, "(") + gsub(g, n[0]) + put(g, tkParRi, ")") + else: + gstmts(g, n, emptyContext) + of nkIfStmt: + putWithSpace(g, tkIf, "if") + gif(g, n) + of nkWhen, nkRecWhen: + putWithSpace(g, tkWhen, "when") + gif(g, n) + of nkWhileStmt: gwhile(g, n) + of nkPragmaBlock: gpragmaBlock(g, n) + of nkCaseStmt, nkRecCase: gcase(g, n) + of nkTryStmt, nkHiddenTryStmt: gtry(g, n) + of nkForStmt, nkParForStmt: gfor(g, n) + of nkBlockStmt, nkBlockExpr: gblock(g, n) + of nkStaticStmt: gstaticStmt(g, n) + of nkAsmStmt: gasm(g, n) + of nkProcDef: + if renderNoProcDefs notin g.flags: putWithSpace(g, tkProc, "proc") + gproc(g, n) + of nkFuncDef: + if renderNoProcDefs notin g.flags: putWithSpace(g, tkFunc, "func") + gproc(g, n) + of nkConverterDef: + if renderNoProcDefs notin g.flags: putWithSpace(g, tkConverter, "converter") + gproc(g, n) + of nkMethodDef: + if renderNoProcDefs notin g.flags: putWithSpace(g, tkMethod, "method") + gproc(g, n) + of nkIteratorDef: + if renderNoProcDefs notin g.flags: putWithSpace(g, tkIterator, "iterator") + gproc(g, n) + of nkMacroDef: + if renderNoProcDefs notin g.flags: putWithSpace(g, tkMacro, "macro") + gproc(g, n) + of nkTemplateDef: + if renderNoProcDefs notin g.flags: putWithSpace(g, tkTemplate, "template") + gproc(g, n) + of nkTypeSection: + gsection(g, n, emptyContext, tkType, "type") + of nkConstSection: + a = initContext() + incl(a.flags, rfInConstExpr) + gsection(g, n, a, tkConst, "const") + of nkVarSection, nkLetSection, nkUsingStmt: + if n.len == 0: return + if n.kind == nkVarSection: putWithSpace(g, tkVar, "var") + elif n.kind == nkLetSection: putWithSpace(g, tkLet, "let") + else: putWithSpace(g, tkUsing, "using") + if n.len > 1: + gcoms(g) + indentNL(g) + for i in 0..<n.len: + optNL(g) + gsub(g, n[i]) + gcoms(g) + dedent(g) + else: + gsub(g, n[0]) + of nkReturnStmt: + putWithSpace(g, tkReturn, "return") + if n.len > 0 and n[0].kind == nkAsgn and renderIr notin g.flags: + gsub(g, n[0], 1) + else: + gsub(g, n, 0) + of nkRaiseStmt: + putWithSpace(g, tkRaise, "raise") + gsub(g, n, 0) + of nkYieldStmt: + putWithSpace(g, tkYield, "yield") + gsub(g, n, 0) + of nkDiscardStmt: + putWithSpace(g, tkDiscard, "discard") + gsub(g, n, 0) + of nkBreakStmt: + putWithSpace(g, tkBreak, "break") + gsub(g, n, 0) + of nkContinueStmt: + putWithSpace(g, tkContinue, "continue") + gsub(g, n, 0) + of nkPragma: + if g.inPragma <= 0: + inc g.inPragma + #if not previousNL(g): + put(g, tkSpaces, Space) + put(g, tkCurlyDotLe, "{.") + gcomma(g, n, emptyContext) + put(g, tkCurlyDotRi, ".}") + dec g.inPragma + else: + gcomma(g, n, emptyContext) + of nkImportStmt, nkExportStmt: + if n.kind == nkImportStmt: + putWithSpace(g, tkImport, "import") + else: + putWithSpace(g, tkExport, "export") + gcoms(g) + indentNL(g) + gcommaAux(g, n, g.indent) + dedent(g) + putNL(g) + of nkImportExceptStmt, nkExportExceptStmt: + if n.kind == nkImportExceptStmt: + putWithSpace(g, tkImport, "import") + else: + putWithSpace(g, tkExport, "export") + gsub(g, n, 0) + put(g, tkSpaces, Space) + putWithSpace(g, tkExcept, "except") + gcommaAux(g, n, g.indent, 1) + gcoms(g) + putNL(g) + of nkFromStmt: + putWithSpace(g, tkFrom, "from") + gsub(g, n, 0) + put(g, tkSpaces, Space) + putWithSpace(g, tkImport, "import") + gcomma(g, n, emptyContext, 1) + putNL(g) + of nkIncludeStmt: + putWithSpace(g, tkInclude, "include") + gcoms(g) + indentNL(g) + gcommaAux(g, n, g.indent) + dedent(g) + putNL(g) + of nkCommentStmt: + gcoms(g) + optNL(g) + of nkOfBranch: + optNL(g) + putWithSpace(g, tkOf, "of") + gcomma(g, n, c, 0, - 2) + putWithSpace(g, tkColon, ":") + gcoms(g) + gstmts(g, lastSon(n), c) + of nkImportAs: + gsub(g, n, 0) + put(g, tkSpaces, Space) + putWithSpace(g, tkAs, "as") + gsub(g, n, 1) + of nkBindStmt: + putWithSpace(g, tkBind, "bind") + gcomma(g, n, c) + of nkMixinStmt: + putWithSpace(g, tkMixin, "mixin") + gcomma(g, n, c) + of nkElifBranch: + optNL(g) + putWithSpace(g, tkElif, "elif") + gsub(g, n, 0) + putWithSpace(g, tkColon, ":") + gcoms(g) + gstmts(g, n[1], c) + of nkElse: + optNL(g) + put(g, tkElse, "else") + putWithSpace(g, tkColon, ":") + gcoms(g) + gstmts(g, n[0], c) + of nkFinally, nkDefer: + optNL(g) + if n.kind == nkFinally: + put(g, tkFinally, "finally") + else: + put(g, tkDefer, "defer") + putWithSpace(g, tkColon, ":") + gcoms(g) + gstmts(g, n[0], c) + of nkExceptBranch: + optNL(g) + if n.len != 1: + putWithSpace(g, tkExcept, "except") + else: + put(g, tkExcept, "except") + gcomma(g, n, 0, -2) + putWithSpace(g, tkColon, ":") + gcoms(g) + gstmts(g, lastSon(n), c) + of nkGenericParams: + proc hasExplicitParams(gp: PNode): bool = + for p in gp: + if p.typ == nil or tfImplicitTypeParam notin p.typ.flags: + return true + return false + + if n.hasExplicitParams: + put(g, tkBracketLe, "[") + gsemicolon(g, n) + put(g, tkBracketRi, "]") + of nkFormalParams: + put(g, tkParLe, "(") + gsemicolon(g, n, 1) + put(g, tkParRi, ")") + if n.len > 0 and n[0].kind != nkEmpty: + putWithSpace(g, tkColon, ":") + gsub(g, n[0]) + of nkTupleTy: + put(g, tkTuple, "tuple") + put(g, tkBracketLe, "[") + gcomma(g, n) + put(g, tkBracketRi, "]") + of nkTupleClassTy: + put(g, tkTuple, "tuple") + of nkComesFrom: + put(g, tkParLe, "(ComesFrom|") + gsub(g, n, 0) + put(g, tkParRi, ")") + of nkGotoState: + var c: TContext = initContext() + putWithSpace g, tkSymbol, "goto" + gsons(g, n, c) + of nkState: + var c: TContext = initContext() + putWithSpace g, tkSymbol, "state" + gsub(g, n[0], c) + putWithSpace(g, tkColon, ":") + indentNL(g) + gsons(g, n, c, 1) + dedent(g) + + of nkBreakState: + put(g, tkTuple, "breakstate") + if renderIds in g.flags: + gsons(g, n, c, 0) + of nkTypeClassTy: + gTypeClassTy(g, n) + of nkError: + putWithSpace(g, tkSymbol, "error") + #gcomma(g, n, c) + gsub(g, n[0], c) + else: + #nkNone, nkExplicitTypeListCall: + internalError(g.config, n.info, "renderer.gsub(" & $n.kind & ')') + +proc renderTree*(n: PNode, renderFlags: TRenderFlags = {}): string = + if n == nil: return "<nil tree>" + var g: TSrcGen = initSrcGen(renderFlags, newPartialConfigRef()) + # do not indent the initial statement list so that + # writeFile("file.nim", repr n) + # produces working Nim code: + if n.kind in {nkStmtList, nkStmtListExpr, nkStmtListType}: + gstmts(g, n, emptyContext, doIndent = false) + else: + gsub(g, n) + result = g.buf + +proc `$`*(n: PNode): string = n.renderTree + +proc renderModule*(n: PNode, outfile: string, + renderFlags: TRenderFlags = {}; + fid = FileIndex(-1); + conf: ConfigRef = nil) = + var + f: File = default(File) + g: TSrcGen = initSrcGen(renderFlags, conf) + g.fid = fid + for i in 0..<n.len: + gsub(g, n[i]) + optNL(g) + case n[i].kind + of nkTypeSection, nkConstSection, nkVarSection, nkLetSection, + nkCommentStmt: putNL(g) + else: discard + gcoms(g) + if open(f, outfile, fmWrite): + write(f, g.buf) + close(f) + else: + rawMessage(g.config, errGenerated, "cannot open file: " & outfile) + +proc initTokRender*(n: PNode, renderFlags: TRenderFlags = {}): TSrcGen = + result = initSrcGen(renderFlags, newPartialConfigRef()) + gsub(result, n) + +proc getNextTok*(r: var TSrcGen, kind: var TokType, literal: var string) = + if r.idx < r.tokens.len: + kind = r.tokens[r.idx].kind + let length = r.tokens[r.idx].length.int + literal = substr(r.buf, r.pos, r.pos + length - 1) + inc(r.pos, length) + inc(r.idx) + else: + kind = tkEof + +proc getTokSym*(r: TSrcGen): PSym = + if r.idx > 0 and r.idx <= r.tokens.len: + result = r.tokens[r.idx-1].sym + else: + result = nil |