diff options
Diffstat (limited to 'compiler/jsgen.nim')
-rw-r--r-- | compiler/jsgen.nim | 3212 |
1 files changed, 3212 insertions, 0 deletions
diff --git a/compiler/jsgen.nim b/compiler/jsgen.nim new file mode 100644 index 000000000..713944def --- /dev/null +++ b/compiler/jsgen.nim @@ -0,0 +1,3212 @@ +# +# +# The Nim Compiler +# (c) Copyright 2015 Andreas Rumpf +# +# See the file "copying.txt", included in this +# distribution, for details about the copyright. +# + +# This is the JavaScript code generator. + +discard """ +The JS code generator contains only 2 tricks: + +Trick 1 +------- +Some locations (for example 'var int') require "fat pointers" (`etyBaseIndex`) +which are pairs (array, index). The derefence operation is then 'array[index]'. +Check `mapType` for the details. + +Trick 2 +------- +It is preferable to generate '||' and '&&' if possible since that is more +idiomatic and hence should be friendlier for the JS JIT implementation. However +code like `foo and (let bar = baz())` cannot be translated this way. Instead +the expressions need to be transformed into statements. `isSimpleExpr` +implements the required case distinction. +""" + + +import + ast, trees, magicsys, options, + nversion, msgs, idents, types, + ropes, wordrecg, renderer, + cgmeth, lowerings, sighashes, modulegraphs, lineinfos, + transf, injectdestructors, sourcemap, astmsgs, pushpoppragmas, + mangleutils + +import pipelineutils + +import std/[json, sets, math, tables, intsets] +import std/strutils except addf + +when defined(nimPreviewSlimSystem): + import std/[assertions, syncio] + +import std/formatfloat + +type + TJSGen = object of PPassContext + module: PSym + graph: ModuleGraph + config: ConfigRef + sigConflicts: CountTable[SigHash] + initProc: PProc + + BModule = ref TJSGen + TJSTypeKind = enum # necessary JS "types" + etyNone, # no type + etyNull, # null type + etyProc, # proc type + etyBool, # bool type + etySeq, # Nim seq or string type + etyInt, # JavaScript's int + etyFloat, # JavaScript's float + etyString, # JavaScript's string + etyObject, # JavaScript's reference to an object + etyBaseIndex # base + index needed + TResKind = enum + resNone, # not set + resExpr, # is some complex expression + resVal, # is a temporary/value/l-value + resCallee # expression is callee + TCompRes = object + kind: TResKind + typ: TJSTypeKind + res: Rope # result part; index if this is an + # (address, index)-tuple + address: Rope # address of an (address, index)-tuple + tmpLoc: Rope # tmp var which stores the (address, index) + # pair to prevent multiple evals. + # the tmp is initialized upon evaling the + # address. + # might be nil. + # (see `maybeMakeTemp`) + + TBlock = object + id: int # the ID of the label; positive means that it + # has been used (i.e. the label should be emitted) + isLoop: bool # whether it's a 'block' or 'while' + + PGlobals = ref object of RootObj + typeInfo, constants, code: Rope + forwarded: seq[PSym] + generatedSyms: IntSet + typeInfoGenerated: IntSet + unique: int # for temp identifier generation + inSystem: bool + + PProc = ref TProc + TProc = object + procDef: PNode + prc: PSym + globals, locals, body: Rope + options: TOptions + optionsStack: seq[(TOptions, TNoteKinds)] + module: BModule + g: PGlobals + beforeRetNeeded: bool + unique: int # for temp identifier generation + blocks: seq[TBlock] + extraIndent: int + previousFileName: string # For frameInfo inside templates. + # legacy: generatedParamCopies and up fields are used for jsNoLambdaLifting + generatedParamCopies: IntSet + up: PProc # up the call chain; required for closure support + +template config*(p: PProc): ConfigRef = p.module.config + +proc indentLine(p: PProc, r: Rope): Rope = + var p = p + if jsNoLambdaLifting in p.config.legacyFeatures: + var ind = 0 + while true: + inc ind, p.blocks.len + p.extraIndent + if p.up == nil or p.up.prc != p.prc.owner: + break + p = p.up + result = repeat(' ', ind*2) & r + else: + let ind = p.blocks.len + p.extraIndent + result = repeat(' ', ind*2) & r + +template line(p: PProc, added: string) = + p.body.add(indentLine(p, rope(added))) + +template lineF(p: PProc, frmt: FormatStr, args: varargs[Rope]) = + p.body.add(indentLine(p, ropes.`%`(frmt, args))) + +template nested(p, body) = + inc p.extraIndent + body + dec p.extraIndent + +proc newGlobals(): PGlobals = + result = PGlobals(forwarded: @[], + generatedSyms: initIntSet(), + typeInfoGenerated: initIntSet() + ) + +proc initCompRes(): TCompRes = + result = TCompRes(address: "", res: "", + tmpLoc: "", typ: etyNone, kind: resNone + ) + +proc rdLoc(a: TCompRes): Rope {.inline.} = + if a.typ != etyBaseIndex: + result = a.res + else: + result = "$1[$2]" % [a.address, a.res] + +proc newProc(globals: PGlobals, module: BModule, procDef: PNode, + options: TOptions): PProc = + result = PProc( + blocks: @[], + optionsStack: if module.initProc != nil: module.initProc.optionsStack + else: @[], + options: options, + module: module, + procDef: procDef, + g: globals, + extraIndent: int(procDef != nil)) + if procDef != nil: result.prc = procDef[namePos].sym + +proc initProcOptions(module: BModule): TOptions = + result = module.config.options + if PGlobals(module.graph.backend).inSystem: + result.excl(optStackTrace) + +proc newInitProc(globals: PGlobals, module: BModule): PProc = + result = newProc(globals, module, nil, initProcOptions(module)) + +const + MappedToObject = {tyObject, tyArray, tyTuple, tyOpenArray, + tySet, tyVarargs} + +proc mapType(typ: PType): TJSTypeKind = + let t = skipTypes(typ, abstractInst) + case t.kind + of tyVar, tyRef, tyPtr: + if skipTypes(t.elementType, abstractInst).kind in MappedToObject: + result = etyObject + else: + result = etyBaseIndex + of tyPointer: + # treat a tyPointer like a typed pointer to an array of bytes + result = etyBaseIndex + of tyRange, tyDistinct, tyOrdinal, tyError, tyLent: + # tyLent is no-op as JS has pass-by-reference semantics + result = mapType(skipModifier t) + of tyInt..tyInt64, tyUInt..tyUInt64, tyEnum, tyChar: result = etyInt + of tyBool: result = etyBool + of tyFloat..tyFloat128: result = etyFloat + of tySet: result = etyObject # map a set to a table + of tyString, tySequence: result = etySeq + of tyObject, tyArray, tyTuple, tyOpenArray, tyVarargs, tyUncheckedArray: + result = etyObject + of tyNil: result = etyNull + of tyGenericParam, tyGenericBody, tyGenericInvocation, + tyNone, tyFromExpr, tyForward, tyEmpty, + tyUntyped, tyTyped, tyTypeDesc, tyBuiltInTypeClass, tyCompositeTypeClass, + tyAnd, tyOr, tyNot, tyAnything, tyVoid: + result = etyNone + of tyGenericInst, tyInferred, tyAlias, tyUserTypeClass, tyUserTypeClassInst, + tySink, tyOwned: + result = mapType(typ.skipModifier) + of tyStatic: + if t.n != nil: result = mapType(skipModifier t) + else: result = etyNone + of tyProc: result = etyProc + of tyCstring: result = etyString + of tyConcept, tyIterable: + raiseAssert "unreachable" + +proc mapType(p: PProc; typ: PType): TJSTypeKind = + result = mapType(typ) + +proc mangleName(m: BModule, s: PSym): Rope = + proc validJsName(name: string): bool = + result = true + const reservedWords = ["abstract", "await", "boolean", "break", "byte", + "case", "catch", "char", "class", "const", "continue", "debugger", + "default", "delete", "do", "double", "else", "enum", "export", "extends", + "false", "final", "finally", "float", "for", "function", "goto", "if", + "implements", "import", "in", "instanceof", "int", "interface", "let", + "long", "native", "new", "null", "package", "private", "protected", + "public", "return", "short", "static", "super", "switch", "synchronized", + "this", "throw", "throws", "transient", "true", "try", "typeof", "var", + "void", "volatile", "while", "with", "yield"] + case name + of reservedWords: + return false + else: + discard + if name[0] in {'0'..'9'}: return false + for chr in name: + if chr notin {'A'..'Z','a'..'z','_','$','0'..'9'}: + return false + result = s.loc.snippet + if result == "": + if s.kind == skField and s.name.s.validJsName: + result = rope(s.name.s) + elif s.kind == skTemp: + result = rope(mangle(s.name.s)) + else: + var x = newStringOfCap(s.name.s.len) + var i = 0 + while i < s.name.s.len: + let c = s.name.s[i] + case c + of 'A'..'Z', 'a'..'z', '_', '0'..'9': + x.add c + else: + x.add("HEX" & toHex(ord(c), 2)) + inc i + result = rope(x) + # From ES5 on reserved words can be used as object field names + if s.kind != skField: + if m.config.hcrOn: + # When hot reloading is enabled, we must ensure that the names + # of functions and types will be preserved across rebuilds: + result.add(idOrSig(s, m.module.name.s, m.sigConflicts, m.config)) + elif s.kind == skParam: + result.add mangleParamExt(s) + elif s.kind in routineKinds: + result.add mangleProcNameExt(m.graph, s) + else: + result.add("_") + result.add(rope(s.id)) + s.loc.snippet = result + +proc escapeJSString(s: string): string = + result = newStringOfCap(s.len + s.len shr 2) + result.add("\"") + for c in items(s): + case c + of '\l': result.add("\\n") + of '\r': result.add("\\r") + of '\t': result.add("\\t") + of '\b': result.add("\\b") + of '\a': result.add("\\a") + of '\e': result.add("\\e") + of '\v': result.add("\\v") + of '\\': result.add("\\\\") + of '\"': result.add("\\\"") + else: result.add(c) + result.add("\"") + +proc makeJSString(s: string, escapeNonAscii = true): Rope = + if escapeNonAscii: + result = strutils.escape(s).rope + else: + result = escapeJSString(s).rope + +proc makeJsNimStrLit(s: string): Rope = + var x = newStringOfCap(4*s.len+1) + x.add "[" + var i = 0 + if i < s.len: + x.addInt int64(s[i]) + inc i + while i < s.len: + x.add "," + x.addInt int64(s[i]) + inc i + x.add "]" + result = rope(x) + + +include jstypes + +proc gen(p: PProc, n: PNode, r: var TCompRes) +proc genStmt(p: PProc, n: PNode) +proc genProc(oldProc: PProc, prc: PSym): Rope +proc genConstant(p: PProc, c: PSym) + +proc useMagic(p: PProc, name: string) = + if name.len == 0: return + var s = magicsys.getCompilerProc(p.module.graph, name) + if s != nil: + internalAssert p.config, s.kind in {skProc, skFunc, skMethod, skConverter} + if not p.g.generatedSyms.containsOrIncl(s.id): + let code = genProc(p, s) + p.g.constants.add(code) + else: + if p.prc != nil: + globalError(p.config, p.prc.info, "system module needs: " & name) + else: + rawMessage(p.config, errGenerated, "system module needs: " & name) + +proc isSimpleExpr(p: PProc; n: PNode): bool = + # calls all the way down --> can stay expression based + case n.kind + of nkCallKinds, nkBracketExpr, nkDotExpr, nkPar, nkTupleConstr, + nkObjConstr, nkBracket, nkCurly, + nkDerefExpr, nkHiddenDeref, nkAddr, nkHiddenAddr, + nkConv, nkHiddenStdConv, nkHiddenSubConv: + for c in n: + if not p.isSimpleExpr(c): return false + result = true + of nkStmtListExpr: + for i in 0..<n.len-1: + if n[i].kind notin {nkCommentStmt, nkEmpty}: return false + result = isSimpleExpr(p, n.lastSon) + else: + result = n.isAtom + +proc getTemp(p: PProc, defineInLocals: bool = true): Rope = + inc(p.unique) + result = "Temporary$1" % [rope(p.unique)] + if defineInLocals: + p.locals.add(p.indentLine("var $1;$n" % [result])) + +proc genAnd(p: PProc, a, b: PNode, r: var TCompRes) = + assert r.kind == resNone + var x, y: TCompRes = default(TCompRes) + if p.isSimpleExpr(a) and p.isSimpleExpr(b): + gen(p, a, x) + gen(p, b, y) + r.kind = resExpr + r.res = "($1 && $2)" % [x.rdLoc, y.rdLoc] + else: + r.res = p.getTemp + r.kind = resVal + # while a and b: + # --> + # while true: + # aa + # if not a: tmp = false + # else: + # bb + # tmp = b + # tmp + gen(p, a, x) + lineF(p, "if (!$1) $2 = false; else {", [x.rdLoc, r.rdLoc]) + p.nested: + gen(p, b, y) + lineF(p, "$2 = $1;", [y.rdLoc, r.rdLoc]) + line(p, "}") + +proc genOr(p: PProc, a, b: PNode, r: var TCompRes) = + assert r.kind == resNone + var x, y: TCompRes = default(TCompRes) + if p.isSimpleExpr(a) and p.isSimpleExpr(b): + gen(p, a, x) + gen(p, b, y) + r.kind = resExpr + r.res = "($1 || $2)" % [x.rdLoc, y.rdLoc] + else: + r.res = p.getTemp + r.kind = resVal + gen(p, a, x) + lineF(p, "if ($1) $2 = true; else {", [x.rdLoc, r.rdLoc]) + p.nested: + gen(p, b, y) + lineF(p, "$2 = $1;", [y.rdLoc, r.rdLoc]) + line(p, "}") + +type + TMagicFrmt = array[0..1, string] + TMagicOps = array[mAddI..mStrToStr, TMagicFrmt] + +const # magic checked op; magic unchecked op; + jsMagics: TMagicOps = [ + mAddI: ["addInt", ""], + mSubI: ["subInt", ""], + mMulI: ["mulInt", ""], + mDivI: ["divInt", ""], + mModI: ["modInt", ""], + mSucc: ["addInt", ""], + mPred: ["subInt", ""], + mAddF64: ["", ""], + mSubF64: ["", ""], + mMulF64: ["", ""], + mDivF64: ["", ""], + mShrI: ["", ""], + mShlI: ["", ""], + mAshrI: ["", ""], + mBitandI: ["", ""], + mBitorI: ["", ""], + mBitxorI: ["", ""], + mMinI: ["nimMin", "nimMin"], + mMaxI: ["nimMax", "nimMax"], + mAddU: ["", ""], + mSubU: ["", ""], + mMulU: ["", ""], + mDivU: ["", ""], + mModU: ["", ""], + mEqI: ["", ""], + mLeI: ["", ""], + mLtI: ["", ""], + mEqF64: ["", ""], + mLeF64: ["", ""], + mLtF64: ["", ""], + mLeU: ["", ""], + mLtU: ["", ""], + mEqEnum: ["", ""], + mLeEnum: ["", ""], + mLtEnum: ["", ""], + mEqCh: ["", ""], + mLeCh: ["", ""], + mLtCh: ["", ""], + mEqB: ["", ""], + mLeB: ["", ""], + mLtB: ["", ""], + mEqRef: ["", ""], + mLePtr: ["", ""], + mLtPtr: ["", ""], + mXor: ["", ""], + mEqCString: ["", ""], + mEqProc: ["", ""], + mUnaryMinusI: ["negInt", ""], + mUnaryMinusI64: ["negInt64", ""], + mAbsI: ["absInt", ""], + mNot: ["", ""], + mUnaryPlusI: ["", ""], + mBitnotI: ["", ""], + mUnaryPlusF64: ["", ""], + mUnaryMinusF64: ["", ""], + mCharToStr: ["nimCharToStr", "nimCharToStr"], + mBoolToStr: ["nimBoolToStr", "nimBoolToStr"], + mCStrToStr: ["cstrToNimstr", "cstrToNimstr"], + mStrToStr: ["", ""]] + +proc needsTemp(p: PProc; n: PNode): bool = + # check if n contains a call to determine + # if a temp should be made to prevent multiple evals + result = false + if n.kind in nkCallKinds + {nkTupleConstr, nkObjConstr, nkBracket, nkCurly}: + return true + for c in n: + if needsTemp(p, c): + return true + +proc maybeMakeTemp(p: PProc, n: PNode; x: TCompRes): tuple[a, tmp: Rope] = + var + a = x.rdLoc + b = a + if needsTemp(p, n): + # if we have tmp just use it + if x.tmpLoc != "" and (mapType(n.typ) == etyBaseIndex or n.kind in {nkHiddenDeref, nkDerefExpr}): + b = "$1[0][$1[1]]" % [x.tmpLoc] + (a: a, tmp: b) + else: + let tmp = p.getTemp + b = tmp + a = "($1 = $2, $1)" % [tmp, a] + (a: a, tmp: b) + else: + (a: a, tmp: b) + +proc maybeMakeTempAssignable(p: PProc, n: PNode; x: TCompRes): tuple[a, tmp: Rope] = + var + a = x.rdLoc + b = a + if needsTemp(p, n): + # if we have tmp just use it + if x.tmpLoc != "" and (mapType(n.typ) == etyBaseIndex or n.kind in {nkHiddenDeref, nkDerefExpr}): + b = "$1[0][$1[1]]" % [x.tmpLoc] + result = (a: a, tmp: b) + elif x.tmpLoc != "" and n.kind == nkBracketExpr: + # genArrayAddr + var + address, index: TCompRes = default(TCompRes) + first: Int128 = Zero + gen(p, n[0], address) + gen(p, n[1], index) + let (m1, tmp1) = maybeMakeTemp(p, n[0], address) + let typ = skipTypes(n[0].typ, abstractPtrs) + if typ.kind == tyArray: + first = firstOrd(p.config, typ.indexType) + if optBoundsCheck in p.options: + useMagic(p, "chckIndx") + if first == 0: # save a couple chars + index.res = "chckIndx($1, 0, ($2).length - 1)" % [index.res, tmp1] + else: + index.res = "chckIndx($1, $2, ($3).length + ($2) - 1) - ($2)" % [ + index.res, rope(first), tmp1] + elif first != 0: + index.res = "($1) - ($2)" % [index.res, rope(first)] + else: + discard # index.res = index.res + let (n1, tmp2) = maybeMakeTemp(p, n[1], index) + result = (a: "$1[$2]" % [m1, n1], tmp: "$1[$2]" % [tmp1, tmp2]) + # could also put here: nkDotExpr -> genFieldAccess, nkCheckedFieldExpr -> genCheckedFieldOp + # but the uses of maybeMakeTempAssignable don't need them + else: + result = (a: a, tmp: b) + else: + result = (a: a, tmp: b) + +template binaryExpr(p: PProc, n: PNode, r: var TCompRes, magic, frmt: string, + reassign = false) = + # $1 and $2 in the `frmt` string bind to lhs and rhs of the expr, + # if $3 or $4 are present they will be substituted with temps for + # lhs and rhs respectively + var x, y: TCompRes = default(TCompRes) + useMagic(p, magic) + gen(p, n[1], x) + gen(p, n[2], y) + + var + a, tmp = x.rdLoc + b, tmp2 = y.rdLoc + when reassign: + (a, tmp) = maybeMakeTempAssignable(p, n[1], x) + else: + when "$3" in frmt: (a, tmp) = maybeMakeTemp(p, n[1], x) + when "$4" in frmt: (b, tmp2) = maybeMakeTemp(p, n[2], y) + + r.res = frmt % [a, b, tmp, tmp2] + r.kind = resExpr + +proc unsignedTrimmer(size: BiggestInt): string = + case size + of 1: "& 0xff" + of 2: "& 0xffff" + of 4: ">>> 0" + else: "" + +proc signedTrimmer(size: BiggestInt): string = + # sign extension is done by shifting to the left and then back to the right + "<< $1 >> $1" % [$(32 - size * 8)] + +proc binaryUintExpr(p: PProc, n: PNode, r: var TCompRes, op: string, + reassign: static[bool] = false) = + var x, y: TCompRes = default(TCompRes) + gen(p, n[1], x) + gen(p, n[2], y) + let size = n[1].typ.skipTypes(abstractRange).size + when reassign: + let (a, tmp) = maybeMakeTempAssignable(p, n[1], x) + if size == 8 and optJsBigInt64 in p.config.globalOptions: + r.res = "$1 = BigInt.asUintN(64, ($4 $2 $3))" % [a, rope op, y.rdLoc, tmp] + else: + let trimmer = unsignedTrimmer(size) + r.res = "$1 = (($5 $2 $3) $4)" % [a, rope op, y.rdLoc, trimmer, tmp] + else: + if size == 8 and optJsBigInt64 in p.config.globalOptions: + r.res = "BigInt.asUintN(64, ($1 $2 $3))" % [x.rdLoc, rope op, y.rdLoc] + else: + let trimmer = unsignedTrimmer(size) + r.res = "(($1 $2 $3) $4)" % [x.rdLoc, rope op, y.rdLoc, trimmer] + r.kind = resExpr + +template ternaryExpr(p: PProc, n: PNode, r: var TCompRes, magic, frmt: string) = + var x, y, z: TCompRes + useMagic(p, magic) + gen(p, n[1], x) + gen(p, n[2], y) + gen(p, n[3], z) + r.res = frmt % [x.rdLoc, y.rdLoc, z.rdLoc] + r.kind = resExpr + +template unaryExpr(p: PProc, n: PNode, r: var TCompRes, magic, frmt: string) = + # $1 binds to n[1], if $2 is present it will be substituted to a tmp of $1 + useMagic(p, magic) + gen(p, n[1], r) + var a, tmp = r.rdLoc + if "$2" in frmt: (a, tmp) = maybeMakeTemp(p, n[1], r) + r.res = frmt % [a, tmp] + r.kind = resExpr + +proc genBreakState(p: PProc, n: PNode, r: var TCompRes) = + var a: TCompRes = default(TCompRes) + # mangle `:state` properly somehow + if n.kind == nkClosure: + gen(p, n[1], a) + r.res = "(($1).HEX3Astate < 0)" % [rdLoc(a)] + else: + gen(p, n, a) + r.res = "((($1.ClE_0).HEX3Astate) < 0)" % [rdLoc(a)] + r.kind = resExpr + +proc arithAux(p: PProc, n: PNode, r: var TCompRes, op: TMagic) = + var + x, y: TCompRes = default(TCompRes) + xLoc, yLoc: Rope = "" + let i = ord(optOverflowCheck notin p.options) + useMagic(p, jsMagics[op][i]) + if n.len > 2: + gen(p, n[1], x) + gen(p, n[2], y) + xLoc = x.rdLoc + yLoc = y.rdLoc + else: + gen(p, n[1], r) + xLoc = r.rdLoc + + template applyFormat(frmt) = + r.res = frmt % [xLoc, yLoc] + template applyFormat(frmtA, frmtB) = + if i == 0: applyFormat(frmtA) else: applyFormat(frmtB) + + template bitwiseExpr(op: string) = + let typ = n[1].typ.skipTypes(abstractVarRange) + if typ.kind in {tyUInt, tyUInt32}: + r.res = "(($1 $2 $3) >>> 0)" % [xLoc, op, yLoc] + else: + r.res = "($1 $2 $3)" % [xLoc, op, yLoc] + + case op + of mAddI: + if i == 0: + if n[1].typ.size == 8 and optJsBigInt64 in p.config.globalOptions: + useMagic(p, "addInt64") + applyFormat("addInt64($1, $2)") + else: + applyFormat("addInt($1, $2)") + else: + applyFormat("($1 + $2)") + of mSubI: + if i == 0: + if n[1].typ.size == 8 and optJsBigInt64 in p.config.globalOptions: + useMagic(p, "subInt64") + applyFormat("subInt64($1, $2)") + else: + applyFormat("subInt($1, $2)") + else: + applyFormat("($1 - $2)") + of mMulI: + if i == 0: + if n[1].typ.size == 8 and optJsBigInt64 in p.config.globalOptions: + useMagic(p, "mulInt64") + applyFormat("mulInt64($1, $2)") + else: + applyFormat("mulInt($1, $2)") + else: + applyFormat("($1 * $2)") + of mDivI: + if n[1].typ.size == 8 and optJsBigInt64 in p.config.globalOptions: + useMagic(p, "divInt64") + applyFormat("divInt64($1, $2)", "$1 / $2") + else: + applyFormat("divInt($1, $2)", "Math.trunc($1 / $2)") + of mModI: + if n[1].typ.size == 8 and optJsBigInt64 in p.config.globalOptions: + useMagic(p, "modInt64") + applyFormat("modInt64($1, $2)", "$1 % $2") + else: + applyFormat("modInt($1, $2)", "Math.trunc($1 % $2)") + of mSucc: + let typ = n[1].typ.skipTypes(abstractVarRange) + case typ.kind + of tyUInt..tyUInt32: + binaryUintExpr(p, n, r, "+") + of tyUInt64: + if optJsBigInt64 in p.config.globalOptions: + applyFormat("BigInt.asUintN(64, $1 + BigInt($2))") + else: binaryUintExpr(p, n, r, "+") + elif typ.kind == tyInt64 and optJsBigInt64 in p.config.globalOptions: + if optOverflowCheck notin p.options: + applyFormat("BigInt.asIntN(64, $1 + BigInt($2))") + else: binaryExpr(p, n, r, "addInt64", "addInt64($1, BigInt($2))") + else: + if optOverflowCheck notin p.options: applyFormat("$1 + $2") + else: binaryExpr(p, n, r, "addInt", "addInt($1, $2)") + of mPred: + let typ = n[1].typ.skipTypes(abstractVarRange) + case typ.kind + of tyUInt..tyUInt32: + binaryUintExpr(p, n, r, "-") + of tyUInt64: + if optJsBigInt64 in p.config.globalOptions: + applyFormat("BigInt.asUintN(64, $1 - BigInt($2))") + else: binaryUintExpr(p, n, r, "-") + elif typ.kind == tyInt64 and optJsBigInt64 in p.config.globalOptions: + if optOverflowCheck notin p.options: + applyFormat("BigInt.asIntN(64, $1 - BigInt($2))") + else: binaryExpr(p, n, r, "subInt64", "subInt64($1, BigInt($2))") + else: + if optOverflowCheck notin p.options: applyFormat("$1 - $2") + else: binaryExpr(p, n, r, "subInt", "subInt($1, $2)") + of mAddF64: applyFormat("($1 + $2)", "($1 + $2)") + of mSubF64: applyFormat("($1 - $2)", "($1 - $2)") + of mMulF64: applyFormat("($1 * $2)", "($1 * $2)") + of mDivF64: applyFormat("($1 / $2)", "($1 / $2)") + of mShrI: + let typ = n[1].typ.skipTypes(abstractVarRange) + if typ.kind == tyInt64 and optJsBigInt64 in p.config.globalOptions: + applyFormat("BigInt.asIntN(64, BigInt.asUintN(64, $1) >> BigInt($2))") + elif typ.kind == tyUInt64 and optJsBigInt64 in p.config.globalOptions: + applyFormat("($1 >> BigInt($2))") + else: + if typ.kind in {tyInt..tyInt32}: + let trimmerU = unsignedTrimmer(typ.size) + let trimmerS = signedTrimmer(typ.size) + r.res = "((($1 $2) >>> $3) $4)" % [xLoc, trimmerU, yLoc, trimmerS] + else: + applyFormat("($1 >>> $2)") + of mShlI: + let typ = n[1].typ.skipTypes(abstractVarRange) + if typ.size == 8: + if typ.kind == tyInt64 and optJsBigInt64 in p.config.globalOptions: + applyFormat("BigInt.asIntN(64, $1 << BigInt($2))") + elif typ.kind == tyUInt64 and optJsBigInt64 in p.config.globalOptions: + applyFormat("BigInt.asUintN(64, $1 << BigInt($2))") + else: + applyFormat("($1 * Math.pow(2, $2))") + else: + if typ.kind in {tyUInt..tyUInt32}: + let trimmer = unsignedTrimmer(typ.size) + r.res = "(($1 << $2) $3)" % [xLoc, yLoc, trimmer] + else: + let trimmer = signedTrimmer(typ.size) + r.res = "(($1 << $2) $3)" % [xLoc, yLoc, trimmer] + of mAshrI: + let typ = n[1].typ.skipTypes(abstractVarRange) + if typ.size == 8: + if optJsBigInt64 in p.config.globalOptions: + applyFormat("($1 >> BigInt($2))") + else: + applyFormat("Math.floor($1 / Math.pow(2, $2))") + else: + if typ.kind in {tyUInt..tyUInt32}: + applyFormat("($1 >>> $2)") + else: + applyFormat("($1 >> $2)") + of mBitandI: bitwiseExpr("&") + of mBitorI: bitwiseExpr("|") + of mBitxorI: bitwiseExpr("^") + of mMinI: applyFormat("nimMin($1, $2)", "nimMin($1, $2)") + of mMaxI: applyFormat("nimMax($1, $2)", "nimMax($1, $2)") + of mAddU: applyFormat("", "") + of mSubU: applyFormat("", "") + of mMulU: applyFormat("", "") + of mDivU: applyFormat("", "") + of mModU: applyFormat("($1 % $2)", "($1 % $2)") + of mEqI: applyFormat("($1 == $2)", "($1 == $2)") + of mLeI: applyFormat("($1 <= $2)", "($1 <= $2)") + of mLtI: applyFormat("($1 < $2)", "($1 < $2)") + of mEqF64: applyFormat("($1 == $2)", "($1 == $2)") + of mLeF64: applyFormat("($1 <= $2)", "($1 <= $2)") + of mLtF64: applyFormat("($1 < $2)", "($1 < $2)") + of mLeU: applyFormat("($1 <= $2)", "($1 <= $2)") + of mLtU: applyFormat("($1 < $2)", "($1 < $2)") + of mEqEnum: applyFormat("($1 == $2)", "($1 == $2)") + of mLeEnum: applyFormat("($1 <= $2)", "($1 <= $2)") + of mLtEnum: applyFormat("($1 < $2)", "($1 < $2)") + of mEqCh: applyFormat("($1 == $2)", "($1 == $2)") + of mLeCh: applyFormat("($1 <= $2)", "($1 <= $2)") + of mLtCh: applyFormat("($1 < $2)", "($1 < $2)") + of mEqB: applyFormat("($1 == $2)", "($1 == $2)") + of mLeB: applyFormat("($1 <= $2)", "($1 <= $2)") + of mLtB: applyFormat("($1 < $2)", "($1 < $2)") + of mEqRef: applyFormat("($1 == $2)", "($1 == $2)") + of mLePtr: applyFormat("($1 <= $2)", "($1 <= $2)") + of mLtPtr: applyFormat("($1 < $2)", "($1 < $2)") + of mXor: applyFormat("($1 != $2)", "($1 != $2)") + of mEqCString: applyFormat("($1 == $2)", "($1 == $2)") + of mEqProc: applyFormat("($1 == $2)", "($1 == $2)") + of mUnaryMinusI: applyFormat("negInt($1)", "-($1)") + of mUnaryMinusI64: applyFormat("negInt64($1)", "-($1)") + of mAbsI: + let typ = n[1].typ.skipTypes(abstractVarRange) + if typ.kind == tyInt64 and optJsBigInt64 in p.config.globalOptions: + useMagic(p, "absInt64") + applyFormat("absInt64($1)", "absInt64($1)") + else: + applyFormat("absInt($1)", "Math.abs($1)") + of mNot: applyFormat("!($1)", "!($1)") + of mUnaryPlusI: applyFormat("+($1)", "+($1)") + of mBitnotI: + let typ = n[1].typ.skipTypes(abstractVarRange) + if typ.kind in {tyUInt..tyUInt64}: + if typ.size == 8 and optJsBigInt64 in p.config.globalOptions: + applyFormat("BigInt.asUintN(64, ~($1))") + else: + let trimmer = unsignedTrimmer(typ.size) + r.res = "(~($1) $2)" % [xLoc, trimmer] + else: + applyFormat("~($1)") + of mUnaryPlusF64: applyFormat("+($1)", "+($1)") + of mUnaryMinusF64: applyFormat("-($1)", "-($1)") + of mCharToStr: applyFormat("nimCharToStr($1)", "nimCharToStr($1)") + of mBoolToStr: applyFormat("nimBoolToStr($1)", "nimBoolToStr($1)") + of mCStrToStr: applyFormat("cstrToNimstr($1)", "cstrToNimstr($1)") + of mStrToStr, mUnown, mIsolate, mFinished: applyFormat("$1", "$1") + else: + assert false, $op + +proc arith(p: PProc, n: PNode, r: var TCompRes, op: TMagic) = + case op + of mAddU: binaryUintExpr(p, n, r, "+") + of mSubU: binaryUintExpr(p, n, r, "-") + of mMulU: binaryUintExpr(p, n, r, "*") + of mDivU: + binaryUintExpr(p, n, r, "/") + if optJsBigInt64 notin p.config.globalOptions and + n[1].typ.skipTypes(abstractRange).size == 8: + # bigint / already truncates + r.res = "Math.trunc($1)" % [r.res] + of mDivI: + arithAux(p, n, r, op) + of mModI: + arithAux(p, n, r, op) + of mCharToStr, mBoolToStr, mCStrToStr, mStrToStr, mEnumToStr: + arithAux(p, n, r, op) + of mEqRef: + if mapType(n[1].typ) != etyBaseIndex: + arithAux(p, n, r, op) + else: + var x, y: TCompRes = default(TCompRes) + gen(p, n[1], x) + gen(p, n[2], y) + r.res = "($# == $# && $# == $#)" % [x.address, y.address, x.res, y.res] + of mEqProc: + if skipTypes(n[1].typ, abstractInst).callConv == ccClosure: + binaryExpr(p, n, r, "cmpClosures", "cmpClosures($1, $2)") + else: + arithAux(p, n, r, op) + else: + arithAux(p, n, r, op) + r.kind = resExpr + +proc hasFrameInfo(p: PProc): bool = + ({optLineTrace, optStackTrace} * p.options == {optLineTrace, optStackTrace}) and + ((p.prc == nil) or not (sfPure in p.prc.flags)) + +proc lineDir(config: ConfigRef, info: TLineInfo, line: int): Rope = + "/* line $2:$3 \"$1\" */$n" % [ + rope(toFullPath(config, info)), rope(line), rope(info.toColumn) + ] + +proc genLineDir(p: PProc, n: PNode) = + let line = toLinenumber(n.info) + if line < 0: + return + if optEmbedOrigSrc in p.config.globalOptions: + lineF(p, "//$1$n", [sourceLine(p.config, n.info)]) + if optLineDir in p.options or optLineDir in p.config.options: + lineF(p, "$1", [lineDir(p.config, n.info, line)]) + if hasFrameInfo(p): + lineF(p, "F.line = $1;$n", [rope(line)]) + let currentFileName = toFilename(p.config, n.info) + if p.previousFileName != currentFileName: + lineF(p, "F.filename = $1;$n", [makeJSString(currentFileName)]) + p.previousFileName = currentFileName + +proc genWhileStmt(p: PProc, n: PNode) = + var cond: TCompRes = default(TCompRes) + internalAssert p.config, isEmptyType(n.typ) + genLineDir(p, n) + inc(p.unique) + setLen(p.blocks, p.blocks.len + 1) + p.blocks[^1].id = -p.unique + p.blocks[^1].isLoop = true + let labl = p.unique.rope + lineF(p, "Label$1: while (true) {$n", [labl]) + p.nested: gen(p, n[0], cond) + lineF(p, "if (!$1) break Label$2;$n", + [cond.res, labl]) + p.nested: genStmt(p, n[1]) + lineF(p, "}$n", [labl]) + setLen(p.blocks, p.blocks.len - 1) + +proc moveInto(p: PProc, src: var TCompRes, dest: TCompRes) = + if src.kind != resNone: + if dest.kind != resNone: + lineF(p, "$1 = $2;$n", [dest.rdLoc, src.rdLoc]) + else: + lineF(p, "$1;$n", [src.rdLoc]) + src.kind = resNone + src.res = "" + +proc genTry(p: PProc, n: PNode, r: var TCompRes) = + # code to generate: + # + # ++excHandler; + # var tmpFramePtr = framePtr; + # try { + # stmts; + # --excHandler; + # } catch (EXCEPTION) { + # var prevJSError = lastJSError; lastJSError = EXCEPTION; + # framePtr = tmpFramePtr; + # --excHandler; + # if (e.typ && e.typ == NTI433 || e.typ == NTI2321) { + # stmts; + # } else if (e.typ && e.typ == NTI32342) { + # stmts; + # } else { + # stmts; + # } + # lastJSError = prevJSError; + # } finally { + # framePtr = tmpFramePtr; + # stmts; + # } + genLineDir(p, n) + if not isEmptyType(n.typ): + r.kind = resVal + r.res = getTemp(p) + inc(p.unique) + var i = 1 + var catchBranchesExist = n.len > 1 and n[i].kind == nkExceptBranch + if catchBranchesExist: + p.body.add("++excHandler;\L") + var tmpFramePtr = rope"F" + lineF(p, "try {$n", []) + var a: TCompRes = default(TCompRes) + gen(p, n[0], a) + moveInto(p, a, r) + var generalCatchBranchExists = false + if catchBranchesExist: + p.body.addf("--excHandler;$n} catch (EXCEPTION) {$n var prevJSError = lastJSError;$n" & + " lastJSError = EXCEPTION;$n --excHandler;$n", []) + if hasFrameInfo(p): + line(p, "framePtr = $1;$n" % [tmpFramePtr]) + while i < n.len and n[i].kind == nkExceptBranch: + if n[i].len == 1: + # general except section: + generalCatchBranchExists = true + if i > 1: lineF(p, "else {$n", []) + gen(p, n[i][0], a) + moveInto(p, a, r) + if i > 1: lineF(p, "}$n", []) + else: + var orExpr: Rope = "" + var excAlias: PNode = nil + + useMagic(p, "isObj") + for j in 0..<n[i].len - 1: + var throwObj: PNode + let it = n[i][j] + + if it.isInfixAs(): + throwObj = it[1] + excAlias = it[2] + # If this is a ``except exc as sym`` branch there must be no following + # nodes + doAssert orExpr == "" + elif it.kind == nkType: + throwObj = it + else: + throwObj = nil + internalError(p.config, n.info, "genTryStmt") + + if orExpr != "": orExpr.add("||") + # Generate the correct type checking code depending on whether this is a + # NIM-native or a JS-native exception + # if isJsObject(throwObj.typ): + if isImportedException(throwObj.typ, p.config): + orExpr.addf("lastJSError instanceof $1", + [throwObj.typ.sym.loc.snippet]) + else: + orExpr.addf("isObj(lastJSError.m_type, $1)", + [genTypeInfo(p, throwObj.typ)]) + + if i > 1: line(p, "else ") + lineF(p, "if (lastJSError && ($1)) {$n", [orExpr]) + # If some branch requires a local alias introduce it here. This is needed + # since JS cannot do ``catch x as y``. + if excAlias != nil: + excAlias.sym.loc.snippet = mangleName(p.module, excAlias.sym) + lineF(p, "var $1 = lastJSError;$n", excAlias.sym.loc.snippet) + gen(p, n[i][^1], a) + moveInto(p, a, r) + lineF(p, "}$n", []) + inc(i) + if catchBranchesExist: + if not generalCatchBranchExists: + useMagic(p, "reraiseException") + line(p, "else {\L") + line(p, "\treraiseException();\L") + line(p, "}\L") + lineF(p, "lastJSError = prevJSError;$n") + line(p, "} finally {\L") + if hasFrameInfo(p): + line(p, "framePtr = $1;$n" % [tmpFramePtr]) + if i < n.len and n[i].kind == nkFinally: + genStmt(p, n[i][0]) + line(p, "}\L") + +proc genRaiseStmt(p: PProc, n: PNode) = + if n[0].kind != nkEmpty: + var a: TCompRes = default(TCompRes) + gen(p, n[0], a) + let typ = skipTypes(n[0].typ, abstractPtrs) + genLineDir(p, n) + useMagic(p, "raiseException") + lineF(p, "raiseException($1, $2);$n", + [a.rdLoc, makeJSString(typ.sym.name.s)]) + else: + genLineDir(p, n) + useMagic(p, "reraiseException") + line(p, "reraiseException();\L") + +proc genCaseJS(p: PProc, n: PNode, r: var TCompRes) = + var + a, b, cond, stmt: TCompRes = default(TCompRes) + genLineDir(p, n) + gen(p, n[0], cond) + let typeKind = skipTypes(n[0].typ, abstractVar+{tyRange}).kind + var transferRange = false + let anyString = typeKind in {tyString, tyCstring} + case typeKind + of tyString: + useMagic(p, "toJSStr") + lineF(p, "switch (toJSStr($1)) {$n", [cond.rdLoc]) + of tyFloat..tyFloat128, tyInt..tyInt64, tyUInt..tyUInt64: + transferRange = true + else: + lineF(p, "switch ($1) {$n", [cond.rdLoc]) + if not isEmptyType(n.typ): + r.kind = resVal + r.res = getTemp(p) + for i in 1..<n.len: + let it = n[i] + let itLen = it.len + case it.kind + of nkOfBranch: + if transferRange: + if i == 1: + lineF(p, "if (", []) + else: + lineF(p, "else if (", []) + for j in 0..<itLen - 1: + let e = it[j] + if e.kind == nkRange: + if transferRange: + gen(p, e[0], a) + gen(p, e[1], b) + if j != itLen - 2: + lineF(p, "$1 >= $2 && $1 <= $3 || $n", [cond.rdLoc, a.rdLoc, b.rdLoc]) + else: + lineF(p, "$1 >= $2 && $1 <= $3", [cond.rdLoc, a.rdLoc, b.rdLoc]) + else: + var v = copyNode(e[0]) + while v.intVal <= e[1].intVal: + gen(p, v, cond) + lineF(p, "case $1:$n", [cond.rdLoc]) + inc(v.intVal) + else: + if anyString: + case e.kind + of nkStrLit..nkTripleStrLit: lineF(p, "case $1:$n", + [makeJSString(e.strVal, false)]) + of nkNilLit: lineF(p, "case null:$n", []) + else: internalError(p.config, e.info, "jsgen.genCaseStmt: 2") + else: + if transferRange: + gen(p, e, a) + if j != itLen - 2: + lineF(p, "$1 == $2 || $n", [cond.rdLoc, a.rdLoc]) + else: + lineF(p, "$1 == $2", [cond.rdLoc, a.rdLoc]) + else: + gen(p, e, a) + lineF(p, "case $1:$n", [a.rdLoc]) + if transferRange: + lineF(p, "){", []) + p.nested: + gen(p, lastSon(it), stmt) + moveInto(p, stmt, r) + if transferRange: + lineF(p, "}$n", []) + else: + lineF(p, "break;$n", []) + of nkElse: + if transferRange: + if n.len == 2: # a dangling else for a case statement + transferRange = false + lineF(p, "switch ($1) {$n", [cond.rdLoc]) + lineF(p, "default: $n", []) + else: + lineF(p, "else{$n", []) + else: + lineF(p, "default: $n", []) + p.nested: + gen(p, it[0], stmt) + moveInto(p, stmt, r) + if transferRange: + lineF(p, "}$n", []) + else: + lineF(p, "break;$n", []) + else: internalError(p.config, it.info, "jsgen.genCaseStmt") + if not transferRange: + lineF(p, "}$n", []) + +proc genBlock(p: PProc, n: PNode, r: var TCompRes) = + inc(p.unique) + let idx = p.blocks.len + if n[0].kind != nkEmpty: + # named block? + if (n[0].kind != nkSym): internalError(p.config, n.info, "genBlock") + var sym = n[0].sym + sym.loc.k = locOther + sym.position = idx+1 + let labl = p.unique + lineF(p, "Label$1: {$n", [labl.rope]) + setLen(p.blocks, idx + 1) + p.blocks[idx].id = - p.unique # negative because it isn't used yet + gen(p, n[1], r) + setLen(p.blocks, idx) + lineF(p, "};$n", [labl.rope]) + +proc genBreakStmt(p: PProc, n: PNode) = + var idx: int + genLineDir(p, n) + if n[0].kind != nkEmpty: + # named break? + assert(n[0].kind == nkSym) + let sym = n[0].sym + assert(sym.loc.k == locOther) + idx = sym.position-1 + else: + # an unnamed 'break' can only break a loop after 'transf' pass: + idx = p.blocks.len - 1 + while idx >= 0 and not p.blocks[idx].isLoop: dec idx + if idx < 0 or not p.blocks[idx].isLoop: + internalError(p.config, n.info, "no loop to break") + p.blocks[idx].id = abs(p.blocks[idx].id) # label is used + lineF(p, "break Label$1;$n", [rope(p.blocks[idx].id)]) + +proc genAsmOrEmitStmt(p: PProc, n: PNode; isAsmStmt = false) = + genLineDir(p, n) + p.body.add p.indentLine("") + let offset = + if isAsmStmt: 1 # first son is pragmas + else: 0 + + for i in offset..<n.len: + let it = n[i] + case it.kind + of nkStrLit..nkTripleStrLit: + p.body.add(it.strVal) + of nkSym: + let v = it.sym + # for backwards compatibility we don't deref syms here :-( + if false: + discard + else: + var r = default(TCompRes) + gen(p, it, r) + + if it.typ.kind == tyPointer: + # A fat pointer is disguised as an array + r.res = r.address + r.address = "" + r.typ = etyNone + elif r.typ == etyBaseIndex: + # Deference first + r.res = "$1[$2]" % [r.address, r.res] + r.address = "" + r.typ = etyNone + + p.body.add(r.rdLoc) + else: + var r: TCompRes = default(TCompRes) + gen(p, it, r) + p.body.add(r.rdLoc) + p.body.add "\L" + +proc genIf(p: PProc, n: PNode, r: var TCompRes) = + var cond, stmt: TCompRes = default(TCompRes) + var toClose = 0 + if not isEmptyType(n.typ): + r.kind = resVal + r.res = getTemp(p) + for i in 0..<n.len: + let it = n[i] + if it.len != 1: + if i > 0: + lineF(p, "else {$n", []) + inc(toClose) + p.nested: gen(p, it[0], cond) + lineF(p, "if ($1) {$n", [cond.rdLoc]) + gen(p, it[1], stmt) + else: + # else part: + lineF(p, "else {$n", []) + p.nested: gen(p, it[0], stmt) + moveInto(p, stmt, r) + lineF(p, "}$n", []) + line(p, repeat('}', toClose) & "\L") + +proc generateHeader(p: PProc, prc: PSym): Rope = + result = "" + let typ = prc.typ + if jsNoLambdaLifting notin p.config.legacyFeatures: + if typ.callConv == ccClosure: + # we treat Env as the `this` parameter of the function + # to keep it simple + let env = prc.ast[paramsPos].lastSon + assert env.kind == nkSym, "env is missing" + env.sym.loc.snippet = "this" + + for i in 1..<typ.n.len: + assert(typ.n[i].kind == nkSym) + var param = typ.n[i].sym + if isCompileTimeOnly(param.typ): continue + if result != "": result.add(", ") + var name = mangleName(p.module, param) + result.add(name) + if mapType(param.typ) == etyBaseIndex: + result.add(", ") + result.add(name) + result.add("_Idx") + +proc countJsParams(typ: PType): int = + result = 0 + for i in 1..<typ.n.len: + assert(typ.n[i].kind == nkSym) + var param = typ.n[i].sym + if isCompileTimeOnly(param.typ): continue + if mapType(param.typ) == etyBaseIndex: + inc result, 2 + else: + inc result + +const + nodeKindsNeedNoCopy = {nkCharLit..nkInt64Lit, nkStrLit..nkTripleStrLit, + nkFloatLit..nkFloat64Lit, nkPar, nkStringToCString, + nkObjConstr, nkTupleConstr, nkBracket, + nkCStringToString, nkCall, nkPrefix, nkPostfix, nkInfix, + nkCommand, nkHiddenCallConv, nkCallStrLit} + +proc needsNoCopy(p: PProc; y: PNode): bool = + return y.kind in nodeKindsNeedNoCopy or + ((mapType(y.typ) != etyBaseIndex or + (jsNoLambdaLifting in p.config.legacyFeatures and y.kind == nkSym and y.sym.kind == skParam)) and + (skipTypes(y.typ, abstractInst).kind in + {tyRef, tyPtr, tyLent, tyVar, tyCstring, tyProc, tyOwned, tyOpenArray} + IntegralTypes)) + +proc genAsgnAux(p: PProc, x, y: PNode, noCopyNeeded: bool) = + var a, b: TCompRes = default(TCompRes) + var xtyp = mapType(p, x.typ) + + # disable `[]=` for cstring + if x.kind == nkBracketExpr and x.len >= 2 and x[0].typ.skipTypes(abstractInst).kind == tyCstring: + localError(p.config, x.info, "cstring doesn't support `[]=` operator") + + gen(p, x, a) + genLineDir(p, y) + gen(p, y, b) + + # we don't care if it's an etyBaseIndex (global) of a string, it's + # still a string that needs to be copied properly: + if x.typ.skipTypes(abstractInst).kind in {tySequence, tyString}: + xtyp = etySeq + case xtyp + of etySeq: + if x.typ.kind in {tyVar, tyLent} or (needsNoCopy(p, y) and needsNoCopy(p, x)) or noCopyNeeded: + lineF(p, "$1 = $2;$n", [a.rdLoc, b.rdLoc]) + else: + useMagic(p, "nimCopy") + lineF(p, "$1 = nimCopy(null, $2, $3);$n", + [a.rdLoc, b.res, genTypeInfo(p, y.typ)]) + of etyObject: + if x.typ.kind in {tyVar, tyLent, tyOpenArray, tyVarargs} or (needsNoCopy(p, y) and needsNoCopy(p, x)) or noCopyNeeded: + lineF(p, "$1 = $2;$n", [a.rdLoc, b.rdLoc]) + else: + useMagic(p, "nimCopy") + # supports proc getF(): var T + if x.kind in {nkHiddenDeref, nkDerefExpr} and x[0].kind in nkCallKinds: + lineF(p, "nimCopy($1, $2, $3);$n", + [a.res, b.res, genTypeInfo(p, x.typ)]) + else: + lineF(p, "$1 = nimCopy($1, $2, $3);$n", + [a.res, b.res, genTypeInfo(p, x.typ)]) + of etyBaseIndex: + if a.typ != etyBaseIndex or b.typ != etyBaseIndex: + if y.kind == nkCall: + let tmp = p.getTemp(false) + lineF(p, "var $1 = $4; $2 = $1[0]; $3 = $1[1];$n", [tmp, a.address, a.res, b.rdLoc]) + elif b.typ == etyBaseIndex: + lineF(p, "$# = [$#, $#];$n", [a.res, b.address, b.res]) + elif b.typ == etyNone: + internalAssert p.config, b.address == "" + lineF(p, "$# = [$#, 0];$n", [a.address, b.res]) + elif x.typ.kind == tyVar and y.typ.kind == tyPtr: + lineF(p, "$# = [$#, $#];$n", [a.res, b.address, b.res]) + lineF(p, "$1 = $2;$n", [a.address, b.res]) + lineF(p, "$1 = $2;$n", [a.rdLoc, b.rdLoc]) + elif a.typ == etyBaseIndex: + # array indexing may not map to var type + if b.address != "": + lineF(p, "$1 = $2; $3 = $4;$n", [a.address, b.address, a.res, b.res]) + else: + lineF(p, "$1 = $2;$n", [a.address, b.res]) + else: + internalError(p.config, x.info, $("genAsgn", b.typ, a.typ)) + elif b.address != "": + lineF(p, "$1 = $2; $3 = $4;$n", [a.address, b.address, a.res, b.res]) + else: + lineF(p, "$1 = $2;$n", [a.address, b.res]) + else: + lineF(p, "$1 = $2;$n", [a.rdLoc, b.rdLoc]) + +proc genAsgn(p: PProc, n: PNode) = + genAsgnAux(p, n[0], n[1], noCopyNeeded=false) + +proc genFastAsgn(p: PProc, n: PNode) = + # 'shallowCopy' always produced 'noCopyNeeded = true' here but this is wrong + # for code like + # while j >= pos: + # dest[i].shallowCopy(dest[j]) + # See bug #5933. So we try to be more compatible with the C backend semantics + # here for 'shallowCopy'. This is an educated guess and might require further + # changes later: + let noCopy = n[0].typ.skipTypes(abstractInst).kind in {tySequence, tyString} + genAsgnAux(p, n[0], n[1], noCopyNeeded=noCopy) + +proc genSwap(p: PProc, n: PNode) = + let stmtList = lowerSwap(p.module.graph, n, p.module.idgen, if p.prc != nil: p.prc else: p.module.module) + assert stmtList.kind == nkStmtList + for i in 0..<stmtList.len: + genStmt(p, stmtList[i]) + +proc getFieldPosition(p: PProc; f: PNode): int = + case f.kind + of nkIntLit..nkUInt64Lit: result = int(f.intVal) + of nkSym: result = f.sym.position + else: + result = 0 + internalError(p.config, f.info, "genFieldPosition") + +proc genFieldAddr(p: PProc, n: PNode, r: var TCompRes) = + var a: TCompRes = default(TCompRes) + r.typ = etyBaseIndex + let b = if n.kind == nkHiddenAddr: n[0] else: n + gen(p, b[0], a) + if skipTypes(b[0].typ, abstractVarRange).kind == tyTuple: + r.res = makeJSString("Field" & $getFieldPosition(p, b[1])) + else: + if b[1].kind != nkSym: internalError(p.config, b[1].info, "genFieldAddr") + var f = b[1].sym + if f.loc.snippet == "": f.loc.snippet = mangleName(p.module, f) + r.res = makeJSString($f.loc.snippet) + internalAssert p.config, a.typ != etyBaseIndex + r.address = a.res + r.kind = resExpr + +proc genFieldAccess(p: PProc, n: PNode, r: var TCompRes) = + gen(p, n[0], r) + r.typ = mapType(n.typ) + let otyp = skipTypes(n[0].typ, abstractVarRange) + + template mkTemp(i: int) = + if r.typ == etyBaseIndex: + if needsTemp(p, n[i]): + let tmp = p.getTemp + r.address = "($1 = $2, $1)[0]" % [tmp, r.res] + r.res = "$1[1]" % [tmp] + r.tmpLoc = tmp + else: + r.address = "$1[0]" % [r.res] + r.res = "$1[1]" % [r.res] + if otyp.kind == tyTuple: + r.res = ("$1.Field$2") % + [r.res, getFieldPosition(p, n[1]).rope] + mkTemp(0) + else: + if n[1].kind != nkSym: internalError(p.config, n[1].info, "genFieldAccess") + var f = n[1].sym + if f.loc.snippet == "": f.loc.snippet = mangleName(p.module, f) + r.res = "$1.$2" % [r.res, f.loc.snippet] + mkTemp(1) + r.kind = resExpr + +proc genAddr(p: PProc, n: PNode, r: var TCompRes) + +proc genCheckedFieldOp(p: PProc, n: PNode, addrTyp: PType, r: var TCompRes) = + internalAssert p.config, n.kind == nkCheckedFieldExpr + # nkDotExpr to access the requested field + let accessExpr = n[0] + # nkCall to check if the discriminant is valid + var checkExpr = n[1] + + let negCheck = checkExpr[0].sym.magic == mNot + if negCheck: + checkExpr = checkExpr[^1] + + # Field symbol + var field = accessExpr[1].sym + internalAssert p.config, field.kind == skField + if field.loc.snippet == "": field.loc.snippet = mangleName(p.module, field) + # Discriminant symbol + let disc = checkExpr[2].sym + internalAssert p.config, disc.kind == skField + if disc.loc.snippet == "": disc.loc.snippet = mangleName(p.module, disc) + + var setx: TCompRes = default(TCompRes) + gen(p, checkExpr[1], setx) + + var obj: TCompRes = default(TCompRes) + gen(p, accessExpr[0], obj) + # Avoid evaluating the LHS twice (one to read the discriminant and one to read + # the field) + let tmp = p.getTemp() + lineF(p, "var $1 = $2;$n", tmp, obj.res) + + useMagic(p, "raiseFieldError2") + useMagic(p, "makeNimstrLit") + useMagic(p, "reprDiscriminant") # no need to offset by firstOrd unlike for cgen + let msg = genFieldDefect(p.config, field.name.s, disc) + lineF(p, "if ($1[$2.$3]$4undefined) { raiseFieldError2(makeNimstrLit($5), reprDiscriminant($2.$3, $6)); }$n", + setx.res, tmp, disc.loc.snippet, if negCheck: "!==" else: "===", + makeJSString(msg), genTypeInfo(p, disc.typ)) + + if addrTyp != nil and mapType(p, addrTyp) == etyBaseIndex: + r.typ = etyBaseIndex + r.res = makeJSString($field.loc.snippet) + r.address = tmp + else: + r.typ = etyNone + r.res = "$1.$2" % [tmp, field.loc.snippet] + r.kind = resExpr + +proc genArrayAddr(p: PProc, n: PNode, r: var TCompRes) = + var + a, b: TCompRes = default(TCompRes) + first: Int128 = Zero + r.typ = etyBaseIndex + let m = if n.kind == nkHiddenAddr: n[0] else: n + gen(p, m[0], a) + gen(p, m[1], b) + #internalAssert p.config, a.typ != etyBaseIndex and b.typ != etyBaseIndex + let (x, tmp) = maybeMakeTemp(p, m[0], a) + r.address = x + var typ = skipTypes(m[0].typ, abstractPtrs) + if typ.kind == tyArray: + first = firstOrd(p.config, typ.indexType) + if optBoundsCheck in p.options: + useMagic(p, "chckIndx") + if first == 0: # save a couple chars + r.res = "chckIndx($1, 0, ($2).length - 1)" % [b.res, tmp] + else: + r.res = "chckIndx($1, $2, ($3).length + ($2) - 1) - ($2)" % [ + b.res, rope(first), tmp] + elif first != 0: + r.res = "($1) - ($2)" % [b.res, rope(first)] + else: + r.res = b.res + r.kind = resExpr + +proc genArrayAccess(p: PProc, n: PNode, r: var TCompRes) = + var ty = skipTypes(n[0].typ, abstractVarRange+tyUserTypeClasses) + if ty.kind in {tyRef, tyPtr, tyLent, tyOwned}: ty = skipTypes(ty.elementType, abstractVarRange) + case ty.kind + of tyArray, tyOpenArray, tySequence, tyString, tyCstring, tyVarargs: + genArrayAddr(p, n, r) + of tyTuple: + genFieldAddr(p, n, r) + else: internalError(p.config, n.info, "expr(nkBracketExpr, " & $ty.kind & ')') + r.typ = mapType(n.typ) + if r.res == "": internalError(p.config, n.info, "genArrayAccess") + if ty.kind == tyCstring: + r.res = "$1.charCodeAt($2)" % [r.address, r.res] + elif r.typ == etyBaseIndex: + if needsTemp(p, n[0]): + let tmp = p.getTemp + r.address = "($1 = $2, $1)[0]" % [tmp, r.rdLoc] + r.res = "$1[1]" % [tmp] + r.tmpLoc = tmp + else: + let x = r.rdLoc + r.address = "$1[0]" % [x] + r.res = "$1[1]" % [x] + else: + r.res = "$1[$2]" % [r.address, r.res] + r.kind = resExpr + +template isIndirect(x: PSym): bool = + let v = x + ({sfAddrTaken, sfGlobal} * v.flags != {} and + #(mapType(v.typ) != etyObject) and + {sfImportc, sfExportc} * v.flags == {} and + v.kind notin {skProc, skFunc, skConverter, skMethod, skIterator, + skConst, skTemp, skLet}) + +proc genSymAddr(p: PProc, n: PNode, typ: PType, r: var TCompRes) = + let s = n.sym + if s.loc.snippet == "": internalError(p.config, n.info, "genAddr: 3") + case s.kind + of skParam: + r.res = s.loc.snippet + r.address = "" + r.typ = etyNone + of skVar, skLet, skResult: + r.kind = resExpr + let jsType = mapType(p): + if typ.isNil: + n.typ + else: + typ + if jsType == etyObject: + # make addr() a no-op: + r.typ = etyNone + if isIndirect(s): + r.res = s.loc.snippet & "[0]" + else: + r.res = s.loc.snippet + r.address = "" + elif {sfGlobal, sfAddrTaken} * s.flags != {} or jsType == etyBaseIndex: + # for ease of code generation, we do not distinguish between + # sfAddrTaken and sfGlobal. + r.typ = etyBaseIndex + r.address = s.loc.snippet + r.res = rope("0") + else: + # 'var openArray' for instance produces an 'addr' but this is harmless: + gen(p, n, r) + #internalError(p.config, n.info, "genAddr: 4 " & renderTree(n)) + else: internalError(p.config, n.info, $("genAddr: 2", s.kind)) + +proc genAddr(p: PProc, n: PNode, r: var TCompRes) = + if n.kind == nkSym: + genSymAddr(p, n, nil, r) + else: + case n[0].kind + of nkSym: + genSymAddr(p, n[0], n.typ, r) + of nkCheckedFieldExpr: + genCheckedFieldOp(p, n[0], n.typ, r) + of nkDotExpr: + if mapType(p, n.typ) == etyBaseIndex: + genFieldAddr(p, n[0], r) + else: + genFieldAccess(p, n[0], r) + of nkBracketExpr: + var ty = skipTypes(n[0].typ, abstractVarRange) + if ty.kind in MappedToObject: + gen(p, n[0], r) + else: + let kindOfIndexedExpr = skipTypes(n[0][0].typ, abstractVarRange+tyUserTypeClasses).kind + case kindOfIndexedExpr + of tyArray, tyOpenArray, tySequence, tyString, tyCstring, tyVarargs: + genArrayAddr(p, n[0], r) + of tyTuple: + genFieldAddr(p, n[0], r) + of tyGenericBody: + genAddr(p, n[^1], r) + else: internalError(p.config, n[0].info, "expr(nkBracketExpr, " & $kindOfIndexedExpr & ')') + of nkObjDownConv: + gen(p, n[0], r) + of nkHiddenDeref: + gen(p, n[0], r) + of nkDerefExpr: + var x = n[0] + if n.kind == nkHiddenAddr: + x = n[0][0] + if n.typ.skipTypes(abstractVar).kind != tyOpenArray: + x.typ = n.typ + gen(p, x, r) + of nkHiddenAddr: + gen(p, n[0], r) + of nkConv: + genAddr(p, n[0], r) + of nkStmtListExpr: + if n.len == 1: gen(p, n[0], r) + else: internalError(p.config, n[0].info, "genAddr for complex nkStmtListExpr") + of nkCallKinds: + if n[0].typ.kind == tyOpenArray: + # 'var openArray' for instance produces an 'addr' but this is harmless: + # namely toOpenArray(a, 1, 3) + gen(p, n[0], r) + else: + internalError(p.config, n[0].info, "genAddr: " & $n[0].kind) + else: + internalError(p.config, n[0].info, "genAddr: " & $n[0].kind) + +proc attachProc(p: PProc; content: Rope; s: PSym) = + p.g.code.add(content) + +proc attachProc(p: PProc; s: PSym) = + let newp = genProc(p, s) + attachProc(p, newp, s) + +proc genProcForSymIfNeeded(p: PProc, s: PSym) = + if not p.g.generatedSyms.containsOrIncl(s.id): + if jsNoLambdaLifting in p.config.legacyFeatures: + let newp = genProc(p, s) + var owner = p + while owner != nil and owner.prc != s.owner: + owner = owner.up + if owner != nil: owner.locals.add(newp) + else: attachProc(p, newp, s) + else: + attachProc(p, s) + +proc genCopyForParamIfNeeded(p: PProc, n: PNode) = + let s = n.sym + if p.prc == s.owner or needsNoCopy(p, n): + return + var owner = p.up + while true: + if owner == nil: + internalError(p.config, n.info, "couldn't find the owner proc of the closed over param: " & s.name.s) + if owner.prc == s.owner: + if not owner.generatedParamCopies.containsOrIncl(s.id): + let copy = "$1 = nimCopy(null, $1, $2);$n" % [s.loc.snippet, genTypeInfo(p, s.typ)] + owner.locals.add(owner.indentLine(copy)) + return + owner = owner.up + +proc genVarInit(p: PProc, v: PSym, n: PNode) + +proc genSym(p: PProc, n: PNode, r: var TCompRes) = + var s = n.sym + case s.kind + of skVar, skLet, skParam, skTemp, skResult, skForVar: + if s.loc.snippet == "": + internalError(p.config, n.info, "symbol has no generated name: " & s.name.s) + if sfCompileTime in s.flags: + genVarInit(p, s, if s.astdef != nil: s.astdef else: newNodeI(nkEmpty, s.info)) + if jsNoLambdaLifting in p.config.legacyFeatures and s.kind == skParam: + genCopyForParamIfNeeded(p, n) + let k = mapType(p, s.typ) + if k == etyBaseIndex: + r.typ = etyBaseIndex + if {sfAddrTaken, sfGlobal} * s.flags != {}: + if isIndirect(s): + r.address = "$1[0][0]" % [s.loc.snippet] + r.res = "$1[0][1]" % [s.loc.snippet] + else: + r.address = "$1[0]" % [s.loc.snippet] + r.res = "$1[1]" % [s.loc.snippet] + else: + r.address = s.loc.snippet + r.res = s.loc.snippet & "_Idx" + elif isIndirect(s): + r.res = "$1[0]" % [s.loc.snippet] + else: + r.res = s.loc.snippet + of skConst: + genConstant(p, s) + if s.loc.snippet == "": + internalError(p.config, n.info, "symbol has no generated name: " & s.name.s) + r.res = s.loc.snippet + of skProc, skFunc, skConverter, skMethod, skIterator: + if sfCompileTime in s.flags: + localError(p.config, n.info, "request to generate code for .compileTime proc: " & + s.name.s) + discard mangleName(p.module, s) + r.res = s.loc.snippet + if lfNoDecl in s.loc.flags or s.magic notin generatedMagics or + {sfImportc, sfInfixCall} * s.flags != {}: + discard + elif s.kind == skMethod and getBody(p.module.graph, s).kind == nkEmpty: + # we cannot produce code for the dispatcher yet: + discard + elif sfForward in s.flags: + p.g.forwarded.add(s) + else: + genProcForSymIfNeeded(p, s) + else: + if s.loc.snippet == "": + internalError(p.config, n.info, "symbol has no generated name: " & s.name.s) + if mapType(p, s.typ) == etyBaseIndex: + r.address = s.loc.snippet + r.res = s.loc.snippet & "_Idx" + else: + r.res = s.loc.snippet + r.kind = resVal + +proc genDeref(p: PProc, n: PNode, r: var TCompRes) = + let it = n[0] + let t = mapType(p, it.typ) + if t == etyObject or it.typ.kind == tyLent: + gen(p, it, r) + else: + var a: TCompRes = default(TCompRes) + gen(p, it, a) + r.kind = a.kind + r.typ = mapType(p, n.typ) + if r.typ == etyBaseIndex: + let tmp = p.getTemp + r.address = "($1 = $2, $1)[0]" % [tmp, a.rdLoc] + r.res = "$1[1]" % [tmp] + r.tmpLoc = tmp + elif a.typ == etyBaseIndex: + if a.tmpLoc != "": + r.tmpLoc = a.tmpLoc + r.res = a.rdLoc + else: + internalError(p.config, n.info, "genDeref") + +proc genArgNoParam(p: PProc, n: PNode, r: var TCompRes) = + var a: TCompRes = default(TCompRes) + gen(p, n, a) + if a.typ == etyBaseIndex: + r.res.add(a.address) + r.res.add(", ") + r.res.add(a.res) + else: + r.res.add(a.res) + +proc genArg(p: PProc, n: PNode, param: PSym, r: var TCompRes; emitted: ptr int = nil) = + var a: TCompRes = default(TCompRes) + gen(p, n, a) + if skipTypes(param.typ, abstractVar).kind in {tyOpenArray, tyVarargs} and + a.typ == etyBaseIndex: + r.res.add("$1[$2]" % [a.address, a.res]) + elif a.typ == etyBaseIndex: + r.res.add(a.address) + r.res.add(", ") + r.res.add(a.res) + if emitted != nil: inc emitted[] + elif n.typ.kind in {tyVar, tyPtr, tyRef, tyLent, tyOwned} and + n.kind in nkCallKinds and mapType(param.typ) == etyBaseIndex: + # this fixes bug #5608: + let tmp = getTemp(p) + r.res.add("($1 = $2, $1[0]), $1[1]" % [tmp, a.rdLoc]) + if emitted != nil: inc emitted[] + else: + r.res.add(a.res) + +proc genArgs(p: PProc, n: PNode, r: var TCompRes; start=1) = + r.res.add("(") + var hasArgs = false + + var typ = skipTypes(n[0].typ, abstractInst) + assert(typ.kind == tyProc) + assert(typ.len == typ.n.len) + var emitted = start-1 + + for i in start..<n.len: + let it = n[i] + var paramType: PNode = nil + if i < typ.len: + assert(typ.n[i].kind == nkSym) + paramType = typ.n[i] + if paramType.typ.isCompileTimeOnly: continue + + if hasArgs: r.res.add(", ") + if paramType.isNil: + genArgNoParam(p, it, r) + else: + genArg(p, it, paramType.sym, r, addr emitted) + inc emitted + hasArgs = true + r.res.add(")") + when false: + # XXX look into this: + let jsp = countJsParams(typ) + if emitted != jsp and tfVarargs notin typ.flags: + localError(p.config, n.info, "wrong number of parameters emitted; expected: " & $jsp & + " but got: " & $emitted) + r.kind = resExpr + +proc genOtherArg(p: PProc; n: PNode; i: int; typ: PType; + generated: var int; r: var TCompRes) = + if i >= n.len: + globalError(p.config, n.info, "wrong importcpp pattern; expected parameter at position " & $i & + " but got only: " & $(n.len-1)) + let it = n[i] + var paramType: PNode = nil + if i < typ.len: + assert(typ.n[i].kind == nkSym) + paramType = typ.n[i] + if paramType.typ.isCompileTimeOnly: return + if paramType.isNil: + genArgNoParam(p, it, r) + else: + genArg(p, it, paramType.sym, r) + inc generated + +proc genPatternCall(p: PProc; n: PNode; pat: string; typ: PType; + r: var TCompRes) = + var i = 0 + var j = 1 + r.kind = resExpr + while i < pat.len: + case pat[i] + of '@': + var generated = 0 + for k in j..<n.len: + if generated > 0: r.res.add(", ") + genOtherArg(p, n, k, typ, generated, r) + inc i + of '#': + var generated = 0 + genOtherArg(p, n, j, typ, generated, r) + inc j + inc i + of '\31': + # unit separator + r.res.add("#") + inc i + of '\29': + # group separator + r.res.add("@") + inc i + else: + let start = i + while i < pat.len: + if pat[i] notin {'@', '#', '\31', '\29'}: inc(i) + else: break + if i - 1 >= start: + r.res.add(substr(pat, start, i - 1)) + +proc genInfixCall(p: PProc, n: PNode, r: var TCompRes) = + # don't call '$' here for efficiency: + let f = n[0].sym + if f.loc.snippet == "": f.loc.snippet = mangleName(p.module, f) + if sfInfixCall in f.flags: + let pat = $n[0].sym.loc.snippet + internalAssert p.config, pat.len > 0 + if pat.contains({'#', '(', '@'}): + var typ = skipTypes(n[0].typ, abstractInst) + assert(typ.kind == tyProc) + genPatternCall(p, n, pat, typ, r) + return + if n.len != 1: + gen(p, n[1], r) + if r.typ == etyBaseIndex: + if r.address == "": + globalError(p.config, n.info, "cannot invoke with infix syntax") + r.res = "$1[$2]" % [r.address, r.res] + r.address = "" + r.typ = etyNone + r.res.add(".") + var op: TCompRes = default(TCompRes) + gen(p, n[0], op) + r.res.add(op.res) + genArgs(p, n, r, 2) + +proc genCall(p: PProc, n: PNode, r: var TCompRes) = + gen(p, n[0], r) + genArgs(p, n, r) + if n.typ != nil: + let t = mapType(n.typ) + if t == etyBaseIndex: + let tmp = p.getTemp + r.address = "($1 = $2, $1)[0]" % [tmp, r.rdLoc] + r.res = "$1[1]" % [tmp] + r.tmpLoc = tmp + r.typ = t + +proc genEcho(p: PProc, n: PNode, r: var TCompRes) = + let n = n[1].skipConv + internalAssert p.config, n.kind == nkBracket + useMagic(p, "toJSStr") # Used in rawEcho + useMagic(p, "rawEcho") + r.res.add("rawEcho(") + for i in 0..<n.len: + let it = n[i] + if it.typ.isCompileTimeOnly: continue + if i > 0: r.res.add(", ") + genArgNoParam(p, it, r) + r.res.add(")") + r.kind = resExpr + +proc putToSeq(s: string, indirect: bool): Rope = + result = rope(s) + if indirect: result = "[$1]" % [result] + +proc createVar(p: PProc, typ: PType, indirect: bool): Rope +proc createRecordVarAux(p: PProc, rec: PNode, excludedFieldIDs: IntSet, output: var Rope) = + case rec.kind + of nkRecList: + for i in 0..<rec.len: + createRecordVarAux(p, rec[i], excludedFieldIDs, output) + of nkRecCase: + createRecordVarAux(p, rec[0], excludedFieldIDs, output) + for i in 1..<rec.len: + createRecordVarAux(p, lastSon(rec[i]), excludedFieldIDs, output) + of nkSym: + # Do not produce code for void types + if isEmptyType(rec.sym.typ): return + if rec.sym.id notin excludedFieldIDs: + if output.len > 0: output.add(", ") + output.addf("$#: ", [mangleName(p.module, rec.sym)]) + output.add(createVar(p, rec.sym.typ, false)) + else: internalError(p.config, rec.info, "createRecordVarAux") + +proc createObjInitList(p: PProc, typ: PType, excludedFieldIDs: IntSet, output: var Rope) = + var t = typ + if objHasTypeField(t): + if output.len > 0: output.add(", ") + output.addf("m_type: $1", [genTypeInfo(p, t)]) + while t != nil: + t = t.skipTypes(skipPtrs) + createRecordVarAux(p, t.n, excludedFieldIDs, output) + t = t.baseClass + +proc arrayTypeForElemType(conf: ConfigRef; typ: PType): string = + let typ = typ.skipTypes(abstractRange) + case typ.kind + of tyInt, tyInt32: "Int32Array" + of tyInt16: "Int16Array" + of tyInt8: "Int8Array" + of tyInt64: + if optJsBigInt64 in conf.globalOptions: + "BigInt64Array" + else: + "" + of tyUInt, tyUInt32: "Uint32Array" + of tyUInt16: "Uint16Array" + of tyUInt8, tyChar, tyBool: "Uint8Array" + of tyUInt64: + if optJsBigInt64 in conf.globalOptions: + "BigUint64Array" + else: + "" + of tyFloat32: "Float32Array" + of tyFloat64, tyFloat: "Float64Array" + of tyEnum: + case typ.size + of 1: "Uint8Array" + of 2: "Uint16Array" + of 4: "Uint32Array" + else: "" + else: "" + +proc createVar(p: PProc, typ: PType, indirect: bool): Rope = + var t = skipTypes(typ, abstractInst) + case t.kind + of tyInt8..tyInt32, tyUInt8..tyUInt32, tyEnum, tyChar: + result = putToSeq("0", indirect) + of tyInt, tyUInt: + if $t.sym.loc.snippet == "bigint": + result = putToSeq("0n", indirect) + else: + result = putToSeq("0", indirect) + of tyInt64, tyUInt64: + if optJsBigInt64 in p.config.globalOptions: + result = putToSeq("0n", indirect) + else: + result = putToSeq("0", indirect) + of tyFloat..tyFloat128: + result = putToSeq("0.0", indirect) + of tyRange, tyGenericInst, tyAlias, tySink, tyOwned, tyLent: + result = createVar(p, skipModifier(typ), indirect) + of tySet: + result = putToSeq("{}", indirect) + of tyBool: + result = putToSeq("false", indirect) + of tyNil: + result = putToSeq("null", indirect) + of tyArray: + let length = toInt(lengthOrd(p.config, t)) + let e = elemType(t) + let jsTyp = arrayTypeForElemType(p.config, e) + if jsTyp.len > 0: + result = "new $1($2)" % [rope(jsTyp), rope(length)] + elif length > 32: + useMagic(p, "arrayConstr") + # XXX: arrayConstr depends on nimCopy. This line shouldn't be necessary. + useMagic(p, "nimCopy") + result = "arrayConstr($1, $2, $3)" % [rope(length), + createVar(p, e, false), genTypeInfo(p, e)] + else: + result = rope("[") + var i = 0 + while i < length: + if i > 0: result.add(", ") + result.add(createVar(p, e, false)) + inc(i) + result.add("]") + if indirect: result = "[$1]" % [result] + of tyTuple: + result = rope("{") + for i in 0..<t.len: + if i > 0: result.add(", ") + result.addf("Field$1: $2", [i.rope, + createVar(p, t[i], false)]) + result.add("}") + if indirect: result = "[$1]" % [result] + of tyObject: + var initList: Rope = "" + createObjInitList(p, t, initIntSet(), initList) + result = ("({$1})") % [initList] + if indirect: result = "[$1]" % [result] + of tyVar, tyPtr, tyRef, tyPointer: + if mapType(p, t) == etyBaseIndex: + result = putToSeq("[null, 0]", indirect) + else: + result = putToSeq("null", indirect) + of tySequence, tyString: + result = putToSeq("[]", indirect) + of tyCstring, tyProc, tyOpenArray: + result = putToSeq("null", indirect) + of tyStatic: + if t.n != nil: + result = createVar(p, skipModifier t, indirect) + else: + internalError(p.config, "createVar: " & $t.kind) + result = "" + else: + internalError(p.config, "createVar: " & $t.kind) + result = "" + +template returnType: untyped = "" + +proc genVarInit(p: PProc, v: PSym, n: PNode) = + var + a: TCompRes = default(TCompRes) + s: Rope + varCode: string + varName = mangleName(p.module, v) + useReloadingGuard = sfGlobal in v.flags and p.config.hcrOn + useGlobalPragmas = sfGlobal in v.flags and ({sfPure, sfThread} * v.flags != {}) + + if v.constraint.isNil: + if useReloadingGuard: + lineF(p, "var $1;$n", varName) + lineF(p, "if ($1 === undefined) {$n", varName) + varCode = $varName + inc p.extraIndent + elif useGlobalPragmas: + lineF(p, "if (globalThis.$1 === undefined) {$n", varName) + varCode = "globalThis." & $varName + inc p.extraIndent + else: + varCode = "var $2" + else: + # Is this really a thought through feature? this basically unused + # feature makes it impossible for almost all format strings in + # this function to be checked at compile time. + varCode = v.constraint.strVal + + if n.kind == nkEmpty: + if not isIndirect(v) and + v.typ.kind in {tyVar, tyPtr, tyLent, tyRef, tyOwned} and mapType(p, v.typ) == etyBaseIndex: + lineF(p, "var $1 = null;$n", [varName]) + lineF(p, "var $1_Idx = 0;$n", [varName]) + else: + line(p, runtimeFormat(varCode & " = $3;$n", [returnType, varName, createVar(p, v.typ, isIndirect(v))])) + else: + gen(p, n, a) + case mapType(p, v.typ) + of etyObject, etySeq: + if v.typ.kind in {tyOpenArray, tyVarargs} or needsNoCopy(p, n): + s = a.res + else: + useMagic(p, "nimCopy") + s = "nimCopy(null, $1, $2)" % [a.res, genTypeInfo(p, n.typ)] + of etyBaseIndex: + let targetBaseIndex = {sfAddrTaken, sfGlobal} * v.flags == {} + if a.typ == etyBaseIndex: + if targetBaseIndex: + line(p, runtimeFormat(varCode & " = $3, $2_Idx = $4;$n", + [returnType, v.loc.snippet, a.address, a.res])) + else: + if isIndirect(v): + line(p, runtimeFormat(varCode & " = [[$3, $4]];$n", + [returnType, v.loc.snippet, a.address, a.res])) + else: + line(p, runtimeFormat(varCode & " = [$3, $4];$n", + [returnType, v.loc.snippet, a.address, a.res])) + else: + if targetBaseIndex: + let tmp = p.getTemp + lineF(p, "var $1 = $2, $3 = $1[0], $3_Idx = $1[1];$n", + [tmp, a.res, v.loc.snippet]) + else: + line(p, runtimeFormat(varCode & " = $3;$n", [returnType, v.loc.snippet, a.res])) + return + else: + s = a.res + if isIndirect(v): + line(p, runtimeFormat(varCode & " = [$3];$n", [returnType, v.loc.snippet, s])) + else: + line(p, runtimeFormat(varCode & " = $3;$n", [returnType, v.loc.snippet, s])) + + if useReloadingGuard or useGlobalPragmas: + dec p.extraIndent + lineF(p, "}$n") + +proc genClosureVar(p: PProc, n: PNode) = + # assert n[2].kind != nkEmpty + # TODO: fixme transform `var env.x` into `var env.x = default()` after + # the order of transf and lambdalifting is fixed + if n[2].kind != nkEmpty: + genAsgnAux(p, n[0], n[2], false) + else: + var a: TCompRes = default(TCompRes) + gen(p, n[0], a) + line(p, runtimeFormat("$1 = $2;$n", [rdLoc(a), createVar(p, n[0].typ, false)])) + +proc genVarStmt(p: PProc, n: PNode) = + for i in 0..<n.len: + var a = n[i] + if a.kind != nkCommentStmt: + if a.kind == nkVarTuple: + let unpacked = lowerTupleUnpacking(p.module.graph, a, p.module.idgen, p.prc) + genStmt(p, unpacked) + else: + assert(a.kind == nkIdentDefs) + if a[0].kind == nkSym: + var v = a[0].sym + if lfNoDecl notin v.loc.flags and sfImportc notin v.flags: + genLineDir(p, a) + if sfCompileTime notin v.flags: + genVarInit(p, v, a[2]) + else: + # lazy emit, done when it's actually used. + if v.ast == nil: v.ast = a[2] + else: # closure + genClosureVar(p, a) + +proc genConstant(p: PProc, c: PSym) = + if lfNoDecl notin c.loc.flags and not p.g.generatedSyms.containsOrIncl(c.id): + let oldBody = move p.body + #genLineDir(p, c.astdef) + genVarInit(p, c, c.astdef) + p.g.constants.add(p.body) + p.body = oldBody + +proc genNew(p: PProc, n: PNode) = + var a: TCompRes = default(TCompRes) + gen(p, n[1], a) + var t = skipTypes(n[1].typ, abstractVar)[0] + if mapType(t) == etyObject: + lineF(p, "$1 = $2;$n", [a.rdLoc, createVar(p, t, false)]) + elif a.typ == etyBaseIndex: + lineF(p, "$1 = [$3]; $2 = 0;$n", [a.address, a.res, createVar(p, t, false)]) + else: + lineF(p, "$1 = [[$2], 0];$n", [a.rdLoc, createVar(p, t, false)]) + +proc genNewSeq(p: PProc, n: PNode) = + var x, y: TCompRes = default(TCompRes) + gen(p, n[1], x) + gen(p, n[2], y) + let t = skipTypes(n[1].typ, abstractVar)[0] + lineF(p, "$1 = new Array($2); for (var i = 0 ; i < $2 ; ++i) { $1[i] = $3; }", [ + x.rdLoc, y.rdLoc, createVar(p, t, false)]) + +proc genOrd(p: PProc, n: PNode, r: var TCompRes) = + case skipTypes(n[1].typ, abstractVar + abstractRange).kind + of tyEnum, tyInt..tyInt32, tyUInt..tyUInt32, tyChar: gen(p, n[1], r) + of tyInt64, tyUInt64: + if optJsBigInt64 in p.config.globalOptions: + unaryExpr(p, n, r, "", "Number($1)") + else: gen(p, n[1], r) + of tyBool: unaryExpr(p, n, r, "", "($1 ? 1 : 0)") + else: internalError(p.config, n.info, "genOrd") + +proc genConStrStr(p: PProc, n: PNode, r: var TCompRes) = + var a: TCompRes = default(TCompRes) + + gen(p, n[1], a) + r.kind = resExpr + if skipTypes(n[1].typ, abstractVarRange).kind == tyChar: + r.res.add("[$1].concat(" % [a.res]) + else: + r.res.add("($1).concat(" % [a.res]) + + for i in 2..<n.len - 1: + gen(p, n[i], a) + if skipTypes(n[i].typ, abstractVarRange).kind == tyChar: + r.res.add("[$1]," % [a.res]) + else: + r.res.add("$1," % [a.res]) + + gen(p, n[^1], a) + if skipTypes(n[^1].typ, abstractVarRange).kind == tyChar: + r.res.add("[$1])" % [a.res]) + else: + r.res.add("$1)" % [a.res]) + +proc genReprAux(p: PProc, n: PNode, r: var TCompRes, magic: string, typ: Rope = "") = + useMagic(p, magic) + r.res.add(magic & "(") + var a: TCompRes = default(TCompRes) + + gen(p, n[1], a) + if magic == "reprAny": + # the pointer argument in reprAny is expandend to + # (pointedto, pointer), so we need to fill it + if a.address.len == 0: + r.res.add(a.res) + r.res.add(", null") + else: + r.res.add("$1, $2" % [a.address, a.res]) + else: + r.res.add(a.res) + + if typ != "": + r.res.add(", ") + r.res.add(typ) + r.res.add(")") + +proc genRepr(p: PProc, n: PNode, r: var TCompRes) = + let t = skipTypes(n[1].typ, abstractVarRange) + case t.kind + of tyInt..tyInt64, tyUInt..tyUInt64: + genReprAux(p, n, r, "reprInt") + of tyChar: + genReprAux(p, n, r, "reprChar") + of tyBool: + genReprAux(p, n, r, "reprBool") + of tyFloat..tyFloat128: + genReprAux(p, n, r, "reprFloat") + of tyString: + genReprAux(p, n, r, "reprStr") + of tyEnum, tyOrdinal: + genReprAux(p, n, r, "reprEnum", genTypeInfo(p, t)) + of tySet: + genReprAux(p, n, r, "reprSet", genTypeInfo(p, t)) + of tyEmpty, tyVoid: + localError(p.config, n.info, "'repr' doesn't support 'void' type") + of tyPointer: + genReprAux(p, n, r, "reprPointer") + of tyOpenArray, tyVarargs: + genReprAux(p, n, r, "reprJSONStringify") + else: + genReprAux(p, n, r, "reprAny", genTypeInfo(p, t)) + r.kind = resExpr + +proc genOf(p: PProc, n: PNode, r: var TCompRes) = + var x: TCompRes = default(TCompRes) + let t = skipTypes(n[2].typ, + abstractVarRange+{tyRef, tyPtr, tyLent, tyTypeDesc, tyOwned}) + gen(p, n[1], x) + if tfFinal in t.flags: + r.res = "($1.m_type == $2)" % [x.res, genTypeInfo(p, t)] + else: + useMagic(p, "isObj") + r.res = "isObj($1.m_type, $2)" % [x.res, genTypeInfo(p, t)] + r.kind = resExpr + +proc genDefault(p: PProc, n: PNode; r: var TCompRes) = + r.res = createVar(p, n.typ, indirect = false) + r.kind = resExpr + +proc genWasMoved(p: PProc, n: PNode) = + # TODO: it should be done by nir + var x: TCompRes = default(TCompRes) + gen(p, n[1], x) + if x.typ == etyBaseIndex: + lineF(p, "$1 = null, $2 = 0;$n", [x.address, x.res]) + else: + var y: TCompRes = default(TCompRes) + genDefault(p, n[1], y) + let (a, _) = maybeMakeTempAssignable(p, n[1], x) + lineF(p, "$1 = $2;$n", [a, y.rdLoc]) + +proc genMove(p: PProc; n: PNode; r: var TCompRes) = + var a: TCompRes = default(TCompRes) + r.kind = resVal + r.res = p.getTemp() + gen(p, n[1], a) + lineF(p, "$1 = $2;$n", [r.rdLoc, a.rdLoc]) + genWasMoved(p, n) + #lineF(p, "$1 = $2;$n", [dest.rdLoc, src.rdLoc]) + +proc genDup(p: PProc; n: PNode; r: var TCompRes) = + var a: TCompRes = default(TCompRes) + r.kind = resVal + r.res = p.getTemp() + gen(p, n[1], a) + lineF(p, "$1 = $2;$n", [r.rdLoc, a.rdLoc]) + +proc genJSArrayConstr(p: PProc, n: PNode, r: var TCompRes) = + var a: TCompRes = default(TCompRes) + r.res = rope("[") + r.kind = resExpr + for i in 0 ..< n.len: + if i > 0: r.res.add(", ") + gen(p, n[i], a) + if a.typ == etyBaseIndex: + r.res.addf("[$1, $2]", [a.address, a.res]) + else: + if not needsNoCopy(p, n[i]): + let typ = n[i].typ.skipTypes(abstractInst) + useMagic(p, "nimCopy") + a.res = "nimCopy(null, $1, $2)" % [a.rdLoc, genTypeInfo(p, typ)] + r.res.add(a.res) + r.res.add("]") + +proc genMagic(p: PProc, n: PNode, r: var TCompRes) = + var + a: TCompRes + line, filen: Rope + var op = n[0].sym.magic + case op + of mOr: genOr(p, n[1], n[2], r) + of mAnd: genAnd(p, n[1], n[2], r) + of mAddI..mStrToStr: arith(p, n, r, op) + of mRepr: genRepr(p, n, r) + of mSwap: genSwap(p, n) + of mAppendStrCh: + binaryExpr(p, n, r, "addChar", + "addChar($1, $2);") + of mAppendStrStr: + var lhs, rhs: TCompRes = default(TCompRes) + gen(p, n[1], lhs) + gen(p, n[2], rhs) + + if skipTypes(n[1].typ, abstractVarRange).kind == tyCstring: + let (b, tmp) = maybeMakeTemp(p, n[2], rhs) + r.res = "if (null != $1) { if (null == $2) $2 = $3; else $2 += $3; }" % + [b, lhs.rdLoc, tmp] + else: + let (a, tmp) = maybeMakeTemp(p, n[1], lhs) + r.res = "$1.push.apply($3, $2);" % [a, rhs.rdLoc, tmp] + r.kind = resExpr + of mAppendSeqElem: + var x, y: TCompRes = default(TCompRes) + gen(p, n[1], x) + gen(p, n[2], y) + if mapType(n[2].typ) == etyBaseIndex: + let c = "[$1, $2]" % [y.address, y.res] + r.res = "$1.push($2);" % [x.rdLoc, c] + elif needsNoCopy(p, n[2]): + r.res = "$1.push($2);" % [x.rdLoc, y.rdLoc] + else: + useMagic(p, "nimCopy") + let c = getTemp(p, defineInLocals=false) + lineF(p, "var $1 = nimCopy(null, $2, $3);$n", + [c, y.rdLoc, genTypeInfo(p, n[2].typ)]) + r.res = "$1.push($2);" % [x.rdLoc, c] + r.kind = resExpr + of mConStrStr: + genConStrStr(p, n, r) + of mEqStr: + binaryExpr(p, n, r, "eqStrings", "eqStrings($1, $2)") + of mLeStr: + binaryExpr(p, n, r, "cmpStrings", "(cmpStrings($1, $2) <= 0)") + of mLtStr: + binaryExpr(p, n, r, "cmpStrings", "(cmpStrings($1, $2) < 0)") + of mIsNil: + # we want to accept undefined, so we == + if mapType(n[1].typ) != etyBaseIndex: + unaryExpr(p, n, r, "", "($1 == null)") + else: + var x: TCompRes = default(TCompRes) + gen(p, n[1], x) + r.res = "($# == null && $# === 0)" % [x.address, x.res] + of mEnumToStr: genRepr(p, n, r) + of mNew, mNewFinalize: genNew(p, n) + of mChr: gen(p, n[1], r) + of mArrToSeq: + # only array literals doesn't need copy + if n[1].kind == nkBracket: + genJSArrayConstr(p, n[1], r) + else: + var x: TCompRes = default(TCompRes) + gen(p, n[1], x) + useMagic(p, "nimCopy") + r.res = "nimCopy(null, $1, $2)" % [x.rdLoc, genTypeInfo(p, n.typ)] + of mOpenArrayToSeq: + genCall(p, n, r) + of mDestroy, mTrace: discard "ignore calls to the default destructor" + of mOrd: genOrd(p, n, r) + of mLengthStr, mLengthSeq, mLengthOpenArray, mLengthArray: + var x: TCompRes = default(TCompRes) + gen(p, n[1], x) + if skipTypes(n[1].typ, abstractInst).kind == tyCstring: + let (a, tmp) = maybeMakeTemp(p, n[1], x) + r.res = "(($1) == null ? 0 : ($2).length)" % [a, tmp] + else: + r.res = "($1).length" % [x.rdLoc] + r.kind = resExpr + of mHigh: + var x: TCompRes = default(TCompRes) + gen(p, n[1], x) + if skipTypes(n[1].typ, abstractInst).kind == tyCstring: + let (a, tmp) = maybeMakeTemp(p, n[1], x) + r.res = "(($1) == null ? -1 : ($2).length - 1)" % [a, tmp] + else: + r.res = "($1).length - 1" % [x.rdLoc] + r.kind = resExpr + of mInc: + let typ = n[1].typ.skipTypes(abstractVarRange) + case typ.kind + of tyUInt..tyUInt32: + binaryUintExpr(p, n, r, "+", true) + of tyUInt64: + if optJsBigInt64 in p.config.globalOptions: + binaryExpr(p, n, r, "", "$1 = BigInt.asUintN(64, $3 + BigInt($2))", true) + else: binaryUintExpr(p, n, r, "+", true) + elif typ.kind == tyInt64 and optJsBigInt64 in p.config.globalOptions: + if optOverflowCheck notin p.options: + binaryExpr(p, n, r, "", "$1 = BigInt.asIntN(64, $3 + BigInt($2))", true) + else: binaryExpr(p, n, r, "addInt64", "$1 = addInt64($3, BigInt($2))", true) + else: + if optOverflowCheck notin p.options: binaryExpr(p, n, r, "", "$1 += $2") + else: binaryExpr(p, n, r, "addInt", "$1 = addInt($3, $2)", true) + of ast.mDec: + let typ = n[1].typ.skipTypes(abstractVarRange) + case typ.kind + of tyUInt..tyUInt32: + binaryUintExpr(p, n, r, "-", true) + of tyUInt64: + if optJsBigInt64 in p.config.globalOptions: + binaryExpr(p, n, r, "", "$1 = BigInt.asUintN(64, $3 - BigInt($2))", true) + else: binaryUintExpr(p, n, r, "-", true) + elif typ.kind == tyInt64 and optJsBigInt64 in p.config.globalOptions: + if optOverflowCheck notin p.options: + binaryExpr(p, n, r, "", "$1 = BigInt.asIntN(64, $3 - BigInt($2))", true) + else: binaryExpr(p, n, r, "subInt64", "$1 = subInt64($3, BigInt($2))", true) + else: + if optOverflowCheck notin p.options: binaryExpr(p, n, r, "", "$1 -= $2") + else: binaryExpr(p, n, r, "subInt", "$1 = subInt($3, $2)", true) + of mSetLengthStr: + binaryExpr(p, n, r, "mnewString", + """if ($1.length < $2) { for (var i = $3.length; i < $4; ++i) $3.push(0); } + else {$3.length = $4; }""") + of mSetLengthSeq: + var x, y: TCompRes = default(TCompRes) + gen(p, n[1], x) + gen(p, n[2], y) + let t = skipTypes(n[1].typ, abstractVar)[0] + let (a, tmp) = maybeMakeTemp(p, n[1], x) + let (b, tmp2) = maybeMakeTemp(p, n[2], y) + r.res = """if ($1.length < $2) { for (var i = $4.length ; i < $5 ; ++i) $4.push($3); } + else { $4.length = $5; }""" % [a, b, createVar(p, t, false), tmp, tmp2] + r.kind = resExpr + of mCard: unaryExpr(p, n, r, "SetCard", "SetCard($1)") + of mLtSet: binaryExpr(p, n, r, "SetLt", "SetLt($1, $2)") + of mLeSet: binaryExpr(p, n, r, "SetLe", "SetLe($1, $2)") + of mEqSet: binaryExpr(p, n, r, "SetEq", "SetEq($1, $2)") + of mMulSet: binaryExpr(p, n, r, "SetMul", "SetMul($1, $2)") + of mPlusSet: binaryExpr(p, n, r, "SetPlus", "SetPlus($1, $2)") + of mMinusSet: binaryExpr(p, n, r, "SetMinus", "SetMinus($1, $2)") + of mIncl: binaryExpr(p, n, r, "", "$1[$2] = true") + of mExcl: binaryExpr(p, n, r, "", "delete $1[$2]") + of mInSet: + binaryExpr(p, n, r, "", "($1[$2] != undefined)") + of mNewSeq: genNewSeq(p, n) + of mNewSeqOfCap: unaryExpr(p, n, r, "", "[]") + of mOf: genOf(p, n, r) + of mDefault, mZeroDefault: genDefault(p, n, r) + of mWasMoved: genWasMoved(p, n) + of mEcho: genEcho(p, n, r) + of mNLen..mNError, mSlurp, mStaticExec: + localError(p.config, n.info, errXMustBeCompileTime % n[0].sym.name.s) + of mNewString: unaryExpr(p, n, r, "mnewString", "mnewString($1)") + of mNewStringOfCap: + unaryExpr(p, n, r, "mnewString", "mnewString(0)") + of mDotDot: + genProcForSymIfNeeded(p, n[0].sym) + genCall(p, n, r) + of mParseBiggestFloat: + useMagic(p, "nimParseBiggestFloat") + genCall(p, n, r) + of mSlice: + # arr.slice([begin[, end]]): 'end' is exclusive + var x, y, z: TCompRes = default(TCompRes) + gen(p, n[1], x) + gen(p, n[2], y) + gen(p, n[3], z) + r.res = "($1.slice($2, $3 + 1))" % [x.rdLoc, y.rdLoc, z.rdLoc] + r.kind = resExpr + of mMove: + genMove(p, n, r) + of mDup: + genDup(p, n, r) + of mEnsureMove: + gen(p, n[1], r) + else: + genCall(p, n, r) + #else internalError(p.config, e.info, 'genMagic: ' + magicToStr[op]); + +proc genSetConstr(p: PProc, n: PNode, r: var TCompRes) = + var + a, b: TCompRes = default(TCompRes) + useMagic(p, "setConstr") + r.res = rope("setConstr(") + r.kind = resExpr + for i in 0..<n.len: + if i > 0: r.res.add(", ") + var it = n[i] + if it.kind == nkRange: + gen(p, it[0], a) + gen(p, it[1], b) + + if it[0].typ.kind == tyBool: + r.res.addf("$1, $2", [a.res, b.res]) + else: + r.res.addf("[$1, $2]", [a.res, b.res]) + else: + gen(p, it, a) + r.res.add(a.res) + r.res.add(")") + # emit better code for constant sets: + if isDeepConstExpr(n): + inc(p.g.unique) + let tmp = rope("ConstSet") & rope(p.g.unique) + p.g.constants.addf("var $1 = $2;$n", [tmp, r.res]) + r.res = tmp + +proc genArrayConstr(p: PProc, n: PNode, r: var TCompRes) = + ## Constructs array or sequence. + ## Nim array of uint8..uint32, int8..int32 maps to JS typed arrays. + ## Nim sequence maps to JS array. + var t = skipTypes(n.typ, abstractInst) + let e = elemType(t) + let jsTyp = arrayTypeForElemType(p.config, e) + if skipTypes(n.typ, abstractVarRange).kind != tySequence and jsTyp.len > 0: + # generate typed array + # for example Nim generates `new Uint8Array([1, 2, 3])` for `[byte(1), 2, 3]` + # TODO use `set` or loop to initialize typed array which improves performances in some situations + var a: TCompRes = default(TCompRes) + r.res = "new $1([" % [rope(jsTyp)] + r.kind = resExpr + for i in 0 ..< n.len: + if i > 0: r.res.add(", ") + gen(p, n[i], a) + r.res.add(a.res) + r.res.add("])") + else: + genJSArrayConstr(p, n, r) + +proc genTupleConstr(p: PProc, n: PNode, r: var TCompRes) = + var a: TCompRes = default(TCompRes) + r.res = rope("{") + r.kind = resExpr + for i in 0..<n.len: + if i > 0: r.res.add(", ") + var it = n[i] + if it.kind == nkExprColonExpr: it = it[1] + gen(p, it, a) + let typ = it.typ.skipTypes(abstractInst) + if a.typ == etyBaseIndex: + r.res.addf("Field$#: [$#, $#]", [i.rope, a.address, a.res]) + else: + if not needsNoCopy(p, it): + useMagic(p, "nimCopy") + a.res = "nimCopy(null, $1, $2)" % [a.rdLoc, genTypeInfo(p, typ)] + r.res.addf("Field$#: $#", [i.rope, a.res]) + r.res.add("}") + +proc genObjConstr(p: PProc, n: PNode, r: var TCompRes) = + var a: TCompRes = default(TCompRes) + r.kind = resExpr + var initList : Rope = "" + var fieldIDs = initIntSet() + let nTyp = n.typ.skipTypes(abstractInst) + for i in 1..<n.len: + if i > 1: initList.add(", ") + var it = n[i] + internalAssert p.config, it.kind == nkExprColonExpr + let val = it[1] + gen(p, val, a) + var f = it[0].sym + if f.loc.snippet == "": f.loc.snippet = mangleName(p.module, f) + fieldIDs.incl(lookupFieldAgain(n.typ.skipTypes({tyDistinct}), f).id) + + let typ = val.typ.skipTypes(abstractInst) + if a.typ == etyBaseIndex: + initList.addf("$#: [$#, $#]", [f.loc.snippet, a.address, a.res]) + else: + if not needsNoCopy(p, val): + useMagic(p, "nimCopy") + a.res = "nimCopy(null, $1, $2)" % [a.rdLoc, genTypeInfo(p, typ)] + initList.addf("$#: $#", [f.loc.snippet, a.res]) + let t = skipTypes(n.typ, abstractInst + skipPtrs) + createObjInitList(p, t, fieldIDs, initList) + r.res = ("{$1}") % [initList] + +proc genConv(p: PProc, n: PNode, r: var TCompRes) = + var dest = skipTypes(n.typ, abstractVarRange) + var src = skipTypes(n[1].typ, abstractVarRange) + gen(p, n[1], r) + if dest.kind == src.kind: + # no-op conversion + return + let toInt = (dest.kind in tyInt..tyInt32) + let fromInt = (src.kind in tyInt..tyInt32) + let toUint = (dest.kind in tyUInt..tyUInt32) + let fromUint = (src.kind in tyUInt..tyUInt32) + if toUint and (fromInt or fromUint): + let trimmer = unsignedTrimmer(dest.size) + r.res = "($1 $2)" % [r.res, trimmer] + elif dest.kind == tyBool: + r.res = "(!!($1))" % [r.res] + r.kind = resExpr + elif toInt: + if src.kind in {tyInt64, tyUInt64} and optJsBigInt64 in p.config.globalOptions: + r.res = "Number($1)" % [r.res] + else: + r.res = "(($1) | 0)" % [r.res] + elif dest.kind == tyInt64 and optJsBigInt64 in p.config.globalOptions: + if fromInt or fromUint or src.kind in {tyBool, tyChar, tyEnum}: + r.res = "BigInt($1)" % [r.res] + elif src.kind in {tyFloat..tyFloat64}: + r.res = "BigInt(Math.trunc($1))" % [r.res] + elif src.kind == tyUInt64: + r.res = "BigInt.asIntN(64, $1)" % [r.res] + elif dest.kind == tyUInt64 and optJsBigInt64 in p.config.globalOptions: + if fromUint or src.kind in {tyBool, tyChar, tyEnum}: + r.res = "BigInt($1)" % [r.res] + elif fromInt: # could be negative + r.res = "BigInt.asUintN(64, BigInt($1))" % [r.res] + elif src.kind in {tyFloat..tyFloat64}: + r.res = "BigInt.asUintN(64, BigInt(Math.trunc($1)))" % [r.res] + elif src.kind == tyInt64: + r.res = "BigInt.asUintN(64, $1)" % [r.res] + elif toUint or dest.kind in tyFloat..tyFloat64: + if src.kind in {tyInt64, tyUInt64} and optJsBigInt64 in p.config.globalOptions: + r.res = "Number($1)" % [r.res] + else: + # TODO: What types must we handle here? + discard + +proc upConv(p: PProc, n: PNode, r: var TCompRes) = + gen(p, n[0], r) # XXX + +proc genRangeChck(p: PProc, n: PNode, r: var TCompRes, magic: string) = + var a, b: TCompRes = default(TCompRes) + gen(p, n[0], r) + let src = skipTypes(n[0].typ, abstractVarRange) + let dest = skipTypes(n.typ, abstractVarRange) + if optRangeCheck notin p.options: + if optJsBigInt64 in p.config.globalOptions and + dest.kind in {tyUInt..tyUInt32, tyInt..tyInt32} and + src.kind in {tyInt64, tyUInt64}: + # conversions to Number are kept + r.res = "Number($1)" % [r.res] + else: + discard + elif dest.kind in {tyUInt..tyUInt64} and checkUnsignedConversions notin p.config.legacyFeatures: + if src.kind in {tyInt64, tyUInt64} and optJsBigInt64 in p.config.globalOptions: + r.res = "BigInt.asUintN($1, $2)" % [$(dest.size * 8), r.res] + else: + r.res = "BigInt.asUintN($1, BigInt($2))" % [$(dest.size * 8), r.res] + if not (dest.kind == tyUInt64 and optJsBigInt64 in p.config.globalOptions): + r.res = "Number($1)" % [r.res] + else: + if src.kind in {tyInt64, tyUInt64} and dest.kind notin {tyInt64, tyUInt64} and optJsBigInt64 in p.config.globalOptions: + # we do a range check anyway, so it's ok if the number gets rounded + r.res = "Number($1)" % [r.res] + gen(p, n[1], a) + gen(p, n[2], b) + useMagic(p, "chckRange") + r.res = "chckRange($1, $2, $3)" % [r.res, a.res, b.res] + r.kind = resExpr + +proc convStrToCStr(p: PProc, n: PNode, r: var TCompRes) = + # we do an optimization here as this is likely to slow down + # much of the code otherwise: + if n[0].kind == nkCStringToString: + gen(p, n[0][0], r) + else: + gen(p, n[0], r) + if r.res == "": internalError(p.config, n.info, "convStrToCStr") + useMagic(p, "toJSStr") + r.res = "toJSStr($1)" % [r.res] + r.kind = resExpr + +proc convCStrToStr(p: PProc, n: PNode, r: var TCompRes) = + # we do an optimization here as this is likely to slow down + # much of the code otherwise: + if n[0].kind == nkStringToCString: + gen(p, n[0][0], r) + else: + gen(p, n[0], r) + if r.res == "": internalError(p.config, n.info, "convCStrToStr") + useMagic(p, "cstrToNimstr") + r.res = "cstrToNimstr($1)" % [r.res] + r.kind = resExpr + +proc genReturnStmt(p: PProc, n: PNode) = + if p.procDef == nil: internalError(p.config, n.info, "genReturnStmt") + p.beforeRetNeeded = true + if n[0].kind != nkEmpty: + genStmt(p, n[0]) + else: + genLineDir(p, n) + lineF(p, "break BeforeRet;$n", []) + +proc frameCreate(p: PProc; procname, filename: Rope): Rope = + const frameFmt = + "var F = {procname: $1, prev: framePtr, filename: $2, line: 0};$n" + + result = p.indentLine(frameFmt % [procname, filename]) + result.add p.indentLine(ropes.`%`("framePtr = F;$n", [])) + +proc frameDestroy(p: PProc): Rope = + result = p.indentLine rope(("framePtr = F.prev;") & "\L") + +proc genProcBody(p: PProc, prc: PSym): Rope = + if hasFrameInfo(p): + result = frameCreate(p, + makeJSString(prc.owner.name.s & '.' & prc.name.s), + makeJSString(toFilenameOption(p.config, prc.info.fileIndex, foStacktrace))) + else: + result = "" + if p.beforeRetNeeded: + result.add p.indentLine("BeforeRet: {\n") + result.add p.body + result.add p.indentLine("};\n") + else: + result.add(p.body) + if prc.typ.callConv == ccSysCall: + result = ("try {$n$1} catch (e) {$n" & + " alert(\"Unhandled exception:\\n\" + e.message + \"\\n\"$n}") % [result] + if hasFrameInfo(p): + result.add(frameDestroy(p)) + +proc optionalLine(p: Rope): Rope = + if p == "": + return "" + else: + return p & "\L" + +proc genProc(oldProc: PProc, prc: PSym): Rope = + ## Generate a JS procedure ('function'). + result = "" + var + resultSym: PSym + a: TCompRes = default(TCompRes) + #if gVerbosity >= 3: + # echo "BEGIN generating code for: " & prc.name.s + var p = newProc(oldProc.g, oldProc.module, prc.ast, prc.options) + p.up = oldProc + var returnStmt: Rope = "" + var resultAsgn: Rope = "" + var name = mangleName(p.module, prc) + let header = generateHeader(p, prc) + if prc.typ.returnType != nil and sfPure notin prc.flags: + resultSym = prc.ast[resultPos].sym + let mname = mangleName(p.module, resultSym) + # otherwise uses "fat pointers" + let useRawPointer = not isIndirect(resultSym) and + resultSym.typ.kind in {tyVar, tyPtr, tyLent, tyRef, tyOwned} and + mapType(p, resultSym.typ) == etyBaseIndex + if useRawPointer: + resultAsgn = p.indentLine(("var $# = null;$n") % [mname]) + resultAsgn.add p.indentLine("var $#_Idx = 0;$n" % [mname]) + else: + let resVar = createVar(p, resultSym.typ, isIndirect(resultSym)) + resultAsgn = p.indentLine(("var $# = $#;$n") % [mname, resVar]) + gen(p, prc.ast[resultPos], a) + if mapType(p, resultSym.typ) == etyBaseIndex: + returnStmt = "return [$#, $#];$n" % [a.address, a.res] + else: + returnStmt = "return $#;$n" % [a.res] + + var transformedBody = transformBody(p.module.graph, p.module.idgen, prc, {}) + if sfInjectDestructors in prc.flags: + transformedBody = injectDestructorCalls(p.module.graph, p.module.idgen, prc, transformedBody) + + p.nested: genStmt(p, transformedBody) + + + if optLineDir in p.config.options: + result = lineDir(p.config, prc.info, toLinenumber(prc.info)) + + var def: Rope + if not prc.constraint.isNil: + def = runtimeFormat(prc.constraint.strVal & " {$n$#$#$#$#$#", + [ returnType, + name, + header, + optionalLine(p.globals), + optionalLine(p.locals), + optionalLine(resultAsgn), + optionalLine(genProcBody(p, prc)), + optionalLine(p.indentLine(returnStmt))]) + else: + # if optLineDir in p.config.options: + # result.add("\L") + + if p.config.hcrOn: + # Here, we introduce thunks that create the equivalent of a jump table + # for all global functions, because references to them may be stored + # in JavaScript variables. The added indirection ensures that such + # references will end up calling the reloaded code. + var thunkName = name + name = name & "IMLP" + result.add("\Lfunction $#() { return $#.apply(this, arguments); }$n" % + [thunkName, name]) + + def = "\Lfunction $#($#) {$n$#$#$#$#$#" % + [ name, + header, + optionalLine(p.globals), + optionalLine(p.locals), + optionalLine(resultAsgn), + optionalLine(genProcBody(p, prc)), + optionalLine(p.indentLine(returnStmt))] + + dec p.extraIndent + result.add p.indentLine(def) + result.add p.indentLine("}\n") + + #if gVerbosity >= 3: + # echo "END generated code for: " & prc.name.s + +proc genStmt(p: PProc, n: PNode) = + var r: TCompRes = default(TCompRes) + gen(p, n, r) + if r.res != "": lineF(p, "$#;$n", [r.res]) + +proc genPragma(p: PProc, n: PNode) = + for i in 0..<n.len: + let it = n[i] + case whichPragma(it) + of wEmit: genAsmOrEmitStmt(p, it[1]) + of wPush: + processPushBackendOption(p.config, p.optionsStack, p.options, n, i+1) + of wPop: + processPopBackendOption(p.config, p.optionsStack, p.options) + else: discard + +proc genCast(p: PProc, n: PNode, r: var TCompRes) = + var dest = skipTypes(n.typ, abstractVarRange) + var src = skipTypes(n[1].typ, abstractVarRange) + gen(p, n[1], r) + if dest.kind == src.kind: + # no-op conversion + return + let toInt = (dest.kind in tyInt..tyInt32) + let toUint = (dest.kind in tyUInt..tyUInt32) + let fromInt = (src.kind in tyInt..tyInt32) + let fromUint = (src.kind in tyUInt..tyUInt32) + + if toUint: + if fromInt or fromUint: + r.res = "Number(BigInt.asUintN($1, BigInt($2)))" % [$(dest.size * 8), r.res] + elif src.kind in {tyInt64, tyUInt64} and optJsBigInt64 in p.config.globalOptions: + r.res = "Number(BigInt.asUintN($1, $2))" % [$(dest.size * 8), r.res] + elif toInt: + if fromInt or fromUint: + r.res = "Number(BigInt.asIntN($1, BigInt($2)))" % [$(dest.size * 8), r.res] + elif src.kind in {tyInt64, tyUInt64} and optJsBigInt64 in p.config.globalOptions: + r.res = "Number(BigInt.asIntN($1, $2))" % [$(dest.size * 8), r.res] + elif dest.kind == tyInt64 and optJsBigInt64 in p.config.globalOptions: + if fromInt or fromUint or src.kind in {tyBool, tyChar, tyEnum}: + r.res = "BigInt($1)" % [r.res] + elif src.kind in {tyFloat..tyFloat64}: + r.res = "BigInt(Math.trunc($1))" % [r.res] + elif src.kind == tyUInt64: + r.res = "BigInt.asIntN(64, $1)" % [r.res] + elif dest.kind == tyUInt64 and optJsBigInt64 in p.config.globalOptions: + if fromUint or src.kind in {tyBool, tyChar, tyEnum}: + r.res = "BigInt($1)" % [r.res] + elif fromInt: # could be negative + r.res = "BigInt.asUintN(64, BigInt($1))" % [r.res] + elif src.kind in {tyFloat..tyFloat64}: + r.res = "BigInt.asUintN(64, BigInt(Math.trunc($1)))" % [r.res] + elif src.kind == tyInt64: + r.res = "BigInt.asUintN(64, $1)" % [r.res] + elif dest.kind in tyFloat..tyFloat64: + if src.kind in {tyInt64, tyUInt64} and optJsBigInt64 in p.config.globalOptions: + r.res = "Number($1)" % [r.res] + elif (src.kind == tyPtr and mapType(p, src) == etyObject) and dest.kind == tyPointer: + r.address = r.res + r.res = "null" + r.typ = etyBaseIndex + elif (dest.kind == tyPtr and mapType(p, dest) == etyObject) and src.kind == tyPointer: + r.res = r.address + r.typ = etyObject + +proc gen(p: PProc, n: PNode, r: var TCompRes) = + r.typ = etyNone + if r.kind != resCallee: r.kind = resNone + #r.address = "" + r.res = "" + + case n.kind + of nkSym: + genSym(p, n, r) + of nkCharLit..nkUInt64Lit: + case n.typ.skipTypes(abstractVarRange).kind + of tyBool: + r.res = if n.intVal == 0: rope"false" else: rope"true" + of tyUInt64: + r.res = rope($cast[BiggestUInt](n.intVal)) + if optJsBigInt64 in p.config.globalOptions: + r.res.add('n') + of tyInt64: + let wrap = n.intVal < 0 # wrap negative integers with parens + if wrap: r.res.add '(' + r.res.addInt n.intVal + if optJsBigInt64 in p.config.globalOptions: + r.res.add('n') + if wrap: r.res.add ')' + else: + let wrap = n.intVal < 0 # wrap negative integers with parens + if wrap: r.res.add '(' + r.res.addInt n.intVal + if wrap: r.res.add ')' + r.kind = resExpr + of nkNilLit: + if isEmptyType(n.typ): + discard + elif mapType(p, n.typ) == etyBaseIndex: + r.typ = etyBaseIndex + r.address = rope"null" + r.res = rope"0" + r.kind = resExpr + else: + r.res = rope"null" + r.kind = resExpr + of nkStrLit..nkTripleStrLit: + if skipTypes(n.typ, abstractVarRange).kind == tyString: + if n.strVal.len <= 64: + r.res = makeJsNimStrLit(n.strVal) + else: + useMagic(p, "makeNimstrLit") + r.res = "makeNimstrLit($1)" % [makeJSString(n.strVal)] + else: + r.res = makeJSString(n.strVal, false) + r.kind = resExpr + of nkFloatLit..nkFloat64Lit: + let f = n.floatVal + case classify(f) + of fcNan: + if signbit(f): + r.res = rope"-NaN" + else: + r.res = rope"NaN" + of fcNegZero: + r.res = rope"-0.0" + of fcZero: + r.res = rope"0.0" + of fcInf: + r.res = rope"Infinity" + of fcNegInf: + r.res = rope"-Infinity" + else: + if n.typ.skipTypes(abstractVarRange).kind == tyFloat32: + r.res.addFloatRoundtrip(f.float32) + else: + r.res.addFloatRoundtrip(f) + r.kind = resExpr + of nkCallKinds: + if isEmptyType(n.typ): + genLineDir(p, n) + if (n[0].kind == nkSym) and (n[0].sym.magic != mNone): + genMagic(p, n, r) + elif n[0].kind == nkSym and sfInfixCall in n[0].sym.flags and + n.len >= 1: + genInfixCall(p, n, r) + else: + genCall(p, n, r) + of nkClosure: + if jsNoLambdaLifting in p.config.legacyFeatures: + gen(p, n[0], r) + else: + let tmp = getTemp(p) + var a: TCompRes = default(TCompRes) + var b: TCompRes = default(TCompRes) + gen(p, n[0], a) + gen(p, n[1], b) + lineF(p, "$1 = $2.bind($3); $1.ClP_0 = $2; $1.ClE_0 = $3;$n", [tmp, a.rdLoc, b.rdLoc]) + r.res = tmp + r.kind = resVal + of nkCurly: genSetConstr(p, n, r) + of nkBracket: genArrayConstr(p, n, r) + of nkPar, nkTupleConstr: genTupleConstr(p, n, r) + of nkObjConstr: genObjConstr(p, n, r) + of nkHiddenStdConv, nkHiddenSubConv, nkConv: genConv(p, n, r) + of nkAddr, nkHiddenAddr: + if n.typ.kind in {tyLent}: + gen(p, n[0], r) + else: + genAddr(p, n, r) + of nkDerefExpr, nkHiddenDeref: + if n.typ.kind in {tyLent}: + gen(p, n[0], r) + else: + genDeref(p, n, r) + of nkBracketExpr: genArrayAccess(p, n, r) + of nkDotExpr: genFieldAccess(p, n, r) + of nkCheckedFieldExpr: genCheckedFieldOp(p, n, nil, r) + of nkObjDownConv: gen(p, n[0], r) + of nkObjUpConv: upConv(p, n, r) + of nkCast: genCast(p, n, r) + of nkChckRangeF: genRangeChck(p, n, r, "chckRangeF") + of nkChckRange64: genRangeChck(p, n, r, "chckRange64") + of nkChckRange: genRangeChck(p, n, r, "chckRange") + of nkStringToCString: convStrToCStr(p, n, r) + of nkCStringToString: convCStrToStr(p, n, r) + of nkEmpty: discard + of nkLambdaKinds: + let s = n[namePos].sym + discard mangleName(p.module, s) + r.res = s.loc.snippet + if lfNoDecl in s.loc.flags or s.magic notin generatedMagics: discard + elif not p.g.generatedSyms.containsOrIncl(s.id): + p.locals.add(genProc(p, s)) + of nkType: r.res = genTypeInfo(p, n.typ) + of nkStmtList, nkStmtListExpr: + # this shows the distinction is nice for backends and should be kept + # in the frontend + let isExpr = not isEmptyType(n.typ) + for i in 0..<n.len - isExpr.ord: + genStmt(p, n[i]) + if isExpr: + gen(p, lastSon(n), r) + of nkBlockStmt, nkBlockExpr: genBlock(p, n, r) + of nkIfStmt, nkIfExpr: genIf(p, n, r) + of nkWhen: + # This is "when nimvm" node + gen(p, n[1][0], r) + of nkWhileStmt: genWhileStmt(p, n) + of nkVarSection, nkLetSection: genVarStmt(p, n) + of nkConstSection: discard + of nkForStmt, nkParForStmt: + internalError(p.config, n.info, "for statement not eliminated") + of nkCaseStmt: genCaseJS(p, n, r) + of nkReturnStmt: genReturnStmt(p, n) + of nkBreakStmt: genBreakStmt(p, n) + of nkAsgn: genAsgn(p, n) + of nkFastAsgn, nkSinkAsgn: genFastAsgn(p, n) + of nkDiscardStmt: + if n[0].kind != nkEmpty: + genLineDir(p, n) + gen(p, n[0], r) + r.res = "(" & r.res & ")" + of nkAsmStmt: + warningDeprecated(p.config, n.info, "'asm' for the JS target is deprecated, use the 'emit' pragma") + genAsmOrEmitStmt(p, n, true) + of nkTryStmt, nkHiddenTryStmt: genTry(p, n, r) + of nkRaiseStmt: genRaiseStmt(p, n) + of nkTypeSection, nkCommentStmt, nkIncludeStmt, + nkImportStmt, nkImportExceptStmt, nkExportStmt, nkExportExceptStmt, + nkFromStmt, nkTemplateDef, nkMacroDef, nkIteratorDef, nkStaticStmt, + nkMixinStmt, nkBindStmt: discard + of nkPragma: genPragma(p, n) + of nkProcDef, nkFuncDef, nkMethodDef, nkConverterDef: + var s = n[namePos].sym + if {sfExportc, sfCompilerProc} * s.flags == {sfExportc}: + genSym(p, n[namePos], r) + r.res = "" + of nkGotoState, nkState: + globalError(p.config, n.info, "not implemented") + of nkBreakState: + genBreakState(p, n[0], r) + of nkPragmaBlock: gen(p, n.lastSon, r) + of nkComesFrom: + discard "XXX to implement for better stack traces" + else: internalError(p.config, n.info, "gen: unknown node type: " & $n.kind) + +proc newModule(g: ModuleGraph; module: PSym): BModule = + ## Create a new JS backend module node. + if g.backend == nil: + g.backend = newGlobals() + result = BModule(module: module, sigConflicts: initCountTable[SigHash](), + graph: g, config: g.config + ) + if sfSystemModule in module.flags: + PGlobals(g.backend).inSystem = true + +proc genHeader(): Rope = + ## Generate the JS header. + result = rope("""/* Generated by the Nim Compiler v$1 */ + var framePtr = null; + var excHandler = 0; + var lastJSError = null; + """.unindent.format(VersionAsString)) + +proc addHcrInitGuards(p: PProc, n: PNode, + moduleLoadedVar: Rope, inInitGuard: var bool) = + if n.kind == nkStmtList: + for child in n: + addHcrInitGuards(p, child, moduleLoadedVar, inInitGuard) + else: + let stmtShouldExecute = n.kind in { + nkProcDef, nkFuncDef, nkMethodDef,nkConverterDef, + nkVarSection, nkLetSection} or nfExecuteOnReload in n.flags + + if inInitGuard: + if stmtShouldExecute: + dec p.extraIndent + line(p, "}\L") + inInitGuard = false + else: + if not stmtShouldExecute: + lineF(p, "if ($1 == undefined) {$n", [moduleLoadedVar]) + inc p.extraIndent + inInitGuard = true + + genStmt(p, n) + +proc genModule(p: PProc, n: PNode) = + ## Generate the JS module code. + ## Called for each top level node in a Nim module. + if optStackTrace in p.options: + p.body.add(frameCreate(p, + makeJSString("module " & p.module.module.name.s), + makeJSString(toFilenameOption(p.config, p.module.module.info.fileIndex, foStacktrace)))) + var transformedN = transformStmt(p.module.graph, p.module.idgen, p.module.module, n) + if sfInjectDestructors in p.module.module.flags: + transformedN = injectDestructorCalls(p.module.graph, p.module.idgen, p.module.module, transformedN) + if p.config.hcrOn and n.kind == nkStmtList: + let moduleSym = p.module.module + var moduleLoadedVar = rope(moduleSym.name.s) & "_loaded" & + idOrSig(moduleSym, moduleSym.name.s, p.module.sigConflicts, p.config) + lineF(p, "var $1;$n", [moduleLoadedVar]) + var inGuardedBlock = false + + addHcrInitGuards(p, transformedN, moduleLoadedVar, inGuardedBlock) + + if inGuardedBlock: + dec p.extraIndent + line(p, "}\L") + + lineF(p, "$1 = true;$n", [moduleLoadedVar]) + else: + genStmt(p, transformedN) + + if optStackTrace in p.options: + p.body.add(frameDestroy(p)) + +proc processJSCodeGen*(b: PPassContext, n: PNode): PNode = + ## Generate JS code for a node. + result = n + let m = BModule(b) + if pipelineutils.skipCodegen(m.config, n): return n + if m.module == nil: internalError(m.config, n.info, "myProcess") + let globals = PGlobals(m.graph.backend) + var p = newInitProc(globals, m) + m.initProc = p + p.unique = globals.unique + genModule(p, n) + p.g.code.add(p.locals) + p.g.code.add(p.body) + +proc wholeCode(graph: ModuleGraph; m: BModule): Rope = + ## Combine source code from all nodes. + let globals = PGlobals(graph.backend) + for prc in globals.forwarded: + if not globals.generatedSyms.containsOrIncl(prc.id): + var p = newInitProc(globals, m) + attachProc(p, prc) + + generateIfMethodDispatchers(graph, m.idgen) + for prc in getDispatchers(graph): + if not globals.generatedSyms.containsOrIncl(prc.id): + var p = newInitProc(globals, m) + attachProc(p, prc) + + result = globals.typeInfo & globals.constants & globals.code + +proc finalJSCodeGen*(graph: ModuleGraph; b: PPassContext, n: PNode): PNode = + ## Finalize JS code generation of a Nim module. + ## Param `n` may contain nodes returned from the last module close call. + var m = BModule(b) + if sfMainModule in m.module.flags: + # Add global destructors to the module. + # This must come before the last call to `myProcess`. + for i in countdown(high(graph.globalDestructors), 0): + n.add graph.globalDestructors[i] + # Process any nodes left over from the last call to `myClose`. + result = processJSCodeGen(b, n) + # Some codegen is different (such as no stacktraces; see `initProcOptions`) + # when `std/system` is being processed. + if sfSystemModule in m.module.flags: + PGlobals(graph.backend).inSystem = false + # Check if codegen should continue before any files are generated. + # It may bail early is if too many errors have been raised. + if pipelineutils.skipCodegen(m.config, n): return n + # Nim modules are compiled into a single JS file. + # If this is the main module, then this is the final call to `myClose`. + if sfMainModule in m.module.flags: + var code = genHeader() & wholeCode(graph, m) + let outFile = m.config.prepareToWriteOutput() + # Generate an optional source map. + if optSourcemap in m.config.globalOptions: + var map: SourceMap + map = genSourceMap($code, outFile.string) + code &= "\n//# sourceMappingURL=$#.map" % [outFile.string] + writeFile(outFile.string & ".map", $(%map)) + # Check if the generated JS code matches the output file, or else + # write it to the file. + if not equalsFile(code, outFile): + if not writeRope(code, outFile): + rawMessage(m.config, errCannotOpenFile, outFile.string) + +proc setupJSgen*(graph: ModuleGraph; s: PSym; idgen: IdGenerator): PPassContext = + result = newModule(graph, s) + result.idgen = idgen |