diff options
author | zah <zahary@gmail.com> | 2018-04-13 20:08:43 +0300 |
---|---|---|
committer | Andreas Rumpf <rumpf_a@web.de> | 2018-04-13 19:08:43 +0200 |
commit | e3037a2f33124edaaba92fec19b951c767eb74f5 (patch) | |
tree | df7b45b4dd68e3adae8aff7af23d632d4a4f11c6 | |
parent | 1d1d6f39a360e3f65051868306f67121a4eeae12 (diff) | |
download | Nim-e3037a2f33124edaaba92fec19b951c767eb74f5.tar.gz |
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
-rw-r--r-- | changelog.md | 15 | ||||
-rw-r--r-- | compiler/ccgtypes.nim | 29 | ||||
-rw-r--r-- | compiler/commands.nim | 4 | ||||
-rw-r--r-- | compiler/jsgen.nim | 76 | ||||
-rw-r--r-- | compiler/jstypes.nim | 4 | ||||
-rw-r--r-- | compiler/options.nim | 3 | ||||
-rw-r--r-- | compiler/sighashes.nim | 31 | ||||
-rw-r--r-- | doc/advopt.txt | 1 | ||||
-rw-r--r-- | doc/lib.rst | 4 | ||||
-rw-r--r-- | doc/nimc.rst | 46 | ||||
-rw-r--r-- | lib/js/jscore.nim | 91 | ||||
-rw-r--r-- | lib/js/jsffi.nim | 24 | ||||
-rw-r--r-- | lib/system.nim | 24 | ||||
-rw-r--r-- | web/website.ini | 1 |
14 files changed, 285 insertions, 68 deletions
diff --git a/changelog.md b/changelog.md index 364047d05..89eb999d1 100644 --- a/changelog.md +++ b/changelog.md @@ -61,6 +61,15 @@ ### Compiler changes -- The VM's instruction count limit was raised to 1 billion instructions in order - to support more complex computations at compile-time. -- Added ``macros.getProjectPath`` and ``ospaths.putEnv`` procs to VM. +- The VM's instruction count limit was raised to 1 billion instructions in + order to support more complex computations at compile-time. + +- 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 + diff --git a/compiler/ccgtypes.nim b/compiler/ccgtypes.nim index cc27bc4e0..56fb379bd 100644 --- a/compiler/ccgtypes.nim +++ b/compiler/ccgtypes.nim @@ -44,38 +44,11 @@ when false: assert p.kind == skPackage 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 = result = s.loc.r if result == nil: 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 writeMangledName(m.ndi, s) diff --git a/compiler/commands.nim b/compiler/commands.nim index 2e9b6f8db..5b8f569ac 100644 --- a/compiler/commands.nim +++ b/compiler/commands.nim @@ -473,6 +473,10 @@ proc processSwitch(switch, arg: string, pass: TCmdLinePass, info: TLineInfo; processOnOffSwitch({optMemTracker}, arg, pass, info) if optMemTracker in gOptions: defineSymbol("memtracker") else: undefSymbol("memtracker") + of "hotreloading": + processOnOffSwitch({optHotReloading}, arg, pass, info) + if optHotReloading in gOptions: defineSymbol("hotreloading") + else: undefSymbol("hotreloading") of "oldnewlines": case arg.normalize of "on": diff --git a/compiler/jsgen.nim b/compiler/jsgen.nim index 357708bb9..727e9209d 100644 --- a/compiler/jsgen.nim +++ b/compiler/jsgen.nim @@ -31,9 +31,9 @@ implements the required case distinction. import 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, - intsets, cgmeth, lowerings + intsets, cgmeth, lowerings, sighashes from modulegraphs import ModuleGraph @@ -43,6 +43,7 @@ type TJSGen = object of TPassContext module: PSym target: TTarget + sigConflicts: CountTable[SigHash] BModule = ref TJSGen TJSTypeKind = enum # necessary JS "types" @@ -210,7 +211,7 @@ proc mapType(p: PProc; typ: PType): TJSTypeKind = if p.target == targetPHP: result = etyObject else: result = mapType(typ) -proc mangleName(s: PSym; target: TTarget): Rope = +proc mangleName(m: BModule, s: PSym): Rope = proc validJsName(name: string): bool = result = true const reservedWords = ["abstract", "await", "boolean", "break", "byte", @@ -235,7 +236,7 @@ proc mangleName(s: PSym; target: TTarget): Rope = if result == nil: if s.kind == skField and s.name.s.validJsName: 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)) else: var x = newStringOfCap(s.name.s.len) @@ -254,8 +255,13 @@ proc mangleName(s: PSym; target: TTarget): Rope = inc i result = rope(x) if s.name.s != "this" and s.kind != skField: - add(result, "_") - add(result, rope(s.id)) + if optHotReloading in gOptions: + # 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 proc escapeJSString(s: string): string = @@ -821,7 +827,7 @@ proc genAsmOrEmitStmt(p: PProc, n: PNode) = # for backwards compatibility we don't deref syms here :-( if v.kind in {skVar, skLet, skTemp, skConst, skResult, skParam, skForVar}: if p.target == targetPHP: p.body.add "$" - p.body.add mangleName(v, p.target) + p.body.add mangleName(p.module, v) else: var r: TCompRes gen(p, it, r) @@ -862,7 +868,7 @@ proc generateHeader(p: PProc, typ: PType): Rope = var param = typ.n.sons[i].sym if isCompileTimeOnly(param.typ): continue if result != nil: add(result, ", ") - var name = mangleName(param, p.target) + var name = mangleName(p.module, param) if p.target == targetJS: add(result, name) if mapType(param.typ) == etyBaseIndex: @@ -1012,7 +1018,7 @@ proc genFieldAddr(p: PProc, n: PNode, r: var TCompRes) = else: if b.sons[1].kind != nkSym: internalError(b.sons[1].info, "genFieldAddr") 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) internalAssert a.typ != etyBaseIndex r.address = a.res @@ -1028,7 +1034,7 @@ proc genFieldAccess(p: PProc, n: PNode, r: var TCompRes) = else: if n.sons[1].kind != nkSym: internalError(n.sons[1].info, "genFieldAccess") 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: r.res = "$1.$2" % [r.res, f.loc.r] else: @@ -1240,7 +1246,7 @@ proc genSym(p: PProc, n: PNode, r: var TCompRes) = r.res = "$" & s.loc.r p.declareGlobal(s.id, r.res) of skProc, skFunc, skConverter, skMethod: - discard mangleName(s, p.target) + discard mangleName(p.module, s) if p.target == targetPHP and r.kind != resCallee: r.res = makeJsString($s.loc.r) else: @@ -1396,7 +1402,7 @@ proc genPatternCall(p: PProc; n: PNode; pat: string; typ: PType; proc genInfixCall(p: PProc, n: PNode, r: var TCompRes) = # don't call '$' here for efficiency: 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: let pat = n.sons[0].sym.loc.r.data internalAssert pat != nil @@ -1467,9 +1473,9 @@ proc createRecordVarAux(p: PProc, rec: PNode, excludedFieldIDs: IntSet, output: if rec.sym.id notin excludedFieldIDs: if output.len > 0: output.add(", ") if p.target == targetJS: - output.addf("$#: ", [mangleName(rec.sym, p.target)]) + output.addf("$#: ", [mangleName(p.module, rec.sym)]) else: - output.addf("'$#' => ", [mangleName(rec.sym, p.target)]) + output.addf("'$#' => ", [mangleName(p.module, rec.sym)]) output.add(createVar(p, rec.sym.typ, false)) else: internalError(rec.info, "createRecordVarAux") @@ -1575,18 +1581,25 @@ proc genVarInit(p: PProc, v: PSym, n: PNode) = a: TCompRes s: Rope varCode: string + varName = mangleName(p.module, v) + useReloadingGuard = sfGlobal in v.flags and optHotReloading in gOptions + 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: varCode = v.constraint.strVal + if n.kind == nkEmpty: - let mname = mangleName(v, p.target) 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: - lineF(p, "var $1_Idx = 0;$n", [ mname ]) + lineF(p, "var $1_Idx = 0;$n", [varName]) else: - discard mangleName(v, p.target) gen(p, n, a) case mapType(p, v.typ) of etyObject, etySeq: @@ -1619,6 +1632,9 @@ proc genVarInit(p: PProc, v: PSym, n: PNode) = else: lineF(p, varCode & " = $3;$n" | "$$$2 = $3;$n", [returnType, v.loc.r, s]) + if useReloadingGuard: + lineF(p, "}$n") + proc genVarStmt(p: PProc, n: PNode) = for i in countup(0, sonsLen(n) - 1): var a = n.sons[i] @@ -2031,7 +2047,7 @@ proc genObjConstr(p: PProc, n: PNode, r: var TCompRes) = let val = it.sons[1] gen(p, val, a) 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) let typ = val.typ.skipTypes(abstractInst) @@ -2158,11 +2174,11 @@ proc genProc(oldProc: PProc, prc: PSym): Rope = p.up = oldProc var returnStmt: Rope = nil var resultAsgn: Rope = nil - let name = mangleName(prc, p.target) + var name = mangleName(p.module, prc) let header = generateHeader(p, prc.typ) if prc.typ.sons[0] != nil and sfPure notin prc.flags: 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)) resultAsgn = p.indentLine(("var $# = $#;$n" | "$$$# = $#;$n") % [mname, resVar]) 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(p.indentLine(returnStmt))] 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$#$#$#$#$#" % [ name, header, @@ -2198,7 +2226,6 @@ proc genProc(oldProc: PProc, prc: PSym): Rope = optionaLine(p.indentLine(returnStmt))] dec p.extraIndent - result = ~tnl result.add p.indentLine(def) result.add p.indentLine(~"}$n") @@ -2332,7 +2359,7 @@ proc gen(p: PProc, n: PNode, r: var TCompRes) = of nkEmpty: discard of nkLambdaKinds: let s = n.sons[namePos].sym - discard mangleName(s, p.target) + discard mangleName(p.module, s) r.res = s.loc.r if lfNoDecl in s.loc.flags or s.magic != mNone: discard elif not p.g.generatedSyms.containsOrIncl(s.id): @@ -2389,6 +2416,7 @@ var globals: PGlobals proc newModule(module: PSym): BModule = new(result) result.module = module + result.sigConflicts = initCountTable[SigHash]() if globals == nil: globals = newGlobals() diff --git a/compiler/jstypes.nim b/compiler/jstypes.nim index 3768acf27..8bd963a65 100644 --- a/compiler/jstypes.nim +++ b/compiler/jstypes.nim @@ -34,7 +34,7 @@ proc genObjectFields(p: PProc, typ: PType, n: PNode): Rope = s = genTypeInfo(p, field.typ) result = ("{kind: 1, offset: \"$1\", len: 0, " & "typ: $2, name: $3, sons: null}") % - [mangleName(field, p.target), s, + [mangleName(p.module, field), s, makeJSString(field.name.s)] of nkRecCase: length = sonsLen(n) @@ -63,7 +63,7 @@ proc genObjectFields(p: PProc, typ: PType, n: PNode): Rope = [u, genObjectFields(p, typ, lastSon(b))]) result = ("{kind: 3, offset: \"$1\", len: $3, " & "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] else: internalError(n.info, "genObjectFields") diff --git a/compiler/options.nim b/compiler/options.nim index be13e15d7..d1428312b 100644 --- a/compiler/options.nim +++ b/compiler/options.nim @@ -36,7 +36,8 @@ type # please make sure we have under 32 options optImplicitStatic, # optimization: implicit at compile time # evaluation optPatterns, # en/disable pattern matching - optMemTracker + optMemTracker, + optHotReloading TOptions* = set[TOption] TGlobalOption* = enum # **keep binary compatible** diff --git a/compiler/sighashes.nim b/compiler/sighashes.nim index 0fea3bbe3..46b83c386 100644 --- a/compiler/sighashes.nim +++ b/compiler/sighashes.nim @@ -9,7 +9,7 @@ ## Computes hash values for routine (proc, method etc) signatures. -import ast, md5 +import ast, md5, tables, ropes from hashes import Hash from astalgo import debug from types import typeToString, preferDesc @@ -326,3 +326,32 @@ proc hashOwner*(s: PSym): SigHash = c &= m.name.s 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) + diff --git a/doc/advopt.txt b/doc/advopt.txt index ac309c222..7d8d81c4f 100644 --- a/doc/advopt.txt +++ b/doc/advopt.txt @@ -60,6 +60,7 @@ Advanced options: --implicitStatic:on|off turn implicit compile time evaluation on|off --patterns:on|off turn pattern matching on|off --memTracker:on|off turn memory tracker on|off + --hotReloading:on|off turn support for hot code reloading on|off --excessiveStackTrace:on|off stack traces use full file paths --oldNewlines:on|off turn on|off the old behaviour of "\n" diff --git a/doc/lib.rst b/doc/lib.rst index 5b3b47f65..f0569070d 100644 --- a/doc/lib.rst +++ b/doc/lib.rst @@ -436,6 +436,10 @@ Modules for JS backend * `asyncjs <asyncjs.html>`_ 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 ------------------ diff --git a/doc/nimc.rst b/doc/nimc.rst index 151510df2..fe414b643 100644 --- a/doc/nimc.rst +++ b/doc/nimc.rst @@ -278,12 +278,12 @@ Define Effect what's in the Nim file with what's in the C header (requires a C compiler with _Static_assert support, like any C11 compiler) -``tempDir`` This symbol takes a string as its value, like +``tempDir`` This symbol takes a string as its value, like ``--define:tempDir:/some/temp/path`` to override the temporary directory returned by ``os.getTempDir()``. - The value **should** end with a directory separator + The value **should** end with a directory separator character. (Relevant for the Android platform) -``useShPath`` This symbol takes a string as its value, like +``useShPath`` This symbol takes a string as its value, like ``--define:useShPath:/opt/sh/bin/sh`` to override the path for the ``sh`` binary, in cases where it is not located in the default location ``/bin/sh`` @@ -325,6 +325,46 @@ Debugger option The ``debugger`` option enables or disables the *Embedded Nim Debugger*. 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 ----------------- diff --git a/lib/js/jscore.nim b/lib/js/jscore.nim new file mode 100644 index 000000000..94aadf87a --- /dev/null +++ b/lib/js/jscore.nim @@ -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.} diff --git a/lib/js/jsffi.nim b/lib/js/jsffi.nim index f34efe9a2..6e48db6c7 100644 --- a/lib/js/jsffi.nim +++ b/lib/js/jsffi.nim @@ -70,22 +70,31 @@ template mangleJsName(name: cstring): cstring = "mangledName" & $nameCounter type - JsRoot* = ref object of RootObj - ## Root type of both JsObject and JsAssoc JsObject* = ref object of JsRoot ## Dynamically typed wrapper around a JavaScript object. JsAssoc*[K, V] = ref object of JsRoot ## Statically typed wrapper around a JavaScript object. + NotString = concept c c isnot string js* = JsObject -var jsarguments* {.importc: "arguments", nodecl}: JsObject - ## JavaScript's arguments pseudo-variable +var + 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 proc newJsObject*: JsObject {. importcpp: "{@}" .} ## Creates a new empty JsObject + proc newJsAssoc*[K, V]: JsAssoc[K, V] {. importcpp: "{@}" .} ## 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(#)" .} ## 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 ## JavaScript's `new` operator -proc jsdelete*(x: auto): JsObject {.importcpp: "(delete #)".} +proc jsDelete*(x: auto): JsObject {.importcpp: "(delete #)".} ## JavaScript's `delete` operator +proc require*(module: cstring): JsObject {.importc.} + ## JavaScript's `require` function + # Conversion to and from JsObject proc to*(x: JsObject, T: typedesc): T {. importcpp: "(#)" .} ## Converts a JsObject `x` to type `T`. diff --git a/lib/system.nim b/lib/system.nim index 5aeddaf39..1d1b2d2d2 100644 --- a/lib/system.nim +++ b/lib/system.nim @@ -649,6 +649,11 @@ when defined(nimNewRuntime): 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.} ## 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 @@ -4080,6 +4085,25 @@ template closureScope*(body: untyped): untyped = ## myClosure() # outputs 3 (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.} when defined(nimconfig): diff --git a/web/website.ini b/web/website.ini index be3fbb6e3..56b7c436f 100644 --- a/web/website.ini +++ b/web/website.ini @@ -77,5 +77,6 @@ webdoc: "wrappers/mysql;wrappers/iup" webdoc: "wrappers/sqlite3;wrappers/postgres;wrappers/tinyc;wrappers/odbcsql" webdoc: "wrappers/pcre" webdoc: "wrappers/openssl" +webdoc: "js/jscore" webdoc: "posix/posix;wrappers/odbcsql" |