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