#
#
#           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, ccgutils, wordrecg, renderer,
  cgmeth, lowerings, sighashes, modulegraphs, lineinfos, rodutils,
  transf, injectdestructors, sourcemap, astmsgs, backendpragmas

import pipelineutils

import json, sets, math, tables, intsets
import strutils except addf

when defined(nimPreviewSlimSystem):
  import std/[assertions, syncio]


type
  TJSGen = object of PPassContext
    module: PSym
    graph: ModuleGraph
    config: ConfigRef
    sigConflicts: CountTable[SigHash]

  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]
    module: BModule
    g: PGlobals
    generatedParamCopies: IntSet
    beforeRetNeeded: bool
    unique: int    # for temp identifier generation
    blocks: seq[TBlock]
    extraIndent: int
    up: PProc     # up the call chain; required for closure support
    declaredGlobals: IntSet
    previousFileName: string  # For frameInfo inside templates.

template config*(p: PProc): ConfigRef = p.module.config

proc indentLine(p: PProc, r: Rope): Rope =
  var p = p
  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

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 =
  new(result)
  result.forwarded = @[]
  result.generatedSyms = initIntSet()
  result.typeInfoGenerated = initIntSet()

proc initCompRes(r: var TCompRes) =
  r.address = ""
  r.res = ""
  r.tmpLoc = ""
  r.typ = etyNone
  r.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: @[],
    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))

proc declareGlobal(p: PProc; id: int; r: Rope) =
  if p.prc != nil and not p.declaredGlobals.containsOrIncl(id):
    p.locals.addf("global $1;$n", [r])

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.lastSon, 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, tyProxy, tyLent:
    # tyLent is no-op as JS has pass-by-reference semantics
    result = mapType(t[0])
  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.lastSon)
  of tyStatic:
    if t.n != nil: result = mapType(lastSon t)
    else: result = etyNone
  of tyProc: result = etyProc
  of tyCstring: result = etyString
  of tyConcept, tyIterable: doAssert false

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.r
  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))
      else:
        result.add("_")
        result.add(rope(s.id))
    s.loc.r = 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:
    if n.isAtom:
      result = true

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
  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
  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"],
    mIntToStr: ["cstrToNimstr", "cstrToNimstr"],
    mInt64ToStr: ["cstrToNimstr", "cstrToNimstr"],
    mFloatToStr: ["cstrToNimstr", "cstrToNimstr"],
    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
  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
        first: Int128
      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[0])
      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
  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
  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 arithAux(p: PProc, n: PNode, r: var TCompRes, op: TMagic) =
  var
    x, y: 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: applyFormat("addInt($1, $2)", "($1 + $2)")
  of mPred: applyFormat("subInt($1, $2)", "($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: 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 mIntToStr: applyFormat("cstrToNimstr(($1) + \"\")", "cstrToNimstr(($1) + \"\")")
  of mInt64ToStr: applyFormat("cstrToNimstr(($1) + \"\")", "cstrToNimstr(($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, mIntToStr, mInt64ToStr, 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
      gen(p, n[1], x)
      gen(p, n[2], y)
      r.res = "($# == $# && $# == $#)" % [x.address, y.address, x.res, y.res]
  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
  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
  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:
          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.r])
        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.r = mangleName(p.module, excAlias.sym)
        lineF(p, "var $1 = lastJSError;$n", excAlias.sym.loc.r)
      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
    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
  genLineDir(p, n)
  gen(p, n[0], cond)
  let typeKind = skipTypes(n[0].typ, abstractVar).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:
         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) =
  genLineDir(p, n)
  p.body.add p.indentLine("")
  for i in 0..<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: 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
      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
  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, typ: PType): Rope =
  result = ""
  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 =
  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 (y.kind == nkSym and y.sym.kind == skParam)) and
          (skipTypes(y.typ, abstractInst).kind in
            {tyRef, tyPtr, tyLent, tyVar, tyCstring, tyProc, tyOwned} + IntegralTypes))

proc genAsgnAux(p: PProc, x, y: PNode, noCopyNeeded: bool) =
  var a, b: 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} 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) =
  var a, b: TCompRes
  gen(p, n[1], a)
  gen(p, n[2], b)
  var tmp = p.getTemp(false)
  if mapType(p, skipTypes(n[1].typ, abstractVar)) == etyBaseIndex:
    let tmp2 = p.getTemp(false)
    if a.typ != etyBaseIndex or b.typ != etyBaseIndex:
      internalError(p.config, n.info, "genSwap")
    lineF(p, "var $1 = $2; $2 = $3; $3 = $1;$n",
             [tmp, a.address, b.address])
    tmp = tmp2
  lineF(p, "var $1 = $2; $2 = $3; $3 = $1;",
           [tmp, a.res, b.res])

proc getFieldPosition(p: PProc; f: PNode): int =
  case f.kind
  of nkIntLit..nkUInt64Lit: result = int(f.intVal)
  of nkSym: result = f.sym.position
  else: internalError(p.config, f.info, "genFieldPosition")

proc genFieldAddr(p: PProc, n: PNode, r: var TCompRes) =
  var a: 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.r == "": f.loc.r = mangleName(p.module, f)
    r.res = makeJSString($f.loc.r)
  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.r == "": f.loc.r = mangleName(p.module, f)
    r.res = "$1.$2" % [r.res, f.loc.r]
    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.r == "": field.loc.r = mangleName(p.module, field)
  # Discriminant symbol
  let disc = checkExpr[2].sym
  internalAssert p.config, disc.kind == skField
  if disc.loc.r == "": disc.loc.r = mangleName(p.module, disc)

  var setx: TCompRes
  gen(p, checkExpr[1], setx)

  var obj: 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.r, 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.r)
    r.address = tmp
  else:
    r.typ = etyNone
    r.res = "$1.$2" % [tmp, field.loc.r]
  r.kind = resExpr

proc genArrayAddr(p: PProc, n: PNode, r: var TCompRes) =
  var
    a, b: TCompRes
    first: Int128
  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[0])
  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)
  if ty.kind in {tyRef, tyPtr, tyLent, tyOwned}: ty = skipTypes(ty.lastSon, 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.r == "": internalError(p.config, n.info, "genAddr: 3")
  case s.kind
  of skParam:
    r.res = s.loc.r
    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.r & "[0]"
      else:
        r.res = s.loc.r
      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.r
      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).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):
    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)

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.r, 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.r == "":
      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 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.r]
          r.res = "$1[0][1]" % [s.loc.r]
        else:
          r.address = "$1[0]" % [s.loc.r]
          r.res = "$1[1]" % [s.loc.r]
      else:
        r.address = s.loc.r
        r.res = s.loc.r & "_Idx"
    elif isIndirect(s):
      r.res = "$1[0]" % [s.loc.r]
    else:
      r.res = s.loc.r
  of skConst:
    genConstant(p, s)
    if s.loc.r == "":
      internalError(p.config, n.info, "symbol has no generated name: " & s.name.s)
    r.res = s.loc.r
  of skProc, skFunc, skConverter, skMethod:
    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.r
    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.r == "":
      internalError(p.config, n.info, "symbol has no generated name: " & s.name.s)
    if mapType(p, s.typ) == etyBaseIndex:
      r.address = s.loc.r
      r.res = s.loc.r & "_Idx"
    else:
      r.res = s.loc.r
  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
    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
  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
  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.r == "": f.loc.r = mangleName(p.module, f)
  if sfInfixCall in f.flags:
    let pat = $n[0].sym.loc.r
    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
  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[0]

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.r == "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, lastSon(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:
    result = putToSeq("null", indirect)
  of tyStatic:
    if t.n != nil:
      result = createVar(p, lastSon 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
    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 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.r, a.address, a.res]))
        else:
          if isIndirect(v):
            line(p, runtimeFormat(varCode & " = [[$3, $4]];$n",
                     [returnType, v.loc.r, a.address, a.res]))
          else:
            line(p, runtimeFormat(varCode & " = [$3, $4];$n",
                     [returnType, v.loc.r, 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.r])
        else:
          line(p, runtimeFormat(varCode & " = $3;$n", [returnType, v.loc.r, a.res]))
      return
    else:
      s = a.res
    if isIndirect(v):
      line(p, runtimeFormat(varCode & " = [$3];$n", [returnType, v.loc.r, s]))
    else:
      line(p, runtimeFormat(varCode & " = $3;$n", [returnType, v.loc.r, s]))

  if useReloadingGuard or useGlobalPragmas:
    dec p.extraIndent
    lineF(p, "}$n")

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)
        assert(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]

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
  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
  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

  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

  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
  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 genReset(p: PProc, n: PNode) =
  var x: TCompRes
  useMagic(p, "genericReset")
  gen(p, n[1], x)
  if x.typ == etyBaseIndex:
    lineF(p, "$1 = null, $2 = 0;$n", [x.address, x.res])
  else:
    let (a, tmp) = maybeMakeTempAssignable(p, n[1], x)
    lineF(p, "$1 = genericReset($3, $2);$n", [a,
                  genTypeInfo(p, n[1].typ), tmp])

proc genMove(p: PProc; n: PNode; r: var TCompRes) =
  var a: TCompRes
  r.kind = resVal
  r.res = p.getTemp()
  gen(p, n[1], a)
  lineF(p, "$1 = $2;$n", [r.rdLoc, a.rdLoc])
  genReset(p, n)
  #lineF(p, "$1 = $2;$n", [dest.rdLoc, src.rdLoc])

proc genDup(p: PProc; n: PNode; r: var TCompRes) =
  var a: 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
  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
    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
    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
      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
      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
    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
    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
    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 mReset, mWasMoved: genReset(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
    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
  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
    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
  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
  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.r == "": f.loc.r = 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.r, 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.r, 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
  gen(p, n[0], r)
  let src = skipTypes(n[0].typ, abstractVarRange)
  let dest = skipTypes(n.typ, abstractVarRange)
  if src.kind in {tyInt64, tyUInt64} and dest.kind notin {tyInt64, tyUInt64} and optJsBigInt64 in p.config.globalOptions:
    r.res = "Number($1)" % [r.res]
  if optRangeCheck notin p.options or (dest.kind in {tyUInt..tyUInt64} and
      checkUnsignedConversions notin p.config.legacyFeatures):
    discard "XXX maybe emit masking instructions here"
  else:
    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').
  var
    resultSym: PSym
    a: 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.typ)
  if prc.typ[0] != 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, dontUseCache)
  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
  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.optionsStack, p.options, n, i+1)
    of wPop:
      processPopBackendOption(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: r.res = rope(f.toStrMaxPrecision)
    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: gen(p, n[0], r)
  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.r
    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 = "var _ = " & r.res
  of nkAsmStmt: genAsmOrEmitStmt(p, n)
  of nkTryStmt, nkHiddenTryStmt: genTry(p, n, r)
  of nkRaiseStmt: genRaiseStmt(p, n)
  of nkTypeSection, nkCommentStmt, nkIncludeStmt,
     nkImportStmt, nkImportExceptStmt, nkExportStmt, nkExportExceptStmt,
     nkFromStmt, nkTemplateDef, nkMacroDef, nkStaticStmt,
     nkMixinStmt, nkBindStmt: discard
  of nkIteratorDef:
    if n[0].sym.typ.callConv == TCallingConvention.ccClosure:
      globalError(p.config, n.info, "Closure iterators are not supported by JS backend!")
  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, "First class iterators not implemented")
  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.
  new(result)
  result.module = module
  result.sigConflicts = initCountTable[SigHash]()
  if g.backend == nil:
    g.backend = newGlobals()
  result.graph = g
  result.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)
  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)

  var disp = generateMethodDispatchers(graph, m.idgen)
  for i in 0..<disp.len:
    let prc = disp[i].sym
    if not globals.generatedSyms.containsOrIncl(prc.id):
      var p = newInitProc(globals, m)
      attachProc(p, prc)

  result = globals.typeInfo & globals.constants & globals.code

proc getClassName(t: PType): Rope =
  var s = t.sym
  if s.isNil or sfAnon in s.flags:
    s = skipTypes(t, abstractPtrs).sym
  if s.isNil or sfAnon in s.flags:
    doAssert(false, "cannot retrieve class name")
  if s.loc.r != "": result = s.loc.r
  else: result = rope(s.name.s)

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
