diff options
Diffstat (limited to 'lib/pure/streams.nim')
-rw-r--r-- | lib/pure/streams.nim | 693 |
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 |