Support code hot reloading for JavaScript projects (#7362)
* Support code hot reloading for JavaScript projects * Add some missing JavaScript symbols and APIs * fix the Travis build * (review changes) remove the js type from the standard library as it doesn't follow NEP-1 * more additions to the DOM module * Follow NEP-1 in jsffi; spell 'hot code reloading' correctly * introduce a jscore module * Document jscore module. * readded js type * Remove the '$' operator that doesn't behave
This commit is contained in:
parent
1d1d6f39a3
commit
e3037a2f33
14 changed files with 285 additions and 68 deletions
15
changelog.md
15
changelog.md
|
|
@ -61,6 +61,15 @@
|
||||||
|
|
||||||
### Compiler changes
|
### Compiler changes
|
||||||
|
|
||||||
- The VM's instruction count limit was raised to 1 billion instructions in order
|
- The VM's instruction count limit was raised to 1 billion instructions in
|
||||||
to support more complex computations at compile-time.
|
order to support more complex computations at compile-time.
|
||||||
- Added ``macros.getProjectPath`` and ``ospaths.putEnv`` procs to VM.
|
|
||||||
|
- Support for hot code reloading has been implemented for the JavaScript
|
||||||
|
target. To use it, compile your code with `--hotReloading:on` and use a
|
||||||
|
helper library such as LiveReload or BrowserSync.
|
||||||
|
|
||||||
|
- Added ``macros.getProjectPath`` and ``ospaths.putEnv`` procs to Nim's virtual
|
||||||
|
machine.
|
||||||
|
|
||||||
|
### Bugfixes
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -44,38 +44,11 @@ when false:
|
||||||
assert p.kind == skPackage
|
assert p.kind == skPackage
|
||||||
result = gDebugInfo.register(p.name.s, m.name.s)
|
result = gDebugInfo.register(p.name.s, m.name.s)
|
||||||
|
|
||||||
proc idOrSig(m: BModule; s: PSym): Rope =
|
|
||||||
if s.kind in routineKinds and s.typ != nil:
|
|
||||||
# signatures for exported routines are reliable enough to
|
|
||||||
# produce a unique name and this means produced C++ is more stable wrt
|
|
||||||
# Nim changes:
|
|
||||||
let sig = hashProc(s)
|
|
||||||
result = rope($sig)
|
|
||||||
#let m = if s.typ.callConv != ccInline: findPendingModule(m, s) else: m
|
|
||||||
let counter = m.sigConflicts.getOrDefault(sig)
|
|
||||||
#if sigs == "_jckmNePK3i2MFnWwZlp6Lg" and s.name.s == "contains":
|
|
||||||
# echo "counter ", counter, " ", s.id
|
|
||||||
if counter != 0:
|
|
||||||
result.add "_" & rope(counter+1)
|
|
||||||
# this minor hack is necessary to make tests/collections/thashes compile.
|
|
||||||
# The inlined hash function's original module is ambiguous so we end up
|
|
||||||
# generating duplicate names otherwise:
|
|
||||||
if s.typ.callConv == ccInline:
|
|
||||||
result.add rope(m.module.name.s)
|
|
||||||
m.sigConflicts.inc(sig)
|
|
||||||
else:
|
|
||||||
let sig = hashNonProc(s)
|
|
||||||
result = rope($sig)
|
|
||||||
let counter = m.sigConflicts.getOrDefault(sig)
|
|
||||||
if counter != 0:
|
|
||||||
result.add "_" & rope(counter+1)
|
|
||||||
m.sigConflicts.inc(sig)
|
|
||||||
|
|
||||||
proc mangleName(m: BModule; s: PSym): Rope =
|
proc mangleName(m: BModule; s: PSym): Rope =
|
||||||
result = s.loc.r
|
result = s.loc.r
|
||||||
if result == nil:
|
if result == nil:
|
||||||
result = s.name.s.mangle.rope
|
result = s.name.s.mangle.rope
|
||||||
add(result, m.idOrSig(s))
|
add(result, idOrSig(s, m.module.name.s, m.sigConflicts))
|
||||||
s.loc.r = result
|
s.loc.r = result
|
||||||
writeMangledName(m.ndi, s)
|
writeMangledName(m.ndi, s)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -473,6 +473,10 @@ proc processSwitch(switch, arg: string, pass: TCmdLinePass, info: TLineInfo;
|
||||||
processOnOffSwitch({optMemTracker}, arg, pass, info)
|
processOnOffSwitch({optMemTracker}, arg, pass, info)
|
||||||
if optMemTracker in gOptions: defineSymbol("memtracker")
|
if optMemTracker in gOptions: defineSymbol("memtracker")
|
||||||
else: undefSymbol("memtracker")
|
else: undefSymbol("memtracker")
|
||||||
|
of "hotreloading":
|
||||||
|
processOnOffSwitch({optHotReloading}, arg, pass, info)
|
||||||
|
if optHotReloading in gOptions: defineSymbol("hotreloading")
|
||||||
|
else: undefSymbol("hotreloading")
|
||||||
of "oldnewlines":
|
of "oldnewlines":
|
||||||
case arg.normalize
|
case arg.normalize
|
||||||
of "on":
|
of "on":
|
||||||
|
|
|
||||||
|
|
@ -31,9 +31,9 @@ implements the required case distinction.
|
||||||
|
|
||||||
import
|
import
|
||||||
ast, astalgo, strutils, hashes, trees, platform, magicsys, extccomp, options,
|
ast, astalgo, strutils, hashes, trees, platform, magicsys, extccomp, options,
|
||||||
nversion, nimsets, msgs, std / sha1, bitsets, idents, types, os,
|
nversion, nimsets, msgs, std / sha1, bitsets, idents, types, os, tables,
|
||||||
times, ropes, math, passes, ccgutils, wordrecg, renderer, rodread, rodutils,
|
times, ropes, math, passes, ccgutils, wordrecg, renderer, rodread, rodutils,
|
||||||
intsets, cgmeth, lowerings
|
intsets, cgmeth, lowerings, sighashes
|
||||||
|
|
||||||
from modulegraphs import ModuleGraph
|
from modulegraphs import ModuleGraph
|
||||||
|
|
||||||
|
|
@ -43,6 +43,7 @@ type
|
||||||
TJSGen = object of TPassContext
|
TJSGen = object of TPassContext
|
||||||
module: PSym
|
module: PSym
|
||||||
target: TTarget
|
target: TTarget
|
||||||
|
sigConflicts: CountTable[SigHash]
|
||||||
|
|
||||||
BModule = ref TJSGen
|
BModule = ref TJSGen
|
||||||
TJSTypeKind = enum # necessary JS "types"
|
TJSTypeKind = enum # necessary JS "types"
|
||||||
|
|
@ -210,7 +211,7 @@ proc mapType(p: PProc; typ: PType): TJSTypeKind =
|
||||||
if p.target == targetPHP: result = etyObject
|
if p.target == targetPHP: result = etyObject
|
||||||
else: result = mapType(typ)
|
else: result = mapType(typ)
|
||||||
|
|
||||||
proc mangleName(s: PSym; target: TTarget): Rope =
|
proc mangleName(m: BModule, s: PSym): Rope =
|
||||||
proc validJsName(name: string): bool =
|
proc validJsName(name: string): bool =
|
||||||
result = true
|
result = true
|
||||||
const reservedWords = ["abstract", "await", "boolean", "break", "byte",
|
const reservedWords = ["abstract", "await", "boolean", "break", "byte",
|
||||||
|
|
@ -235,7 +236,7 @@ proc mangleName(s: PSym; target: TTarget): Rope =
|
||||||
if result == nil:
|
if result == nil:
|
||||||
if s.kind == skField and s.name.s.validJsName:
|
if s.kind == skField and s.name.s.validJsName:
|
||||||
result = rope(s.name.s)
|
result = rope(s.name.s)
|
||||||
elif target == targetJS or s.kind == skTemp:
|
elif m.target == targetJS or s.kind == skTemp:
|
||||||
result = rope(mangle(s.name.s))
|
result = rope(mangle(s.name.s))
|
||||||
else:
|
else:
|
||||||
var x = newStringOfCap(s.name.s.len)
|
var x = newStringOfCap(s.name.s.len)
|
||||||
|
|
@ -254,8 +255,13 @@ proc mangleName(s: PSym; target: TTarget): Rope =
|
||||||
inc i
|
inc i
|
||||||
result = rope(x)
|
result = rope(x)
|
||||||
if s.name.s != "this" and s.kind != skField:
|
if s.name.s != "this" and s.kind != skField:
|
||||||
add(result, "_")
|
if optHotReloading in gOptions:
|
||||||
add(result, rope(s.id))
|
# When hot reloading is enabled, we must ensure that the names
|
||||||
|
# of functions and types will be preserved across rebuilds:
|
||||||
|
add(result, idOrSig(s, m.module.name.s, m.sigConflicts))
|
||||||
|
else:
|
||||||
|
add(result, "_")
|
||||||
|
add(result, rope(s.id))
|
||||||
s.loc.r = result
|
s.loc.r = result
|
||||||
|
|
||||||
proc escapeJSString(s: string): string =
|
proc escapeJSString(s: string): string =
|
||||||
|
|
@ -821,7 +827,7 @@ proc genAsmOrEmitStmt(p: PProc, n: PNode) =
|
||||||
# for backwards compatibility we don't deref syms here :-(
|
# for backwards compatibility we don't deref syms here :-(
|
||||||
if v.kind in {skVar, skLet, skTemp, skConst, skResult, skParam, skForVar}:
|
if v.kind in {skVar, skLet, skTemp, skConst, skResult, skParam, skForVar}:
|
||||||
if p.target == targetPHP: p.body.add "$"
|
if p.target == targetPHP: p.body.add "$"
|
||||||
p.body.add mangleName(v, p.target)
|
p.body.add mangleName(p.module, v)
|
||||||
else:
|
else:
|
||||||
var r: TCompRes
|
var r: TCompRes
|
||||||
gen(p, it, r)
|
gen(p, it, r)
|
||||||
|
|
@ -862,7 +868,7 @@ proc generateHeader(p: PProc, typ: PType): Rope =
|
||||||
var param = typ.n.sons[i].sym
|
var param = typ.n.sons[i].sym
|
||||||
if isCompileTimeOnly(param.typ): continue
|
if isCompileTimeOnly(param.typ): continue
|
||||||
if result != nil: add(result, ", ")
|
if result != nil: add(result, ", ")
|
||||||
var name = mangleName(param, p.target)
|
var name = mangleName(p.module, param)
|
||||||
if p.target == targetJS:
|
if p.target == targetJS:
|
||||||
add(result, name)
|
add(result, name)
|
||||||
if mapType(param.typ) == etyBaseIndex:
|
if mapType(param.typ) == etyBaseIndex:
|
||||||
|
|
@ -1012,7 +1018,7 @@ proc genFieldAddr(p: PProc, n: PNode, r: var TCompRes) =
|
||||||
else:
|
else:
|
||||||
if b.sons[1].kind != nkSym: internalError(b.sons[1].info, "genFieldAddr")
|
if b.sons[1].kind != nkSym: internalError(b.sons[1].info, "genFieldAddr")
|
||||||
var f = b.sons[1].sym
|
var f = b.sons[1].sym
|
||||||
if f.loc.r == nil: f.loc.r = mangleName(f, p.target)
|
if f.loc.r == nil: f.loc.r = mangleName(p.module, f)
|
||||||
r.res = makeJSString($f.loc.r)
|
r.res = makeJSString($f.loc.r)
|
||||||
internalAssert a.typ != etyBaseIndex
|
internalAssert a.typ != etyBaseIndex
|
||||||
r.address = a.res
|
r.address = a.res
|
||||||
|
|
@ -1028,7 +1034,7 @@ proc genFieldAccess(p: PProc, n: PNode, r: var TCompRes) =
|
||||||
else:
|
else:
|
||||||
if n.sons[1].kind != nkSym: internalError(n.sons[1].info, "genFieldAccess")
|
if n.sons[1].kind != nkSym: internalError(n.sons[1].info, "genFieldAccess")
|
||||||
var f = n.sons[1].sym
|
var f = n.sons[1].sym
|
||||||
if f.loc.r == nil: f.loc.r = mangleName(f, p.target)
|
if f.loc.r == nil: f.loc.r = mangleName(p.module, f)
|
||||||
if p.target == targetJS:
|
if p.target == targetJS:
|
||||||
r.res = "$1.$2" % [r.res, f.loc.r]
|
r.res = "$1.$2" % [r.res, f.loc.r]
|
||||||
else:
|
else:
|
||||||
|
|
@ -1240,7 +1246,7 @@ proc genSym(p: PProc, n: PNode, r: var TCompRes) =
|
||||||
r.res = "$" & s.loc.r
|
r.res = "$" & s.loc.r
|
||||||
p.declareGlobal(s.id, r.res)
|
p.declareGlobal(s.id, r.res)
|
||||||
of skProc, skFunc, skConverter, skMethod:
|
of skProc, skFunc, skConverter, skMethod:
|
||||||
discard mangleName(s, p.target)
|
discard mangleName(p.module, s)
|
||||||
if p.target == targetPHP and r.kind != resCallee:
|
if p.target == targetPHP and r.kind != resCallee:
|
||||||
r.res = makeJsString($s.loc.r)
|
r.res = makeJsString($s.loc.r)
|
||||||
else:
|
else:
|
||||||
|
|
@ -1396,7 +1402,7 @@ proc genPatternCall(p: PProc; n: PNode; pat: string; typ: PType;
|
||||||
proc genInfixCall(p: PProc, n: PNode, r: var TCompRes) =
|
proc genInfixCall(p: PProc, n: PNode, r: var TCompRes) =
|
||||||
# don't call '$' here for efficiency:
|
# don't call '$' here for efficiency:
|
||||||
let f = n[0].sym
|
let f = n[0].sym
|
||||||
if f.loc.r == nil: f.loc.r = mangleName(f, p.target)
|
if f.loc.r == nil: f.loc.r = mangleName(p.module, f)
|
||||||
if sfInfixCall in f.flags:
|
if sfInfixCall in f.flags:
|
||||||
let pat = n.sons[0].sym.loc.r.data
|
let pat = n.sons[0].sym.loc.r.data
|
||||||
internalAssert pat != nil
|
internalAssert pat != nil
|
||||||
|
|
@ -1467,9 +1473,9 @@ proc createRecordVarAux(p: PProc, rec: PNode, excludedFieldIDs: IntSet, output:
|
||||||
if rec.sym.id notin excludedFieldIDs:
|
if rec.sym.id notin excludedFieldIDs:
|
||||||
if output.len > 0: output.add(", ")
|
if output.len > 0: output.add(", ")
|
||||||
if p.target == targetJS:
|
if p.target == targetJS:
|
||||||
output.addf("$#: ", [mangleName(rec.sym, p.target)])
|
output.addf("$#: ", [mangleName(p.module, rec.sym)])
|
||||||
else:
|
else:
|
||||||
output.addf("'$#' => ", [mangleName(rec.sym, p.target)])
|
output.addf("'$#' => ", [mangleName(p.module, rec.sym)])
|
||||||
output.add(createVar(p, rec.sym.typ, false))
|
output.add(createVar(p, rec.sym.typ, false))
|
||||||
else: internalError(rec.info, "createRecordVarAux")
|
else: internalError(rec.info, "createRecordVarAux")
|
||||||
|
|
||||||
|
|
@ -1575,18 +1581,25 @@ proc genVarInit(p: PProc, v: PSym, n: PNode) =
|
||||||
a: TCompRes
|
a: TCompRes
|
||||||
s: Rope
|
s: Rope
|
||||||
varCode: string
|
varCode: string
|
||||||
|
varName = mangleName(p.module, v)
|
||||||
|
useReloadingGuard = sfGlobal in v.flags and optHotReloading in gOptions
|
||||||
|
|
||||||
if v.constraint.isNil:
|
if v.constraint.isNil:
|
||||||
varCode = "var $2"
|
if useReloadingGuard:
|
||||||
|
lineF(p, "var $1;$n", varName)
|
||||||
|
lineF(p, "if ($1 === undefined) {$n", varName)
|
||||||
|
varCode = $varName
|
||||||
|
else:
|
||||||
|
varCode = "var $2"
|
||||||
else:
|
else:
|
||||||
varCode = v.constraint.strVal
|
varCode = v.constraint.strVal
|
||||||
|
|
||||||
if n.kind == nkEmpty:
|
if n.kind == nkEmpty:
|
||||||
let mname = mangleName(v, p.target)
|
|
||||||
lineF(p, varCode & " = $3;$n" | "$$$2 = $3;$n",
|
lineF(p, varCode & " = $3;$n" | "$$$2 = $3;$n",
|
||||||
[returnType, mname, createVar(p, v.typ, isIndirect(v))])
|
[returnType, varName, createVar(p, v.typ, isIndirect(v))])
|
||||||
if v.typ.kind in {tyVar, tyPtr, tyLent, tyRef} and mapType(p, v.typ) == etyBaseIndex:
|
if v.typ.kind in {tyVar, tyPtr, tyLent, tyRef} and mapType(p, v.typ) == etyBaseIndex:
|
||||||
lineF(p, "var $1_Idx = 0;$n", [ mname ])
|
lineF(p, "var $1_Idx = 0;$n", [varName])
|
||||||
else:
|
else:
|
||||||
discard mangleName(v, p.target)
|
|
||||||
gen(p, n, a)
|
gen(p, n, a)
|
||||||
case mapType(p, v.typ)
|
case mapType(p, v.typ)
|
||||||
of etyObject, etySeq:
|
of etyObject, etySeq:
|
||||||
|
|
@ -1619,6 +1632,9 @@ proc genVarInit(p: PProc, v: PSym, n: PNode) =
|
||||||
else:
|
else:
|
||||||
lineF(p, varCode & " = $3;$n" | "$$$2 = $3;$n", [returnType, v.loc.r, s])
|
lineF(p, varCode & " = $3;$n" | "$$$2 = $3;$n", [returnType, v.loc.r, s])
|
||||||
|
|
||||||
|
if useReloadingGuard:
|
||||||
|
lineF(p, "}$n")
|
||||||
|
|
||||||
proc genVarStmt(p: PProc, n: PNode) =
|
proc genVarStmt(p: PProc, n: PNode) =
|
||||||
for i in countup(0, sonsLen(n) - 1):
|
for i in countup(0, sonsLen(n) - 1):
|
||||||
var a = n.sons[i]
|
var a = n.sons[i]
|
||||||
|
|
@ -2031,7 +2047,7 @@ proc genObjConstr(p: PProc, n: PNode, r: var TCompRes) =
|
||||||
let val = it.sons[1]
|
let val = it.sons[1]
|
||||||
gen(p, val, a)
|
gen(p, val, a)
|
||||||
var f = it.sons[0].sym
|
var f = it.sons[0].sym
|
||||||
if f.loc.r == nil: f.loc.r = mangleName(f, p.target)
|
if f.loc.r == nil: f.loc.r = mangleName(p.module, f)
|
||||||
fieldIDs.incl(f.id)
|
fieldIDs.incl(f.id)
|
||||||
|
|
||||||
let typ = val.typ.skipTypes(abstractInst)
|
let typ = val.typ.skipTypes(abstractInst)
|
||||||
|
|
@ -2158,11 +2174,11 @@ proc genProc(oldProc: PProc, prc: PSym): Rope =
|
||||||
p.up = oldProc
|
p.up = oldProc
|
||||||
var returnStmt: Rope = nil
|
var returnStmt: Rope = nil
|
||||||
var resultAsgn: Rope = nil
|
var resultAsgn: Rope = nil
|
||||||
let name = mangleName(prc, p.target)
|
var name = mangleName(p.module, prc)
|
||||||
let header = generateHeader(p, prc.typ)
|
let header = generateHeader(p, prc.typ)
|
||||||
if prc.typ.sons[0] != nil and sfPure notin prc.flags:
|
if prc.typ.sons[0] != nil and sfPure notin prc.flags:
|
||||||
resultSym = prc.ast.sons[resultPos].sym
|
resultSym = prc.ast.sons[resultPos].sym
|
||||||
let mname = mangleName(resultSym, p.target)
|
let mname = mangleName(p.module, resultSym)
|
||||||
let resVar = createVar(p, resultSym.typ, isIndirect(resultSym))
|
let resVar = createVar(p, resultSym.typ, isIndirect(resultSym))
|
||||||
resultAsgn = p.indentLine(("var $# = $#;$n" | "$$$# = $#;$n") % [mname, resVar])
|
resultAsgn = p.indentLine(("var $# = $#;$n" | "$$$# = $#;$n") % [mname, resVar])
|
||||||
if resultSym.typ.kind in {tyVar, tyPtr, tyLent, tyRef} and
|
if resultSym.typ.kind in {tyVar, tyPtr, tyLent, tyRef} and
|
||||||
|
|
@ -2188,6 +2204,18 @@ proc genProc(oldProc: PProc, prc: PSym): Rope =
|
||||||
optionaLine(genProcBody(p, prc)),
|
optionaLine(genProcBody(p, prc)),
|
||||||
optionaLine(p.indentLine(returnStmt))]
|
optionaLine(p.indentLine(returnStmt))]
|
||||||
else:
|
else:
|
||||||
|
result = ~tnl
|
||||||
|
|
||||||
|
if optHotReloading in gOptions:
|
||||||
|
# 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("function $#() { return $#.apply(this, arguments); }$n" %
|
||||||
|
[thunkName, name])
|
||||||
|
|
||||||
def = "function $#($#) {$n$#$#$#$#$#" %
|
def = "function $#($#) {$n$#$#$#$#$#" %
|
||||||
[ name,
|
[ name,
|
||||||
header,
|
header,
|
||||||
|
|
@ -2198,7 +2226,6 @@ proc genProc(oldProc: PProc, prc: PSym): Rope =
|
||||||
optionaLine(p.indentLine(returnStmt))]
|
optionaLine(p.indentLine(returnStmt))]
|
||||||
|
|
||||||
dec p.extraIndent
|
dec p.extraIndent
|
||||||
result = ~tnl
|
|
||||||
result.add p.indentLine(def)
|
result.add p.indentLine(def)
|
||||||
result.add p.indentLine(~"}$n")
|
result.add p.indentLine(~"}$n")
|
||||||
|
|
||||||
|
|
@ -2332,7 +2359,7 @@ proc gen(p: PProc, n: PNode, r: var TCompRes) =
|
||||||
of nkEmpty: discard
|
of nkEmpty: discard
|
||||||
of nkLambdaKinds:
|
of nkLambdaKinds:
|
||||||
let s = n.sons[namePos].sym
|
let s = n.sons[namePos].sym
|
||||||
discard mangleName(s, p.target)
|
discard mangleName(p.module, s)
|
||||||
r.res = s.loc.r
|
r.res = s.loc.r
|
||||||
if lfNoDecl in s.loc.flags or s.magic != mNone: discard
|
if lfNoDecl in s.loc.flags or s.magic != mNone: discard
|
||||||
elif not p.g.generatedSyms.containsOrIncl(s.id):
|
elif not p.g.generatedSyms.containsOrIncl(s.id):
|
||||||
|
|
@ -2389,6 +2416,7 @@ var globals: PGlobals
|
||||||
proc newModule(module: PSym): BModule =
|
proc newModule(module: PSym): BModule =
|
||||||
new(result)
|
new(result)
|
||||||
result.module = module
|
result.module = module
|
||||||
|
result.sigConflicts = initCountTable[SigHash]()
|
||||||
if globals == nil:
|
if globals == nil:
|
||||||
globals = newGlobals()
|
globals = newGlobals()
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -34,7 +34,7 @@ proc genObjectFields(p: PProc, typ: PType, n: PNode): Rope =
|
||||||
s = genTypeInfo(p, field.typ)
|
s = genTypeInfo(p, field.typ)
|
||||||
result = ("{kind: 1, offset: \"$1\", len: 0, " &
|
result = ("{kind: 1, offset: \"$1\", len: 0, " &
|
||||||
"typ: $2, name: $3, sons: null}") %
|
"typ: $2, name: $3, sons: null}") %
|
||||||
[mangleName(field, p.target), s,
|
[mangleName(p.module, field), s,
|
||||||
makeJSString(field.name.s)]
|
makeJSString(field.name.s)]
|
||||||
of nkRecCase:
|
of nkRecCase:
|
||||||
length = sonsLen(n)
|
length = sonsLen(n)
|
||||||
|
|
@ -63,7 +63,7 @@ proc genObjectFields(p: PProc, typ: PType, n: PNode): Rope =
|
||||||
[u, genObjectFields(p, typ, lastSon(b))])
|
[u, genObjectFields(p, typ, lastSon(b))])
|
||||||
result = ("{kind: 3, offset: \"$1\", len: $3, " &
|
result = ("{kind: 3, offset: \"$1\", len: $3, " &
|
||||||
"typ: $2, name: $4, sons: [$5]}") % [
|
"typ: $2, name: $4, sons: [$5]}") % [
|
||||||
mangleName(field, p.target), s,
|
mangleName(p.module, field), s,
|
||||||
rope(lengthOrd(field.typ)), makeJSString(field.name.s), result]
|
rope(lengthOrd(field.typ)), makeJSString(field.name.s), result]
|
||||||
else: internalError(n.info, "genObjectFields")
|
else: internalError(n.info, "genObjectFields")
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -36,7 +36,8 @@ type # please make sure we have under 32 options
|
||||||
optImplicitStatic, # optimization: implicit at compile time
|
optImplicitStatic, # optimization: implicit at compile time
|
||||||
# evaluation
|
# evaluation
|
||||||
optPatterns, # en/disable pattern matching
|
optPatterns, # en/disable pattern matching
|
||||||
optMemTracker
|
optMemTracker,
|
||||||
|
optHotReloading
|
||||||
|
|
||||||
TOptions* = set[TOption]
|
TOptions* = set[TOption]
|
||||||
TGlobalOption* = enum # **keep binary compatible**
|
TGlobalOption* = enum # **keep binary compatible**
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@
|
||||||
|
|
||||||
## Computes hash values for routine (proc, method etc) signatures.
|
## Computes hash values for routine (proc, method etc) signatures.
|
||||||
|
|
||||||
import ast, md5
|
import ast, md5, tables, ropes
|
||||||
from hashes import Hash
|
from hashes import Hash
|
||||||
from astalgo import debug
|
from astalgo import debug
|
||||||
from types import typeToString, preferDesc
|
from types import typeToString, preferDesc
|
||||||
|
|
@ -326,3 +326,32 @@ proc hashOwner*(s: PSym): SigHash =
|
||||||
c &= m.name.s
|
c &= m.name.s
|
||||||
|
|
||||||
md5Final c, result.Md5Digest
|
md5Final c, result.Md5Digest
|
||||||
|
|
||||||
|
proc idOrSig*(s: PSym, currentModule: string,
|
||||||
|
sigCollisions: var CountTable[SigHash]): Rope =
|
||||||
|
if s.kind in routineKinds and s.typ != nil:
|
||||||
|
# signatures for exported routines are reliable enough to
|
||||||
|
# produce a unique name and this means produced C++ is more stable wrt
|
||||||
|
# Nim changes:
|
||||||
|
let sig = hashProc(s)
|
||||||
|
result = rope($sig)
|
||||||
|
#let m = if s.typ.callConv != ccInline: findPendingModule(m, s) else: m
|
||||||
|
let counter = sigCollisions.getOrDefault(sig)
|
||||||
|
#if sigs == "_jckmNePK3i2MFnWwZlp6Lg" and s.name.s == "contains":
|
||||||
|
# echo "counter ", counter, " ", s.id
|
||||||
|
if counter != 0:
|
||||||
|
result.add "_" & rope(counter+1)
|
||||||
|
# this minor hack is necessary to make tests/collections/thashes compile.
|
||||||
|
# The inlined hash function's original module is ambiguous so we end up
|
||||||
|
# generating duplicate names otherwise:
|
||||||
|
if s.typ.callConv == ccInline:
|
||||||
|
result.add rope(currentModule)
|
||||||
|
sigCollisions.inc(sig)
|
||||||
|
else:
|
||||||
|
let sig = hashNonProc(s)
|
||||||
|
result = rope($sig)
|
||||||
|
let counter = sigCollisions.getOrDefault(sig)
|
||||||
|
if counter != 0:
|
||||||
|
result.add "_" & rope(counter+1)
|
||||||
|
sigCollisions.inc(sig)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -60,6 +60,7 @@ Advanced options:
|
||||||
--implicitStatic:on|off turn implicit compile time evaluation on|off
|
--implicitStatic:on|off turn implicit compile time evaluation on|off
|
||||||
--patterns:on|off turn pattern matching on|off
|
--patterns:on|off turn pattern matching on|off
|
||||||
--memTracker:on|off turn memory tracker on|off
|
--memTracker:on|off turn memory tracker on|off
|
||||||
|
--hotReloading:on|off turn support for hot code reloading on|off
|
||||||
--excessiveStackTrace:on|off
|
--excessiveStackTrace:on|off
|
||||||
stack traces use full file paths
|
stack traces use full file paths
|
||||||
--oldNewlines:on|off turn on|off the old behaviour of "\n"
|
--oldNewlines:on|off turn on|off the old behaviour of "\n"
|
||||||
|
|
|
||||||
|
|
@ -436,6 +436,10 @@ Modules for JS backend
|
||||||
* `asyncjs <asyncjs.html>`_
|
* `asyncjs <asyncjs.html>`_
|
||||||
Types and macros for writing asynchronous procedures in JavaScript.
|
Types and macros for writing asynchronous procedures in JavaScript.
|
||||||
|
|
||||||
|
* `jscore <jscore.html>`_
|
||||||
|
Wrapper of core JavaScript functions. For most purposes you should be using
|
||||||
|
the ``math``, ``json``, and ``times`` stdlib modules instead of this module.
|
||||||
|
|
||||||
Deprecated modules
|
Deprecated modules
|
||||||
------------------
|
------------------
|
||||||
|
|
||||||
|
|
|
||||||
40
doc/nimc.rst
40
doc/nimc.rst
|
|
@ -325,6 +325,46 @@ Debugger option
|
||||||
The ``debugger`` option enables or disables the *Embedded Nim Debugger*.
|
The ``debugger`` option enables or disables the *Embedded Nim Debugger*.
|
||||||
See the documentation of endb_ for further information.
|
See the documentation of endb_ for further information.
|
||||||
|
|
||||||
|
Hot code reloading
|
||||||
|
------------------
|
||||||
|
**Note:** At the moment hot code reloading is supported only in
|
||||||
|
JavaScript projects.
|
||||||
|
|
||||||
|
The `hotReloading` option enables special compilation mode where changes in
|
||||||
|
the code can be applied automatically to a running program. The code reloading
|
||||||
|
happens at the granularity of an individual module. When a module is reloaded,
|
||||||
|
Nim will preserve the state of all global variables which are initialized with
|
||||||
|
a standard variable declaration in the code. All other top level code will be
|
||||||
|
executed repeatedly on each reload. If you want to prevent this behavior, you
|
||||||
|
can guard a block of code with the `once` construct:
|
||||||
|
|
||||||
|
.. code-block:: Nim
|
||||||
|
var settings = initTable[string, string]()
|
||||||
|
|
||||||
|
once:
|
||||||
|
myInit()
|
||||||
|
|
||||||
|
for k, v in loadSettings():
|
||||||
|
settings[k] = v
|
||||||
|
|
||||||
|
If you want to reset the state of a global variable on each reload, just
|
||||||
|
re-assign a value anywhere within the top-level code:
|
||||||
|
|
||||||
|
.. code-block:: Nim
|
||||||
|
var lastReload: Time
|
||||||
|
|
||||||
|
lastReload = now()
|
||||||
|
resetProgramState()
|
||||||
|
|
||||||
|
**Known limitations:** In the JavaScript target, global variables using the
|
||||||
|
`codegenDecl` pragma will be re-initialized on each reload. Please guard the
|
||||||
|
initialization with a `once` block to work-around this.
|
||||||
|
|
||||||
|
**Usage in JavaScript projects:**
|
||||||
|
|
||||||
|
Once your code is compiled for hot reloading, you can use a framework such
|
||||||
|
as `LiveReload <http://livereload.com/>` or `BrowserSync <https://browsersync.io/>`
|
||||||
|
to implement the actual reloading behavior in your project.
|
||||||
|
|
||||||
Breakpoint pragma
|
Breakpoint pragma
|
||||||
-----------------
|
-----------------
|
||||||
|
|
|
||||||
91
lib/js/jscore.nim
Normal file
91
lib/js/jscore.nim
Normal file
|
|
@ -0,0 +1,91 @@
|
||||||
|
## This module wraps core JavaScript functions.
|
||||||
|
##
|
||||||
|
## Unless your application has very
|
||||||
|
## specific requirements and solely targets JavaScript, you should be using
|
||||||
|
## the relevant functions in the ``math``, ``json``, and ``times`` stdlib
|
||||||
|
## modules instead.
|
||||||
|
|
||||||
|
when not defined(js) and not defined(Nimdoc):
|
||||||
|
{.error: "This module only works on the JavaScript platform".}
|
||||||
|
|
||||||
|
type
|
||||||
|
MathLib* = ref object
|
||||||
|
JsonLib* = ref object
|
||||||
|
DateLib* = ref object
|
||||||
|
DateTime* = ref object
|
||||||
|
|
||||||
|
var
|
||||||
|
Math* {.importc, nodecl.}: MathLib
|
||||||
|
Date* {.importc, nodecl.}: DateLib
|
||||||
|
JSON* {.importc, nodecl.}: JsonLib
|
||||||
|
|
||||||
|
{.push importcpp.}
|
||||||
|
|
||||||
|
# Math library
|
||||||
|
proc abs*(m: MathLib, a: SomeNumber): SomeNumber
|
||||||
|
proc acos*(m: MathLib, a: SomeNumber): float
|
||||||
|
proc acosh*(m: MathLib, a: SomeNumber): float
|
||||||
|
proc asin*(m: MathLib, a: SomeNumber): float
|
||||||
|
proc asinh*(m: MathLib, a: SomeNumber): float
|
||||||
|
proc atan*(m: MathLib, a: SomeNumber): float
|
||||||
|
proc atan2*(m: MathLib, a: SomeNumber): float
|
||||||
|
proc atanh*(m: MathLib, a: SomeNumber): float
|
||||||
|
proc cbrt*(m: MathLib, f: SomeReal): SomeReal
|
||||||
|
proc ceil*(m: MathLib, f: SomeReal): SomeReal
|
||||||
|
proc clz32*(m: MathLib, f: SomeInteger): int
|
||||||
|
proc cos*(m: MathLib, a: SomeNumber): float
|
||||||
|
proc cosh*(m: MathLib, a: SomeNumber): float
|
||||||
|
proc exp*(m: MathLib, a: SomeNumber): float
|
||||||
|
proc expm1*(m: MathLib, a: SomeNumber): float
|
||||||
|
proc floor*(m: MathLib, f: SomeReal): int
|
||||||
|
proc fround*(m: MathLib, f: SomeReal): float32
|
||||||
|
proc hypot*(m: MathLib, args: varargs[distinct SomeNumber]): float
|
||||||
|
proc imul*(m: MathLib, a, b: int32): int32
|
||||||
|
proc log*(m: MathLib, a: SomeNumber): float
|
||||||
|
proc log10*(m: MathLib, a: SomeNumber): float
|
||||||
|
proc log1p*(m: MathLib, a: SomeNumber): float
|
||||||
|
proc log2*(m: MathLib, a: SomeNumber): float
|
||||||
|
proc max*(m: MathLib, a, b: SomeNumber): SomeNumber
|
||||||
|
proc min*[T: SomeNumber | JsRoot](m: MathLib, a, b: T): T
|
||||||
|
proc pow*(m: MathLib, a, b: distinct SomeNumber): float
|
||||||
|
proc random*(m: MathLib): float
|
||||||
|
proc round*(m: MathLib, f: SomeReal): int
|
||||||
|
proc sign*(m: MathLib, f: SomeNumber): int
|
||||||
|
proc sin*(m: MathLib, a: SomeNumber): float
|
||||||
|
proc sinh*(m: MathLib, a: SomeNumber): float
|
||||||
|
proc sqrt*(m: MathLib, f: SomeReal): SomeReal
|
||||||
|
proc tan*(m: MathLib, a: SomeNumber): float
|
||||||
|
proc tanh*(m: MathLib, a: SomeNumber): float
|
||||||
|
proc trunc*(m: MathLib, f: SomeReal): int
|
||||||
|
|
||||||
|
# Date library
|
||||||
|
proc now*(d: DateLib): int
|
||||||
|
proc UTC*(d: DateLib): int
|
||||||
|
proc parse*(d: DateLib, s: cstring): int
|
||||||
|
|
||||||
|
proc newDate*(): DateTime {.
|
||||||
|
importcpp: "new Date()".}
|
||||||
|
|
||||||
|
proc newDate*(date: int|string): DateTime {.
|
||||||
|
importcpp: "new Date(#)".}
|
||||||
|
|
||||||
|
proc newDate*(year, month, day, hours, minutes,
|
||||||
|
seconds, milliseconds: int): DateTime {.
|
||||||
|
importcpp: "new Date(#,#,#,#,#,#,#)".}
|
||||||
|
|
||||||
|
proc getDay*(d: DateTime): int
|
||||||
|
proc getFullYear*(d: DateTime): int
|
||||||
|
proc getHours*(d: DateTime): int
|
||||||
|
proc getMilliseconds*(d: DateTime): int
|
||||||
|
proc getMinutes*(d: DateTime): int
|
||||||
|
proc getMonth*(d: DateTime): int
|
||||||
|
proc getSeconds*(d: DateTime): int
|
||||||
|
proc getYear*(d: DateTime): int
|
||||||
|
proc getTime*(d: DateTime): int
|
||||||
|
proc toString*(d: DateTime): cstring
|
||||||
|
|
||||||
|
#JSON library
|
||||||
|
proc stringify*(l: JsonLib, s: JsRoot): cstring
|
||||||
|
proc parse*(l: JsonLib, s: cstring): JsRoot
|
||||||
|
|
||||||
|
{.pop.}
|
||||||
|
|
@ -70,22 +70,31 @@ template mangleJsName(name: cstring): cstring =
|
||||||
"mangledName" & $nameCounter
|
"mangledName" & $nameCounter
|
||||||
|
|
||||||
type
|
type
|
||||||
JsRoot* = ref object of RootObj
|
|
||||||
## Root type of both JsObject and JsAssoc
|
|
||||||
JsObject* = ref object of JsRoot
|
JsObject* = ref object of JsRoot
|
||||||
## Dynamically typed wrapper around a JavaScript object.
|
## Dynamically typed wrapper around a JavaScript object.
|
||||||
JsAssoc*[K, V] = ref object of JsRoot
|
JsAssoc*[K, V] = ref object of JsRoot
|
||||||
## Statically typed wrapper around a JavaScript object.
|
## Statically typed wrapper around a JavaScript object.
|
||||||
|
|
||||||
NotString = concept c
|
NotString = concept c
|
||||||
c isnot string
|
c isnot string
|
||||||
js* = JsObject
|
js* = JsObject
|
||||||
|
|
||||||
var jsarguments* {.importc: "arguments", nodecl}: JsObject
|
var
|
||||||
## JavaScript's arguments pseudo-variable
|
jsArguments* {.importc: "arguments", nodecl}: JsObject
|
||||||
|
## JavaScript's arguments pseudo-variable
|
||||||
|
jsNull* {.importc: "null", nodecl.}: JsObject
|
||||||
|
## JavaScript's null literal
|
||||||
|
jsUndefined* {.importc: "undefined", nodecl.}: JsObject
|
||||||
|
## JavaScript's undefined literal
|
||||||
|
jsDirname* {.importc: "__dirname", nodecl.}: cstring
|
||||||
|
## JavaScript's __dirname pseudo-variable
|
||||||
|
jsFilename* {.importc: "__filename", nodecl.}: cstring
|
||||||
|
## JavaScript's __filename pseudo-variable
|
||||||
|
|
||||||
# New
|
# New
|
||||||
proc newJsObject*: JsObject {. importcpp: "{@}" .}
|
proc newJsObject*: JsObject {. importcpp: "{@}" .}
|
||||||
## Creates a new empty JsObject
|
## Creates a new empty JsObject
|
||||||
|
|
||||||
proc newJsAssoc*[K, V]: JsAssoc[K, V] {. importcpp: "{@}" .}
|
proc newJsAssoc*[K, V]: JsAssoc[K, V] {. importcpp: "{@}" .}
|
||||||
## Creates a new empty JsAssoc with key type `K` and value type `V`.
|
## Creates a new empty JsAssoc with key type `K` and value type `V`.
|
||||||
|
|
||||||
|
|
@ -97,13 +106,16 @@ proc hasOwnProperty*(x: JsObject, prop: cstring): bool
|
||||||
proc jsTypeOf*(x: JsObject): cstring {. importcpp: "typeof(#)" .}
|
proc jsTypeOf*(x: JsObject): cstring {. importcpp: "typeof(#)" .}
|
||||||
## Returns the name of the JsObject's JavaScript type as a cstring.
|
## Returns the name of the JsObject's JavaScript type as a cstring.
|
||||||
|
|
||||||
proc jsnew*(x: auto): JsObject {.importcpp: "(new #)".}
|
proc jsNew*(x: auto): JsObject {.importcpp: "(new #)".}
|
||||||
## Turns a regular function call into an invocation of the
|
## Turns a regular function call into an invocation of the
|
||||||
## JavaScript's `new` operator
|
## JavaScript's `new` operator
|
||||||
|
|
||||||
proc jsdelete*(x: auto): JsObject {.importcpp: "(delete #)".}
|
proc jsDelete*(x: auto): JsObject {.importcpp: "(delete #)".}
|
||||||
## JavaScript's `delete` operator
|
## JavaScript's `delete` operator
|
||||||
|
|
||||||
|
proc require*(module: cstring): JsObject {.importc.}
|
||||||
|
## JavaScript's `require` function
|
||||||
|
|
||||||
# Conversion to and from JsObject
|
# Conversion to and from JsObject
|
||||||
proc to*(x: JsObject, T: typedesc): T {. importcpp: "(#)" .}
|
proc to*(x: JsObject, T: typedesc): T {. importcpp: "(#)" .}
|
||||||
## Converts a JsObject `x` to type `T`.
|
## Converts a JsObject `x` to type `T`.
|
||||||
|
|
|
||||||
|
|
@ -649,6 +649,11 @@ when defined(nimNewRuntime):
|
||||||
ESynch: Exception
|
ESynch: Exception
|
||||||
].}
|
].}
|
||||||
|
|
||||||
|
when defined(js) or defined(nimdoc):
|
||||||
|
type
|
||||||
|
JsRoot* = ref object of RootObj
|
||||||
|
## Root type of the JavaScript object hierarchy
|
||||||
|
|
||||||
proc unsafeNew*[T](a: var ref T, size: Natural) {.magic: "New", noSideEffect.}
|
proc unsafeNew*[T](a: var ref T, size: Natural) {.magic: "New", noSideEffect.}
|
||||||
## creates a new object of type ``T`` and returns a safe (traced)
|
## creates a new object of type ``T`` and returns a safe (traced)
|
||||||
## reference to it in ``a``. This is **unsafe** as it allocates an object
|
## reference to it in ``a``. This is **unsafe** as it allocates an object
|
||||||
|
|
@ -4080,6 +4085,25 @@ template closureScope*(body: untyped): untyped =
|
||||||
## myClosure() # outputs 3
|
## myClosure() # outputs 3
|
||||||
(proc() = body)()
|
(proc() = body)()
|
||||||
|
|
||||||
|
template once*(body: untyped): untyped =
|
||||||
|
## Executes a block of code only once (the first time the block is reached).
|
||||||
|
## When hot code reloading is enabled, protects top-level code from being
|
||||||
|
## re-executed on each module reload.
|
||||||
|
##
|
||||||
|
## .. code-block:: nim
|
||||||
|
## proc draw(t: Triangle) =
|
||||||
|
## once:
|
||||||
|
## graphicsInit()
|
||||||
|
##
|
||||||
|
## line(t.p1, t.p2)
|
||||||
|
## line(t.p2, t.p3)
|
||||||
|
## line(t.p3, t.p1)
|
||||||
|
##
|
||||||
|
var alreadyExecuted {.global.} = false
|
||||||
|
if not alreadyExecuted:
|
||||||
|
alreadyExecuted = true
|
||||||
|
body
|
||||||
|
|
||||||
{.pop.} #{.push warning[GcMem]: off, warning[Uninit]: off.}
|
{.pop.} #{.push warning[GcMem]: off, warning[Uninit]: off.}
|
||||||
|
|
||||||
when defined(nimconfig):
|
when defined(nimconfig):
|
||||||
|
|
|
||||||
|
|
@ -77,5 +77,6 @@ webdoc: "wrappers/mysql;wrappers/iup"
|
||||||
webdoc: "wrappers/sqlite3;wrappers/postgres;wrappers/tinyc;wrappers/odbcsql"
|
webdoc: "wrappers/sqlite3;wrappers/postgres;wrappers/tinyc;wrappers/odbcsql"
|
||||||
webdoc: "wrappers/pcre"
|
webdoc: "wrappers/pcre"
|
||||||
webdoc: "wrappers/openssl"
|
webdoc: "wrappers/openssl"
|
||||||
|
webdoc: "js/jscore"
|
||||||
|
|
||||||
webdoc: "posix/posix;wrappers/odbcsql"
|
webdoc: "posix/posix;wrappers/odbcsql"
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue