diff options
Diffstat (limited to 'lib/pure/streams.nim')
-rw-r--r-- | lib/pure/streams.nim | 243 |
1 files changed, 133 insertions, 110 deletions
diff --git a/lib/pure/streams.nim b/lib/pure/streams.nim index 62a074bd8..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 +## ```Nim +## import std/streams ## -## import 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 -## -## import streams +## ```Nim +## import std/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>`_ 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,7 +233,7 @@ 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: @@ -242,6 +259,9 @@ proc readDataStr*(s: Stream, buffer: var string, slice: Slice[int]): int = result = s.readDataStrImpl(s, buffer, slice) else: # fallback + 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) template jsOrVmBlock(caseJsOrVm, caseElse: untyped): untyped = @@ -293,7 +313,7 @@ when (NimMajor, NimMinor) >= (1, 3) or 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: @@ -309,7 +329,7 @@ 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: @@ -333,9 +353,9 @@ proc write*[T](s: Stream, x: T) = ## **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)) + ## ```Nim + ## s.writeData(s, unsafeAddr(x), sizeof(x)) + ## ``` runnableExamples: var strm = newStringStream("") strm.write("abcde") @@ -921,26 +941,24 @@ 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.untaint, length) - when defined(js): - let L = readData(s, addr(str), length) +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: - let L = readData(s, cstring(str.string), length) - if L != len(str): setLen(str.untaint, L) + 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 TaintedString) {.since: (1, 3).} = +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): TaintedString = +proc readStr*(s: Stream, length: int): string = ## Reads a string of length `length` from the stream `s`. Raises `IOError` if ## an error occurred. runnableExamples: @@ -950,23 +968,23 @@ proc readStr*(s: Stream, length: int): TaintedString = doAssert strm.readStr(2) == "e" doAssert strm.readStr(2) == "" strm.close() - result = newString(length).TaintedString + result = newString(length) readStrPrivate(s, length, result) -proc peekStrPrivate(s: Stream, length: int, str: var TaintedString) = - if length > len(str): setLen(str.untaint, length) +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.string), length) - if L != len(str): setLen(str.untaint, L) + let L = peekData(s, cstring(str), length) + if L != len(str): setLen(str, L) -proc peekStr*(s: Stream, length: int, str: var TaintedString) {.since: (1, 3).} = +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): TaintedString = +proc peekStr*(s: Stream, length: int): string = ## Peeks a string of length `length` from the stream `s`. Raises `IOError` if ## an error occurred. runnableExamples: @@ -977,10 +995,10 @@ proc peekStr*(s: Stream, length: int): TaintedString = doAssert strm.readStr(2) == "ab" doAssert strm.peekStr(2) == "cd" strm.close() - result = newString(length).TaintedString + result = newString(length) peekStrPrivate(s, length, result) -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. ## @@ -992,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 = "" @@ -1010,7 +1028,7 @@ proc readLine*(s: Stream, line: var TaintedString): bool = result = s.readLineImpl(s, line) else: # fallback - line.untaint.setLen(0) + line.setLen(0) while true: var c = readChar(s) if c == '\c': @@ -1020,10 +1038,10 @@ proc readLine*(s: Stream, line: var TaintedString): bool = elif c == '\0': if line.len > 0: break else: return false - line.untaint.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. ## @@ -1034,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") @@ -1054,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" @@ -1071,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: @@ -1082,17 +1100,17 @@ proc readLine*(s: Stream): TaintedString = if c == '\L' or c == '\0': break else: - result.untaint.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" @@ -1106,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] @@ -1121,7 +1139,7 @@ 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 @@ -1158,10 +1176,7 @@ when (NimMajor, NimMinor) < (1, 3) and defined(js): 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) @@ -1187,7 +1202,7 @@ when (NimMajor, NimMinor) < (1, 3) and defined(js): if readBytes < bufferSize: break -else: # after 1.3 or JS not defined +else: # after 1.3 or JS not defined proc ssAtEnd(s: Stream): bool = var s = StringStream(s) return s.pos >= s.data.len @@ -1202,6 +1217,11 @@ else: # after 1.3 or JS not defined 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: jsOrVmBlock: @@ -1261,12 +1281,9 @@ else: # after 1.3 or JS not defined 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`. ## ## See also: @@ -1285,6 +1302,11 @@ else: # after 1.3 or JS not defined 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 @@ -1333,7 +1355,7 @@ 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 = +proc fsReadLine(s: Stream, line: var string): bool = result = readLine(FileStream(s).f, line) proc newFileStream*(f: File): owned FileStream = @@ -1345,7 +1367,7 @@ proc newFileStream*(f: File): owned FileStream = ## * `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>`_ + ## 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. @@ -1384,7 +1406,7 @@ proc newFileStream*(filename: string, mode: FileMode = fmRead, ## 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>`_. + ## <syncio.html>`_ for a list of available `FileMode enums <syncio.html#FileMode>`_. ## ## **Note:** ## * **This function returns nil in case of failure.** @@ -1401,7 +1423,7 @@ proc newFileStream*(filename: string, mode: FileMode = fmRead, ## * `openFileStream proc <#openFileStream,string,FileMode,int>`_ creates a ## file stream from the file name and the mode. runnableExamples: - from os import removeFile + from std/os import removeFile var strm = newFileStream("somefile.txt", fmWrite) if not isNil(strm): strm.writeLine("The first line") @@ -1469,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) @@ -1517,6 +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) |