summary refs log tree commit diff stats
path: root/lib/pure/streams.nim
diff options
context:
space:
mode:
Diffstat (limited to 'lib/pure/streams.nim')
-rw-r--r--lib/pure/streams.nim693
1 files changed, 408 insertions, 285 deletions
diff --git a/lib/pure/streams.nim b/lib/pure/streams.nim
index cdb881960..56f49d7b1 100644
--- a/lib/pure/streams.nim
+++ b/lib/pure/streams.nim
@@ -15,6 +15,11 @@
 ## Other modules may provide other implementations for this standard
 ## stream interface.
 ##
+## .. warning:: Due to the use of `pointer`, the `readData`, `peekData` and
+## `writeData` interfaces are not available on the compile-time VM, and must
+## be cast from a `ptr string` on the JS backend. However, `readDataStr` is
+## available generally in place of `readData`.
+##
 ## Basic usage
 ## ===========
 ##
@@ -27,76 +32,78 @@
 ## StringStream example
 ## --------------------
 ##
-## .. code-block:: Nim
-##
-##  import streams
+##   ```Nim
+##   import std/streams
 ##
-##  var strm = newStringStream("""The first line
-##  the second line
-##  the third line""")
+##   var strm = newStringStream("""The first line
+##   the second line
+##   the third line""")
 ##
-##  var line = ""
+##   var line = ""
 ##
-##  while strm.readLine(line):
-##    echo line
+##   while strm.readLine(line):
+##     echo line
 ##
-##  # Output:
-##  # The first line
-##  # the second line
-##  # the third line
+##   # Output:
+##   # The first line
+##   # the second line
+##   # the third line
 ##
-##  strm.close()
+##   strm.close()
+##   ```
 ##
 ## FileStream example
 ## ------------------
 ##
 ## Write file stream example:
 ##
-## .. code-block:: Nim
-##
-##  import streams
+##   ```Nim
+##   import std/streams
 ##
-##  var strm = newFileStream("somefile.txt", fmWrite)
-##  var line = ""
+##   var strm = newFileStream("somefile.txt", fmWrite)
+##   var line = ""
 ##
-##  if not isNil(strm):
-##    strm.writeLine("The first line")
-##    strm.writeLine("the second line")
-##    strm.writeLine("the third line")
-##    strm.close()
+##   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
+##   # Output (somefile.txt):
+##   # The first line
+##   # the second line
+##   # the third line
+##   ```
 ##
 ## Read file stream example:
 ##
-## .. code-block:: Nim
+##   ```Nim
+##   import std/streams
 ##
-##  import streams
+##   var strm = newFileStream("somefile.txt", fmRead)
+##   var line = ""
 ##
-##  var strm = newFileStream("somefile.txt", fmRead)
-##  var line = ""
+##   if not isNil(strm):
+##     while strm.readLine(line):
+##       echo line
+##     strm.close()
 ##
-##  if not isNil(strm):
-##    while strm.readLine(line):
-##      echo line
-##    strm.close()
-##
-##  # Output:
-##  # The first line
-##  # the second line
-##  # the third line
+##   # Output:
+##   # The first line
+##   # the second line
+##   # the third line
+##   ```
 ##
 ## See also
 ## ========
 ## * `asyncstreams module <asyncstreams.html>`_
-## * `io module <io.html>`_ for `FileMode enum <io.html#FileMode>`_
+## * `io module <syncio.html>`_ for `FileMode enum <syncio.html#FileMode>`_
 
-include "system/inclrtl"
+import std/private/since
 
-const taintMode = compileOption("taintmode")
+when defined(nimPreviewSlimSystem):
+  import std/syncio
+  export FileMode
 
 proc newEIO(msg: string): owned(ref IOError) =
   new(result)
@@ -113,7 +120,7 @@ type
     ## * That these fields here shouldn't be used directly.
     ##   They are accessible so that a stream implementation can override them.
     closeImpl*: proc (s: Stream)
-      {.nimcall, raises: [Exception, IOError, OSError], tags: [WriteIOEffect], gcsafe.}
+      {.nimcall, raises: [IOError, OSError], tags: [WriteIOEffect], gcsafe.}
     atEndImpl*: proc (s: Stream): bool
       {.nimcall, raises: [Defect, IOError, OSError], tags: [], gcsafe.}
     setPositionImpl*: proc (s: Stream, pos: int)
@@ -124,7 +131,7 @@ type
     readDataStrImpl*: proc (s: Stream, buffer: var string, slice: Slice[int]): int
       {.nimcall, raises: [Defect, IOError, OSError], tags: [ReadIOEffect], gcsafe.}
 
-    readLineImpl*: proc(s: Stream, line: var TaintedString): bool
+    readLineImpl*: proc(s: Stream, line: var string): bool
       {.nimcall, raises: [Defect, IOError, OSError], tags: [ReadIOEffect], gcsafe.}
 
     readDataImpl*: proc (s: Stream, buffer: pointer, bufLen: int): int
@@ -146,7 +153,7 @@ proc flush*(s: Stream) =
   ## See also:
   ## * `close proc <#close,Stream>`_
   runnableExamples:
-    from os import removeFile
+    from std/os import removeFile
 
     var strm = newFileStream("somefile.txt", fmWrite)
 
@@ -172,10 +179,20 @@ proc close*(s: Stream) =
   ## See also:
   ## * `flush proc <#flush,Stream>`_
   runnableExamples:
-    var strm = newStringStream("The first line\nthe second line\nthe third line")
-    ## do something...
-    strm.close()
-  if not isNil(s.closeImpl): s.closeImpl(s)
+    block:
+      let strm = newStringStream("The first line\nthe second line\nthe third line")
+      ## do something...
+      strm.close()
+      
+    block:
+      let strm = newFileStream("amissingfile.txt")
+      # deferring works even if newFileStream fails
+      defer: strm.close()
+      if not isNil(strm):
+        ## do something...
+
+  if not isNil(s) and not isNil(s.closeImpl):
+    s.closeImpl(s)
 
 proc atEnd*(s: Stream): bool =
   ## Checks if more data can be read from `s`. Returns ``true`` if all data has
@@ -216,6 +233,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]
@@ -239,13 +259,26 @@ proc readDataStr*(s: Stream, buffer: var string, slice: Slice[int]): int =
     result = s.readDataStrImpl(s, buffer, slice)
   else:
     # fallback
-    result = s.readData(addr buffer[0], buffer.len)
+    when declared(prepareMutation):
+      # buffer might potentially be a CoW literal with ARC
+      prepareMutation(buffer)
+    result = s.readData(addr buffer[slice.a], slice.b + 1 - slice.a)
 
-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 +286,7 @@ when not defined(js):
       strm.close()
 
     const bufferSize = 1024
-    when nimvm:
+    jsOrVmBlock:
       var buffer2: string
       buffer2.setLen(bufferSize)
       while true:
@@ -265,7 +298,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 +313,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 +329,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,9 +350,12 @@ 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:
   ##
-  ## .. code-block:: Nim
+  ## **Note:** Not available for JS backend. Use `write(Stream, string)
+  ## <#write,Stream,string>`_ for now.
   ##
-  ##     s.writeData(s, unsafeAddr(x), sizeof(x))
+  ##   ```Nim
+  ##   s.writeData(s, unsafeAddr(x), sizeof(x))
+  ##   ```
   runnableExamples:
     var strm = newStringStream("")
     strm.write("abcde")
@@ -324,7 +366,7 @@ proc write*[T](s: Stream, x: T) =
   writeData(s, unsafeAddr(x), sizeof(x))
 
 proc write*(s: Stream, x: string) =
-  ## Writes the string `x` to the the stream `s`. No length field or
+  ## Writes the string `x` to the stream `s`. No length field or
   ## terminating zero is written.
   runnableExamples:
     var strm = newStringStream("")
@@ -336,7 +378,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 +413,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 +432,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 +463,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 +481,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 +494,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 +519,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 +542,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 +561,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 +582,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 +601,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 +622,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 +641,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 +662,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 +681,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 +702,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 +721,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 +742,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 +761,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 +782,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 +802,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 +823,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 +842,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 +863,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 +882,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 +903,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 +922,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,7 +941,24 @@ proc peekFloat64*(s: Stream): float64 =
 
   peek(s, result)
 
-proc readStr*(s: Stream, length: int): TaintedString =
+proc readStrPrivate(s: Stream, length: int, str: var string) =
+  if length > len(str): setLen(str, length)
+  var L: int
+  when nimvm:
+    L = readDataStr(s, str, 0..length-1)
+  else:
+    when defined(js):
+      L = readData(s, addr(str), length)
+    else:
+      L = readData(s, cstring(str), length)
+  if L != len(str): setLen(str, L)
+
+proc readStr*(s: Stream, length: int, str: var string) {.since: (1, 3).} =
+  ## Reads a string of length `length` from the stream `s`. Raises `IOError` if
+  ## an error occurred.
+  readStrPrivate(s, length, str)
+
+proc readStr*(s: Stream, length: int): string =
   ## Reads a string of length `length` from the stream `s`. Raises `IOError` if
   ## an error occurred.
   runnableExamples:
@@ -851,12 +968,23 @@ proc readStr*(s: Stream, length: int): TaintedString =
     doAssert strm.readStr(2) == "e"
     doAssert strm.readStr(2) == ""
     strm.close()
+  result = newString(length)
+  readStrPrivate(s, length, result)
 
-  result = newString(length).TaintedString
-  var L = readData(s, cstring(result), length)
-  if L != length: setLen(result.string, L)
+proc peekStrPrivate(s: Stream, length: int, str: var string) =
+  if length > len(str): setLen(str, length)
+  when defined(js):
+    let L = peekData(s, addr(str), length)
+  else:
+    let L = peekData(s, cstring(str), length)
+  if L != len(str): setLen(str, L)
 
-proc peekStr*(s: Stream, length: int): TaintedString =
+proc peekStr*(s: Stream, length: int, str: var string) {.since: (1, 3).} =
+  ## Peeks a string of length `length` from the stream `s`. Raises `IOError` if
+  ## an error occurred.
+  peekStrPrivate(s, length, str)
+
+proc peekStr*(s: Stream, length: int): string =
   ## Peeks a string of length `length` from the stream `s`. Raises `IOError` if
   ## an error occurred.
   runnableExamples:
@@ -867,12 +995,10 @@ proc peekStr*(s: Stream, length: int): TaintedString =
     doAssert strm.readStr(2) == "ab"
     doAssert strm.peekStr(2) == "cd"
     strm.close()
+  result = newString(length)
+  peekStrPrivate(s, length, result)
 
-  result = newString(length).TaintedString
-  var L = peekData(s, cstring(result), length)
-  if L != length: setLen(result.string, L)
-
-proc readLine*(s: Stream, line: var TaintedString): bool =
+proc readLine*(s: Stream, line: var string): bool =
   ## Reads a line of text from the stream `s` into `line`. `line` must not be
   ## ``nil``! May throw an IO exception.
   ##
@@ -884,7 +1010,7 @@ proc readLine*(s: Stream, line: var TaintedString): bool =
   ## See also:
   ## * `readLine(Stream) proc <#readLine,Stream>`_
   ## * `peekLine(Stream) proc <#peekLine,Stream>`_
-  ## * `peekLine(Stream, TaintedString) proc <#peekLine,Stream,TaintedString>`_
+  ## * `peekLine(Stream, string) proc <#peekLine,Stream,string>`_
   runnableExamples:
     var strm = newStringStream("The first line\nthe second line\nthe third line")
     var line = ""
@@ -902,13 +1028,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.setLen(0)
     while true:
       var c = readChar(s)
       if c == '\c':
@@ -918,16 +1038,10 @@ 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.add(c)
     result = true
 
-proc peekLine*(s: Stream, line: var TaintedString): bool =
+proc peekLine*(s: Stream, line: var string): bool =
   ## Peeks a line of text from the stream `s` into `line`. `line` must not be
   ## ``nil``! May throw an IO exception.
   ##
@@ -938,7 +1052,7 @@ proc peekLine*(s: Stream, line: var TaintedString): bool =
   ##
   ## See also:
   ## * `readLine(Stream) proc <#readLine,Stream>`_
-  ## * `readLine(Stream, TaintedString) proc <#readLine,Stream,TaintedString>`_
+  ## * `readLine(Stream, string) proc <#readLine,Stream,string>`_
   ## * `peekLine(Stream) proc <#peekLine,Stream>`_
   runnableExamples:
     var strm = newStringStream("The first line\nthe second line\nthe third line")
@@ -958,15 +1072,15 @@ proc peekLine*(s: Stream, line: var TaintedString): bool =
   defer: setPosition(s, pos)
   result = readLine(s, line)
 
-proc readLine*(s: Stream): TaintedString =
+proc readLine*(s: Stream): string =
   ## Reads a line from a stream `s`. Raises `IOError` if an error occurred.
   ##
   ## **Note:** This is not very efficient.
   ##
   ## See also:
-  ## * `readLine(Stream, TaintedString) proc <#readLine,Stream,TaintedString>`_
+  ## * `readLine(Stream, string) proc <#readLine,Stream,string>`_
   ## * `peekLine(Stream) proc <#peekLine,Stream>`_
-  ## * `peekLine(Stream, TaintedString) proc <#peekLine,Stream,TaintedString>`_
+  ## * `peekLine(Stream, string) proc <#peekLine,Stream,string>`_
   runnableExamples:
     var strm = newStringStream("The first line\nthe second line\nthe third line")
     doAssert strm.readLine() == "The first line"
@@ -975,7 +1089,7 @@ proc readLine*(s: Stream): TaintedString =
     doAssertRaises(IOError): discard strm.readLine()
     strm.close()
 
-  result = TaintedString""
+  result = ""
   if s.atEnd:
     raise newEIO("cannot read from stream")
   while true:
@@ -986,17 +1100,17 @@ proc readLine*(s: Stream): TaintedString =
     if c == '\L' or c == '\0':
       break
     else:
-      result.string.add(c)
+      result.add(c)
 
-proc peekLine*(s: Stream): TaintedString =
+proc peekLine*(s: Stream): string =
   ## Peeks a line from a stream `s`. Raises `IOError` if an error occurred.
   ##
   ## **Note:** This is not very efficient.
   ##
   ## See also:
   ## * `readLine(Stream) proc <#readLine,Stream>`_
-  ## * `readLine(Stream, TaintedString) proc <#readLine,Stream,TaintedString>`_
-  ## * `peekLine(Stream, TaintedString) proc <#peekLine,Stream,TaintedString>`_
+  ## * `readLine(Stream, string) proc <#readLine,Stream,string>`_
+  ## * `peekLine(Stream, string) proc <#peekLine,Stream,string>`_
   runnableExamples:
     var strm = newStringStream("The first line\nthe second line\nthe third line")
     doAssert strm.peekLine() == "The first line"
@@ -1010,13 +1124,13 @@ proc peekLine*(s: Stream): TaintedString =
   defer: setPosition(s, pos)
   result = readLine(s)
 
-iterator lines*(s: Stream): TaintedString =
+iterator lines*(s: Stream): string =
   ## Iterates over every line in the stream.
   ## The iteration is based on ``readLine``.
   ##
   ## See also:
   ## * `readLine(Stream) proc <#readLine,Stream>`_
-  ## * `readLine(Stream, TaintedString) proc <#readLine,Stream,TaintedString>`_
+  ## * `readLine(Stream, string) proc <#readLine,Stream,string>`_
   runnableExamples:
     var strm = newStringStream("The first line\nthe second line\nthe third line")
     var lines: seq[string]
@@ -1025,24 +1139,20 @@ iterator lines*(s: Stream): TaintedString =
     doAssert lines == @["The first line", "the second line", "the third line"]
     strm.close()
 
-  var line: TaintedString
+  var line: string
   while s.readLine(line):
     yield line
 
 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
@@ -1066,10 +1176,7 @@ when defined(js): #This section exists so that string streams work at compile ti
 
   proc ssClose(s: Stream) {.compileTime.} =
     var s = StringStream(s)
-    when defined(nimNoNilSeqs):
-      s.data = ""
-    else:
-      s.data = nil
+    s.data = ""
 
   proc newStringStream*(s: string = ""): owned StringStream {.compileTime.} =
     new(result)
@@ -1095,7 +1202,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
@@ -1110,11 +1217,16 @@ else:
 
   proc ssReadDataStr(s: Stream, buffer: var string, slice: Slice[int]): int =
     var s = StringStream(s)
+    when nimvm:
+      discard
+    else:
+      when declared(prepareMutation):
+        prepareMutation(buffer) # buffer might potentially be a CoW literal with ARC
     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:
@@ -1124,7 +1236,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
@@ -1133,7 +1252,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
 
@@ -1143,21 +1269,23 @@ 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) =
     var s = StringStream(s)
-    when defined(nimNoNilSeqs):
-      s.data = ""
-    else:
-      s.data = nil
+    s.data = ""
 
-  proc newStringStream*(s: string = ""): owned StringStream =
+  proc newStringStream*(s: sink 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.
@@ -1174,168 +1302,176 @@ else:
 
     new(result)
     result.data = s
+    when nimvm:
+      discard
+    else:
+      when declared(prepareMutation):
+        prepareMutation(result.data) # Allows us to mutate using `addr` logic like `copyMem`, otherwise it errors.
     result.pos = 0
     result.closeImpl = ssClose
     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 string): 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 <syncio.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
+  ## <syncio.html>`_ for a list of available `FileMode enums <syncio.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 std/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
@@ -1355,7 +1491,7 @@ when false:
     # do not import windows as this increases compile times:
     discard
   else:
-    import posix
+    import std/posix
 
     proc hsSetPosition(s: FileHandleStream, pos: int) =
       discard lseek(s.handle, pos, SEEK_SET)
@@ -1403,20 +1539,7 @@ when false:
       of fmReadWrite: flags = O_RDWR or int(O_CREAT)
       of fmReadWriteExisting: flags = O_RDWR
       of fmAppend: flags = O_WRONLY or int(O_CREAT) or O_APPEND
+      static: raiseAssert "unreachable" # handle bug #17888
       var handle = open(filename, flags)
       if handle < 0: raise newEOS("posix.open() call failed")
     result = newFileHandleStream(handle)
-
-when isMainModule and defined(testing):
-  var ss = newStringStream("The quick brown fox jumped over the lazy dog.\nThe lazy dog ran")
-  assert(ss.getPosition == 0)
-  assert(ss.peekStr(5) == "The q")
-  assert(ss.getPosition == 0) # haven't moved
-  assert(ss.readStr(5) == "The q")
-  assert(ss.getPosition == 5) # did move
-  assert(ss.peekLine() == "uick brown fox jumped over the lazy dog.")
-  assert(ss.getPosition == 5) # haven't moved
-  var str = newString(100)
-  assert(ss.peekLine(str))
-  assert(str == "uick brown fox jumped over the lazy dog.")
-  assert(ss.getPosition == 5) # haven't moved