summary refs log tree commit diff stats
diff options
context:
space:
mode:
-rw-r--r--changelog.md11
-rw-r--r--doc/backends.rst12
-rw-r--r--doc/nims.rst2
-rw-r--r--lib/pure/cstrutils.nim64
-rw-r--r--lib/pure/lexbase.nim3
-rw-r--r--lib/pure/marshal.nim10
-rw-r--r--lib/pure/parsesql.nim64
-rw-r--r--lib/pure/ropes.nim77
-rw-r--r--lib/pure/streams.nim476
-rw-r--r--lib/pure/strtabs.nim4
-rw-r--r--lib/pure/unittest.nim2
-rw-r--r--lib/pure/volatile.nim18
-rw-r--r--lib/system/jssys.nim2
-rw-r--r--tests/js/tstdlib_imports.nim71
-rw-r--r--tests/js/tstdlib_various.nim194
-rw-r--r--tests/js/tstreams.nim22
-rw-r--r--tests/stdlib/thtmlparser.nim4
-rw-r--r--tests/stdlib/tparscfg.nim61
-rw-r--r--tests/stdlib/tparsesql.nim3
-rw-r--r--tests/stdlib/tpegs.nim1
-rw-r--r--tests/stdlib/tstdlib_various.nim25
-rw-r--r--tests/test_nimscript.nims88
22 files changed, 855 insertions, 359 deletions
diff --git a/changelog.md b/changelog.md
index f39f68856..e7ab1a298 100644
--- a/changelog.md
+++ b/changelog.md
@@ -37,6 +37,17 @@
   and this can now throw in edge cases where `getCurrentDir` throws.
   `relativePath` also now works for js with `-d:nodejs`.
 
+- JavaScript and NimScript standard library changes: `streams.StringStream` is
+  now supported in JavaScript, with the limitation that any buffer `pointer`s
+  used must be castable to `ptr string`, any incompatible pointer type will not
+  work. The `lexbase` and `streams` modules used to fail to compile on
+  NimScript due to a bug, but this has been fixed.
+  
+  The following modules now compile on both JS and NimScript: `parsecsv`,
+  `parsecfg`, `parsesql`, `xmlparser`, `htmlparser` and `ropes`. Additionally
+  supported for JS is `cstrutils.startsWith` and `cstrutils.endsWith`, for
+  NimScript: `json`, `parsejson`, `strtabs` and `unidecode`. 
+
 - Added `streams.readStr` and `streams.peekStr` overloads to
   accept an existing string to modify, which avoids memory
   allocations, similar to `streams.readLine` (#13857).
diff --git a/doc/backends.rst b/doc/backends.rst
index 1a6729ee6..52c16b9f7 100644
--- a/doc/backends.rst
+++ b/doc/backends.rst
@@ -76,13 +76,15 @@ available. This includes:
 * manual memory management (``alloc``, etc.)
 * casting and other unsafe operations (``cast`` operator, ``zeroMem``, etc.)
 * file management
-* most modules of the standard library
+* OS-specific operations
+* threading, coroutines
+* some modules of the standard library
 * proper 64 bit integer arithmetic
-* unsigned integer arithmetic
 
-However, the modules `strutils <strutils.html>`_, `math <math.html>`_, and
-`times <times.html>`_ are available! To access the DOM, use the `dom
-<dom.html>`_ module that is only available for the JavaScript platform.
+To compensate, the standard library has modules `catered to the JS backend
+<https://nim-lang.org/docs/lib.html#pure-libraries-modules-for-js-backend>`_
+and more support will come in the future (for instance, Node.js bindings
+to get OS info).
 
 To compile a Nim module into a ``.js`` file use the ``js`` command; the
 default is a ``.js`` file that is supposed to be referenced in an ``.html``
diff --git a/doc/nims.rst b/doc/nims.rst
index 8e3638d62..c1d434476 100644
--- a/doc/nims.rst
+++ b/doc/nims.rst
@@ -52,8 +52,6 @@ NimScript is subject to some limitations caused by the implementation of the VM
 
 * ``random.randomize()`` requires an ``int64`` explicitly passed as argument, you *must* pass a Seed integer.
 
-* ``unicode`` can be imported, but not ``unidecode``.
-
 
 Standard library modules
 ========================
diff --git a/lib/pure/cstrutils.nim b/lib/pure/cstrutils.nim
index fe9ceb68b..a2a8fbc2f 100644
--- a/lib/pure/cstrutils.nim
+++ b/lib/pure/cstrutils.nim
@@ -19,29 +19,43 @@ proc toLowerAscii(c: char): char {.inline.} =
   else:
     result = c
 
-proc startsWith*(s, prefix: cstring): bool {.noSideEffect,
-  rtl, extern: "csuStartsWith".} =
-  ## Returns true iff ``s`` starts with ``prefix``.
-  ##
-  ## If ``prefix == ""`` true is returned.
-  var i = 0
-  while true:
-    if prefix[i] == '\0': return true
-    if s[i] != prefix[i]: return false
-    inc(i)
+when defined(js):
+  proc startsWith*(s, prefix: cstring): bool {.noSideEffect,
+    importjs: "#.startsWith(#)".}
 
-proc endsWith*(s, suffix: cstring): bool {.noSideEffect,
-  rtl, extern: "csuEndsWith".} =
-  ## Returns true iff ``s`` ends with ``suffix``.
-  ##
-  ## If ``suffix == ""`` true is returned.
-  let slen = s.len
-  var i = 0
-  var j = slen - len(suffix)
-  while i+j <% slen:
-    if s[i+j] != suffix[i]: return false
-    inc(i)
-  if suffix[i] == '\0': return true
+  proc endsWith*(s, suffix: cstring): bool {.noSideEffect,
+    importjs: "#.endsWith(#)".}
+  
+  # JS string has more operations that might warrant its own module:
+  # https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String
+else:
+  proc startsWith*(s, prefix: cstring): bool {.noSideEffect,
+    rtl, extern: "csuStartsWith".} =
+    ## Returns true iff ``s`` starts with ``prefix``.
+    ##
+    ## If ``prefix == ""`` true is returned.
+    ## 
+    ## JS backend uses native ``String.prototype.startsWith``.
+    var i = 0
+    while true:
+      if prefix[i] == '\0': return true
+      if s[i] != prefix[i]: return false
+      inc(i)
+
+  proc endsWith*(s, suffix: cstring): bool {.noSideEffect,
+    rtl, extern: "csuEndsWith".} =
+    ## Returns true iff ``s`` ends with ``suffix``.
+    ##
+    ## If ``suffix == ""`` true is returned.
+    ## 
+    ## JS backend uses native ``String.prototype.endsWith``.
+    let slen = s.len
+    var i = 0
+    var j = slen - len(suffix)
+    while i+j <% slen:
+      if s[i+j] != suffix[i]: return false
+      inc(i)
+    if suffix[i] == '\0': return true
 
 proc cmpIgnoreStyle*(a, b: cstring): int {.noSideEffect,
   rtl, extern: "csuCmpIgnoreStyle".} =
@@ -53,6 +67,9 @@ proc cmpIgnoreStyle*(a, b: cstring): int {.noSideEffect,
   ## | 0 iff a == b
   ## | < 0 iff a < b
   ## | > 0 iff a > b
+  ## 
+  ## Not supported for JS backend, use `strutils.cmpIgnoreStyle
+  ## <https://nim-lang.org/docs/strutils.html#cmpIgnoreStyle%2Cstring%2Cstring>`_ instead.
   var i = 0
   var j = 0
   while true:
@@ -72,6 +89,9 @@ proc cmpIgnoreCase*(a, b: cstring): int {.noSideEffect,
   ## | 0 iff a == b
   ## | < 0 iff a < b
   ## | > 0 iff a > b
+  ## 
+  ## Not supported for JS backend, use `strutils.cmpIgnoreCase
+  ## <https://nim-lang.org/docs/strutils.html#cmpIgnoreCase%2Cstring%2Cstring>`_ instead.
   var i = 0
   while true:
     var aa = toLowerAscii(a[i])
diff --git a/lib/pure/lexbase.nim b/lib/pure/lexbase.nim
index 8bc96c82c..27225ab8d 100644
--- a/lib/pure/lexbase.nim
+++ b/lib/pure/lexbase.nim
@@ -52,7 +52,8 @@ proc fillBuffer(L: var BaseLexer) =
   toCopy = L.buf.len - (L.sentinel + 1)
   assert(toCopy >= 0)
   if toCopy > 0:
-    when defined(js):
+    when defined(js) or defined(nimscript):
+      # nimscript has to be here to avoid compiling other branch (moveMem)
       for i in 0 ..< toCopy:
         L.buf[i] = L.buf[L.sentinel + 1 + i]
     else:
diff --git a/lib/pure/marshal.nim b/lib/pure/marshal.nim
index daf313b5c..b6ad2e20f 100644
--- a/lib/pure/marshal.nim
+++ b/lib/pure/marshal.nim
@@ -50,8 +50,14 @@
 ## * `streams module <streams.html>`_
 ## * `json module <json.html>`_
 
-when defined(nimV2):
-  {.error: """marshal module is not supported in new runtime.
+const unsupportedPlatform =
+  when defined(nimV2): "new runtime"
+  elif defined(js): "javascript"
+  elif defined(nimscript): "nimscript"
+  else: ""
+
+when unsupportedPlatform != "":
+  {.error: "marshal module is not supported in " & unsupportedPlatform & """.
 Please use alternative packages for serialization. 
 It is possible to reimplement this module using generics and type traits. 
 Please contribute new implementation.""".}
diff --git a/lib/pure/parsesql.nim b/lib/pure/parsesql.nim
index b84c1a744..68e75e5fa 100644
--- a/lib/pure/parsesql.nim
+++ b/lib/pure/parsesql.nim
@@ -557,7 +557,14 @@ type
     tok: Token
 
 proc newNode*(k: SqlNodeKind): SqlNode =
-  result = SqlNode(kind: k)
+  when defined(js): # bug #14117
+    case k
+    of LiteralNodes:
+      result = SqlNode(kind: k, strVal: "")
+    else:
+      result = SqlNode(kind: k, sons: @[])
+  else:
+    result = SqlNode(kind: k)
 
 proc newNode*(k: SqlNodeKind, s: string): SqlNode =
   result = SqlNode(kind: k)
@@ -1469,34 +1476,33 @@ proc treeRepr*(s: SqlNode): string =
   result = newStringOfCap(128)
   treeReprAux(s, 0, result)
 
-when not defined(js):
-  import streams
+import streams
 
-  proc open(L: var SqlLexer, input: Stream, filename: string) =
-    lexbase.open(L, input)
-    L.filename = filename
+proc open(L: var SqlLexer, input: Stream, filename: string) =
+  lexbase.open(L, input)
+  L.filename = filename
 
-  proc open(p: var SqlParser, input: Stream, filename: string) =
-    ## opens the parser `p` and assigns the input stream `input` to it.
-    ## `filename` is only used for error messages.
-    open(SqlLexer(p), input, filename)
-    p.tok.kind = tkInvalid
-    p.tok.literal = ""
-    getTok(p)
+proc open(p: var SqlParser, input: Stream, filename: string) =
+  ## opens the parser `p` and assigns the input stream `input` to it.
+  ## `filename` is only used for error messages.
+  open(SqlLexer(p), input, filename)
+  p.tok.kind = tkInvalid
+  p.tok.literal = ""
+  getTok(p)
 
-  proc parseSQL*(input: Stream, filename: string): SqlNode =
-    ## parses the SQL from `input` into an AST and returns the AST.
-    ## `filename` is only used for error messages.
-    ## Syntax errors raise an `SqlParseError` exception.
-    var p: SqlParser
-    open(p, input, filename)
-    try:
-      result = parse(p)
-    finally:
-      close(p)
-
-  proc parseSQL*(input: string, filename = ""): SqlNode =
-    ## parses the SQL from `input` into an AST and returns the AST.
-    ## `filename` is only used for error messages.
-    ## Syntax errors raise an `SqlParseError` exception.
-    parseSQL(newStringStream(input), "")
+proc parseSQL*(input: Stream, filename: string): SqlNode =
+  ## parses the SQL from `input` into an AST and returns the AST.
+  ## `filename` is only used for error messages.
+  ## Syntax errors raise an `SqlParseError` exception.
+  var p: SqlParser
+  open(p, input, filename)
+  try:
+    result = parse(p)
+  finally:
+    close(p)
+
+proc parseSQL*(input: string, filename = ""): SqlNode =
+  ## parses the SQL from `input` into an AST and returns the AST.
+  ## `filename` is only used for error messages.
+  ## Syntax errors raise an `SqlParseError` exception.
+  parseSQL(newStringStream(input), "")
diff --git a/lib/pure/ropes.nim b/lib/pure/ropes.nim
index f5023cb6c..41d6211b4 100644
--- a/lib/pure/ropes.nim
+++ b/lib/pure/ropes.nim
@@ -287,46 +287,47 @@ proc addf*(c: var Rope, frmt: string, args: openArray[Rope]) {.
   ## shortcut for ``add(c, frmt % args)``.
   add(c, frmt % args)
 
-const
-  bufSize = 1024 # 1 KB is reasonable
-
-proc equalsFile*(r: Rope, f: File): bool {.rtl, extern: "nro$1File".} =
-  ## returns true if the contents of the file `f` equal `r`.
-  var
-    buf: array[bufSize, char]
-    bpos = buf.len
-    blen = buf.len
-
-  for s in leaves(r):
-    var spos = 0
-    let slen = s.len
-    while spos < slen:
-      if bpos == blen:
-        # Read more data
-        bpos = 0
-        blen = readBuffer(f, addr(buf[0]), buf.len)
-        if blen == 0: # no more data in file
+when not defined(js) and not defined(nimscript):
+  const
+    bufSize = 1024 # 1 KB is reasonable
+
+  proc equalsFile*(r: Rope, f: File): bool {.rtl, extern: "nro$1File".} =
+    ## returns true if the contents of the file `f` equal `r`.
+    var
+      buf: array[bufSize, char]
+      bpos = buf.len
+      blen = buf.len
+
+    for s in leaves(r):
+      var spos = 0
+      let slen = s.len
+      while spos < slen:
+        if bpos == blen:
+          # Read more data
+          bpos = 0
+          blen = readBuffer(f, addr(buf[0]), buf.len)
+          if blen == 0: # no more data in file
+            result = false
+            return
+        let n = min(blen - bpos, slen - spos)
+        # TODO There's gotta be a better way of comparing here...
+        if not equalMem(addr(buf[bpos]),
+                        cast[pointer](cast[int](cstring(s))+spos), n):
           result = false
           return
-      let n = min(blen - bpos, slen - spos)
-      # TODO There's gotta be a better way of comparing here...
-      if not equalMem(addr(buf[bpos]),
-                      cast[pointer](cast[int](cstring(s))+spos), n):
-        result = false
-        return
-      spos += n
-      bpos += n
-
-  result = readBuffer(f, addr(buf[0]), 1) == 0 # check that we've read all
-
-proc equalsFile*(r: Rope, filename: string): bool {.rtl, extern: "nro$1Str".} =
-  ## returns true if the contents of the file `f` equal `r`. If `f` does not
-  ## exist, false is returned.
-  var f: File
-  result = open(f, filename)
-  if result:
-    result = equalsFile(r, f)
-    close(f)
+        spos += n
+        bpos += n
+
+    result = readBuffer(f, addr(buf[0]), 1) == 0 # check that we've read all
+
+  proc equalsFile*(r: Rope, filename: string): bool {.rtl, extern: "nro$1Str".} =
+    ## returns true if the contents of the file `f` equal `r`. If `f` does not
+    ## exist, false is returned.
+    var f: File
+    result = open(f, filename)
+    if result:
+      result = equalsFile(r, f)
+      close(f)
 
 new(N) # init dummy node for splay algorithm
 
diff --git a/lib/pure/streams.nim b/lib/pure/streams.nim
index 4af4ae2ec..8efe5b227 100644
--- a/lib/pure/streams.nim
+++ b/lib/pure/streams.nim
@@ -216,6 +216,9 @@ proc getPosition*(s: Stream): int =
 
 proc readData*(s: Stream, buffer: pointer, bufLen: int): int =
   ## Low level proc that reads data into an untyped `buffer` of `bufLen` size.
+  ## 
+  ## **JS note:** `buffer` is treated as a ``ptr string`` and written to between
+  ## ``0..<bufLen``.
   runnableExamples:
     var strm = newStringStream("abcde")
     var buffer: array[6, char]
@@ -241,11 +244,21 @@ proc readDataStr*(s: Stream, buffer: var string, slice: Slice[int]): int =
     # fallback
     result = s.readData(addr buffer[0], buffer.len)
 
-when not defined(js):
+template jsOrVmBlock(caseJsOrVm, caseElse: untyped): untyped =
+  when nimvm:
+    block:
+      caseJsOrVm
+  else:
+    block:
+      when defined(js) or defined(nimscript):
+        # nimscript has to be here to avoid semantic checking of caseElse
+        caseJsOrVm
+      else:
+        caseElse
+
+when (NimMajor, NimMinor) >= (1, 3) or not defined(js):
   proc readAll*(s: Stream): string =
     ## Reads all available data.
-    ##
-    ## **Note:** Not available for JS backend.
     runnableExamples:
       var strm = newStringStream("The first line\nthe second line\nthe third line")
       doAssert strm.readAll() == "The first line\nthe second line\nthe third line"
@@ -253,7 +266,7 @@ when not defined(js):
       strm.close()
 
     const bufferSize = 1024
-    when nimvm:
+    jsOrVmBlock:
       var buffer2: string
       buffer2.setLen(bufferSize)
       while true:
@@ -265,7 +278,7 @@ when not defined(js):
         result[prevLen..<prevLen+readBytes] = buffer2[0..<readBytes]
         if readBytes < bufferSize:
           break
-    else:
+    do: # not JS or VM
       var buffer {.noinit.}: array[bufferSize, char]
       while true:
         let readBytes = readData(s, addr(buffer[0]), bufferSize)
@@ -280,6 +293,9 @@ when not defined(js):
 proc peekData*(s: Stream, buffer: pointer, bufLen: int): int =
   ## Low level proc that reads data into an untyped `buffer` of `bufLen` size
   ## without moving stream position.
+  ## 
+  ## **JS note:** `buffer` is treated as a ``ptr string`` and written to between
+  ## ``0..<bufLen``.
   runnableExamples:
     var strm = newStringStream("abcde")
     var buffer: array[6, char]
@@ -293,6 +309,9 @@ proc peekData*(s: Stream, buffer: pointer, bufLen: int): int =
 proc writeData*(s: Stream, buffer: pointer, bufLen: int) =
   ## Low level proc that writes an untyped `buffer` of `bufLen` size
   ## to the stream `s`.
+  ## 
+  ## **JS note:** `buffer` is treated as a ``ptr string`` and read between
+  ## ``0..<bufLen``.
   runnableExamples:
     ## writeData
     var strm = newStringStream("")
@@ -311,6 +330,9 @@ proc writeData*(s: Stream, buffer: pointer, bufLen: int) =
 proc write*[T](s: Stream, x: T) =
   ## Generic write procedure. Writes `x` to the stream `s`. Implementation:
   ##
+  ## **Note:** Not available for JS backend. Use `write(Stream, string)
+  ## <#write,Stream,string>`_ for now.
+  ##
   ## .. code-block:: Nim
   ##
   ##     s.writeData(s, unsafeAddr(x), sizeof(x))
@@ -336,7 +358,12 @@ proc write*(s: Stream, x: string) =
   when nimvm:
     writeData(s, cstring(x), x.len)
   else:
-    if x.len > 0: writeData(s, cstring(x), x.len)
+    if x.len > 0:
+      when defined(js):
+        var x = x
+        writeData(s, addr(x), x.len)
+      else:
+        writeData(s, cstring(x), x.len)
 
 proc write*(s: Stream, args: varargs[string, `$`]) =
   ## Writes one or more strings to the the stream. No length fields or
@@ -366,6 +393,8 @@ proc writeLine*(s: Stream, args: varargs[string, `$`]) =
 
 proc read*[T](s: Stream, result: var T) =
   ## Generic read procedure. Reads `result` from the stream `s`.
+  ##
+  ## **Note:** Not available for JS backend. Use `readStr <#readStr,Stream,int>`_ for now.
   runnableExamples:
     var strm = newStringStream("012")
     ## readInt
@@ -383,6 +412,8 @@ proc read*[T](s: Stream, result: var T) =
 
 proc peek*[T](s: Stream, result: var T) =
   ## Generic peek procedure. Peeks `result` from the stream `s`.
+  ##
+  ## **Note:** Not available for JS backend. Use `peekStr <#peekStr,Stream,int>`_ for now.
   runnableExamples:
     var strm = newStringStream("012")
     ## peekInt
@@ -412,11 +443,11 @@ proc readChar*(s: Stream): char =
     doAssert strm.readChar() == '\x00'
     strm.close()
 
-  when nimvm:
+  jsOrVmBlock:
     var str = " "
-    if readDataStr(s, str, 0..<sizeof(result)) != 1: result = '\0'
+    if readDataStr(s, str, 0..0) != 1: result = '\0'
     else: result = str[0]
-  else:
+  do:
     if readData(s, addr(result), sizeof(result)) != 1: result = '\0'
 
 proc peekChar*(s: Stream): char =
@@ -430,7 +461,12 @@ proc peekChar*(s: Stream): char =
     doAssert strm.peekChar() == '\x00'
     strm.close()
 
-  if peekData(s, addr(result), sizeof(result)) != 1: result = '\0'
+  when defined(js):
+    var str = " "
+    if peekData(s, addr(str), sizeof(result)) != 1: result = '\0'
+    else: result = str[0]
+  else:
+    if peekData(s, addr(result), sizeof(result)) != 1: result = '\0'
 
 proc readBool*(s: Stream): bool =
   ## Reads a bool from the stream `s`.
@@ -438,6 +474,8 @@ proc readBool*(s: Stream): bool =
   ## A bool is one byte long and it is `true` for every non-zero
   ## (`0000_0000`) value.
   ## Raises `IOError` if an error occurred.
+  ##
+  ## **Note:** Not available for JS backend. Use `readStr <#readStr,Stream,int>`_ for now.
   runnableExamples:
     var strm = newStringStream()
     ## setup for reading data
@@ -461,6 +499,8 @@ proc peekBool*(s: Stream): bool =
   ## A bool is one byte long and it is `true` for every non-zero
   ## (`0000_0000`) value.
   ## Raises `IOError` if an error occurred.
+  ##
+  ## **Note:** Not available for JS backend. Use `peekStr <#peekStr,Stream,int>`_ for now.
   runnableExamples:
     var strm = newStringStream()
     ## setup for reading data
@@ -482,6 +522,8 @@ proc peekBool*(s: Stream): bool =
 
 proc readInt8*(s: Stream): int8 =
   ## Reads an int8 from the stream `s`. Raises `IOError` if an error occurred.
+  ##
+  ## **Note:** Not available for JS backend. Use `readStr <#readStr,Stream,int>`_ for now.
   runnableExamples:
     var strm = newStringStream()
     ## setup for reading data
@@ -499,6 +541,8 @@ proc readInt8*(s: Stream): int8 =
 
 proc peekInt8*(s: Stream): int8 =
   ## Peeks an int8 from the stream `s`. Raises `IOError` if an error occurred.
+  ##
+  ## **Note:** Not available for JS backend. Use `peekStr <#peekStr,Stream,int>`_ for now.
   runnableExamples:
     var strm = newStringStream()
     ## setup for reading data
@@ -518,6 +562,8 @@ proc peekInt8*(s: Stream): int8 =
 
 proc readInt16*(s: Stream): int16 =
   ## Reads an int16 from the stream `s`. Raises `IOError` if an error occurred.
+  ##
+  ## **Note:** Not available for JS backend. Use `readStr <#readStr,Stream,int>`_ for now.
   runnableExamples:
     var strm = newStringStream()
     ## setup for reading data
@@ -535,6 +581,8 @@ proc readInt16*(s: Stream): int16 =
 
 proc peekInt16*(s: Stream): int16 =
   ## Peeks an int16 from the stream `s`. Raises `IOError` if an error occurred.
+  ##
+  ## **Note:** Not available for JS backend. Use `peekStr <#peekStr,Stream,int>`_ for now.
   runnableExamples:
     var strm = newStringStream()
     ## setup for reading data
@@ -554,6 +602,8 @@ proc peekInt16*(s: Stream): int16 =
 
 proc readInt32*(s: Stream): int32 =
   ## Reads an int32 from the stream `s`. Raises `IOError` if an error occurred.
+  ##
+  ## **Note:** Not available for JS backend. Use `readStr <#readStr,Stream,int>`_ for now.
   runnableExamples:
     var strm = newStringStream()
     ## setup for reading data
@@ -571,6 +621,8 @@ proc readInt32*(s: Stream): int32 =
 
 proc peekInt32*(s: Stream): int32 =
   ## Peeks an int32 from the stream `s`. Raises `IOError` if an error occurred.
+  ##
+  ## **Note:** Not available for JS backend. Use `peekStr <#peekStr,Stream,int>`_ for now.
   runnableExamples:
     var strm = newStringStream()
     ## setup for reading data
@@ -590,6 +642,8 @@ proc peekInt32*(s: Stream): int32 =
 
 proc readInt64*(s: Stream): int64 =
   ## Reads an int64 from the stream `s`. Raises `IOError` if an error occurred.
+  ##
+  ## **Note:** Not available for JS backend. Use `readStr <#readStr,Stream,int>`_ for now.
   runnableExamples:
     var strm = newStringStream()
     ## setup for reading data
@@ -607,6 +661,8 @@ proc readInt64*(s: Stream): int64 =
 
 proc peekInt64*(s: Stream): int64 =
   ## Peeks an int64 from the stream `s`. Raises `IOError` if an error occurred.
+  ##
+  ## **Note:** Not available for JS backend. Use `peekStr <#peekStr,Stream,int>`_ for now.
   runnableExamples:
     var strm = newStringStream()
     ## setup for reading data
@@ -626,6 +682,8 @@ proc peekInt64*(s: Stream): int64 =
 
 proc readUint8*(s: Stream): uint8 =
   ## Reads an uint8 from the stream `s`. Raises `IOError` if an error occurred.
+  ##
+  ## **Note:** Not available for JS backend. Use `readStr <#readStr,Stream,int>`_ for now.
   runnableExamples:
     var strm = newStringStream()
     ## setup for reading data
@@ -643,6 +701,8 @@ proc readUint8*(s: Stream): uint8 =
 
 proc peekUint8*(s: Stream): uint8 =
   ## Peeks an uint8 from the stream `s`. Raises `IOError` if an error occurred.
+  ##
+  ## **Note:** Not available for JS backend. Use `peekStr <#peekStr,Stream,int>`_ for now.
   runnableExamples:
     var strm = newStringStream()
     ## setup for reading data
@@ -662,6 +722,8 @@ proc peekUint8*(s: Stream): uint8 =
 
 proc readUint16*(s: Stream): uint16 =
   ## Reads an uint16 from the stream `s`. Raises `IOError` if an error occurred.
+  ##
+  ## **Note:** Not available for JS backend. Use `readStr <#readStr,Stream,int>`_ for now.
   runnableExamples:
     var strm = newStringStream()
     ## setup for reading data
@@ -679,6 +741,8 @@ proc readUint16*(s: Stream): uint16 =
 
 proc peekUint16*(s: Stream): uint16 =
   ## Peeks an uint16 from the stream `s`. Raises `IOError` if an error occurred.
+  ##
+  ## **Note:** Not available for JS backend. Use `peekStr <#peekStr,Stream,int>`_ for now.
   runnableExamples:
     var strm = newStringStream()
     ## setup for reading data
@@ -698,6 +762,8 @@ proc peekUint16*(s: Stream): uint16 =
 
 proc readUint32*(s: Stream): uint32 =
   ## Reads an uint32 from the stream `s`. Raises `IOError` if an error occurred.
+  ##
+  ## **Note:** Not available for JS backend. Use `readStr <#readStr,Stream,int>`_ for now.
   runnableExamples:
     var strm = newStringStream()
     ## setup for reading data
@@ -716,6 +782,8 @@ proc readUint32*(s: Stream): uint32 =
 
 proc peekUint32*(s: Stream): uint32 =
   ## Peeks an uint32 from the stream `s`. Raises `IOError` if an error occurred.
+  ##
+  ## **Note:** Not available for JS backend. Use `peekStr <#peekStr,Stream,int>`_ for now.
   runnableExamples:
     var strm = newStringStream()
     ## setup for reading data
@@ -735,6 +803,8 @@ proc peekUint32*(s: Stream): uint32 =
 
 proc readUint64*(s: Stream): uint64 =
   ## Reads an uint64 from the stream `s`. Raises `IOError` if an error occurred.
+  ##
+  ## **Note:** Not available for JS backend. Use `readStr <#readStr,Stream,int>`_ for now.
   runnableExamples:
     var strm = newStringStream()
     ## setup for reading data
@@ -752,6 +822,8 @@ proc readUint64*(s: Stream): uint64 =
 
 proc peekUint64*(s: Stream): uint64 =
   ## Peeks an uint64 from the stream `s`. Raises `IOError` if an error occurred.
+  ##
+  ## **Note:** Not available for JS backend. Use `peekStr <#peekStr,Stream,int>`_ for now.
   runnableExamples:
     var strm = newStringStream()
     ## setup for reading data
@@ -771,6 +843,8 @@ proc peekUint64*(s: Stream): uint64 =
 
 proc readFloat32*(s: Stream): float32 =
   ## Reads a float32 from the stream `s`. Raises `IOError` if an error occurred.
+  ##
+  ## **Note:** Not available for JS backend. Use `readStr <#readStr,Stream,int>`_ for now.
   runnableExamples:
     var strm = newStringStream()
     ## setup for reading data
@@ -788,6 +862,8 @@ proc readFloat32*(s: Stream): float32 =
 
 proc peekFloat32*(s: Stream): float32 =
   ## Peeks a float32 from the stream `s`. Raises `IOError` if an error occurred.
+  ##
+  ## **Note:** Not available for JS backend. Use `peekStr <#peekStr,Stream,int>`_ for now.
   runnableExamples:
     var strm = newStringStream()
     ## setup for reading data
@@ -807,6 +883,8 @@ proc peekFloat32*(s: Stream): float32 =
 
 proc readFloat64*(s: Stream): float64 =
   ## Reads a float64 from the stream `s`. Raises `IOError` if an error occurred.
+  ##
+  ## **Note:** Not available for JS backend. Use `readStr <#readStr,Stream,int>`_ for now.
   runnableExamples:
     var strm = newStringStream()
     ## setup for reading data
@@ -824,6 +902,8 @@ proc readFloat64*(s: Stream): float64 =
 
 proc peekFloat64*(s: Stream): float64 =
   ## Peeks a float64 from the stream `s`. Raises `IOError` if an error occurred.
+  ##
+  ## **Note:** Not available for JS backend. Use `peekStr <#peekStr,Stream,int>`_ for now.
   runnableExamples:
     var strm = newStringStream()
     ## setup for reading data
@@ -841,10 +921,19 @@ proc peekFloat64*(s: Stream): float64 =
 
   peek(s, result)
 
+template untaint(s: var TaintedString): var string =
+  when taintMode: # for VM, bug #12282
+    s.string
+  else:
+    s
+
 proc readStrPrivate(s: Stream, length: int, str: var TaintedString) =
-  if length > len(str): setLen(str.string, length)
-  var L = readData(s, cstring(str), length)
-  if L != len(str): setLen(str.string, L)
+  if length > len(str): setLen(str.untaint, length)
+  when defined(js):
+    let L = readData(s, addr(str), length)
+  else:
+    let L = readData(s, cstring(str.string), length)
+  if L != len(str): setLen(str.untaint, L)
 
 proc readStr*(s: Stream, length: int, str: var TaintedString) {.since: (1, 3).} =
   ## Reads a string of length `length` from the stream `s`. Raises `IOError` if
@@ -865,9 +954,12 @@ proc readStr*(s: Stream, length: int): TaintedString =
   readStrPrivate(s, length, result)
 
 proc peekStrPrivate(s: Stream, length: int, str: var TaintedString) =
-  if length > len(str): setLen(str.string, length)
-  var L = peekData(s, cstring(str), length)
-  if L != len(str): setLen(str.string, L)
+  if length > len(str): setLen(str.untaint, length)
+  when defined(js):
+    let L = peekData(s, addr(str), length)
+  else:
+    let L = peekData(s, cstring(str.string), length)
+  if L != len(str): setLen(str.untaint, L)
 
 proc peekStr*(s: Stream, length: int, str: var TaintedString) {.since: (1, 3).} =
   ## Peeks a string of length `length` from the stream `s`. Raises `IOError` if
@@ -918,13 +1010,7 @@ proc readLine*(s: Stream, line: var TaintedString): bool =
     result = s.readLineImpl(s, line)
   else:
     # fallback
-    when nimvm: #Bug #12282
-      when taintMode:
-        line.string.setLen(0)
-      else:
-        line.setLen(0)
-    else:
-      line.string.setLen(0)
+    line.untaint.setLen(0)
     while true:
       var c = readChar(s)
       if c == '\c':
@@ -934,13 +1020,7 @@ proc readLine*(s: Stream, line: var TaintedString): bool =
       elif c == '\0':
         if line.len > 0: break
         else: return false
-      when nimvm: #Bug #12282
-        when taintMode:
-          line.string.add(c)
-        else:
-          line.add(c)
-      else:
-        line.string.add(c)
+      line.untaint.add(c)
     result = true
 
 proc peekLine*(s: Stream, line: var TaintedString): bool =
@@ -1002,7 +1082,7 @@ proc readLine*(s: Stream): TaintedString =
     if c == '\L' or c == '\0':
       break
     else:
-      result.string.add(c)
+      result.untaint.add(c)
 
 proc peekLine*(s: Stream): TaintedString =
   ## Peeks a line from a stream `s`. Raises `IOError` if an error occurred.
@@ -1048,17 +1128,13 @@ iterator lines*(s: Stream): TaintedString =
 type
   StringStream* = ref StringStreamObj
     ## A stream that encapsulates a string.
-    ##
-    ## **Note:** Not available for JS backend.
   StringStreamObj* = object of StreamObj
     ## A string stream object.
-    ##
-    ## **Note:** Not available for JS backend.
     data*: string ## A string data.
                   ## This is updated when called `writeLine` etc.
     pos: int
 
-when defined(js): #This section exists so that string streams work at compile time for the js backend
+when (NimMajor, NimMinor) < (1, 3) and defined(js):
   proc ssAtEnd(s: Stream): bool {.compileTime.} =
     var s = StringStream(s)
     return s.pos >= s.data.len
@@ -1111,7 +1187,7 @@ when defined(js): #This section exists so that string streams work at compile ti
       if readBytes < bufferSize:
         break
 
-else:
+else: # after 1.3 or JS not defined  
   proc ssAtEnd(s: Stream): bool =
     var s = StringStream(s)
     return s.pos >= s.data.len
@@ -1128,9 +1204,9 @@ else:
     var s = StringStream(s)
     result = min(slice.b + 1 - slice.a, s.data.len - s.pos)
     if result > 0:
-      when nimvm:
+      jsOrVmBlock:
         buffer[slice.a..<slice.a+result] = s.data[s.pos..<s.pos+result]
-      else:
+      do:
         copyMem(unsafeAddr buffer[slice.a], addr s.data[s.pos], result)
       inc(s.pos, result)
     else:
@@ -1140,7 +1216,14 @@ else:
     var s = StringStream(s)
     result = min(bufLen, s.data.len - s.pos)
     if result > 0:
-      copyMem(buffer, addr(s.data[s.pos]), result)
+      when defined(js):
+        try:
+          cast[ptr string](buffer)[][0..<result] = s.data[s.pos..<s.pos+result]
+        except:
+          raise newException(Defect, "could not read string stream, " &
+            "did you use a non-string buffer pointer?", getCurrentException())
+      elif not defined(nimscript):
+        copyMem(buffer, addr(s.data[s.pos]), result)
       inc(s.pos, result)
     else:
       result = 0
@@ -1149,7 +1232,14 @@ else:
     var s = StringStream(s)
     result = min(bufLen, s.data.len - s.pos)
     if result > 0:
-      copyMem(buffer, addr(s.data[s.pos]), result)
+      when defined(js):
+        try:
+          cast[ptr string](buffer)[][0..<result] = s.data[s.pos..<s.pos+result]
+        except:
+          raise newException(Defect, "could not peek string stream, " &
+            "did you use a non-string buffer pointer?", getCurrentException())
+      elif not defined(nimscript):
+        copyMem(buffer, addr(s.data[s.pos]), result)
     else:
       result = 0
 
@@ -1159,7 +1249,14 @@ else:
       return
     if s.pos + bufLen > s.data.len:
       setLen(s.data, s.pos + bufLen)
-    copyMem(addr(s.data[s.pos]), buffer, bufLen)
+    when defined(js):
+      try:
+        s.data[s.pos..<s.pos+bufLen] = cast[ptr string](buffer)[][0..<bufLen]
+      except:
+        raise newException(Defect, "could not write to string stream, " &
+          "did you use a non-string buffer pointer?", getCurrentException())
+    elif not defined(nimscript):
+      copyMem(addr(s.data[s.pos]), buffer, bufLen)
     inc(s.pos, bufLen)
 
   proc ssClose(s: Stream) =
@@ -1172,8 +1269,6 @@ else:
   proc newStringStream*(s: string = ""): owned StringStream =
     ## Creates a new stream from the string `s`.
     ##
-    ## **Note:** Not available for JS backend.
-    ##
     ## See also:
     ## * `newFileStream proc <#newFileStream,File>`_ creates a file stream from
     ##   opened File.
@@ -1195,163 +1290,166 @@ else:
     result.atEndImpl = ssAtEnd
     result.setPositionImpl = ssSetPosition
     result.getPositionImpl = ssGetPosition
-    result.readDataImpl = ssReadData
-    result.peekDataImpl = ssPeekData
-    result.writeDataImpl = ssWriteData
     result.readDataStrImpl = ssReadDataStr
+    when nimvm:
+      discard
+    else:
+      result.readDataImpl = ssReadData
+      result.peekDataImpl = ssPeekData
+      result.writeDataImpl = ssWriteData
 
-  type
-    FileStream* = ref FileStreamObj
-      ## A stream that encapsulates a `File`.
-      ##
-      ## **Note:** Not available for JS backend.
-    FileStreamObj* = object of Stream
-      ## A file stream object.
-      ##
-      ## **Note:** Not available for JS backend.
-      f: File
-
-  proc fsClose(s: Stream) =
-    if FileStream(s).f != nil:
-      close(FileStream(s).f)
-      FileStream(s).f = nil
-  proc fsFlush(s: Stream) = flushFile(FileStream(s).f)
-  proc fsAtEnd(s: Stream): bool = return endOfFile(FileStream(s).f)
-  proc fsSetPosition(s: Stream, pos: int) = setFilePos(FileStream(s).f, pos)
-  proc fsGetPosition(s: Stream): int = return int(getFilePos(FileStream(s).f))
-
-  proc fsReadData(s: Stream, buffer: pointer, bufLen: int): int =
-    result = readBuffer(FileStream(s).f, buffer, bufLen)
-
-  proc fsReadDataStr(s: Stream, buffer: var string, slice: Slice[int]): int =
-    result = readBuffer(FileStream(s).f, addr buffer[slice.a], slice.b + 1 - slice.a)
-
-  proc fsPeekData(s: Stream, buffer: pointer, bufLen: int): int =
-    let pos = fsGetPosition(s)
-    defer: fsSetPosition(s, pos)
-    result = readBuffer(FileStream(s).f, buffer, bufLen)
-
-  proc fsWriteData(s: Stream, buffer: pointer, bufLen: int) =
-    if writeBuffer(FileStream(s).f, buffer, bufLen) != bufLen:
-      raise newEIO("cannot write to stream")
-
-  proc fsReadLine(s: Stream, line: var TaintedString): bool =
-    result = readLine(FileStream(s).f, line)
-
-  proc newFileStream*(f: File): owned FileStream =
-    ## Creates a new stream from the file `f`.
+type
+  FileStream* = ref FileStreamObj
+    ## A stream that encapsulates a `File`.
     ##
     ## **Note:** Not available for JS backend.
+  FileStreamObj* = object of Stream
+    ## A file stream object.
     ##
-    ## See also:
-    ## * `newStringStream proc <#newStringStream,string>`_ creates a new stream
-    ##   from string.
-    ## * `newFileStream proc <#newFileStream,string,FileMode,int>`_ is the same
-    ##   as using `open proc <io.html#open,File,string,FileMode,int>`_
-    ##   on Examples.
-    ## * `openFileStream proc <#openFileStream,string,FileMode,int>`_ creates a
-    ##   file stream from the file name and the mode.
-    runnableExamples:
-      ## Input (somefile.txt):
+    ## **Note:** Not available for JS backend.
+    f: File
+
+proc fsClose(s: Stream) =
+  if FileStream(s).f != nil:
+    close(FileStream(s).f)
+    FileStream(s).f = nil
+proc fsFlush(s: Stream) = flushFile(FileStream(s).f)
+proc fsAtEnd(s: Stream): bool = return endOfFile(FileStream(s).f)
+proc fsSetPosition(s: Stream, pos: int) = setFilePos(FileStream(s).f, pos)
+proc fsGetPosition(s: Stream): int = return int(getFilePos(FileStream(s).f))
+
+proc fsReadData(s: Stream, buffer: pointer, bufLen: int): int =
+  result = readBuffer(FileStream(s).f, buffer, bufLen)
+
+proc fsReadDataStr(s: Stream, buffer: var string, slice: Slice[int]): int =
+  result = readBuffer(FileStream(s).f, addr buffer[slice.a], slice.b + 1 - slice.a)
+
+proc fsPeekData(s: Stream, buffer: pointer, bufLen: int): int =
+  let pos = fsGetPosition(s)
+  defer: fsSetPosition(s, pos)
+  result = readBuffer(FileStream(s).f, buffer, bufLen)
+
+proc fsWriteData(s: Stream, buffer: pointer, bufLen: int) =
+  if writeBuffer(FileStream(s).f, buffer, bufLen) != bufLen:
+    raise newEIO("cannot write to stream")
+
+proc fsReadLine(s: Stream, line: var TaintedString): bool =
+  result = readLine(FileStream(s).f, line)
+
+proc newFileStream*(f: File): owned FileStream =
+  ## Creates a new stream from the file `f`.
+  ##
+  ## **Note:** Not available for JS backend.
+  ##
+  ## See also:
+  ## * `newStringStream proc <#newStringStream,string>`_ creates a new stream
+  ##   from string.
+  ## * `newFileStream proc <#newFileStream,string,FileMode,int>`_ is the same
+  ##   as using `open proc <io.html#open,File,string,FileMode,int>`_
+  ##   on Examples.
+  ## * `openFileStream proc <#openFileStream,string,FileMode,int>`_ creates a
+  ##   file stream from the file name and the mode.
+  runnableExamples:
+    ## Input (somefile.txt):
+    ## The first line
+    ## the second line
+    ## the third line
+    var f: File
+    if open(f, "somefile.txt", fmRead, -1):
+      var strm = newFileStream(f)
+      var line = ""
+      while strm.readLine(line):
+        echo line
+      ## Output:
       ## The first line
       ## the second line
       ## the third line
-      var f: File
-      if open(f, "somefile.txt", fmRead, -1):
-        var strm = newFileStream(f)
-        var line = ""
-        while strm.readLine(line):
-          echo line
-        ## Output:
-        ## The first line
-        ## the second line
-        ## the third line
-        strm.close()
+      strm.close()
 
-    new(result)
-    result.f = f
-    result.closeImpl = fsClose
-    result.atEndImpl = fsAtEnd
-    result.setPositionImpl = fsSetPosition
-    result.getPositionImpl = fsGetPosition
-    result.readDataStrImpl = fsReadDataStr
-    result.readDataImpl = fsReadData
-    result.readLineImpl = fsReadLine
-    result.peekDataImpl = fsPeekData
-    result.writeDataImpl = fsWriteData
-    result.flushImpl = fsFlush
-
-  proc newFileStream*(filename: string, mode: FileMode = fmRead,
-      bufSize: int = -1): owned FileStream =
-    ## Creates a new stream from the file named `filename` with the mode `mode`.
-    ##
-    ## If the file cannot be opened, `nil` is returned. See the `io module
-    ## <io.html>`_ for a list of available `FileMode enums <io.html#FileMode>`_.
-    ##
-    ## **Note:**
-    ## * **This function returns nil in case of failure.**
-    ##   To prevent unexpected behavior and ensure proper error handling,
-    ##   use `openFileStream proc <#openFileStream,string,FileMode,int>`_
-    ##   instead.
-    ## * Not available for JS backend.
-    ##
-    ## See also:
-    ## * `newStringStream proc <#newStringStream,string>`_ creates a new stream
-    ##   from string.
-    ## * `newFileStream proc <#newFileStream,File>`_ creates a file stream from
-    ##   opened File.
-    ## * `openFileStream proc <#openFileStream,string,FileMode,int>`_ creates a
-    ##   file stream from the file name and the mode.
-    runnableExamples:
-      from os import removeFile
-      var strm = newFileStream("somefile.txt", fmWrite)
-      if not isNil(strm):
-        strm.writeLine("The first line")
-        strm.writeLine("the second line")
-        strm.writeLine("the third line")
-        strm.close()
-        ## Output (somefile.txt)
-        ## The first line
-        ## the second line
-        ## the third line
-        removeFile("somefile.txt")
+  new(result)
+  result.f = f
+  result.closeImpl = fsClose
+  result.atEndImpl = fsAtEnd
+  result.setPositionImpl = fsSetPosition
+  result.getPositionImpl = fsGetPosition
+  result.readDataStrImpl = fsReadDataStr
+  result.readDataImpl = fsReadData
+  result.readLineImpl = fsReadLine
+  result.peekDataImpl = fsPeekData
+  result.writeDataImpl = fsWriteData
+  result.flushImpl = fsFlush
+
+proc newFileStream*(filename: string, mode: FileMode = fmRead,
+    bufSize: int = -1): owned FileStream =
+  ## Creates a new stream from the file named `filename` with the mode `mode`.
+  ##
+  ## If the file cannot be opened, `nil` is returned. See the `io module
+  ## <io.html>`_ for a list of available `FileMode enums <io.html#FileMode>`_.
+  ##
+  ## **Note:**
+  ## * **This function returns nil in case of failure.**
+  ##   To prevent unexpected behavior and ensure proper error handling,
+  ##   use `openFileStream proc <#openFileStream,string,FileMode,int>`_
+  ##   instead.
+  ## * Not available for JS backend.
+  ##
+  ## See also:
+  ## * `newStringStream proc <#newStringStream,string>`_ creates a new stream
+  ##   from string.
+  ## * `newFileStream proc <#newFileStream,File>`_ creates a file stream from
+  ##   opened File.
+  ## * `openFileStream proc <#openFileStream,string,FileMode,int>`_ creates a
+  ##   file stream from the file name and the mode.
+  runnableExamples:
+    from os import removeFile
+    var strm = newFileStream("somefile.txt", fmWrite)
+    if not isNil(strm):
+      strm.writeLine("The first line")
+      strm.writeLine("the second line")
+      strm.writeLine("the third line")
+      strm.close()
+      ## Output (somefile.txt)
+      ## The first line
+      ## the second line
+      ## the third line
+      removeFile("somefile.txt")
 
-    var f: File
-    if open(f, filename, mode, bufSize): result = newFileStream(f)
+  var f: File
+  if open(f, filename, mode, bufSize): result = newFileStream(f)
 
-  proc openFileStream*(filename: string, mode: FileMode = fmRead,
-      bufSize: int = -1): owned FileStream =
-    ## Creates a new stream from the file named `filename` with the mode `mode`.
-    ## If the file cannot be opened, an IO exception is raised.
-    ##
-    ## **Note:** Not available for JS backend.
-    ##
-    ## See also:
-    ## * `newStringStream proc <#newStringStream,string>`_ creates a new stream
-    ##   from string.
-    ## * `newFileStream proc <#newFileStream,File>`_ creates a file stream from
-    ##   opened File.
-    ## * `newFileStream proc <#newFileStream,string,FileMode,int>`_  creates a
-    ##   file stream from the file name and the mode.
-    runnableExamples:
-      try:
-        ## Input (somefile.txt):
-        ## The first line
-        ## the second line
-        ## the third line
-        var strm = openFileStream("somefile.txt")
-        echo strm.readLine()
-        ## Output:
-        ## The first line
-        strm.close()
-      except:
-        stderr.write getCurrentExceptionMsg()
+proc openFileStream*(filename: string, mode: FileMode = fmRead,
+    bufSize: int = -1): owned FileStream =
+  ## Creates a new stream from the file named `filename` with the mode `mode`.
+  ## If the file cannot be opened, an IO exception is raised.
+  ##
+  ## **Note:** Not available for JS backend.
+  ##
+  ## See also:
+  ## * `newStringStream proc <#newStringStream,string>`_ creates a new stream
+  ##   from string.
+  ## * `newFileStream proc <#newFileStream,File>`_ creates a file stream from
+  ##   opened File.
+  ## * `newFileStream proc <#newFileStream,string,FileMode,int>`_  creates a
+  ##   file stream from the file name and the mode.
+  runnableExamples:
+    try:
+      ## Input (somefile.txt):
+      ## The first line
+      ## the second line
+      ## the third line
+      var strm = openFileStream("somefile.txt")
+      echo strm.readLine()
+      ## Output:
+      ## The first line
+      strm.close()
+    except:
+      stderr.write getCurrentExceptionMsg()
 
-    var f: File
-    if open(f, filename, mode, bufSize):
-      return newFileStream(f)
-    else:
-      raise newEIO("cannot open file stream: " & filename)
+  var f: File
+  if open(f, filename, mode, bufSize):
+    return newFileStream(f)
+  else:
+    raise newEIO("cannot open file stream: " & filename)
 
 when false:
   type
diff --git a/lib/pure/strtabs.nim b/lib/pure/strtabs.nim
index c6b44a3ca..11aa851a1 100644
--- a/lib/pure/strtabs.nim
+++ b/lib/pure/strtabs.nim
@@ -52,7 +52,7 @@ runnableExamples:
 import
   hashes, strutils
 
-when defined(js):
+when defined(js) or defined(nimscript):
   {.pragma: rtlFunc.}
 else:
   {.pragma: rtlFunc, rtl.}
@@ -298,7 +298,7 @@ proc raiseFormatException(s: string) =
 proc getValue(t: StringTableRef, flags: set[FormatFlag], key: string): string =
   if hasKey(t, key): return t.getOrDefault(key)
   # hm difficult: assume safety in taint mode here. XXX This is dangerous!
-  when defined(js):
+  when defined(js) or defined(nimscript):
     result = ""
   else:
     if useEnvironment in flags: result = os.getEnv(key).string
diff --git a/lib/pure/unittest.nim b/lib/pure/unittest.nim
index ee2c5fe22..eb478f60f 100644
--- a/lib/pure/unittest.nim
+++ b/lib/pure/unittest.nim
@@ -251,7 +251,7 @@ method testEnded*(formatter: ConsoleOutputFormatter, testResult: TestResult) =
     template rawPrint() = echo(prefix, "[", $testResult.status, "] ",
         testResult.testName)
     when not defined(ECMAScript):
-      if formatter.colorOutput and not defined(ECMAScript):
+      if formatter.colorOutput:
         var color = case testResult.status
           of TestStatus.OK: fgGreen
           of TestStatus.FAILED: fgRed
diff --git a/lib/pure/volatile.nim b/lib/pure/volatile.nim
index b3705a199..208f0fcaa 100644
--- a/lib/pure/volatile.nim
+++ b/lib/pure/volatile.nim
@@ -13,18 +13,24 @@
 template volatileLoad*[T](src: ptr T): T =
   ## Generates a volatile load of the value stored in the container `src`.
   ## Note that this only effects code generation on `C` like backends
-  when defined(js):
+  when nimvm:
     src[]
   else:
-    var res: T
-    {.emit: [res, " = (*(", type(src[]), " volatile*)", src, ");"].}
-    res
+    when defined(js):
+      src[]
+    else:
+      var res: T
+      {.emit: [res, " = (*(", type(src[]), " volatile*)", src, ");"].}
+      res
 
 template volatileStore*[T](dest: ptr T, val: T) =
   ## Generates a volatile store into the container `dest` of the value
   ## `val`. Note that this only effects code generation on `C` like
   ## backends
-  when defined(js):
+  when nimvm:
     dest[] = val
   else:
-    {.emit: ["*((", type(dest[]), " volatile*)(", dest, ")) = ", val, ";"].}
+    when defined(js):
+      dest[] = val
+    else:
+      {.emit: ["*((", type(dest[]), " volatile*)(", dest, ")) = ", val, ";"].}
diff --git a/lib/system/jssys.nim b/lib/system/jssys.nim
index ec55733e1..b16f2cbbc 100644
--- a/lib/system/jssys.nim
+++ b/lib/system/jssys.nim
@@ -7,7 +7,7 @@
 #    distribution, for details about the copyright.
 #
 
-import system/indexerrors
+include system/indexerrors
 
 proc log*(s: cstring) {.importc: "console.log", varargs, nodecl.}
 
diff --git a/tests/js/tstdlib_imports.nim b/tests/js/tstdlib_imports.nim
new file mode 100644
index 000000000..7611b7dcb
--- /dev/null
+++ b/tests/js/tstdlib_imports.nim
@@ -0,0 +1,71 @@
+discard """
+  action: compile
+"""
+
+{.warning[UnusedImport]: off.}
+
+import std/[
+  # Core:
+  bitops, typetraits, lenientops, macros, volatile, typeinfo,
+  # fails: endians, rlocks
+  # works but shouldn't: cpuinfo, locks
+
+  # Algorithms:
+  algorithm, sequtils,
+  
+  # Collections:
+  critbits, deques, heapqueue, intsets, lists, options, sets,
+  sharedlist, tables,
+  # fails: sharedtables
+
+  # Strings:
+  cstrutils, editdistance, wordwrap, parseutils, ropes,
+  pegs, punycode, strformat, strmisc, strscans, strtabs,
+  strutils, unicode, unidecode,
+  # fails: encodings
+
+  # Time handling:
+  monotimes, times,
+
+  # Generic operator system services:
+  os, streams,
+  # fails: distros, dynlib, marshal, memfiles, osproc, terminal
+
+  # Math libraries:
+  complex, math, mersenne, random, rationals, stats, sums,
+  # works but shouldn't: fenv
+
+  # Internet protocols:
+  cookies, httpcore, mimetypes, uri,
+  # fails: asyncdispatch, asyncfile, asyncftpclient, asynchttpserver,
+  # asyncnet, cgi, httpclient, nativesockets, net, selectors, smtp
+  # works but shouldn't test: asyncstreams, asyncfutures
+  
+  # Threading:
+  # fails: threadpool
+
+  # Parsers:
+  htmlparser, json, lexbase, parsecfg, parsecsv, parsesql, parsexml,
+  # fails: parseopt
+
+  # XML processing:
+  xmltree, xmlparser,
+
+  # Generators:
+  htmlgen,
+
+  # Hashing:
+  base64, hashes,
+  # fails: md5, oids, sha1
+
+  # Miscellaneous:
+  colors, logging, sugar, unittest, varints,
+  # fails: browsers, coro
+  # works but shouldn't: segfaults
+
+  # Modules for JS backend:
+  asyncjs, dom, jsconsole, jscore, jsffi,
+
+  # Unlisted in lib.html:
+  decls, compilesettings, with, wrapnils
+]
diff --git a/tests/js/tstdlib_various.nim b/tests/js/tstdlib_various.nim
new file mode 100644
index 000000000..d19f40c39
--- /dev/null
+++ b/tests/js/tstdlib_various.nim
@@ -0,0 +1,194 @@
+discard """
+output: '''
+abc
+def
+definition
+prefix
+xyz
+def
+definition
+Hi Andreas! How do you feel, Rumpf?
+
+@[0, 2, 1]
+@[1, 0, 2]
+@[1, 2, 0]
+@[2, 0, 1]
+@[2, 1, 0]
+@[2, 0, 1]
+@[1, 2, 0]
+@[1, 0, 2]
+@[0, 2, 1]
+@[0, 1, 2]
+[5]
+[4, 5]
+[3, 4, 5]
+[2, 3, 4, 5]
+[2, 3, 4, 5, 6]
+[1, 2, 3, 4, 5, 6]
+'''
+"""
+
+import
+  critbits, cstrutils, sets, strutils, tables, random, algorithm, ropes,
+  lists, htmlgen, xmltree, strtabs
+
+
+block tcritbits:
+  var r: CritBitTree[void]
+  r.incl "abc"
+  r.incl "xyz"
+  r.incl "def"
+  r.incl "definition"
+  r.incl "prefix"
+  doAssert r.contains"def"
+  #r.del "def"
+
+  for w in r.items:
+    echo w
+  for w in r.itemsWithPrefix("de"):
+    echo w
+
+
+
+block testequivalence:
+  doAssert(toHashSet(@[1,2,3]) <= toHashSet(@[1,2,3,4]), "equivalent or subset")
+  doAssert(toHashSet(@[1,2,3]) <= toHashSet(@[1,2,3]), "equivalent or subset")
+  doAssert((not(toHashSet(@[1,2,3]) <= toHashSet(@[1,2]))), "equivalent or subset")
+  doAssert(toHashSet(@[1,2,3]) <= toHashSet(@[1,2,3,4]), "strict subset")
+  doAssert((not(toHashSet(@[1,2,3]) < toHashSet(@[1,2,3]))), "strict subset")
+  doAssert((not(toHashSet(@[1,2,3]) < toHashSet(@[1,2]))), "strict subset")
+  doAssert((not(toHashSet(@[1,2,3]) == toHashSet(@[1,2,3,4]))), "==")
+  doAssert(toHashSet(@[1,2,3]) == toHashSet(@[1,2,3]), "==")
+  doAssert((not(toHashSet(@[1,2,3]) == toHashSet(@[1,2]))), "==")
+
+
+
+block tformat:
+  echo("Hi $1! How do you feel, $2?\n" % ["Andreas", "Rumpf"])
+
+
+
+block tnilecho:
+  var x = @["1", "", "3"]
+  doAssert $x == """@["1", "", "3"]"""
+
+
+
+block torderedtable:
+  var t = initOrderedTable[int,string]()
+
+  # this tests issue #5917
+  var data = newSeq[int]()
+  for i in 0..<1000:
+    var x = rand(1000)
+    if x notin t: data.add(x)
+    t[x] = "meh"
+
+  # this checks that keys are re-inserted
+  # in order when table is enlarged.
+  var i = 0
+  for k, v in t:
+    doAssert(k == data[i])
+    doAssert(v == "meh")
+    inc(i)
+
+
+
+block tpermutations:
+  var v = @[0, 1, 2]
+  while v.nextPermutation():
+    echo v
+  while v.prevPermutation():
+    echo v
+
+
+block tropes:
+  var
+    r1 = rope("")
+    r2 = rope("123")
+  doAssert r1.len == 0
+  doAssert r2.len == 3
+  doAssert $r1 == ""
+  doAssert $r2 == "123"
+
+  r1.add("123")
+  r2.add("456")
+  doAssert r1.len == 3
+  doAssert r2.len == 6
+  doAssert $r1 == "123"
+  doAssert $r2 == "123456"
+  doAssert $r1[1] == "2"
+  doAssert $r2[2] == "3"
+
+
+block tsinglylinkedring:
+  var r = initSinglyLinkedRing[int]()
+  r.prepend(5)
+  echo r
+  r.prepend(4)
+  echo r
+  r.prepend(3)
+  echo r
+  r.prepend(2)
+  echo r
+  r.append(6)
+  echo r
+  r.prepend(1)
+  echo r
+
+block tsplit:
+  var s = ""
+  for w in split("|abc|xy|z", {'|'}):
+    s.add("#")
+    s.add(w)
+
+  doAssert s == "##abc#xy#z"
+
+block tsplit2:
+  var s = ""
+  for w in split("|abc|xy|z", {'|'}):
+    s.add("#")
+    s.add(w)
+
+  var errored = false
+  try:
+    discard "hello".split("")
+  except AssertionError:
+    errored = true
+  doAssert errored
+
+block txmlgen:
+  var nim = "Nim"
+  doAssert h1(a(href="http://force7.de/nim", nim)) ==
+    "<h1><a href=\"http://force7.de/nim\">Nim</a></h1>"
+
+block txmltree:
+  var x = <>a(href="nim.de", newText("www.nim-test.de"))
+
+  doAssert($x == "<a href=\"nim.de\">www.nim-test.de</a>")
+  doAssert(newText("foo").innerText == "foo")
+  doAssert(newEntity("bar").innerText == "bar")
+  doAssert(newComment("baz").innerText == "")
+
+  let y = newXmlTree("x", [
+    newText("foo"),
+    newXmlTree("y", [
+      newText("bar")
+    ])
+  ])
+  doAssert(y.innerText == "foobar")
+
+
+
+block tcstrutils:
+  let s = cstring "abcdef"
+  doAssert s.startsWith("a")
+  doAssert not s.startsWith("b")
+  doAssert s.endsWith("f")
+  doAssert not s.endsWith("a")
+
+  let a = cstring "abracadabra"
+  doAssert a.startsWith("abra")
+  doAssert not a.startsWith("bra")
+  doAssert a.endsWith("abra")
+  doAssert not a.endsWith("dab")
diff --git a/tests/js/tstreams.nim b/tests/js/tstreams.nim
new file mode 100644
index 000000000..43c26e01a
--- /dev/null
+++ b/tests/js/tstreams.nim
@@ -0,0 +1,22 @@
+discard """
+  output: '''
+I
+AM
+GROOT
+'''
+"""
+
+import streams
+
+var s = newStringStream("I\nAM\nGROOT")
+doAssert s.peekStr(1) == "I"
+doAssert s.peekChar() == 'I'
+for line in s.lines:
+  echo line
+s.close
+
+var s2 = newStringStream("abc")
+doAssert s2.readAll == "abc"
+s2.write("def")
+doAssert s2.data == "abcdef"
+s2.close
diff --git a/tests/stdlib/thtmlparser.nim b/tests/stdlib/thtmlparser.nim
index ccf2f6202..e7dcd50d5 100644
--- a/tests/stdlib/thtmlparser.nim
+++ b/tests/stdlib/thtmlparser.nim
@@ -1,6 +1,6 @@
 discard """
+  targets: "c js"
   output: '''
-@[]
 true
 https://example.com/test?format=jpg&name=orig##
 https://example.com/test?format=jpg&name=orig##text
@@ -40,7 +40,7 @@ block t2813:
     """
   var errors: seq[string] = @[]
   let tree = parseHtml(newStringStream(html), "test.html", errors)
-  echo errors # Errors: </thead> expected,...
+  doAssert errors.len == 0 # Errors: </thead> expected,...
 
   var len = tree.findAll("tr").len # len = 6
   var rows: seq[XmlNode] = @[]
diff --git a/tests/stdlib/tparscfg.nim b/tests/stdlib/tparscfg.nim
index fc735f3eb..ddb9b02b7 100644
--- a/tests/stdlib/tparscfg.nim
+++ b/tests/stdlib/tparscfg.nim
@@ -1,29 +1,5 @@
 discard """
-output: '''
-utf-8
-on
-hello
-lihf8515
-10214028
-lihaifeng@wxm.com
-===
-charset=utf-8
-[Package]
-name=hello
---threads:on
-[Author]
-name=lhf
-qq=10214028
-email="lihaifeng@wxm.com"
-===
-charset=utf-8
-[Package]
-name=hello
---threads:on
-[Author]
-name=lihf8515
-qq=10214028
-'''
+  targets: "c js"
 """
 import parsecfg, streams
 
@@ -46,24 +22,35 @@ var pname = dict2.getSectionValue("Package","name")
 var name = dict2.getSectionValue("Author","name")
 var qq = dict2.getSectionValue("Author","qq")
 var email = dict2.getSectionValue("Author","email")
-echo charset
-echo threads
-echo pname
-echo name
-echo qq
-echo email
-
-echo "==="
+doAssert charset == "utf-8"
+doAssert threads == "on"
+doAssert pname == "hello"
+doAssert name == "lihf8515"
+doAssert qq == "10214028"
+doAssert email == "lihaifeng@wxm.com"
 
 ## Modifying a configuration file.
 var dict3 = loadConfig(newStringStream(ss.data))
 dict3.setSectionKey("Author","name","lhf")
-write(stdout, $dict3)
-
-echo "==="
+doAssert $dict3 == """charset=utf-8
+[Package]
+name=hello
+--threads:on
+[Author]
+name=lhf
+qq=10214028
+email="lihaifeng@wxm.com"
+"""
 
 ## Deleting a section key in a configuration file.
 var dict4 = loadConfig(newStringStream(ss.data))
 dict4.delSectionKey("Author","email")
-write(stdout, $dict4)
+doAssert $dict4 == """charset=utf-8
+[Package]
+name=hello
+--threads:on
+[Author]
+name=lihf8515
+qq=10214028
+"""
 
diff --git a/tests/stdlib/tparsesql.nim b/tests/stdlib/tparsesql.nim
index 8cf8fa848..ba9e601a1 100644
--- a/tests/stdlib/tparsesql.nim
+++ b/tests/stdlib/tparsesql.nim
@@ -1,3 +1,6 @@
+discard """
+  targets: "c js"
+"""
 import parsesql
 
 doAssert $parseSQL("SELECT foo FROM table;") == "select foo from table;"
diff --git a/tests/stdlib/tpegs.nim b/tests/stdlib/tpegs.nim
index b07442dcb..2990a44a5 100644
--- a/tests/stdlib/tpegs.nim
+++ b/tests/stdlib/tpegs.nim
@@ -1,4 +1,5 @@
 discard """
+  targets: "c js"
   output: '''
 PEG AST traversal output
 ------------------------
diff --git a/tests/stdlib/tstdlib_various.nim b/tests/stdlib/tstdlib_various.nim
index d1723df78..6856fcd79 100644
--- a/tests/stdlib/tstdlib_various.nim
+++ b/tests/stdlib/tstdlib_various.nim
@@ -38,8 +38,8 @@ true
 """
 
 import
-  critbits, sets, strutils, tables, random, algorithm, re, ropes, segfaults,
-  lists, parsesql, streams, os, htmlgen, xmltree, strtabs
+  critbits, cstrutils, sets, strutils, tables, random, algorithm, re, ropes,
+  segfaults, lists, parsesql, streams, os, htmlgen, xmltree, strtabs
 
 
 block tcritbits:
@@ -245,3 +245,24 @@ block txmltree:
     ])
   ])
   doAssert(y.innerText == "foobar")
+
+
+block tcstrutils:
+  let s = cstring "abcdef"
+  doAssert s.startsWith("a")
+  doAssert not s.startsWith("b")
+  doAssert s.endsWith("f")
+  doAssert not s.endsWith("a")
+
+  let a = cstring "abracadabra"
+  doAssert a.startsWith("abra")
+  doAssert not a.startsWith("bra")
+  doAssert a.endsWith("abra")
+  doAssert not a.endsWith("dab")
+
+  doAssert cmpIgnoreCase(cstring "FooBar", "foobar") == 0
+  doAssert cmpIgnoreCase(cstring "bar", "Foo") < 0
+  doAssert cmpIgnoreCase(cstring "Foo5", "foo4") > 0
+
+  doAssert cmpIgnoreStyle(cstring "foo_bar", "FooBar") == 0
+  doAssert cmpIgnoreStyle(cstring "foo_bar_5", "FooBar4") > 0
diff --git a/tests/test_nimscript.nims b/tests/test_nimscript.nims
index 91b23efbf..0b454ad1c 100644
--- a/tests/test_nimscript.nims
+++ b/tests/test_nimscript.nims
@@ -1,26 +1,74 @@
 # This nimscript is used to test if the following modules can be imported
 # http://nim-lang.org/docs/nims.html
 
-import algorithm
-import base64
-import colors
-import hashes
-import lists
-import math
-# import marshal
-import options
-import os
-# import parsecfg
-# import parseopt
-import parseutils
-# import pegs
-import deques
-import sequtils
-import strutils
-import tables
-import unicode
-import uri
-import macros
+{.warning[UnusedImport]: off.}
+
+import std/[
+  # Core:
+  bitops, typetraits, lenientops, macros, volatile,
+  # fails: typeinfo, endians
+  # works but shouldn't: cpuinfo, rlocks, locks
+
+  # Algorithms:
+  algorithm, sequtils,
+  
+  # Collections:
+  critbits, deques, heapqueue, intsets, lists, options, sets,
+  sharedlist, tables,
+  # fails: sharedtables
+
+  # Strings:
+  editdistance, wordwrap, parseutils, ropes,
+  pegs, punycode, strformat, strmisc, strscans, strtabs,
+  strutils, unicode, unidecode,
+  # works but shouldn't: cstrutils, encodings
+
+  # Time handling:
+  # fails: monotimes, times
+  # but times.getTime() implemented for VM
+
+  # Generic operator system services:
+  os, streams,
+  # fails: distros, dynlib, marshal, memfiles, osproc, terminal
+
+  # Math libraries:
+  complex, math, mersenne, random, rationals, stats, sums,
+  # works but shouldn't: fenv
+
+  # Internet protocols:
+  httpcore, mimetypes, uri,
+  # fails: asyncdispatch, asyncfile, asyncftpclient, asynchttpserver,
+  # asyncnet, cgi, cookies, httpclient, nativesockets, net, selectors, smtp
+  # works but shouldn't test: asyncstreams, asyncfutures
+  
+  # Threading:
+  # fails: threadpool
+
+  # Parsers:
+  htmlparser, json, lexbase, parsecfg, parsecsv, parsesql, parsexml,
+  # fails: parseopt
+
+  # XML processing:
+  xmltree, xmlparser,
+
+  # Generators:
+  htmlgen,
+
+  # Hashing:
+  base64, hashes,
+  # fails: md5, oids, sha1
+
+  # Miscellaneous:
+  colors, sugar, varints,
+  # fails: browsers, coro, logging (times), segfaults, unittest (uses methods)
+
+  # Modules for JS backend:
+  # fails: asyncjs, dom, jsconsole, jscore, jsffi,
+
+  # Unlisted in lib.html:
+  decls, compilesettings, with, wrapnils
+]
+
 
 block:
   doAssert "./foo//./bar/".normalizedPath == "foo/bar".unixToNativePath