StringStream & more stdlib modules support for JS/NimScript (#14095)

* StringStream & more stdlib modules support for JS/NimScript

* change back pegs test in line with #14134
This commit is contained in:
hlaaftana 2020-04-28 20:44:52 +03:00 • committed by GitHub
commit cd9af6b804
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
22 changed files with 852 additions and 356 deletions

View file

@ -37,6 +37,17 @@
and this can now throw in edge cases where `getCurrentDir` throws. and this can now throw in edge cases where `getCurrentDir` throws.
`relativePath` also now works for js with `-d:nodejs`. `relativePath` also now works for js with `-d:nodejs`.
- JavaScript and NimScript standard library changes: `streams.StringStream` is
now supported in JavaScript, with the limitation that any buffer `pointer`s
used must be castable to `ptr string`, any incompatible pointer type will not
work. The `lexbase` and `streams` modules used to fail to compile on
NimScript due to a bug, but this has been fixed.
The following modules now compile on both JS and NimScript: `parsecsv`,
`parsecfg`, `parsesql`, `xmlparser`, `htmlparser` and `ropes`. Additionally
supported for JS is `cstrutils.startsWith` and `cstrutils.endsWith`, for
NimScript: `json`, `parsejson`, `strtabs` and `unidecode`.
- Added `streams.readStr` and `streams.peekStr` overloads to - Added `streams.readStr` and `streams.peekStr` overloads to
accept an existing string to modify, which avoids memory accept an existing string to modify, which avoids memory
allocations, similar to `streams.readLine` (#13857). allocations, similar to `streams.readLine` (#13857).

View file

@ -76,13 +76,15 @@ available. This includes:
* manual memory management (``alloc``, etc.) * manual memory management (``alloc``, etc.)
* casting and other unsafe operations (``cast`` operator, ``zeroMem``, etc.) * casting and other unsafe operations (``cast`` operator, ``zeroMem``, etc.)
* file management * file management
* most modules of the standard library * OS-specific operations
* threading, coroutines
* some modules of the standard library
* proper 64 bit integer arithmetic * proper 64 bit integer arithmetic
* unsigned integer arithmetic
However, the modules `strutils <strutils.html>`_, `math <math.html>`_, and To compensate, the standard library has modules `catered to the JS backend
`times <times.html>`_ are available! To access the DOM, use the `dom <https://nim-lang.org/docs/lib.html#pure-libraries-modules-for-js-backend>`_
<dom.html>`_ module that is only available for the JavaScript platform. and more support will come in the future (for instance, Node.js bindings
to get OS info).
To compile a Nim module into a ``.js`` file use the ``js`` command; the To compile a Nim module into a ``.js`` file use the ``js`` command; the
default is a ``.js`` file that is supposed to be referenced in an ``.html`` default is a ``.js`` file that is supposed to be referenced in an ``.html``

View file

@ -52,8 +52,6 @@ NimScript is subject to some limitations caused by the implementation of the VM
* ``random.randomize()`` requires an ``int64`` explicitly passed as argument, you *must* pass a Seed integer. * ``random.randomize()`` requires an ``int64`` explicitly passed as argument, you *must* pass a Seed integer.
* ``unicode`` can be imported, but not ``unidecode``.
Standard library modules Standard library modules
======================== ========================

View file

@ -19,29 +19,43 @@ proc toLowerAscii(c: char): char {.inline.} =
else: else:
result = c result = c
proc startsWith*(s, prefix: cstring): bool {.noSideEffect, when defined(js):
rtl, extern: "csuStartsWith".} = proc startsWith*(s, prefix: cstring): bool {.noSideEffect,
## Returns true iff ``s`` starts with ``prefix``. importjs: "#.startsWith(#)".}
##
## If ``prefix == ""`` true is returned.
var i = 0
while true:
if prefix[i] == '\0': return true
if s[i] != prefix[i]: return false
inc(i)
proc endsWith*(s, suffix: cstring): bool {.noSideEffect, proc endsWith*(s, suffix: cstring): bool {.noSideEffect,
rtl, extern: "csuEndsWith".} = importjs: "#.endsWith(#)".}
## Returns true iff ``s`` ends with ``suffix``.
## # JS string has more operations that might warrant its own module:
## If ``suffix == ""`` true is returned. # https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String
let slen = s.len else:
var i = 0 proc startsWith*(s, prefix: cstring): bool {.noSideEffect,
var j = slen - len(suffix) rtl, extern: "csuStartsWith".} =
while i+j <% slen: ## Returns true iff ``s`` starts with ``prefix``.
if s[i+j] != suffix[i]: return false ##
inc(i) ## If ``prefix == ""`` true is returned.
if suffix[i] == '\0': return true ##
## JS backend uses native ``String.prototype.startsWith``.
var i = 0
while true:
if prefix[i] == '\0': return true
if s[i] != prefix[i]: return false
inc(i)
proc endsWith*(s, suffix: cstring): bool {.noSideEffect,
rtl, extern: "csuEndsWith".} =
## Returns true iff ``s`` ends with ``suffix``.
##
## If ``suffix == ""`` true is returned.
##
## JS backend uses native ``String.prototype.endsWith``.
let slen = s.len
var i = 0
var j = slen - len(suffix)
while i+j <% slen:
if s[i+j] != suffix[i]: return false
inc(i)
if suffix[i] == '\0': return true
proc cmpIgnoreStyle*(a, b: cstring): int {.noSideEffect, proc cmpIgnoreStyle*(a, b: cstring): int {.noSideEffect,
rtl, extern: "csuCmpIgnoreStyle".} = rtl, extern: "csuCmpIgnoreStyle".} =
@ -53,6 +67,9 @@ proc cmpIgnoreStyle*(a, b: cstring): int {.noSideEffect,
## | 0 iff a == b ## | 0 iff a == b
## | < 0 iff a < b ## | < 0 iff a < b
## | > 0 iff a > b ## | > 0 iff a > b
##
## Not supported for JS backend, use `strutils.cmpIgnoreStyle
## <https://nim-lang.org/docs/strutils.html#cmpIgnoreStyle%2Cstring%2Cstring>`_ instead.
var i = 0 var i = 0
var j = 0 var j = 0
while true: while true:
@ -72,6 +89,9 @@ proc cmpIgnoreCase*(a, b: cstring): int {.noSideEffect,
## | 0 iff a == b ## | 0 iff a == b
## | < 0 iff a < b ## | < 0 iff a < b
## | > 0 iff a > b ## | > 0 iff a > b
##
## Not supported for JS backend, use `strutils.cmpIgnoreCase
## <https://nim-lang.org/docs/strutils.html#cmpIgnoreCase%2Cstring%2Cstring>`_ instead.
var i = 0 var i = 0
while true: while true:
var aa = toLowerAscii(a[i]) var aa = toLowerAscii(a[i])

View file

@ -52,7 +52,8 @@ proc fillBuffer(L: var BaseLexer) =
toCopy = L.buf.len - (L.sentinel + 1) toCopy = L.buf.len - (L.sentinel + 1)
assert(toCopy >= 0) assert(toCopy >= 0)
if toCopy > 0: if toCopy > 0:
when defined(js): when defined(js) or defined(nimscript):
# nimscript has to be here to avoid compiling other branch (moveMem)
for i in 0 ..< toCopy: for i in 0 ..< toCopy:
L.buf[i] = L.buf[L.sentinel + 1 + i] L.buf[i] = L.buf[L.sentinel + 1 + i]
else: else:

View file

@ -50,8 +50,14 @@
## * `streams module <streams.html>`_ ## * `streams module <streams.html>`_
## * `json module <json.html>`_ ## * `json module <json.html>`_
when defined(nimV2): const unsupportedPlatform =
{.error: """marshal module is not supported in new runtime. when defined(nimV2): "new runtime"
elif defined(js): "javascript"
elif defined(nimscript): "nimscript"
else: ""
when unsupportedPlatform != "":
{.error: "marshal module is not supported in " & unsupportedPlatform & """.
Please use alternative packages for serialization. Please use alternative packages for serialization.
It is possible to reimplement this module using generics and type traits. It is possible to reimplement this module using generics and type traits.
Please contribute new implementation.""".} Please contribute new implementation.""".}

View file

@ -557,7 +557,14 @@ type
tok: Token tok: Token
proc newNode*(k: SqlNodeKind): SqlNode = proc newNode*(k: SqlNodeKind): SqlNode =
result = SqlNode(kind: k) when defined(js): # bug #14117
case k
of LiteralNodes:
result = SqlNode(kind: k, strVal: "")
else:
result = SqlNode(kind: k, sons: @[])
else:
result = SqlNode(kind: k)
proc newNode*(k: SqlNodeKind, s: string): SqlNode = proc newNode*(k: SqlNodeKind, s: string): SqlNode =
result = SqlNode(kind: k) result = SqlNode(kind: k)
@ -1469,34 +1476,33 @@ proc treeRepr*(s: SqlNode): string =
result = newStringOfCap(128) result = newStringOfCap(128)
treeReprAux(s, 0, result) treeReprAux(s, 0, result)
when not defined(js): import streams
import streams
proc open(L: var SqlLexer, input: Stream, filename: string) = proc open(L: var SqlLexer, input: Stream, filename: string) =
lexbase.open(L, input) lexbase.open(L, input)
L.filename = filename L.filename = filename
proc open(p: var SqlParser, input: Stream, filename: string) = proc open(p: var SqlParser, input: Stream, filename: string) =
## opens the parser `p` and assigns the input stream `input` to it. ## opens the parser `p` and assigns the input stream `input` to it.
## `filename` is only used for error messages. ## `filename` is only used for error messages.
open(SqlLexer(p), input, filename) open(SqlLexer(p), input, filename)
p.tok.kind = tkInvalid p.tok.kind = tkInvalid
p.tok.literal = "" p.tok.literal = ""
getTok(p) getTok(p)
proc parseSQL*(input: Stream, filename: string): SqlNode = proc parseSQL*(input: Stream, filename: string): SqlNode =
## parses the SQL from `input` into an AST and returns the AST. ## parses the SQL from `input` into an AST and returns the AST.
## `filename` is only used for error messages. ## `filename` is only used for error messages.
## Syntax errors raise an `SqlParseError` exception. ## Syntax errors raise an `SqlParseError` exception.
var p: SqlParser var p: SqlParser
open(p, input, filename) open(p, input, filename)
try: try:
result = parse(p) result = parse(p)
finally: finally:
close(p) close(p)
proc parseSQL*(input: string, filename = ""): SqlNode = proc parseSQL*(input: string, filename = ""): SqlNode =
## parses the SQL from `input` into an AST and returns the AST. ## parses the SQL from `input` into an AST and returns the AST.
## `filename` is only used for error messages. ## `filename` is only used for error messages.
## Syntax errors raise an `SqlParseError` exception. ## Syntax errors raise an `SqlParseError` exception.
parseSQL(newStringStream(input), "") parseSQL(newStringStream(input), "")

View file

@ -287,46 +287,47 @@ proc addf*(c: var Rope, frmt: string, args: openArray[Rope]) {.
## shortcut for ``add(c, frmt % args)``. ## shortcut for ``add(c, frmt % args)``.
add(c, frmt % args) add(c, frmt % args)
const when not defined(js) and not defined(nimscript):
bufSize = 1024 # 1 KB is reasonable const
bufSize = 1024 # 1 KB is reasonable
proc equalsFile*(r: Rope, f: File): bool {.rtl, extern: "nro$1File".} = proc equalsFile*(r: Rope, f: File): bool {.rtl, extern: "nro$1File".} =
## returns true if the contents of the file `f` equal `r`. ## returns true if the contents of the file `f` equal `r`.
var var
buf: array[bufSize, char] buf: array[bufSize, char]
bpos = buf.len bpos = buf.len
blen = buf.len blen = buf.len
for s in leaves(r): for s in leaves(r):
var spos = 0 var spos = 0
let slen = s.len let slen = s.len
while spos < slen: while spos < slen:
if bpos == blen: if bpos == blen:
# Read more data # Read more data
bpos = 0 bpos = 0
blen = readBuffer(f, addr(buf[0]), buf.len) blen = readBuffer(f, addr(buf[0]), buf.len)
if blen == 0: # no more data in file if blen == 0: # no more data in file
result = false
return
let n = min(blen - bpos, slen - spos)
# TODO There's gotta be a better way of comparing here...
if not equalMem(addr(buf[bpos]),
cast[pointer](cast[int](cstring(s))+spos), n):
result = false result = false
return return
let n = min(blen - bpos, slen - spos) spos += n
# TODO There's gotta be a better way of comparing here... bpos += n
if not equalMem(addr(buf[bpos]),
cast[pointer](cast[int](cstring(s))+spos), n):
result = false
return
spos += n
bpos += n
result = readBuffer(f, addr(buf[0]), 1) == 0 # check that we've read all result = readBuffer(f, addr(buf[0]), 1) == 0 # check that we've read all
proc equalsFile*(r: Rope, filename: string): bool {.rtl, extern: "nro$1Str".} = proc equalsFile*(r: Rope, filename: string): bool {.rtl, extern: "nro$1Str".} =
## returns true if the contents of the file `f` equal `r`. If `f` does not ## returns true if the contents of the file `f` equal `r`. If `f` does not
## exist, false is returned. ## exist, false is returned.
var f: File var f: File
result = open(f, filename) result = open(f, filename)
if result: if result:
result = equalsFile(r, f) result = equalsFile(r, f)
close(f) close(f)
new(N) # init dummy node for splay algorithm new(N) # init dummy node for splay algorithm

View file

@ -216,6 +216,9 @@ proc getPosition*(s: Stream): int =
proc readData*(s: Stream, buffer: pointer, bufLen: int): int = proc readData*(s: Stream, buffer: pointer, bufLen: int): int =
## Low level proc that reads data into an untyped `buffer` of `bufLen` size. ## 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: runnableExamples:
var strm = newStringStream("abcde") var strm = newStringStream("abcde")
var buffer: array[6, char] var buffer: array[6, char]
@ -241,11 +244,21 @@ proc readDataStr*(s: Stream, buffer: var string, slice: Slice[int]): int =
# fallback # fallback
result = s.readData(addr buffer[0], buffer.len) result = s.readData(addr buffer[0], buffer.len)
when not defined(js): template jsOrVmBlock(caseJsOrVm, caseElse: untyped): untyped =
when nimvm:
block:
caseJsOrVm
else:
block:
when defined(js) or defined(nimscript):
# nimscript has to be here to avoid semantic checking of caseElse
caseJsOrVm
else:
caseElse
when (NimMajor, NimMinor) >= (1, 3) or not defined(js):
proc readAll*(s: Stream): string = proc readAll*(s: Stream): string =
## Reads all available data. ## Reads all available data.
##
## **Note:** Not available for JS backend.
runnableExamples: runnableExamples:
var strm = newStringStream("The first line\nthe second line\nthe third line") var strm = newStringStream("The first line\nthe second line\nthe third line")
doAssert strm.readAll() == "The first line\nthe second line\nthe third line" doAssert strm.readAll() == "The first line\nthe second line\nthe third line"
@ -253,7 +266,7 @@ when not defined(js):
strm.close() strm.close()
const bufferSize = 1024 const bufferSize = 1024
when nimvm: jsOrVmBlock:
var buffer2: string var buffer2: string
buffer2.setLen(bufferSize) buffer2.setLen(bufferSize)
while true: while true:
@ -265,7 +278,7 @@ when not defined(js):
result[prevLen..<prevLen+readBytes] = buffer2[0..<readBytes] result[prevLen..<prevLen+readBytes] = buffer2[0..<readBytes]
if readBytes < bufferSize: if readBytes < bufferSize:
break break
else: do: # not JS or VM
var buffer {.noinit.}: array[bufferSize, char] var buffer {.noinit.}: array[bufferSize, char]
while true: while true:
let readBytes = readData(s, addr(buffer[0]), bufferSize) let readBytes = readData(s, addr(buffer[0]), bufferSize)
@ -280,6 +293,9 @@ when not defined(js):
proc peekData*(s: Stream, buffer: pointer, bufLen: int): int = proc peekData*(s: Stream, buffer: pointer, bufLen: int): int =
## Low level proc that reads data into an untyped `buffer` of `bufLen` size ## Low level proc that reads data into an untyped `buffer` of `bufLen` size
## without moving stream position. ## without moving stream position.
##
## **JS note:** `buffer` is treated as a ``ptr string`` and written to between
## ``0..<bufLen``.
runnableExamples: runnableExamples:
var strm = newStringStream("abcde") var strm = newStringStream("abcde")
var buffer: array[6, char] var buffer: array[6, char]
@ -293,6 +309,9 @@ proc peekData*(s: Stream, buffer: pointer, bufLen: int): int =
proc writeData*(s: Stream, buffer: pointer, bufLen: int) = proc writeData*(s: Stream, buffer: pointer, bufLen: int) =
## Low level proc that writes an untyped `buffer` of `bufLen` size ## Low level proc that writes an untyped `buffer` of `bufLen` size
## to the stream `s`. ## to the stream `s`.
##
## **JS note:** `buffer` is treated as a ``ptr string`` and read between
## ``0..<bufLen``.
runnableExamples: runnableExamples:
## writeData ## writeData
var strm = newStringStream("") var strm = newStringStream("")
@ -311,6 +330,9 @@ proc writeData*(s: Stream, buffer: pointer, bufLen: int) =
proc write*[T](s: Stream, x: T) = proc write*[T](s: Stream, x: T) =
## Generic write procedure. Writes `x` to the stream `s`. Implementation: ## Generic write procedure. Writes `x` to the stream `s`. Implementation:
## ##
## **Note:** Not available for JS backend. Use `write(Stream, string)
## <#write,Stream,string>`_ for now.
##
## .. code-block:: Nim ## .. code-block:: Nim
## ##
## s.writeData(s, unsafeAddr(x), sizeof(x)) ## s.writeData(s, unsafeAddr(x), sizeof(x))
@ -336,7 +358,12 @@ proc write*(s: Stream, x: string) =
when nimvm: when nimvm:
writeData(s, cstring(x), x.len) writeData(s, cstring(x), x.len)
else: 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, `$`]) = proc write*(s: Stream, args: varargs[string, `$`]) =
## Writes one or more strings to the the stream. No length fields or ## Writes one or more strings to the the stream. No length fields or
@ -366,6 +393,8 @@ proc writeLine*(s: Stream, args: varargs[string, `$`]) =
proc read*[T](s: Stream, result: var T) = proc read*[T](s: Stream, result: var T) =
## Generic read procedure. Reads `result` from the stream `s`. ## Generic read procedure. Reads `result` from the stream `s`.
##
## **Note:** Not available for JS backend. Use `readStr <#readStr,Stream,int>`_ for now.
runnableExamples: runnableExamples:
var strm = newStringStream("012") var strm = newStringStream("012")
## readInt ## readInt
@ -383,6 +412,8 @@ proc read*[T](s: Stream, result: var T) =
proc peek*[T](s: Stream, result: var T) = proc peek*[T](s: Stream, result: var T) =
## Generic peek procedure. Peeks `result` from the stream `s`. ## Generic peek procedure. Peeks `result` from the stream `s`.
##
## **Note:** Not available for JS backend. Use `peekStr <#peekStr,Stream,int>`_ for now.
runnableExamples: runnableExamples:
var strm = newStringStream("012") var strm = newStringStream("012")
## peekInt ## peekInt
@ -412,11 +443,11 @@ proc readChar*(s: Stream): char =
doAssert strm.readChar() == '\x00' doAssert strm.readChar() == '\x00'
strm.close() strm.close()
when nimvm: jsOrVmBlock:
var str = " " 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: result = str[0]
else: do:
if readData(s, addr(result), sizeof(result)) != 1: result = '\0' if readData(s, addr(result), sizeof(result)) != 1: result = '\0'
proc peekChar*(s: Stream): char = proc peekChar*(s: Stream): char =
@ -430,7 +461,12 @@ proc peekChar*(s: Stream): char =
doAssert strm.peekChar() == '\x00' doAssert strm.peekChar() == '\x00'
strm.close() 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 = proc readBool*(s: Stream): bool =
## Reads a bool from the stream `s`. ## Reads a bool from the stream `s`.
@ -438,6 +474,8 @@ proc readBool*(s: Stream): bool =
## A bool is one byte long and it is `true` for every non-zero ## A bool is one byte long and it is `true` for every non-zero
## (`0000_0000`) value. ## (`0000_0000`) value.
## Raises `IOError` if an error occurred. ## Raises `IOError` if an error occurred.
##
## **Note:** Not available for JS backend. Use `readStr <#readStr,Stream,int>`_ for now.
runnableExamples: runnableExamples:
var strm = newStringStream() var strm = newStringStream()
## setup for reading data ## setup for reading data
@ -461,6 +499,8 @@ proc peekBool*(s: Stream): bool =
## A bool is one byte long and it is `true` for every non-zero ## A bool is one byte long and it is `true` for every non-zero
## (`0000_0000`) value. ## (`0000_0000`) value.
## Raises `IOError` if an error occurred. ## Raises `IOError` if an error occurred.
##
## **Note:** Not available for JS backend. Use `peekStr <#peekStr,Stream,int>`_ for now.
runnableExamples: runnableExamples:
var strm = newStringStream() var strm = newStringStream()
## setup for reading data ## setup for reading data
@ -482,6 +522,8 @@ proc peekBool*(s: Stream): bool =
proc readInt8*(s: Stream): int8 = proc readInt8*(s: Stream): int8 =
## Reads an int8 from the stream `s`. Raises `IOError` if an error occurred. ## 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: runnableExamples:
var strm = newStringStream() var strm = newStringStream()
## setup for reading data ## setup for reading data
@ -499,6 +541,8 @@ proc readInt8*(s: Stream): int8 =
proc peekInt8*(s: Stream): int8 = proc peekInt8*(s: Stream): int8 =
## Peeks an int8 from the stream `s`. Raises `IOError` if an error occurred. ## 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: runnableExamples:
var strm = newStringStream() var strm = newStringStream()
## setup for reading data ## setup for reading data
@ -518,6 +562,8 @@ proc peekInt8*(s: Stream): int8 =
proc readInt16*(s: Stream): int16 = proc readInt16*(s: Stream): int16 =
## Reads an int16 from the stream `s`. Raises `IOError` if an error occurred. ## 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: runnableExamples:
var strm = newStringStream() var strm = newStringStream()
## setup for reading data ## setup for reading data
@ -535,6 +581,8 @@ proc readInt16*(s: Stream): int16 =
proc peekInt16*(s: Stream): int16 = proc peekInt16*(s: Stream): int16 =
## Peeks an int16 from the stream `s`. Raises `IOError` if an error occurred. ## 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: runnableExamples:
var strm = newStringStream() var strm = newStringStream()
## setup for reading data ## setup for reading data
@ -554,6 +602,8 @@ proc peekInt16*(s: Stream): int16 =
proc readInt32*(s: Stream): int32 = proc readInt32*(s: Stream): int32 =
## Reads an int32 from the stream `s`. Raises `IOError` if an error occurred. ## 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: runnableExamples:
var strm = newStringStream() var strm = newStringStream()
## setup for reading data ## setup for reading data
@ -571,6 +621,8 @@ proc readInt32*(s: Stream): int32 =
proc peekInt32*(s: Stream): int32 = proc peekInt32*(s: Stream): int32 =
## Peeks an int32 from the stream `s`. Raises `IOError` if an error occurred. ## 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: runnableExamples:
var strm = newStringStream() var strm = newStringStream()
## setup for reading data ## setup for reading data
@ -590,6 +642,8 @@ proc peekInt32*(s: Stream): int32 =
proc readInt64*(s: Stream): int64 = proc readInt64*(s: Stream): int64 =
## Reads an int64 from the stream `s`. Raises `IOError` if an error occurred. ## 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: runnableExamples:
var strm = newStringStream() var strm = newStringStream()
## setup for reading data ## setup for reading data
@ -607,6 +661,8 @@ proc readInt64*(s: Stream): int64 =
proc peekInt64*(s: Stream): int64 = proc peekInt64*(s: Stream): int64 =
## Peeks an int64 from the stream `s`. Raises `IOError` if an error occurred. ## 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: runnableExamples:
var strm = newStringStream() var strm = newStringStream()
## setup for reading data ## setup for reading data
@ -626,6 +682,8 @@ proc peekInt64*(s: Stream): int64 =
proc readUint8*(s: Stream): uint8 = proc readUint8*(s: Stream): uint8 =
## Reads an uint8 from the stream `s`. Raises `IOError` if an error occurred. ## 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: runnableExamples:
var strm = newStringStream() var strm = newStringStream()
## setup for reading data ## setup for reading data
@ -643,6 +701,8 @@ proc readUint8*(s: Stream): uint8 =
proc peekUint8*(s: Stream): uint8 = proc peekUint8*(s: Stream): uint8 =
## Peeks an uint8 from the stream `s`. Raises `IOError` if an error occurred. ## 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: runnableExamples:
var strm = newStringStream() var strm = newStringStream()
## setup for reading data ## setup for reading data
@ -662,6 +722,8 @@ proc peekUint8*(s: Stream): uint8 =
proc readUint16*(s: Stream): uint16 = proc readUint16*(s: Stream): uint16 =
## Reads an uint16 from the stream `s`. Raises `IOError` if an error occurred. ## 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: runnableExamples:
var strm = newStringStream() var strm = newStringStream()
## setup for reading data ## setup for reading data
@ -679,6 +741,8 @@ proc readUint16*(s: Stream): uint16 =
proc peekUint16*(s: Stream): uint16 = proc peekUint16*(s: Stream): uint16 =
## Peeks an uint16 from the stream `s`. Raises `IOError` if an error occurred. ## 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: runnableExamples:
var strm = newStringStream() var strm = newStringStream()
## setup for reading data ## setup for reading data
@ -698,6 +762,8 @@ proc peekUint16*(s: Stream): uint16 =
proc readUint32*(s: Stream): uint32 = proc readUint32*(s: Stream): uint32 =
## Reads an uint32 from the stream `s`. Raises `IOError` if an error occurred. ## 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: runnableExamples:
var strm = newStringStream() var strm = newStringStream()
## setup for reading data ## setup for reading data
@ -716,6 +782,8 @@ proc readUint32*(s: Stream): uint32 =
proc peekUint32*(s: Stream): uint32 = proc peekUint32*(s: Stream): uint32 =
## Peeks an uint32 from the stream `s`. Raises `IOError` if an error occurred. ## 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: runnableExamples:
var strm = newStringStream() var strm = newStringStream()
## setup for reading data ## setup for reading data
@ -735,6 +803,8 @@ proc peekUint32*(s: Stream): uint32 =
proc readUint64*(s: Stream): uint64 = proc readUint64*(s: Stream): uint64 =
## Reads an uint64 from the stream `s`. Raises `IOError` if an error occurred. ## 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: runnableExamples:
var strm = newStringStream() var strm = newStringStream()
## setup for reading data ## setup for reading data
@ -752,6 +822,8 @@ proc readUint64*(s: Stream): uint64 =
proc peekUint64*(s: Stream): uint64 = proc peekUint64*(s: Stream): uint64 =
## Peeks an uint64 from the stream `s`. Raises `IOError` if an error occurred. ## 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: runnableExamples:
var strm = newStringStream() var strm = newStringStream()
## setup for reading data ## setup for reading data
@ -771,6 +843,8 @@ proc peekUint64*(s: Stream): uint64 =
proc readFloat32*(s: Stream): float32 = proc readFloat32*(s: Stream): float32 =
## Reads a float32 from the stream `s`. Raises `IOError` if an error occurred. ## 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: runnableExamples:
var strm = newStringStream() var strm = newStringStream()
## setup for reading data ## setup for reading data
@ -788,6 +862,8 @@ proc readFloat32*(s: Stream): float32 =
proc peekFloat32*(s: Stream): float32 = proc peekFloat32*(s: Stream): float32 =
## Peeks a float32 from the stream `s`. Raises `IOError` if an error occurred. ## 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: runnableExamples:
var strm = newStringStream() var strm = newStringStream()
## setup for reading data ## setup for reading data
@ -807,6 +883,8 @@ proc peekFloat32*(s: Stream): float32 =
proc readFloat64*(s: Stream): float64 = proc readFloat64*(s: Stream): float64 =
## Reads a float64 from the stream `s`. Raises `IOError` if an error occurred. ## 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: runnableExamples:
var strm = newStringStream() var strm = newStringStream()
## setup for reading data ## setup for reading data
@ -824,6 +902,8 @@ proc readFloat64*(s: Stream): float64 =
proc peekFloat64*(s: Stream): float64 = proc peekFloat64*(s: Stream): float64 =
## Peeks a float64 from the stream `s`. Raises `IOError` if an error occurred. ## 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: runnableExamples:
var strm = newStringStream() var strm = newStringStream()
## setup for reading data ## setup for reading data
@ -841,10 +921,19 @@ proc peekFloat64*(s: Stream): float64 =
peek(s, result) 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) = proc readStrPrivate(s: Stream, length: int, str: var TaintedString) =
if length > len(str): setLen(str.string, length) if length > len(str): setLen(str.untaint, length)
var L = readData(s, cstring(str), length) when defined(js):
if L != len(str): setLen(str.string, L) let L = readData(s, addr(str), length)
else:
let L = readData(s, cstring(str.string), length)
if L != len(str): setLen(str.untaint, L)
proc readStr*(s: Stream, length: int, str: var TaintedString) {.since: (1, 3).} = proc readStr*(s: Stream, length: int, str: var TaintedString) {.since: (1, 3).} =
## Reads a string of length `length` from the stream `s`. Raises `IOError` if ## Reads a string of length `length` from the stream `s`. Raises `IOError` if
@ -865,9 +954,12 @@ proc readStr*(s: Stream, length: int): TaintedString =
readStrPrivate(s, length, result) readStrPrivate(s, length, result)
proc peekStrPrivate(s: Stream, length: int, str: var TaintedString) = proc peekStrPrivate(s: Stream, length: int, str: var TaintedString) =
if length > len(str): setLen(str.string, length) if length > len(str): setLen(str.untaint, length)
var L = peekData(s, cstring(str), length) when defined(js):
if L != len(str): setLen(str.string, L) let L = peekData(s, addr(str), length)
else:
let L = peekData(s, cstring(str.string), length)
if L != len(str): setLen(str.untaint, L)
proc peekStr*(s: Stream, length: int, str: var TaintedString) {.since: (1, 3).} = proc peekStr*(s: Stream, length: int, str: var TaintedString) {.since: (1, 3).} =
## Peeks a string of length `length` from the stream `s`. Raises `IOError` if ## Peeks a string of length `length` from the stream `s`. Raises `IOError` if
@ -918,13 +1010,7 @@ proc readLine*(s: Stream, line: var TaintedString): bool =
result = s.readLineImpl(s, line) result = s.readLineImpl(s, line)
else: else:
# fallback # fallback
when nimvm: #Bug #12282 line.untaint.setLen(0)
when taintMode:
line.string.setLen(0)
else:
line.setLen(0)
else:
line.string.setLen(0)
while true: while true:
var c = readChar(s) var c = readChar(s)
if c == '\c': if c == '\c':
@ -934,13 +1020,7 @@ proc readLine*(s: Stream, line: var TaintedString): bool =
elif c == '\0': elif c == '\0':
if line.len > 0: break if line.len > 0: break
else: return false else: return false
when nimvm: #Bug #12282 line.untaint.add(c)
when taintMode:
line.string.add(c)
else:
line.add(c)
else:
line.string.add(c)
result = true result = true
proc peekLine*(s: Stream, line: var TaintedString): bool = proc peekLine*(s: Stream, line: var TaintedString): bool =
@ -1002,7 +1082,7 @@ proc readLine*(s: Stream): TaintedString =
if c == '\L' or c == '\0': if c == '\L' or c == '\0':
break break
else: else:
result.string.add(c) result.untaint.add(c)
proc peekLine*(s: Stream): TaintedString = proc peekLine*(s: Stream): TaintedString =
## Peeks a line from a stream `s`. Raises `IOError` if an error occurred. ## Peeks a line from a stream `s`. Raises `IOError` if an error occurred.
@ -1048,17 +1128,13 @@ iterator lines*(s: Stream): TaintedString =
type type
StringStream* = ref StringStreamObj StringStream* = ref StringStreamObj
## A stream that encapsulates a string. ## A stream that encapsulates a string.
##
## **Note:** Not available for JS backend.
StringStreamObj* = object of StreamObj StringStreamObj* = object of StreamObj
## A string stream object. ## A string stream object.
##
## **Note:** Not available for JS backend.
data*: string ## A string data. data*: string ## A string data.
## This is updated when called `writeLine` etc. ## This is updated when called `writeLine` etc.
pos: int 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.} = proc ssAtEnd(s: Stream): bool {.compileTime.} =
var s = StringStream(s) var s = StringStream(s)
return s.pos >= s.data.len return s.pos >= s.data.len
@ -1111,7 +1187,7 @@ when defined(js): #This section exists so that string streams work at compile ti
if readBytes < bufferSize: if readBytes < bufferSize:
break break
else: else: # after 1.3 or JS not defined
proc ssAtEnd(s: Stream): bool = proc ssAtEnd(s: Stream): bool =
var s = StringStream(s) var s = StringStream(s)
return s.pos >= s.data.len return s.pos >= s.data.len
@ -1128,9 +1204,9 @@ else:
var s = StringStream(s) var s = StringStream(s)
result = min(slice.b + 1 - slice.a, s.data.len - s.pos) result = min(slice.b + 1 - slice.a, s.data.len - s.pos)
if result > 0: if result > 0:
when nimvm: jsOrVmBlock:
buffer[slice.a..<slice.a+result] = s.data[s.pos..<s.pos+result] 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) copyMem(unsafeAddr buffer[slice.a], addr s.data[s.pos], result)
inc(s.pos, result) inc(s.pos, result)
else: else:
@ -1140,7 +1216,14 @@ else:
var s = StringStream(s) var s = StringStream(s)
result = min(bufLen, s.data.len - s.pos) result = min(bufLen, s.data.len - s.pos)
if result > 0: 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) inc(s.pos, result)
else: else:
result = 0 result = 0
@ -1149,7 +1232,14 @@ else:
var s = StringStream(s) var s = StringStream(s)
result = min(bufLen, s.data.len - s.pos) result = min(bufLen, s.data.len - s.pos)
if result > 0: 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: else:
result = 0 result = 0
@ -1159,7 +1249,14 @@ else:
return return
if s.pos + bufLen > s.data.len: if s.pos + bufLen > s.data.len:
setLen(s.data, s.pos + bufLen) 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) inc(s.pos, bufLen)
proc ssClose(s: Stream) = proc ssClose(s: Stream) =
@ -1172,8 +1269,6 @@ else:
proc newStringStream*(s: string = ""): owned StringStream = proc newStringStream*(s: string = ""): owned StringStream =
## Creates a new stream from the string `s`. ## Creates a new stream from the string `s`.
## ##
## **Note:** Not available for JS backend.
##
## See also: ## See also:
## * `newFileStream proc <#newFileStream,File>`_ creates a file stream from ## * `newFileStream proc <#newFileStream,File>`_ creates a file stream from
## opened File. ## opened File.
@ -1195,163 +1290,166 @@ else:
result.atEndImpl = ssAtEnd result.atEndImpl = ssAtEnd
result.setPositionImpl = ssSetPosition result.setPositionImpl = ssSetPosition
result.getPositionImpl = ssGetPosition result.getPositionImpl = ssGetPosition
result.readDataImpl = ssReadData
result.peekDataImpl = ssPeekData
result.writeDataImpl = ssWriteData
result.readDataStrImpl = ssReadDataStr result.readDataStrImpl = ssReadDataStr
when nimvm:
discard
else:
result.readDataImpl = ssReadData
result.peekDataImpl = ssPeekData
result.writeDataImpl = ssWriteData
type type
FileStream* = ref FileStreamObj FileStream* = ref FileStreamObj
## A stream that encapsulates a `File`. ## 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`.
## ##
## **Note:** Not available for JS backend. ## **Note:** Not available for JS backend.
FileStreamObj* = object of Stream
## A file stream object.
## ##
## See also: ## **Note:** Not available for JS backend.
## * `newStringStream proc <#newStringStream,string>`_ creates a new stream f: File
## from string.
## * `newFileStream proc <#newFileStream,string,FileMode,int>`_ is the same proc fsClose(s: Stream) =
## as using `open proc <io.html#open,File,string,FileMode,int>`_ if FileStream(s).f != nil:
## on Examples. close(FileStream(s).f)
## * `openFileStream proc <#openFileStream,string,FileMode,int>`_ creates a FileStream(s).f = nil
## file stream from the file name and the mode. proc fsFlush(s: Stream) = flushFile(FileStream(s).f)
runnableExamples: proc fsAtEnd(s: Stream): bool = return endOfFile(FileStream(s).f)
proc fsSetPosition(s: Stream, pos: int) = setFilePos(FileStream(s).f, pos)
proc fsGetPosition(s: Stream): int = return int(getFilePos(FileStream(s).f))
proc fsReadData(s: Stream, buffer: pointer, bufLen: int): int =
result = readBuffer(FileStream(s).f, buffer, bufLen)
proc fsReadDataStr(s: Stream, buffer: var string, slice: Slice[int]): int =
result = readBuffer(FileStream(s).f, addr buffer[slice.a], slice.b + 1 - slice.a)
proc fsPeekData(s: Stream, buffer: pointer, bufLen: int): int =
let pos = fsGetPosition(s)
defer: fsSetPosition(s, pos)
result = readBuffer(FileStream(s).f, buffer, bufLen)
proc fsWriteData(s: Stream, buffer: pointer, bufLen: int) =
if writeBuffer(FileStream(s).f, buffer, bufLen) != bufLen:
raise newEIO("cannot write to stream")
proc fsReadLine(s: Stream, line: var TaintedString): bool =
result = readLine(FileStream(s).f, line)
proc newFileStream*(f: File): owned FileStream =
## Creates a new stream from the file `f`.
##
## **Note:** Not available for JS backend.
##
## See also:
## * `newStringStream proc <#newStringStream,string>`_ creates a new stream
## from string.
## * `newFileStream proc <#newFileStream,string,FileMode,int>`_ is the same
## as using `open proc <io.html#open,File,string,FileMode,int>`_
## on Examples.
## * `openFileStream proc <#openFileStream,string,FileMode,int>`_ creates a
## file stream from the file name and the mode.
runnableExamples:
## Input (somefile.txt):
## The first line
## the second line
## the third line
var f: File
if open(f, "somefile.txt", fmRead, -1):
var strm = newFileStream(f)
var line = ""
while strm.readLine(line):
echo line
## Output:
## The first line
## the second line
## the third line
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")
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): ## Input (somefile.txt):
## The first line ## The first line
## the second line ## the second line
## the third line ## the third line
var f: File var strm = openFileStream("somefile.txt")
if open(f, "somefile.txt", fmRead, -1): echo strm.readLine()
var strm = newFileStream(f) ## Output:
var line = "" ## The first line
while strm.readLine(line): strm.close()
echo line except:
## Output: stderr.write getCurrentExceptionMsg()
## The first line
## the second line
## the third line
strm.close()
new(result) var f: File
result.f = f if open(f, filename, mode, bufSize):
result.closeImpl = fsClose return newFileStream(f)
result.atEndImpl = fsAtEnd else:
result.setPositionImpl = fsSetPosition raise newEIO("cannot open file stream: " & filename)
result.getPositionImpl = fsGetPosition
result.readDataStrImpl = fsReadDataStr
result.readDataImpl = fsReadData
result.readLineImpl = fsReadLine
result.peekDataImpl = fsPeekData
result.writeDataImpl = fsWriteData
result.flushImpl = fsFlush
proc newFileStream*(filename: string, mode: FileMode = fmRead,
bufSize: int = -1): owned FileStream =
## Creates a new stream from the file named `filename` with the mode `mode`.
##
## If the file cannot be opened, `nil` is returned. See the `io module
## <io.html>`_ for a list of available `FileMode enums <io.html#FileMode>`_.
##
## **Note:**
## * **This function returns nil in case of failure.**
## To prevent unexpected behavior and ensure proper error handling,
## use `openFileStream proc <#openFileStream,string,FileMode,int>`_
## instead.
## * Not available for JS backend.
##
## See also:
## * `newStringStream proc <#newStringStream,string>`_ creates a new stream
## from string.
## * `newFileStream proc <#newFileStream,File>`_ creates a file stream from
## opened File.
## * `openFileStream proc <#openFileStream,string,FileMode,int>`_ creates a
## file stream from the file name and the mode.
runnableExamples:
from os import removeFile
var strm = newFileStream("somefile.txt", fmWrite)
if not isNil(strm):
strm.writeLine("The first line")
strm.writeLine("the second line")
strm.writeLine("the third line")
strm.close()
## Output (somefile.txt)
## The first line
## the second line
## the third line
removeFile("somefile.txt")
var f: File
if open(f, filename, mode, bufSize): result = newFileStream(f)
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)
when false: when false:
type type

View file

@ -52,7 +52,7 @@ runnableExamples:
import import
hashes, strutils hashes, strutils
when defined(js): when defined(js) or defined(nimscript):
{.pragma: rtlFunc.} {.pragma: rtlFunc.}
else: else:
{.pragma: rtlFunc, rtl.} {.pragma: rtlFunc, rtl.}
@ -298,7 +298,7 @@ proc raiseFormatException(s: string) =
proc getValue(t: StringTableRef, flags: set[FormatFlag], key: string): string = proc getValue(t: StringTableRef, flags: set[FormatFlag], key: string): string =
if hasKey(t, key): return t.getOrDefault(key) if hasKey(t, key): return t.getOrDefault(key)
# hm difficult: assume safety in taint mode here. XXX This is dangerous! # hm difficult: assume safety in taint mode here. XXX This is dangerous!
when defined(js): when defined(js) or defined(nimscript):
result = "" result = ""
else: else:
if useEnvironment in flags: result = os.getEnv(key).string if useEnvironment in flags: result = os.getEnv(key).string

View file

@ -251,7 +251,7 @@ method testEnded*(formatter: ConsoleOutputFormatter, testResult: TestResult) =
template rawPrint() = echo(prefix, "[", $testResult.status, "] ", template rawPrint() = echo(prefix, "[", $testResult.status, "] ",
testResult.testName) testResult.testName)
when not defined(ECMAScript): when not defined(ECMAScript):
if formatter.colorOutput and not defined(ECMAScript): if formatter.colorOutput:
var color = case testResult.status var color = case testResult.status
of TestStatus.OK: fgGreen of TestStatus.OK: fgGreen
of TestStatus.FAILED: fgRed of TestStatus.FAILED: fgRed

View file

@ -13,18 +13,24 @@
template volatileLoad*[T](src: ptr T): T = template volatileLoad*[T](src: ptr T): T =
## Generates a volatile load of the value stored in the container `src`. ## Generates a volatile load of the value stored in the container `src`.
## Note that this only effects code generation on `C` like backends ## Note that this only effects code generation on `C` like backends
when defined(js): when nimvm:
src[] src[]
else: else:
var res: T when defined(js):
{.emit: [res, " = (*(", type(src[]), " volatile*)", src, ");"].} src[]
res else:
var res: T
{.emit: [res, " = (*(", type(src[]), " volatile*)", src, ");"].}
res
template volatileStore*[T](dest: ptr T, val: T) = template volatileStore*[T](dest: ptr T, val: T) =
## Generates a volatile store into the container `dest` of the value ## Generates a volatile store into the container `dest` of the value
## `val`. Note that this only effects code generation on `C` like ## `val`. Note that this only effects code generation on `C` like
## backends ## backends
when defined(js): when nimvm:
dest[] = val dest[] = val
else: else:
{.emit: ["*((", type(dest[]), " volatile*)(", dest, ")) = ", val, ";"].} when defined(js):
dest[] = val
else:
{.emit: ["*((", type(dest[]), " volatile*)(", dest, ")) = ", val, ";"].}

View file

@ -7,7 +7,7 @@
# distribution, for details about the copyright. # distribution, for details about the copyright.
# #
import system/indexerrors include system/indexerrors
proc log*(s: cstring) {.importc: "console.log", varargs, nodecl.} proc log*(s: cstring) {.importc: "console.log", varargs, nodecl.}

View file

@ -0,0 +1,71 @@
discard """
action: compile
"""
{.warning[UnusedImport]: off.}
import std/[
# Core:
bitops, typetraits, lenientops, macros, volatile, typeinfo,
# fails: endians, rlocks
# works but shouldn't: cpuinfo, locks
# Algorithms:
algorithm, sequtils,
# Collections:
critbits, deques, heapqueue, intsets, lists, options, sets,
sharedlist, tables,
# fails: sharedtables
# Strings:
cstrutils, editdistance, wordwrap, parseutils, ropes,
pegs, punycode, strformat, strmisc, strscans, strtabs,
strutils, unicode, unidecode,
# fails: encodings
# Time handling:
monotimes, times,
# Generic operator system services:
os, streams,
# fails: distros, dynlib, marshal, memfiles, osproc, terminal
# Math libraries:
complex, math, mersenne, random, rationals, stats, sums,
# works but shouldn't: fenv
# Internet protocols:
cookies, httpcore, mimetypes, uri,
# fails: asyncdispatch, asyncfile, asyncftpclient, asynchttpserver,
# asyncnet, cgi, httpclient, nativesockets, net, selectors, smtp
# works but shouldn't test: asyncstreams, asyncfutures
# Threading:
# fails: threadpool
# Parsers:
htmlparser, json, lexbase, parsecfg, parsecsv, parsesql, parsexml,
# fails: parseopt
# XML processing:
xmltree, xmlparser,
# Generators:
htmlgen,
# Hashing:
base64, hashes,
# fails: md5, oids, sha1
# Miscellaneous:
colors, logging, sugar, unittest, varints,
# fails: browsers, coro
# works but shouldn't: segfaults
# Modules for JS backend:
asyncjs, dom, jsconsole, jscore, jsffi,
# Unlisted in lib.html:
decls, compilesettings, with, wrapnils
]

View file

@ -0,0 +1,194 @@
discard """
output: '''
abc
def
definition
prefix
xyz
def
definition
Hi Andreas! How do you feel, Rumpf?
@[0, 2, 1]
@[1, 0, 2]
@[1, 2, 0]
@[2, 0, 1]
@[2, 1, 0]
@[2, 0, 1]
@[1, 2, 0]
@[1, 0, 2]
@[0, 2, 1]
@[0, 1, 2]
[5]
[4, 5]
[3, 4, 5]
[2, 3, 4, 5]
[2, 3, 4, 5, 6]
[1, 2, 3, 4, 5, 6]
'''
"""
import
critbits, cstrutils, sets, strutils, tables, random, algorithm, ropes,
lists, htmlgen, xmltree, strtabs
block tcritbits:
var r: CritBitTree[void]
r.incl "abc"
r.incl "xyz"
r.incl "def"
r.incl "definition"
r.incl "prefix"
doAssert r.contains"def"
#r.del "def"
for w in r.items:
echo w
for w in r.itemsWithPrefix("de"):
echo w
block testequivalence:
doAssert(toHashSet(@[1,2,3]) <= toHashSet(@[1,2,3,4]), "equivalent or subset")
doAssert(toHashSet(@[1,2,3]) <= toHashSet(@[1,2,3]), "equivalent or subset")
doAssert((not(toHashSet(@[1,2,3]) <= toHashSet(@[1,2]))), "equivalent or subset")
doAssert(toHashSet(@[1,2,3]) <= toHashSet(@[1,2,3,4]), "strict subset")
doAssert((not(toHashSet(@[1,2,3]) < toHashSet(@[1,2,3]))), "strict subset")
doAssert((not(toHashSet(@[1,2,3]) < toHashSet(@[1,2]))), "strict subset")
doAssert((not(toHashSet(@[1,2,3]) == toHashSet(@[1,2,3,4]))), "==")
doAssert(toHashSet(@[1,2,3]) == toHashSet(@[1,2,3]), "==")
doAssert((not(toHashSet(@[1,2,3]) == toHashSet(@[1,2]))), "==")
block tformat:
echo("Hi $1! How do you feel, $2?\n" % ["Andreas", "Rumpf"])
block tnilecho:
var x = @["1", "", "3"]
doAssert $x == """@["1", "", "3"]"""
block torderedtable:
var t = initOrderedTable[int,string]()
# this tests issue #5917
var data = newSeq[int]()
for i in 0..<1000:
var x = rand(1000)
if x notin t: data.add(x)
t[x] = "meh"
# this checks that keys are re-inserted
# in order when table is enlarged.
var i = 0
for k, v in t:
doAssert(k == data[i])
doAssert(v == "meh")
inc(i)
block tpermutations:
var v = @[0, 1, 2]
while v.nextPermutation():
echo v
while v.prevPermutation():
echo v
block tropes:
var
r1 = rope("")
r2 = rope("123")
doAssert r1.len == 0
doAssert r2.len == 3
doAssert $r1 == ""
doAssert $r2 == "123"
r1.add("123")
r2.add("456")
doAssert r1.len == 3
doAssert r2.len == 6
doAssert $r1 == "123"
doAssert $r2 == "123456"
doAssert $r1[1] == "2"
doAssert $r2[2] == "3"
block tsinglylinkedring:
var r = initSinglyLinkedRing[int]()
r.prepend(5)
echo r
r.prepend(4)
echo r
r.prepend(3)
echo r
r.prepend(2)
echo r
r.append(6)
echo r
r.prepend(1)
echo r
block tsplit:
var s = ""
for w in split("|abc|xy|z", {'|'}):
s.add("#")
s.add(w)
doAssert s == "##abc#xy#z"
block tsplit2:
var s = ""
for w in split("|abc|xy|z", {'|'}):
s.add("#")
s.add(w)
var errored = false
try:
discard "hello".split("")
except AssertionError:
errored = true
doAssert errored
block txmlgen:
var nim = "Nim"
doAssert h1(a(href="http://force7.de/nim", nim)) ==
"<h1><a href=\"http://force7.de/nim\">Nim</a></h1>"
block txmltree:
var x = <>a(href="nim.de", newText("www.nim-test.de"))
doAssert($x == "<a href=\"nim.de\">www.nim-test.de</a>")
doAssert(newText("foo").innerText == "foo")
doAssert(newEntity("bar").innerText == "bar")
doAssert(newComment("baz").innerText == "")
let y = newXmlTree("x", [
newText("foo"),
newXmlTree("y", [
newText("bar")
])
])
doAssert(y.innerText == "foobar")
block tcstrutils:
let s = cstring "abcdef"
doAssert s.startsWith("a")
doAssert not s.startsWith("b")
doAssert s.endsWith("f")
doAssert not s.endsWith("a")
let a = cstring "abracadabra"
doAssert a.startsWith("abra")
doAssert not a.startsWith("bra")
doAssert a.endsWith("abra")
doAssert not a.endsWith("dab")

22
tests/js/tstreams.nim Normal file
View file

@ -0,0 +1,22 @@
discard """
output: '''
I
AM
GROOT
'''
"""
import streams
var s = newStringStream("I\nAM\nGROOT")
doAssert s.peekStr(1) == "I"
doAssert s.peekChar() == 'I'
for line in s.lines:
echo line
s.close
var s2 = newStringStream("abc")
doAssert s2.readAll == "abc"
s2.write("def")
doAssert s2.data == "abcdef"
s2.close

View file

@ -1,6 +1,6 @@
discard """ discard """
targets: "c js"
output: ''' output: '''
@[]
true true
https://example.com/test?format=jpg&name=orig## https://example.com/test?format=jpg&name=orig##
https://example.com/test?format=jpg&name=orig##text https://example.com/test?format=jpg&name=orig##text
@ -40,7 +40,7 @@ block t2813:
""" """
var errors: seq[string] = @[] var errors: seq[string] = @[]
let tree = parseHtml(newStringStream(html), "test.html", errors) let tree = parseHtml(newStringStream(html), "test.html", errors)
echo errors # Errors: </thead> expected,... doAssert errors.len == 0 # Errors: </thead> expected,...
var len = tree.findAll("tr").len # len = 6 var len = tree.findAll("tr").len # len = 6
var rows: seq[XmlNode] = @[] var rows: seq[XmlNode] = @[]

View file

@ -1,29 +1,5 @@
discard """ discard """
output: ''' targets: "c js"
utf-8
on
hello
lihf8515
10214028
lihaifeng@wxm.com
===
charset=utf-8
[Package]
name=hello
--threads:on
[Author]
name=lhf
qq=10214028
email="lihaifeng@wxm.com"
===
charset=utf-8
[Package]
name=hello
--threads:on
[Author]
name=lihf8515
qq=10214028
'''
""" """
import parsecfg, streams import parsecfg, streams
@ -46,24 +22,35 @@ var pname = dict2.getSectionValue("Package","name")
var name = dict2.getSectionValue("Author","name") var name = dict2.getSectionValue("Author","name")
var qq = dict2.getSectionValue("Author","qq") var qq = dict2.getSectionValue("Author","qq")
var email = dict2.getSectionValue("Author","email") var email = dict2.getSectionValue("Author","email")
echo charset doAssert charset == "utf-8"
echo threads doAssert threads == "on"
echo pname doAssert pname == "hello"
echo name doAssert name == "lihf8515"
echo qq doAssert qq == "10214028"
echo email doAssert email == "lihaifeng@wxm.com"
echo "==="
## Modifying a configuration file. ## Modifying a configuration file.
var dict3 = loadConfig(newStringStream(ss.data)) var dict3 = loadConfig(newStringStream(ss.data))
dict3.setSectionKey("Author","name","lhf") dict3.setSectionKey("Author","name","lhf")
write(stdout, $dict3) doAssert $dict3 == """charset=utf-8
[Package]
echo "===" name=hello
--threads:on
[Author]
name=lhf
qq=10214028
email="lihaifeng@wxm.com"
"""
## Deleting a section key in a configuration file. ## Deleting a section key in a configuration file.
var dict4 = loadConfig(newStringStream(ss.data)) var dict4 = loadConfig(newStringStream(ss.data))
dict4.delSectionKey("Author","email") dict4.delSectionKey("Author","email")
write(stdout, $dict4) doAssert $dict4 == """charset=utf-8
[Package]
name=hello
--threads:on
[Author]
name=lihf8515
qq=10214028
"""

View file

@ -1,3 +1,6 @@
discard """
targets: "c js"
"""
import parsesql import parsesql
doAssert $parseSQL("SELECT foo FROM table;") == "select foo from table;" doAssert $parseSQL("SELECT foo FROM table;") == "select foo from table;"

View file

@ -1,4 +1,5 @@
discard """ discard """
targets: "c js"
output: ''' output: '''
PEG AST traversal output PEG AST traversal output
------------------------ ------------------------

View file

@ -38,8 +38,8 @@ true
""" """
import import
critbits, sets, strutils, tables, random, algorithm, re, ropes, segfaults, critbits, cstrutils, sets, strutils, tables, random, algorithm, re, ropes,
lists, parsesql, streams, os, htmlgen, xmltree, strtabs segfaults, lists, parsesql, streams, os, htmlgen, xmltree, strtabs
block tcritbits: block tcritbits:
@ -245,3 +245,24 @@ block txmltree:
]) ])
]) ])
doAssert(y.innerText == "foobar") doAssert(y.innerText == "foobar")
block tcstrutils:
let s = cstring "abcdef"
doAssert s.startsWith("a")
doAssert not s.startsWith("b")
doAssert s.endsWith("f")
doAssert not s.endsWith("a")
let a = cstring "abracadabra"
doAssert a.startsWith("abra")
doAssert not a.startsWith("bra")
doAssert a.endsWith("abra")
doAssert not a.endsWith("dab")
doAssert cmpIgnoreCase(cstring "FooBar", "foobar") == 0
doAssert cmpIgnoreCase(cstring "bar", "Foo") < 0
doAssert cmpIgnoreCase(cstring "Foo5", "foo4") > 0
doAssert cmpIgnoreStyle(cstring "foo_bar", "FooBar") == 0
doAssert cmpIgnoreStyle(cstring "foo_bar_5", "FooBar4") > 0

View file

@ -1,26 +1,74 @@
# This nimscript is used to test if the following modules can be imported # This nimscript is used to test if the following modules can be imported
# http://nim-lang.org/docs/nims.html # http://nim-lang.org/docs/nims.html
import algorithm {.warning[UnusedImport]: off.}
import base64
import colors import std/[
import hashes # Core:
import lists bitops, typetraits, lenientops, macros, volatile,
import math # fails: typeinfo, endians
# import marshal # works but shouldn't: cpuinfo, rlocks, locks
import options
import os # Algorithms:
# import parsecfg algorithm, sequtils,
# import parseopt
import parseutils # Collections:
# import pegs critbits, deques, heapqueue, intsets, lists, options, sets,
import deques sharedlist, tables,
import sequtils # fails: sharedtables
import strutils
import tables # Strings:
import unicode editdistance, wordwrap, parseutils, ropes,
import uri pegs, punycode, strformat, strmisc, strscans, strtabs,
import macros strutils, unicode, unidecode,
# works but shouldn't: cstrutils, encodings
# Time handling:
# fails: monotimes, times
# but times.getTime() implemented for VM
# Generic operator system services:
os, streams,
# fails: distros, dynlib, marshal, memfiles, osproc, terminal
# Math libraries:
complex, math, mersenne, random, rationals, stats, sums,
# works but shouldn't: fenv
# Internet protocols:
httpcore, mimetypes, uri,
# fails: asyncdispatch, asyncfile, asyncftpclient, asynchttpserver,
# asyncnet, cgi, cookies, httpclient, nativesockets, net, selectors, smtp
# works but shouldn't test: asyncstreams, asyncfutures
# Threading:
# fails: threadpool
# Parsers:
htmlparser, json, lexbase, parsecfg, parsecsv, parsesql, parsexml,
# fails: parseopt
# XML processing:
xmltree, xmlparser,
# Generators:
htmlgen,
# Hashing:
base64, hashes,
# fails: md5, oids, sha1
# Miscellaneous:
colors, sugar, varints,
# fails: browsers, coro, logging (times), segfaults, unittest (uses methods)
# Modules for JS backend:
# fails: asyncjs, dom, jsconsole, jscore, jsffi,
# Unlisted in lib.html:
decls, compilesettings, with, wrapnils
]
block: block:
doAssert "./foo//./bar/".normalizedPath == "foo/bar".unixToNativePath doAssert "./foo//./bar/".normalizedPath == "foo/bar".unixToNativePath