summary refs log tree commit diff stats
path: root/lib/system/jssys.nim
diff options
context:
space:
mode:
Diffstat (limited to 'lib/system/jssys.nim')
-rw-r--r--lib/system/jssys.nim768
1 files changed, 768 insertions, 0 deletions
diff --git a/lib/system/jssys.nim b/lib/system/jssys.nim
new file mode 100644
index 000000000..5599240fd
--- /dev/null
+++ b/lib/system/jssys.nim
@@ -0,0 +1,768 @@
+#
+#
+#            Nim's Runtime Library
+#        (c) Copyright 2015 Andreas Rumpf
+#
+#    See the file "copying.txt", included in this
+#    distribution, for details about the copyright.
+#
+
+include system/indexerrors
+import std/private/miscdollars
+
+proc log*(s: cstring) {.importc: "console.log", varargs, nodecl.}
+
+type
+  PSafePoint = ptr SafePoint
+  SafePoint {.compilerproc, final.} = object
+    prev: PSafePoint # points to next safe point
+    exc: ref Exception
+
+  PCallFrame = ptr CallFrame
+  CallFrame {.importc, nodecl, final.} = object
+    prev: PCallFrame
+    procname: cstring
+    line: int # current line number
+    filename: cstring
+
+  PJSError = ref object
+    columnNumber {.importc.}: int
+    fileName {.importc.}: cstring
+    lineNumber {.importc.}: int
+    message {.importc.}: cstring
+    stack {.importc.}: cstring
+
+  JSRef = ref RootObj # Fake type.
+
+var
+  framePtr {.importc, nodecl, volatile.}: PCallFrame
+  excHandler {.importc, nodecl, volatile.}: int = 0
+  lastJSError {.importc, nodecl, volatile.}: PJSError = nil
+
+{.push stacktrace: off, profiler:off.}
+proc nimBoolToStr(x: bool): string {.compilerproc.} =
+  if x: result = "true"
+  else: result = "false"
+
+proc nimCharToStr(x: char): string {.compilerproc.} =
+  result = newString(1)
+  result[0] = x
+
+proc isNimException(): bool {.asmNoStackFrame.} =
+  {.emit: "return `lastJSError` && `lastJSError`.m_type;".}
+
+proc getCurrentException*(): ref Exception {.compilerRtl, benign.} =
+  if isNimException(): result = cast[ref Exception](lastJSError)
+
+proc getCurrentExceptionMsg*(): string =
+  if lastJSError != nil:
+    if isNimException():
+      return cast[Exception](lastJSError).msg
+    else:
+      var msg: cstring
+      {.emit: """
+      if (`lastJSError`.message !== undefined) {
+        `msg` = `lastJSError`.message;
+      }
+      """.}
+      if not msg.isNil:
+        return $msg
+  return ""
+
+proc setCurrentException*(exc: ref Exception) =
+  lastJSError = cast[PJSError](exc)
+
+proc closureIterSetupExc(e: ref Exception) {.compilerproc, inline.} =
+  ## Used to set up exception handling for closure iterators
+  setCurrentException(e)
+
+proc auxWriteStackTrace(f: PCallFrame): string =
+  type
+    TempFrame = tuple[procname: cstring, line: int, filename: cstring]
+  var
+    it = f
+    i = 0
+    total = 0
+    tempFrames: array[0..63, TempFrame]
+  while it != nil and i <= high(tempFrames):
+    tempFrames[i].procname = it.procname
+    tempFrames[i].line = it.line
+    tempFrames[i].filename = it.filename
+    inc(i)
+    inc(total)
+    it = it.prev
+  while it != nil:
+    inc(total)
+    it = it.prev
+  result = ""
+  # if the buffer overflowed print '...':
+  if total != i:
+    add(result, "(")
+    add(result, $(total-i))
+    add(result, " calls omitted) ...\n")
+  for j in countdown(i-1, 0):
+    result.toLocation($tempFrames[j].filename, tempFrames[j].line, 0)
+    add(result, " at ")
+    add(result, tempFrames[j].procname)
+    add(result, "\n")
+
+proc rawWriteStackTrace(): string =
+  if framePtr != nil:
+    result = "Traceback (most recent call last)\n" & auxWriteStackTrace(framePtr)
+  else:
+    result = "No stack traceback available\n"
+
+proc writeStackTrace() =
+  var trace = rawWriteStackTrace()
+  trace.setLen(trace.len - 1)
+  echo trace
+
+proc getStackTrace*(): string = rawWriteStackTrace()
+proc getStackTrace*(e: ref Exception): string = e.trace
+
+proc unhandledException(e: ref Exception) {.
+    compilerproc, asmNoStackFrame.} =
+  var buf = ""
+  if e.msg.len != 0:
+    add(buf, "Error: unhandled exception: ")
+    add(buf, e.msg)
+  else:
+    add(buf, "Error: unhandled exception")
+  add(buf, " [")
+  add(buf, e.name)
+  add(buf, "]\n")
+  when NimStackTrace:
+    add(buf, rawWriteStackTrace())
+  let cbuf = cstring(buf)
+  when NimStackTrace:
+    framePtr = nil
+  {.emit: """
+  if (typeof(Error) !== "undefined") {
+    throw new Error(`cbuf`);
+  }
+  else {
+    throw `cbuf`;
+  }
+  """.}
+
+proc raiseException(e: ref Exception, ename: cstring) {.
+    compilerproc, asmNoStackFrame.} =
+  e.name = ename
+  if excHandler == 0:
+    unhandledException(e)
+  when NimStackTrace:
+    e.trace = rawWriteStackTrace()
+  {.emit: "throw `e`;".}
+
+proc reraiseException() {.compilerproc, asmNoStackFrame.} =
+  if lastJSError == nil:
+    raise newException(ReraiseDefect, "no exception to reraise")
+  else:
+    if excHandler == 0:
+      if isNimException():
+        unhandledException(cast[ref Exception](lastJSError))
+
+    {.emit: "throw lastJSError;".}
+
+proc raiseOverflow {.exportc: "raiseOverflow", noreturn, compilerproc.} =
+  raise newException(OverflowDefect, "over- or underflow")
+
+proc raiseDivByZero {.exportc: "raiseDivByZero", noreturn, compilerproc.} =
+  raise newException(DivByZeroDefect, "division by zero")
+
+proc raiseRangeError() {.compilerproc, noreturn.} =
+  raise newException(RangeDefect, "value out of range")
+
+proc raiseIndexError(i, a, b: int) {.compilerproc, noreturn.} =
+  raise newException(IndexDefect, formatErrorIndexBound(int(i), int(a), int(b)))
+
+proc raiseFieldError2(f: string, discVal: string) {.compilerproc, noreturn.} =
+  raise newException(FieldDefect, formatFieldDefect(f, discVal))
+
+proc setConstr() {.varargs, asmNoStackFrame, compilerproc.} =
+  {.emit: """
+    var result = {};
+    for (var i = 0; i < arguments.length; ++i) {
+      var x = arguments[i];
+      if (typeof(x) == "object") {
+        for (var j = x[0]; j <= x[1]; ++j) {
+          result[j] = true;
+        }
+      } else {
+        result[x] = true;
+      }
+    }
+    return result;
+  """.}
+
+proc makeNimstrLit(c: cstring): string {.asmNoStackFrame, compilerproc.} =
+  {.emit: """
+  var result = [];
+  for (var i = 0; i < `c`.length; ++i) {
+    result[i] = `c`.charCodeAt(i);
+  }
+  return result;
+  """.}
+
+proc cstrToNimstr(c: cstring): string {.asmNoStackFrame, compilerproc.} =
+  {.emit: """
+  var ln = `c`.length;
+  var result = new Array(ln);
+  var r = 0;
+  for (var i = 0; i < ln; ++i) {
+    var ch = `c`.charCodeAt(i);
+
+    if (ch < 128) {
+      result[r] = ch;
+    }
+    else {
+      if (ch < 2048) {
+        result[r] = (ch >> 6) | 192;
+      }
+      else {
+        if (ch < 55296 || ch >= 57344) {
+          result[r] = (ch >> 12) | 224;
+        }
+        else {
+            ++i;
+            ch = 65536 + (((ch & 1023) << 10) | (`c`.charCodeAt(i) & 1023));
+            result[r] = (ch >> 18) | 240;
+            ++r;
+            result[r] = ((ch >> 12) & 63) | 128;
+        }
+        ++r;
+        result[r] = ((ch >> 6) & 63) | 128;
+      }
+      ++r;
+      result[r] = (ch & 63) | 128;
+    }
+    ++r;
+  }
+  return result;
+  """.}
+
+proc toJSStr(s: string): cstring {.compilerproc.} =
+  proc fromCharCode(c: char): cstring {.importc: "String.fromCharCode".}
+  proc join(x: openArray[cstring]; d = cstring""): cstring {.
+    importcpp: "#.join(@)".}
+  proc decodeURIComponent(x: cstring): cstring {.
+    importc: "decodeURIComponent".}
+
+  proc toHexString(c: char; d = 16): cstring {.importcpp: "#.toString(@)".}
+
+  proc log(x: cstring) {.importc: "console.log".}
+
+  var res = newSeq[cstring](s.len)
+  var i = 0
+  var j = 0
+  while i < s.len:
+    var c = s[i]
+    if c < '\128':
+      res[j] = fromCharCode(c)
+      inc i
+    else:
+      var helper = newSeq[cstring]()
+      while true:
+        let code = toHexString(c)
+        if code.len == 1:
+          helper.add cstring"%0"
+        else:
+          helper.add cstring"%"
+        helper.add code
+        inc i
+        if i >= s.len or s[i] < '\128': break
+        c = s[i]
+      try:
+        res[j] = decodeURIComponent join(helper)
+      except:
+        res[j] = join(helper)
+    inc j
+  setLen(res, j)
+  result = join(res)
+
+proc mnewString(len: int): string {.asmNoStackFrame, compilerproc.} =
+  {.emit: """
+    var result = new Array(`len`);
+    for (var i = 0; i < `len`; i++) {result[i] = 0;}
+    return result;
+  """.}
+
+proc SetCard(a: int): int {.compilerproc, asmNoStackFrame.} =
+  # argument type is a fake
+  {.emit: """
+    var result = 0;
+    for (var elem in `a`) { ++result; }
+    return result;
+  """.}
+
+proc SetEq(a, b: int): bool {.compilerproc, asmNoStackFrame.} =
+  {.emit: """
+    for (var elem in `a`) { if (!`b`[elem]) return false; }
+    for (var elem in `b`) { if (!`a`[elem]) return false; }
+    return true;
+  """.}
+
+proc SetLe(a, b: int): bool {.compilerproc, asmNoStackFrame.} =
+  {.emit: """
+    for (var elem in `a`) { if (!`b`[elem]) return false; }
+    return true;
+  """.}
+
+proc SetLt(a, b: int): bool {.compilerproc.} =
+  result = SetLe(a, b) and not SetEq(a, b)
+
+proc SetMul(a, b: int): int {.compilerproc, asmNoStackFrame.} =
+  {.emit: """
+    var result = {};
+    for (var elem in `a`) {
+      if (`b`[elem]) { result[elem] = true; }
+    }
+    return result;
+  """.}
+
+proc SetPlus(a, b: int): int {.compilerproc, asmNoStackFrame.} =
+  {.emit: """
+    var result = {};
+    for (var elem in `a`) { result[elem] = true; }
+    for (var elem in `b`) { result[elem] = true; }
+    return result;
+  """.}
+
+proc SetMinus(a, b: int): int {.compilerproc, asmNoStackFrame.} =
+  {.emit: """
+    var result = {};
+    for (var elem in `a`) {
+      if (!`b`[elem]) { result[elem] = true; }
+    }
+    return result;
+  """.}
+
+proc cmpStrings(a, b: string): int {.asmNoStackFrame, compilerproc.} =
+  {.emit: """
+    if (`a` == `b`) return 0;
+    if (!`a`) return -1;
+    if (!`b`) return 1;
+    for (var i = 0; i < `a`.length && i < `b`.length; i++) {
+      var result = `a`[i] - `b`[i];
+      if (result != 0) return result;
+    }
+    return `a`.length - `b`.length;
+  """.}
+
+proc cmp(x, y: string): int =
+  when nimvm:
+    if x == y: result = 0
+    elif x < y: result = -1
+    else: result = 1
+  else:
+    result = cmpStrings(x, y)
+
+proc eqStrings(a, b: string): bool {.asmNoStackFrame, compilerproc.} =
+  {.emit: """
+    if (`a` == `b`) return true;
+    if (`a` === null && `b`.length == 0) return true;
+    if (`b` === null && `a`.length == 0) return true;
+    if ((!`a`) || (!`b`)) return false;
+    var alen = `a`.length;
+    if (alen != `b`.length) return false;
+    for (var i = 0; i < alen; ++i)
+      if (`a`[i] != `b`[i]) return false;
+    return true;
+  """.}
+
+when defined(kwin):
+  proc rawEcho {.compilerproc, asmNoStackFrame.} =
+    {.emit: """
+      var buf = "";
+      for (var i = 0; i < arguments.length; ++i) {
+        buf += `toJSStr`(arguments[i]);
+      }
+      print(buf);
+    """.}
+
+elif not defined(nimOldEcho):
+  proc ewriteln(x: cstring) = log(x)
+
+  proc rawEcho {.compilerproc, asmNoStackFrame.} =
+    {.emit: """
+      var buf = "";
+      for (var i = 0; i < arguments.length; ++i) {
+        buf += `toJSStr`(arguments[i]);
+      }
+      console.log(buf);
+    """.}
+
+else:
+  proc ewriteln(x: cstring) =
+    var node : JSRef
+    {.emit: "`node` = document.getElementsByTagName('body')[0];".}
+    if node.isNil:
+      raise newException(ValueError, "<body> element does not exist yet!")
+    {.emit: """
+    `node`.appendChild(document.createTextNode(`x`));
+    `node`.appendChild(document.createElement("br"));
+    """.}
+
+  proc rawEcho {.compilerproc.} =
+    var node : JSRef
+    {.emit: "`node` = document.getElementsByTagName('body')[0];".}
+    if node.isNil:
+      raise newException(IOError, "<body> element does not exist yet!")
+    {.emit: """
+    for (var i = 0; i < arguments.length; ++i) {
+      var x = `toJSStr`(arguments[i]);
+      `node`.appendChild(document.createTextNode(x));
+    }
+    `node`.appendChild(document.createElement("br"));
+    """.}
+
+# Arithmetic:
+proc checkOverflowInt(a: int) {.asmNoStackFrame, compilerproc.} =
+  {.emit: """
+    if (`a` > 2147483647 || `a` < -2147483648) `raiseOverflow`();
+  """.}
+
+proc addInt(a, b: int): int {.asmNoStackFrame, compilerproc.} =
+  {.emit: """
+    var result = `a` + `b`;
+    `checkOverflowInt`(result);
+    return result;
+  """.}
+
+proc subInt(a, b: int): int {.asmNoStackFrame, compilerproc.} =
+  {.emit: """
+    var result = `a` - `b`;
+    `checkOverflowInt`(result);
+    return result;
+  """.}
+
+proc mulInt(a, b: int): int {.asmNoStackFrame, compilerproc.} =
+  {.emit: """
+    var result = `a` * `b`;
+    `checkOverflowInt`(result);
+    return result;
+  """.}
+
+proc divInt(a, b: int): int {.asmNoStackFrame, compilerproc.} =
+  {.emit: """
+    if (`b` == 0) `raiseDivByZero`();
+    if (`b` == -1 && `a` == 2147483647) `raiseOverflow`();
+    return Math.trunc(`a` / `b`);
+  """.}
+
+proc modInt(a, b: int): int {.asmNoStackFrame, compilerproc.} =
+  {.emit: """
+    if (`b` == 0) `raiseDivByZero`();
+    if (`b` == -1 && `a` == 2147483647) `raiseOverflow`();
+    return Math.trunc(`a` % `b`);
+  """.}
+
+proc checkOverflowInt64(a: int64) {.asmNoStackFrame, compilerproc.} =
+  {.emit: """
+    if (`a` > 9223372036854775807n || `a` < -9223372036854775808n) `raiseOverflow`();
+  """.}
+
+proc addInt64(a, b: int64): int64 {.asmNoStackFrame, compilerproc.} =
+  {.emit: """
+    var result = `a` + `b`;
+    `checkOverflowInt64`(result);
+    return result;
+  """.}
+
+proc subInt64(a, b: int64): int64 {.asmNoStackFrame, compilerproc.} =
+  {.emit: """
+    var result = `a` - `b`;
+    `checkOverflowInt64`(result);
+    return result;
+  """.}
+
+proc mulInt64(a, b: int64): int64 {.asmNoStackFrame, compilerproc.} =
+  {.emit: """
+    var result = `a` * `b`;
+    `checkOverflowInt64`(result);
+    return result;
+  """.}
+
+proc divInt64(a, b: int64): int64 {.asmNoStackFrame, compilerproc.} =
+  {.emit: """
+    if (`b` == 0n) `raiseDivByZero`();
+    if (`b` == -1n && `a` == 9223372036854775807n) `raiseOverflow`();
+    return `a` / `b`;
+  """.}
+
+proc modInt64(a, b: int64): int64 {.asmNoStackFrame, compilerproc.} =
+  {.emit: """
+    if (`b` == 0n) `raiseDivByZero`();
+    if (`b` == -1n && `a` == 9223372036854775807n) `raiseOverflow`();
+    return `a` % `b`;
+  """.}
+
+proc negInt(a: int): int {.compilerproc.} =
+  result = a*(-1)
+
+proc negInt64(a: int64): int64 {.compilerproc.} =
+  result = a*(-1)
+
+proc absInt(a: int): int {.compilerproc.} =
+  result = if a < 0: a*(-1) else: a
+
+proc absInt64(a: int64): int64 {.compilerproc.} =
+  result = if a < 0: a*(-1) else: a
+
+proc nimMin(a, b: int): int {.compilerproc.} = return if a <= b: a else: b
+proc nimMax(a, b: int): int {.compilerproc.} = return if a >= b: a else: b
+
+proc chckNilDisp(p: JSRef) {.compilerproc.} =
+  if p == nil:
+    sysFatal(NilAccessDefect, "cannot dispatch; dispatcher is nil")
+
+include "system/hti"
+
+proc isFatPointer(ti: PNimType): bool =
+  # This has to be consistent with the code generator!
+  return ti.base.kind notin {tyObject,
+    tyArray, tyArrayConstr, tyTuple,
+    tyOpenArray, tySet, tyVar, tyRef, tyPtr}
+
+proc nimCopy(dest, src: JSRef, ti: PNimType): JSRef {.compilerproc.}
+
+proc nimCopyAux(dest, src: JSRef, n: ptr TNimNode) {.compilerproc.} =
+  case n.kind
+  of nkNone: sysAssert(false, "nimCopyAux")
+  of nkSlot:
+    {.emit: """
+      `dest`[`n`.offset] = nimCopy(`dest`[`n`.offset], `src`[`n`.offset], `n`.typ);
+    """.}
+  of nkList:
+    {.emit: """
+    for (var i = 0; i < `n`.sons.length; i++) {
+      nimCopyAux(`dest`, `src`, `n`.sons[i]);
+    }
+    """.}
+  of nkCase:
+    {.emit: """
+      `dest`[`n`.offset] = nimCopy(`dest`[`n`.offset], `src`[`n`.offset], `n`.typ);
+      for (var i = 0; i < `n`.sons.length; ++i) {
+        nimCopyAux(`dest`, `src`, `n`.sons[i][1]);
+      }
+    """.}
+
+proc nimCopy(dest, src: JSRef, ti: PNimType): JSRef =
+  case ti.kind
+  of tyPtr, tyRef, tyVar, tyNil:
+    if not isFatPointer(ti):
+      result = src
+    else:
+      {.emit: "`result` = [`src`[0], `src`[1]];".}
+  of tySet:
+    {.emit: """
+      if (`dest` === null || `dest` === undefined) {
+        `dest` = {};
+      }
+      else {
+        for (var key in `dest`) { delete `dest`[key]; }
+      }
+      for (var key in `src`) { `dest`[key] = `src`[key]; }
+      `result` = `dest`;
+    """.}
+  of tyTuple, tyObject:
+    if ti.base != nil: result = nimCopy(dest, src, ti.base)
+    elif ti.kind == tyObject:
+      {.emit: "`result` = (`dest` === null || `dest` === undefined) ? {m_type: `ti`} : `dest`;".}
+    else:
+      {.emit: "`result` = (`dest` === null || `dest` === undefined) ? {} : `dest`;".}
+    nimCopyAux(result, src, ti.node)
+  of tyArrayConstr, tyArray:
+    # In order to prevent a type change (TypedArray -> Array) and to have better copying performance,
+    # arrays constructors are considered separately
+    {.emit: """
+      if(ArrayBuffer.isView(`src`)) { 
+        if(`dest` === null || `dest` === undefined || `dest`.length != `src`.length) {
+          `dest` = new `src`.constructor(`src`);
+        } else {
+          `dest`.set(`src`, 0);
+        }
+        `result` = `dest`;
+      } else {
+        if (`src` === null) {
+          `result` = null;
+        }
+        else {
+          if (`dest` === null || `dest` === undefined || `dest`.length != `src`.length) {
+            `dest` = new Array(`src`.length);
+          }
+          `result` = `dest`;
+          for (var i = 0; i < `src`.length; ++i) {
+            `result`[i] = nimCopy(`result`[i], `src`[i], `ti`.base);
+          }
+        }
+      }
+    """.}
+  of tySequence, tyOpenArray:
+    {.emit: """
+      if (`src` === null) {
+        `result` = null;
+      }
+      else {
+        if (`dest` === null || `dest` === undefined || `dest`.length != `src`.length) {
+          `dest` = new Array(`src`.length);
+        }
+        `result` = `dest`;
+        for (var i = 0; i < `src`.length; ++i) {
+          `result`[i] = nimCopy(`result`[i], `src`[i], `ti`.base);
+        }
+      }
+    """.}
+  of tyString:
+    {.emit: """
+      if (`src` !== null) {
+        `result` = `src`.slice(0);
+      }
+    """.}
+  else:
+    result = src
+
+proc arrayConstr(len: int, value: JSRef, typ: PNimType): JSRef {.
+                asmNoStackFrame, compilerproc.} =
+  # types are fake
+  {.emit: """
+    var result = new Array(`len`);
+    for (var i = 0; i < `len`; ++i) result[i] = nimCopy(null, `value`, `typ`);
+    return result;
+  """.}
+
+proc chckIndx(i, a, b: int): int {.compilerproc.} =
+  if i >= a and i <= b: return i
+  else: raiseIndexError(i, a, b)
+
+proc chckRange(i, a, b: int): int {.compilerproc.} =
+  if i >= a and i <= b: return i
+  else: raiseRangeError()
+
+proc chckObj(obj, subclass: PNimType) {.compilerproc.} =
+  # checks if obj is of type subclass:
+  var x = obj
+  if x == subclass: return # optimized fast path
+  while x != subclass:
+    if x == nil:
+      raise newException(ObjectConversionDefect, "invalid object conversion")
+    x = x.base
+
+proc isObj(obj, subclass: PNimType): bool {.compilerproc.} =
+  # checks if obj is of type subclass:
+  var x = obj
+  if x == subclass: return true # optimized fast path
+  while x != subclass:
+    if x == nil: return false
+    x = x.base
+  return true
+
+proc addChar(x: string, c: char) {.compilerproc, asmNoStackFrame.} =
+  {.emit: "`x`.push(`c`);".}
+
+{.pop.}
+
+proc tenToThePowerOf(b: int): BiggestFloat =
+  # xxx deadcode
+  var b = b
+  var a = 10.0
+  result = 1.0
+  while true:
+    if (b and 1) == 1:
+      result = result * a
+    b = b shr 1
+    if b == 0: break
+    a = a * a
+
+const
+  IdentChars = {'a'..'z', 'A'..'Z', '0'..'9', '_'}
+
+
+proc parseFloatNative(a: openarray[char]): float =
+  var str = ""
+  for x in a:
+    str.add x
+
+  let cstr = cstring str
+
+  {.emit: """
+  `result` = Number(`cstr`);
+  """.}
+
+proc nimParseBiggestFloat(s: openarray[char], number: var BiggestFloat): int {.compilerproc.} =
+  var sign: bool
+  var i = 0
+  if s[i] == '+': inc(i)
+  elif s[i] == '-':
+    sign = true
+    inc(i)
+  if s[i] == 'N' or s[i] == 'n':
+    if s[i+1] == 'A' or s[i+1] == 'a':
+      if s[i+2] == 'N' or s[i+2] == 'n':
+        if s[i+3] notin IdentChars:
+          number = NaN
+          return i+3
+    return 0
+  if s[i] == 'I' or s[i] == 'i':
+    if s[i+1] == 'N' or s[i+1] == 'n':
+      if s[i+2] == 'F' or s[i+2] == 'f':
+        if s[i+3] notin IdentChars:
+          number = if sign: -Inf else: Inf
+          return i+3
+    return 0
+
+  var buf: string
+    # we could also use an `array[char, N]` buffer to avoid reallocs, or
+    # use a 2-pass algorithm that first computes the length.
+  if sign: buf.add '-'
+  template addInc =
+    buf.add s[i]
+    inc(i)
+  template eatUnderscores =
+    while s[i] == '_': inc(i)
+  while s[i] in {'0'..'9'}: # Read integer part
+    buf.add s[i]
+    inc(i)
+    eatUnderscores()
+  if s[i] == '.': # Decimal?
+    addInc()
+    while s[i] in {'0'..'9'}: # Read fractional part
+      addInc()
+      eatUnderscores()
+  # Again, read integer and fractional part
+  if buf.len == ord(sign): return 0
+  if s[i] in {'e', 'E'}: # Exponent?
+    addInc()
+    if s[i] == '+': inc(i)
+    elif s[i] == '-': addInc()
+    if s[i] notin {'0'..'9'}: return 0
+    while s[i] in {'0'..'9'}:
+      addInc()
+      eatUnderscores()
+  number = parseFloatNative(buf)
+  result = i
+
+# Workaround for IE, IE up to version 11 lacks 'Math.trunc'. We produce
+# 'Math.trunc' for Nim's ``div`` and ``mod`` operators:
+when defined(nimJsMathTruncPolyfill):
+  {.emit: """
+if (!Math.trunc) {
+  Math.trunc = function(v) {
+    v = +v;
+    if (!isFinite(v)) return v;
+    return (v - v % 1) || (v < 0 ? -0 : v === 0 ? v : 0);
+  };
+}
+""".}
+
+proc cmpClosures(a, b: JSRef): bool {.compilerproc, asmNoStackFrame.} =
+  # Both `a` and `b` need to be a closure
+  {.emit: """
+    if (`a` !== null && `a`.ClP_0 !== undefined &&
+        `b` !== null && `b`.ClP_0 !== undefined) {
+      return `a`.ClP_0 == `b`.ClP_0 && `a`.ClE_0 == `b`.ClE_0;
+    } else {
+      return `a` == `b`;
+    }
+  """
+  .}