diff options
author | jrfondren <41455523+jrfondren@users.noreply.github.com> | 2019-05-03 13:03:45 -0500 |
---|---|---|
committer | GitHub <noreply@github.com> | 2019-05-03 13:03:45 -0500 |
commit | 8cadeb960597a47a09100bdda05672f177d158d2 (patch) | |
tree | afe59c7bc9f5502801754e0f7fead84552a3d4e6 /lib | |
parent | 6dfde0e931176405491987e14969f68d81957730 (diff) | |
parent | 515ab81477c1c3e4811c4fbf43a3ff81b87be970 (diff) | |
download | Nim-8cadeb960597a47a09100bdda05672f177d158d2.tar.gz |
Merge branch 'devel' into expand-amb-identifier-output
Diffstat (limited to 'lib')
39 files changed, 1880 insertions, 794 deletions
diff --git a/lib/core/macros.nim b/lib/core/macros.nim index 7a8755299..6a4c094d0 100644 --- a/lib/core/macros.nim +++ b/lib/core/macros.nim @@ -1076,20 +1076,24 @@ proc expectKind*(n: NimNode; k: set[NimNodeKind]) {.compileTime.} = ## macros that check the AST that is passed to them. if n.kind notin k: error("Expected one of " & $k & ", got " & $n.kind, n) -proc newProc*(name = newEmptyNode(); params: openArray[NimNode] = [newEmptyNode()]; - body: NimNode = newStmtList(), procType = nnkProcDef): NimNode {.compileTime.} = +proc newProc*(name = newEmptyNode(); + params: openArray[NimNode] = [newEmptyNode()]; + body: NimNode = newStmtList(); + procType = nnkProcDef; + pragmas: NimNode = newEmptyNode()): NimNode {.compileTime.} = ## shortcut for creating a new proc ## ## The ``params`` array must start with the return type of the proc, ## followed by a list of IdentDefs which specify the params. if procType notin RoutineNodes: error("Expected one of " & $RoutineNodes & ", got " & $procType) + pragmas.expectKind({nnkEmpty, nnkPragma}) result = newNimNode(procType).add( name, newEmptyNode(), newEmptyNode(), newNimNode(nnkFormalParams).add(params), - newEmptyNode(), # pragmas + pragmas, newEmptyNode(), body) diff --git a/lib/core/runtime_v2.nim b/lib/core/runtime_v2.nim index f76fd2f3e..0165833b4 100644 --- a/lib/core/runtime_v2.nim +++ b/lib/core/runtime_v2.nim @@ -47,8 +47,9 @@ proc nimNewObj(size: int): pointer {.compilerRtl.} = when defined(nimscript): discard elif defined(useMalloc): - result = c_malloc(s) +! sizeof(RefHeader) - nimZeroMem(result, s) + var orig = c_malloc(s) + nimZeroMem(orig, s) + result = orig +! sizeof(RefHeader) else: result = alloc0(s) +! sizeof(RefHeader) inc allocs diff --git a/lib/core/seqs.nim b/lib/core/seqs.nim index a79f0c2f5..9c88040ba 100644 --- a/lib/core/seqs.nim +++ b/lib/core/seqs.nim @@ -104,6 +104,10 @@ proc newSeqPayload(cap, elemSize: int): pointer {.compilerRtl, raises: [].} = proc prepareSeqAdd(len: int; p: pointer; addlen, elemSize: int): pointer {. compilerRtl, noSideEffect, raises: [].} = {.noSideEffect.}: + template `+!`(p: pointer, s: int): pointer = + cast[pointer](cast[int](p) +% s) + + const headerSize = sizeof(int) + sizeof(Allocator) if addlen <= 0: result = p elif p == nil: @@ -112,14 +116,23 @@ proc prepareSeqAdd(len: int; p: pointer; addlen, elemSize: int): pointer {. # Note: this means we cannot support things that have internal pointers as # they get reallocated here. This needs to be documented clearly. var p = cast[ptr PayloadBase](p) - let allocator = if p.allocator == nil: getLocalAllocator() else: p.allocator let cap = max(resize(p.cap), len+addlen) - var q = cast[ptr PayloadBase](allocator.realloc(allocator, p, - sizeof(int) + sizeof(Allocator) + elemSize * p.cap, - sizeof(int) + sizeof(Allocator) + elemSize * cap)) - q.allocator = allocator - q.cap = cap - result = q + if p.allocator == nil: + let allocator = getLocalAllocator() + var q = cast[ptr PayloadBase](allocator.alloc(allocator, + headerSize + elemSize * cap)) + copyMem(q +! headerSize, p +! headerSize, len * elemSize) + q.allocator = allocator + q.cap = cap + result = q + else: + let allocator = p.allocator + var q = cast[ptr PayloadBase](allocator.realloc(allocator, p, + headerSize + elemSize * p.cap, + headerSize + elemSize * cap)) + q.allocator = allocator + q.cap = cap + result = q proc shrink*[T](x: var seq[T]; newLen: Natural) = mixin `=destroy` @@ -140,6 +153,20 @@ proc grow*[T](x: var seq[T]; newLen: Natural; value: T) = for i in oldLen .. newLen-1: xu.p.data[i] = value +proc add*[T](x: var seq[T]; value: sink T) {.magic: "AppendSeqElem", noSideEffect.} = + ## Generic proc for adding a data item `y` to a container `x`. + ## + ## For containers that have an order, `add` means *append*. New generic + ## containers should also call their adding proc `add` for consistency. + ## Generic code becomes much easier to write if the Nim naming scheme is + ## respected. + let oldLen = x.len + var xu = cast[ptr NimSeqV2[T]](addr x) + if xu.p == nil or xu.p.cap < oldLen+1: + xu.p = cast[typeof(xu.p)](prepareSeqAdd(oldLen, xu.p, 1, sizeof(T))) + xu.len = oldLen+1 + xu.p.data[oldLen] = value + proc setLen[T](s: var seq[T], newlen: Natural) = {.noSideEffect.}: if newlen < s.len: diff --git a/lib/impure/db_mysql.nim b/lib/impure/db_mysql.nim index 2fcb56202..9c24de33a 100644 --- a/lib/impure/db_mysql.nim +++ b/lib/impure/db_mysql.nim @@ -14,7 +14,7 @@ ## `db_postgres <db_postgres.html>`_. ## ## Parameter substitution -## ---------------------- +## ====================== ## ## All ``db_*`` modules support the same form of parameter substitution. ## That is, using the ``?`` (question mark) to signify the place where a @@ -25,10 +25,10 @@ ## ## ## Examples -## -------- +## ======== ## ## Opening a connection to a database -## ================================== +## ---------------------------------- ## ## .. code-block:: Nim ## import db_mysql @@ -36,7 +36,7 @@ ## db.close() ## ## Creating a table -## ================ +## ---------------- ## ## .. code-block:: Nim ## db.exec(sql"DROP TABLE IF EXISTS myTable") @@ -45,14 +45,14 @@ ## name varchar(50) not null)""")) ## ## Inserting data -## ============== +## -------------- ## ## .. code-block:: Nim ## db.exec(sql"INSERT INTO myTable (id, name) VALUES (0, ?)", ## "Dominik") ## ## Larger example -## ============== +## -------------- ## ## .. code-block:: Nim ## diff --git a/lib/impure/db_odbc.nim b/lib/impure/db_odbc.nim index b621d652d..4c33a5a82 100644 --- a/lib/impure/db_odbc.nim +++ b/lib/impure/db_odbc.nim @@ -20,7 +20,7 @@ ## `db_mysql <db_mysql.html>`_. ## ## Parameter substitution -## ---------------------- +## ====================== ## ## All ``db_*`` modules support the same form of parameter substitution. ## That is, using the ``?`` (question mark) to signify the place where a @@ -31,10 +31,10 @@ ## ## ## Examples -## -------- +## ======== ## ## Opening a connection to a database -## ================================== +## ---------------------------------- ## ## .. code-block:: Nim ## import db_odbc @@ -42,7 +42,7 @@ ## db.close() ## ## Creating a table -## ================ +## ---------------- ## ## .. code-block:: Nim ## db.exec(sql"DROP TABLE IF EXISTS myTable") @@ -51,14 +51,14 @@ ## name varchar(50) not null)""")) ## ## Inserting data -## ============== +## -------------- ## ## .. code-block:: Nim ## db.exec(sql"INSERT INTO myTable (id, name) VALUES (0, ?)", ## "Andreas") ## ## Large example -## ============= +## ------------- ## ## .. code-block:: Nim ## diff --git a/lib/impure/db_postgres.nim b/lib/impure/db_postgres.nim index 800673ca0..ec804072f 100644 --- a/lib/impure/db_postgres.nim +++ b/lib/impure/db_postgres.nim @@ -14,7 +14,7 @@ ## `db_mysql <db_mysql.html>`_. ## ## Parameter substitution -## ---------------------- +## ====================== ## ## All ``db_*`` modules support the same form of parameter substitution. ## That is, using the ``?`` (question mark) to signify the place where a @@ -38,10 +38,10 @@ ## 3) ## ## Examples -## -------- +## ======== ## ## Opening a connection to a database -## ================================== +## ---------------------------------- ## ## .. code-block:: Nim ## import db_postgres @@ -49,7 +49,7 @@ ## db.close() ## ## Creating a table -## ================ +## ---------------- ## ## .. code-block:: Nim ## db.exec(sql"DROP TABLE IF EXISTS myTable") @@ -58,7 +58,7 @@ ## name varchar(50) not null)""")) ## ## Inserting data -## ============== +## -------------- ## ## .. code-block:: Nim ## db.exec(sql"INSERT INTO myTable (id, name) VALUES (0, ?)", diff --git a/lib/impure/db_sqlite.nim b/lib/impure/db_sqlite.nim index 3910992bb..ad2be5882 100644 --- a/lib/impure/db_sqlite.nim +++ b/lib/impure/db_sqlite.nim @@ -14,7 +14,7 @@ ## `db_mysql <db_mysql.html>`_. ## ## Parameter substitution -## ---------------------- +## ====================== ## ## All ``db_*`` modules support the same form of parameter substitution. ## That is, using the ``?`` (question mark) to signify the place where a @@ -24,10 +24,10 @@ ## sql"INSERT INTO myTable (colA, colB, colC) VALUES (?, ?, ?)" ## ## Examples -## -------- +## ======== ## ## Opening a connection to a database -## ================================== +## ---------------------------------- ## ## .. code-block:: Nim ## import db_sqlite @@ -35,7 +35,7 @@ ## db.close() ## ## Creating a table -## ================ +## ---------------- ## ## .. code-block:: Nim ## db.exec(sql"DROP TABLE IF EXISTS myTable") @@ -44,14 +44,14 @@ ## name varchar(50) not null)""")) ## ## Inserting data -## ============== +## -------------- ## ## .. code-block:: Nim ## db.exec(sql"INSERT INTO myTable (id, name) VALUES (0, ?)", ## "Jack") ## ## Larger example -## ============== +## -------------- ## ## .. code-block:: nim ## diff --git a/lib/impure/nre.nim b/lib/impure/nre.nim index 5c5125ba1..7014af42a 100644 --- a/lib/impure/nre.nim +++ b/lib/impure/nre.nim @@ -6,18 +6,6 @@ # distribution, for details about the copyright. # - -from pcre import nil -import nre/private/util -import tables -from strutils import `%` -from math import ceil -import options -from unicode import runeLenAt - -export options - - ## What is NRE? ## ============ ## @@ -67,6 +55,15 @@ runnableExamples: let matchBounds = firstVowel.get().captureBounds[-1] doAssert matchBounds.a == 1 +from pcre import nil +import nre/private/util +import tables +from strutils import `%` +from math import ceil +import options +from unicode import runeLenAt + +export options # Type definitions {{{ type diff --git a/lib/js/asyncjs.nim b/lib/js/asyncjs.nim index 7439b66e1..219b1bed5 100644 --- a/lib/js/asyncjs.nim +++ b/lib/js/asyncjs.nim @@ -50,7 +50,7 @@ ## proc loadGame(name: string): Future[Game] {.async.} ## ## JavaScript compatibility -## ~~~~~~~~~~~~~~~~~~~~~~~~~ +## ======================== ## ## Nim currently generates `async/await` JavaScript code which is supported in modern ## EcmaScript and most modern versions of browsers, Node.js and Electron. diff --git a/lib/pure/asyncdispatch.nim b/lib/pure/asyncdispatch.nim index 08fa60dbc..2fb00d6a7 100644 --- a/lib/pure/asyncdispatch.nim +++ b/lib/pure/asyncdispatch.nim @@ -7,23 +7,6 @@ # distribution, for details about the copyright. # -include "system/inclrtl" - -import os, tables, strutils, times, heapqueue, lists, options, asyncstreams -import options, math -import asyncfutures except callSoon - -import nativesockets, net, deques - -export Port, SocketFlag -export asyncfutures except callSoon -export asyncstreams - -#{.injectStmt: newGcInvariant().} - -## AsyncDispatch -## ************* -## ## This module implements asynchronous IO. This includes a dispatcher, ## a ``Future`` type implementation, and an ``async`` macro which allows ## asynchronous code to be written in a synchronous style with the ``await`` @@ -82,7 +65,7 @@ export asyncstreams ## error), if there is no error however it returns the value of the future. ## ## Asynchronous procedures -## ----------------------- +## ======================= ## ## Asynchronous procedures remove the pain of working with callbacks. They do ## this by allowing you to write asynchronous code the same way as you would @@ -116,7 +99,7 @@ export asyncstreams ## exceptions in async procs. ## ## Handling Exceptions -## ~~~~~~~~~~~~~~~~~~~ +## ------------------- ## ## The most reliable way to handle exceptions is to use ``yield`` on a future ## then check the future's ``failed`` property. For example: @@ -142,7 +125,7 @@ export asyncstreams ## ## ## Discarding futures -## ------------------ +## ================== ## ## Futures should **never** be discarded. This is because they may contain ## errors. If you do not care for the result of a Future then you should @@ -151,17 +134,31 @@ export asyncstreams ## ``waitFor`` for that purpose. ## ## Examples -## -------- +## ======== ## ## For examples take a look at the documentation for the modules implementing ## asynchronous IO. A good place to start is the ## `asyncnet module <asyncnet.html>`_. ## ## Limitations/Bugs -## ---------------- +## ================ ## ## * The effect system (``raises: []``) does not work with async procedures. +include "system/inclrtl" + +import os, tables, strutils, times, heapqueue, lists, options, asyncstreams +import options, math +import asyncfutures except callSoon + +import nativesockets, net, deques + +export Port, SocketFlag +export asyncfutures except callSoon +export asyncstreams + +#{.injectStmt: newGcInvariant().} + # TODO: Check if yielded future is nil and throw a more meaningful exception type @@ -1827,4 +1824,4 @@ proc setEvent*(ev: AsyncEvent) {.deprecated.} = ## Set event ``ev`` to signaled state. ## ## **Deprecated since v0.18.0:** Use ``trigger`` instead. - ev.trigger() \ No newline at end of file + ev.trigger() diff --git a/lib/pure/asyncftpclient.nim b/lib/pure/asyncftpclient.nim index d28e9fb57..3e741c304 100644 --- a/lib/pure/asyncftpclient.nim +++ b/lib/pure/asyncftpclient.nim @@ -16,7 +16,7 @@ ## * Navigation through the FTP server's directories. ## ## Connecting to an FTP server -## ------------------------ +## =========================== ## ## In order to begin any sort of transfer of files you must first ## connect to an FTP server. You can do so with the ``connect`` procedure. @@ -34,7 +34,7 @@ ## client will be connected after the ``await ftp.connect()`` call. ## ## Uploading a new file -## -------------------- +## ==================== ## ## After a connection is made you can use the ``store`` procedure to upload ## a new file to the FTP server. Make sure to check you are in the correct @@ -53,7 +53,7 @@ ## waitFor(main()) ## ## Checking the progress of a file transfer -## ---------------------------------------- +## ======================================== ## ## The progress of either a file upload or a file download can be checked ## by specifying a ``onProgressChanged`` procedure to the ``store`` or diff --git a/lib/pure/asynchttpserver.nim b/lib/pure/asynchttpserver.nim index 4aed42683..4045609a4 100644 --- a/lib/pure/asynchttpserver.nim +++ b/lib/pure/asynchttpserver.nim @@ -14,9 +14,8 @@ ## application you should use a reverse proxy (for example nginx) instead of ## allowing users to connect directly to this server. ## -## ## Basic usage -## ----------- +## =========== ## ## This example will create an HTTP server on port 8080. The server will ## respond to all requests with a ``200 OK`` response code and "Hello World" diff --git a/lib/pure/asyncmacro.nim b/lib/pure/asyncmacro.nim index 23ddf4777..d2750f728 100644 --- a/lib/pure/asyncmacro.nim +++ b/lib/pure/asyncmacro.nim @@ -7,8 +7,6 @@ # distribution, for details about the copyright. # -## AsyncMacro -## ************* ## `asyncdispatch` module depends on the `asyncmacro` module to work properly. import macros, strutils, asyncfutures diff --git a/lib/pure/asyncnet.nim b/lib/pure/asyncnet.nim index 94429a657..d7cb5a18a 100644 --- a/lib/pure/asyncnet.nim +++ b/lib/pure/asyncnet.nim @@ -11,7 +11,7 @@ ## asynchronous dispatcher defined in the ``asyncdispatch`` module. ## ## Asynchronous IO in Nim -## ---------------------- +## ====================== ## ## Async IO in Nim consists of multiple layers (from highest to lowest): ## @@ -49,7 +49,7 @@ ## over all the layers, providing some extra features such as buffering. ## ## SSL -## ---- +## === ## ## SSL can be enabled by compiling with the ``-d:ssl`` flag. ## @@ -58,10 +58,10 @@ ## the newly created SSL context to get an SSL socket. ## ## Examples -## -------- +## ======== ## ## Chat server -## ^^^^^^^^^^^ +## ----------- ## ## The following example demonstrates a simple chat server. ## diff --git a/lib/pure/base64.nim b/lib/pure/base64.nim index 427f93926..5985592ff 100644 --- a/lib/pure/base64.nim +++ b/lib/pure/base64.nim @@ -15,7 +15,6 @@ ## bytes (i.e., a total of 24 bits) can therefore be represented by ## four 6-bit Base64 digits. ## -## ## Basic usage ## =========== ## diff --git a/lib/pure/collections/hashcommon.nim b/lib/pure/collections/hashcommon.nim new file mode 100644 index 000000000..bbc6d401d --- /dev/null +++ b/lib/pure/collections/hashcommon.nim @@ -0,0 +1,68 @@ +# +# +# Nim's Runtime Library +# (c) Copyright 2019 Andreas Rumpf +# +# See the file "copying.txt", included in this +# distribution, for details about the copyright. +# + +# An ``include`` file which contains common code for +# hash sets and tables. + +const + growthFactor = 2 + +when not defined(nimHasDefault): + template default[T](t: typedesc[T]): T = + var v: T + v + +# hcode for real keys cannot be zero. hcode==0 signifies an empty slot. These +# two procs retain clarity of that encoding without the space cost of an enum. +proc isEmpty(hcode: Hash): bool {.inline.} = + result = hcode == 0 + +proc isFilled(hcode: Hash): bool {.inline.} = + result = hcode != 0 + +proc nextTry(h, maxHash: Hash): Hash {.inline.} = + result = (h + 1) and maxHash + +proc mustRehash(length, counter: int): bool {.inline.} = + assert(length > counter) + result = (length * 2 < counter * 3) or (length - counter < 4) + +template rawGetKnownHCImpl() {.dirty.} = + if t.dataLen == 0: + return -1 + var h: Hash = hc and maxHash(t) # start with real hash value + while isFilled(t.data[h].hcode): + # Compare hc THEN key with boolean short circuit. This makes the common case + # zero ==key's for missing (e.g.inserts) and exactly one ==key for present. + # It does slow down succeeding lookups by one extra Hash cmp&and..usually + # just a few clock cycles, generally worth it for any non-integer-like A. + if t.data[h].hcode == hc and t.data[h].key == key: + return h + h = nextTry(h, maxHash(t)) + result = -1 - h # < 0 => MISSING; insert idx = -1 - result + +proc rawGetKnownHC[X, A](t: X, key: A, hc: Hash): int {.inline.} = + rawGetKnownHCImpl() + +template genHashImpl(key, hc: typed) = + hc = hash(key) + if hc == 0: # This almost never taken branch should be very predictable. + hc = 314159265 # Value doesn't matter; Any non-zero favorite is fine. + +template genHash(key: typed): Hash = + var res: Hash + genHashImpl(key, res) + res + +template rawGetImpl() {.dirty.} = + genHashImpl(key, hc) + rawGetKnownHCImpl() + +proc rawGet[X, A](t: X, key: A, hc: var Hash): int {.inline.} = + rawGetImpl() diff --git a/lib/pure/collections/lists.nim b/lib/pure/collections/lists.nim index 1fd32c9fa..2278be218 100644 --- a/lib/pure/collections/lists.nim +++ b/lib/pure/collections/lists.nim @@ -80,14 +80,15 @@ type DoublyLinkedNodeObj*[T] = object ## A node a doubly linked list consists of. ## ## It consists of a `value` field, and pointers to `next` and `prev`. - next*, prev*: ref DoublyLinkedNodeObj[T] + next*: <//>(ref DoublyLinkedNodeObj[T]) + prev*: ref DoublyLinkedNodeObj[T] value*: T DoublyLinkedNode*[T] = ref DoublyLinkedNodeObj[T] SinglyLinkedNodeObj*[T] = object ## A node a singly linked list consists of. ## ## It consists of a `value` field, and a pointer to `next`. - next*: ref SinglyLinkedNodeObj[T] + next*: <//>(ref SinglyLinkedNodeObj[T]) value*: T SinglyLinkedNode*[T] = ref SinglyLinkedNodeObj[T] @@ -95,19 +96,22 @@ type ## ## Use `initSinglyLinkedList proc <#initSinglyLinkedList>`_ to create ## a new empty list. - head*, tail*: SinglyLinkedNode[T] + head*: <//>(SinglyLinkedNode[T]) + tail*: SinglyLinkedNode[T] DoublyLinkedList*[T] = object ## A doubly linked list. ## ## Use `initDoublyLinkedList proc <#initDoublyLinkedList>`_ to create ## a new empty list. - head*, tail*: DoublyLinkedNode[T] + head*: <//>(DoublyLinkedNode[T]) + tail*: DoublyLinkedNode[T] SinglyLinkedRing*[T] = object ## A singly linked ring. ## ## Use `initSinglyLinkedRing proc <#initSinglyLinkedRing>`_ to create ## a new empty ring. - head*, tail*: SinglyLinkedNode[T] + head*: <//>(SinglyLinkedNode[T]) + tail*: SinglyLinkedNode[T] DoublyLinkedRing*[T] = object ## A doubly linked ring. ## @@ -147,7 +151,7 @@ proc initDoublyLinkedRing*[T](): DoublyLinkedRing[T] = var a = initDoublyLinkedRing[int]() discard -proc newDoublyLinkedNode*[T](value: T): DoublyLinkedNode[T] = +proc newDoublyLinkedNode*[T](value: T): <//>(DoublyLinkedNode[T]) = ## Creates a new doubly linked node with the given `value`. runnableExamples: var n = newDoublyLinkedNode[int](5) @@ -156,7 +160,7 @@ proc newDoublyLinkedNode*[T](value: T): DoublyLinkedNode[T] = new(result) result.value = value -proc newSinglyLinkedNode*[T](value: T): SinglyLinkedNode[T] = +proc newSinglyLinkedNode*[T](value: T): <//>(SinglyLinkedNode[T]) = ## Creates a new singly linked node with the given `value`. runnableExamples: var n = newSinglyLinkedNode[int](5) diff --git a/lib/pure/collections/sequtils.nim b/lib/pure/collections/sequtils.nim index 253340379..e39c1fb80 100644 --- a/lib/pure/collections/sequtils.nim +++ b/lib/pure/collections/sequtils.nim @@ -949,6 +949,14 @@ macro mapLiterals*(constructor, op: untyped; ## works for nested tuples of arrays of sets etc. result = mapLitsImpl(constructor, op, nested.boolVal) +iterator items*[T](xs: iterator: T): T = + ## iterates over each element yielded by a closure iterator. This may + ## not seem particularly useful on its own, but this allows closure + ## iterators to be used by the the mapIt, filterIt, allIt, anyIt, etc. + ## templates. + for x in xs(): + yield x + when isMainModule: import strutils from algorithm import sorted @@ -1382,5 +1390,13 @@ when isMainModule: doAssert outp == @[@["a", "b"], @["c", "d"]] + block: + proc iter(len: int): auto = + result = iterator(): int = + for i in 0..<len: + yield i + + doAssert: iter(3).mapIt(2*it).foldl(a + b) == 6 + when not defined(testing): echo "Finished doc tests" diff --git a/lib/pure/collections/setimpl.nim b/lib/pure/collections/setimpl.nim new file mode 100644 index 000000000..eb131b540 --- /dev/null +++ b/lib/pure/collections/setimpl.nim @@ -0,0 +1,153 @@ +# +# +# Nim's Runtime Library +# (c) Copyright 2019 Andreas Rumpf +# +# See the file "copying.txt", included in this +# distribution, for details about the copyright. +# + +# An ``include`` file for the different hash set implementations. + + +template maxHash(t): untyped = high(t.data) +template dataLen(t): untyped = len(t.data) + +include hashcommon + +template initImpl(s: typed, size: int) = + assert isPowerOfTwo(size) + when s is OrderedSet: + s.first = -1 + s.last = -1 + s.counter = 0 + newSeq(s.data, size) + +template rawInsertImpl() {.dirty.} = + if data.len == 0: + initImpl(s, defaultInitialSize) + data[h].key = key + data[h].hcode = hc + +proc rawInsert[A](s: var HashSet[A], data: var KeyValuePairSeq[A], key: A, + hc: Hash, h: Hash) = + rawInsertImpl() + +proc enlarge[A](s: var HashSet[A]) = + var n: KeyValuePairSeq[A] + newSeq(n, len(s.data) * growthFactor) + swap(s.data, n) # n is now old seq + for i in countup(0, high(n)): + if isFilled(n[i].hcode): + var j = -1 - rawGetKnownHC(s, n[i].key, n[i].hcode) + rawInsert(s, s.data, n[i].key, n[i].hcode, j) + +template inclImpl() {.dirty.} = + if s.data.len == 0: + initImpl(s, defaultInitialSize) + var hc: Hash + var index = rawGet(s, key, hc) + if index < 0: + if mustRehash(len(s.data), s.counter): + enlarge(s) + index = rawGetKnownHC(s, key, hc) + rawInsert(s, s.data, key, hc, -1 - index) + inc(s.counter) + +template containsOrInclImpl() {.dirty.} = + if s.data.len == 0: + initImpl(s, defaultInitialSize) + var hc: Hash + var index = rawGet(s, key, hc) + if index >= 0: + result = true + else: + if mustRehash(len(s.data), s.counter): + enlarge(s) + index = rawGetKnownHC(s, key, hc) + rawInsert(s, s.data, key, hc, -1 - index) + inc(s.counter) + +template doWhile(a, b) = + while true: + b + if not a: break + +proc exclImpl[A](s: var HashSet[A], key: A) : bool {. inline .} = + var hc: Hash + var i = rawGet(s, key, hc) + var msk = high(s.data) + result = true + + if i >= 0: + result = false + dec(s.counter) + while true: # KnuthV3 Algo6.4R adapted for i=i+1 instead of i=i-1 + var j = i # The correctness of this depends on (h+1) in nextTry, + var r = j # though may be adaptable to other simple sequences. + s.data[i].hcode = 0 # mark current EMPTY + s.data[i].key = default(type(s.data[i].key)) + doWhile((i >= r and r > j) or (r > j and j > i) or (j > i and i >= r)): + i = (i + 1) and msk # increment mod table size + if isEmpty(s.data[i].hcode): # end of collision cluster; So all done + return + r = s.data[i].hcode and msk # "home" location of key@i + shallowCopy(s.data[j], s.data[i]) # data[i] will be marked EMPTY next loop + +template dollarImpl() {.dirty.} = + result = "{" + for key in items(s): + if result.len > 1: result.add(", ") + result.addQuoted(key) + result.add("}") + + + +# --------------------------- OrderedSet ------------------------------ + +proc rawGet[A](t: OrderedSet[A], key: A, hc: var Hash): int {.inline.} = + rawGetImpl() + +proc rawInsert[A](s: var OrderedSet[A], data: var OrderedKeyValuePairSeq[A], + key: A, hc: Hash, h: Hash) = + rawInsertImpl() + data[h].next = -1 + if s.first < 0: s.first = h + if s.last >= 0: data[s.last].next = h + s.last = h + +proc enlarge[A](s: var OrderedSet[A]) = + var n: OrderedKeyValuePairSeq[A] + newSeq(n, len(s.data) * growthFactor) + var h = s.first + s.first = -1 + s.last = -1 + swap(s.data, n) + while h >= 0: + var nxt = n[h].next + if isFilled(n[h].hcode): + var j = -1 - rawGetKnownHC(s, n[h].key, n[h].hcode) + rawInsert(s, s.data, n[h].key, n[h].hcode, j) + h = nxt + +proc exclImpl[A](s: var OrderedSet[A], key: A) : bool {.inline.} = + if len(s.data) == 0: + return true + var n: OrderedKeyValuePairSeq[A] + newSeq(n, len(s.data)) + var h = s.first + s.first = -1 + s.last = -1 + swap(s.data, n) + let hc = genHash(key) + result = true + while h >= 0: + var nxt = n[h].next + if isFilled(n[h].hcode): + if n[h].hcode == hc and n[h].key == key: + dec s.counter + result = false + else: + var j = -1 - rawGetKnownHC(s, n[h].key, n[h].hcode) + rawInsert(s, s.data, n[h].key, n[h].hcode, j) + h = nxt diff --git a/lib/pure/collections/sets.nim b/lib/pure/collections/sets.nim index 29bf31f96..86a72533e 100644 --- a/lib/pure/collections/sets.nim +++ b/lib/pure/collections/sets.nim @@ -26,7 +26,7 @@ ## `symmetric difference <#symmetricDifference,HashSet[A],HashSet[A]>`_ ## ## .. code-block:: -## echo toHashSet([9, 5, 1]) # {9, 1, 5} +## echo toHashSet([9, 5, 1]) # {9, 1, 5} ## echo toOrderedSet([9, 5, 1]) # {9, 5, 1} ## ## let @@ -69,148 +69,32 @@ type data: KeyValuePairSeq[A] counter: int +type + OrderedKeyValuePair[A] = tuple[ + hcode: Hash, next: int, key: A] + OrderedKeyValuePairSeq[A] = seq[OrderedKeyValuePair[A]] + OrderedSet* {.myShallow.} [A] = object ## \ + ## A generic hash set that remembers insertion order. + ## + ## Use `init proc <#init,OrderedSet[A],int>`_ or `initOrderedSet proc + ## <#initOrderedSet,int>`_ before calling other procs on it. + data: OrderedKeyValuePairSeq[A] + counter, first, last: int -# ---------------------- helpers ----------------------------------- - -const growthFactor = 2 - -when not defined(nimHasDefault): - template default[T](t: typedesc[T]): T = - ## Used by clear methods to get a default value. - var v: T - v - -# hcode for real keys cannot be zero. hcode==0 signifies an empty slot. These -# two procs retain clarity of that encoding without the space cost of an enum. -proc isEmpty(hcode: Hash): bool {.inline.} = - result = hcode == 0 - -proc isFilled(hcode: Hash): bool {.inline.} = - result = hcode != 0 - -proc nextTry(h, maxHash: Hash): Hash {.inline.} = - result = (h + 1) and maxHash - -template rawGetKnownHCImpl() {.dirty.} = - var h: Hash = hc and high(s.data) # start with real hash value - while isFilled(s.data[h].hcode): - # Compare hc THEN key with boolean short circuit. This makes the common case - # zero ==key's for missing (e.g.inserts) and exactly one ==key for present. - # It does slow down succeeding lookups by one extra Hash cmp&and..usually - # just a few clock cycles, generally worth it for any non-integer-like A. - if s.data[h].hcode == hc and s.data[h].key == key: # compare hc THEN key - return h - h = nextTry(h, high(s.data)) - result = -1 - h # < 0 => MISSING; insert idx = -1 - result - -template genHash(key: typed): Hash = - var hc = hash(key) - if hc == 0: # This almost never taken branch should be very predictable. - hc = 314159265 # Value doesn't matter; Any non-zero favorite is fine. - hc - -template rawGetImpl() {.dirty.} = - hc = genHash(key) - rawGetKnownHCImpl() - -template rawInsertImpl() {.dirty.} = - data[h].key = key - data[h].hcode = hc - -proc rawGetKnownHC[A](s: HashSet[A], key: A, hc: Hash): int {.inline.} = - rawGetKnownHCImpl() - -proc rawGet[A](s: HashSet[A], key: A, hc: var Hash): int {.inline.} = - rawGetImpl() - -proc rawInsert[A](s: var HashSet[A], data: var KeyValuePairSeq[A], key: A, - hc: Hash, h: Hash) = - rawInsertImpl() - -proc enlarge[A](s: var HashSet[A]) = - var n: KeyValuePairSeq[A] - newSeq(n, len(s.data) * growthFactor) - swap(s.data, n) # n is now old seq - for i in countup(0, high(n)): - if isFilled(n[i].hcode): - var j = -1 - rawGetKnownHC(s, n[i].key, n[i].hcode) - rawInsert(s, s.data, n[i].key, n[i].hcode, j) - -template inclImpl() {.dirty.} = - var hc: Hash - var index = rawGet(s, key, hc) - if index < 0: - if mustRehash(len(s.data), s.counter): - enlarge(s) - index = rawGetKnownHC(s, key, hc) - rawInsert(s, s.data, key, hc, -1 - index) - inc(s.counter) - -template containsOrInclImpl() {.dirty.} = - var hc: Hash - var index = rawGet(s, key, hc) - if index >= 0: - result = true - else: - if mustRehash(len(s.data), s.counter): - enlarge(s) - index = rawGetKnownHC(s, key, hc) - rawInsert(s, s.data, key, hc, -1 - index) - inc(s.counter) - -template doWhile(a, b) = - while true: - b - if not a: break - -proc exclImpl[A](s: var HashSet[A], key: A) : bool {. inline .} = - assert s.isValid, "The set needs to be initialized." - var hc: Hash - var i = rawGet(s, key, hc) - var msk = high(s.data) - result = true +const + defaultInitialSize* = 64 - if i >= 0: - result = false - dec(s.counter) - while true: # KnuthV3 Algo6.4R adapted for i=i+1 instead of i=i-1 - var j = i # The correctness of this depends on (h+1) in nextTry, - var r = j # though may be adaptable to other simple sequences. - s.data[i].hcode = 0 # mark current EMPTY - s.data[i].key = default(type(s.data[i].key)) - doWhile((i >= r and r > j) or (r > j and j > i) or (j > i and i >= r)): - i = (i + 1) and msk # increment mod table size - if isEmpty(s.data[i].hcode): # end of collision cluster; So all done - return - r = s.data[i].hcode and msk # "home" location of key@i - shallowCopy(s.data[j], s.data[i]) # data[i] will be marked EMPTY next loop - -proc mustRehash(length, counter: int): bool {.inline.} = - assert(length > counter) - result = (length * 2 < counter * 3) or (length - counter < 4) - -template dollarImpl() {.dirty.} = - result = "{" - for key in items(s): - if result.len > 1: result.add(", ") - result.addQuoted(key) - result.add("}") +include setimpl proc rightSize*(count: Natural): int {.inline.} - - - - - - # --------------------------------------------------------------------- # ------------------------------ HashSet ------------------------------ # --------------------------------------------------------------------- -proc init*[A](s: var HashSet[A], initialSize=64) = +proc init*[A](s: var HashSet[A], initialSize = defaultInitialSize) = ## Initializes a hash set. ## ## The `initialSize` parameter needs to be a power of two (default: 64). @@ -218,9 +102,8 @@ proc init*[A](s: var HashSet[A], initialSize=64) = ## `math.nextPowerOfTwo proc <math.html#nextPowerOfTwo>`_ or `rightSize proc ## <#rightSize,Natural>`_ from this module. ## - ## All set variables must be initialized before - ## use with other procs from this module, with the exception of `isValid proc - ## <#isValid,HashSet[A]>`_ and `len() <#len,HashSet[A]>`_. + ## Starting from Nim v0.20, sets are initialized by default and it is + ## not necessary to call this function explicitly. ## ## You can call this proc on a previously initialized hash set, which will ## discard all its values. This might be more convenient than iterating over @@ -235,42 +118,26 @@ proc init*[A](s: var HashSet[A], initialSize=64) = init(a) assert a.isValid - assert isPowerOfTwo(initialSize) - s.counter = 0 - newSeq(s.data, initialSize) + initImpl(s, initialSize) -proc initHashSet*[A](initialSize=64): HashSet[A] = +proc initHashSet*[A](initialSize = defaultInitialSize): HashSet[A] = ## Wrapper around `init proc <#init,HashSet[A],int>`_ for initialization of ## hash sets. ## ## Returns an empty hash set you can assign directly in ``var`` blocks in a ## single line. ## + ## Starting from Nim v0.20, sets are initialized by default and it is + ## not necessary to call this function explicitly. + ## ## See also: ## * `toHashSet proc <#toHashSet,openArray[A]>`_ runnableExamples: var a = initHashSet[int]() - assert a.isValid a.incl(3) assert len(a) == 1 - result.init(initialSize) -proc isValid*[A](s: HashSet[A]): bool = - ## Returns `true` if the set has been initialized (with `initHashSet proc - ## <#initHashSet,int>`_ or `init proc <#init,HashSet[A],int>`_). - ## - ## Most operations over an uninitialized set will crash at runtime and - ## `assert <system.html#assert>`_ in debug builds. You can use this proc in - ## your own procs to verify that sets passed to your procs are correctly - ## initialized. - ## - ## **Examples:** - ## - ## .. code-block :: - ## proc savePreferences(options: HashSet[string]) = - ## assert options.isValid, "Pass an initialized set!" - ## # Do stuff here, may crash in release builds! - result = s.data.len > 0 + result.init(initialSize) proc `[]`*[A](s: var HashSet[A], key: A): var A = ## Returns the element that is actually stored in `s` which has the same @@ -278,7 +145,6 @@ proc `[]`*[A](s: var HashSet[A], key: A): var A = ## ## This is useful when one overloaded `hash` and `==` but still needs ## reference semantics for sharing. - assert s.isValid, "The set needs to be initialized." var hc: Hash var index = rawGet(s, key, hc) if index >= 0: result = s.data[index].key @@ -305,7 +171,6 @@ proc contains*[A](s: HashSet[A], key: A): bool = assert values.contains(2) assert 2 in values - assert s.isValid, "The set needs to be initialized." var hc: Hash var index = rawGet(s, key, hc) result = index >= 0 @@ -325,7 +190,6 @@ proc incl*[A](s: var HashSet[A], key: A) = values.incl(2) assert values.len == 1 - assert s.isValid, "The set needs to be initialized." inclImpl() proc incl*[A](s: var HashSet[A], other: HashSet[A]) = @@ -344,8 +208,6 @@ proc incl*[A](s: var HashSet[A], other: HashSet[A]) = values.incl(others) assert values.len == 5 - assert s.isValid, "The set `s` needs to be initialized." - assert other.isValid, "The set `other` needs to be initialized." for item in other: incl(s, item) proc toHashSet*[A](keys: openArray[A]): HashSet[A] = @@ -368,14 +230,6 @@ proc toHashSet*[A](keys: openArray[A]): HashSet[A] = result = initHashSet[A](rightSize(keys.len)) for key in items(keys): result.incl(key) -proc initSet*[A](initialSize=64): HashSet[A] {.deprecated: - "Deprecated since v0.20, use `initHashSet`"} = initHashSet[A](initialSize) - ## Deprecated since v0.20, use `initHashSet`. - -proc toSet*[A](keys: openArray[A]): HashSet[A] {.deprecated: - "Deprecated since v0.20, use `toHashSet`"} = toHashSet[A](keys) - ## Deprecated since v0.20, use `toHashSet`. - iterator items*[A](s: HashSet[A]): A = ## Iterates over elements of the set `s`. ## @@ -395,8 +249,7 @@ iterator items*[A](s: HashSet[A]): A = ## assert a.len == 2 ## echo b ## # --> {(a: 1, b: 3), (a: 0, b: 4)} - assert s.isValid, "The set needs to be initialized." - for h in 0..high(s.data): + for h in 0 .. high(s.data): if isFilled(s.data[h].hcode): yield s.data[h].key proc containsOrIncl*[A](s: var HashSet[A], key: A): bool = @@ -417,7 +270,6 @@ proc containsOrIncl*[A](s: var HashSet[A], key: A): bool = assert values.containsOrIncl(2) == true assert values.containsOrIncl(3) == false - assert s.isValid, "The set needs to be initialized." containsOrInclImpl() proc excl*[A](s: var HashSet[A], key: A) = @@ -434,6 +286,7 @@ proc excl*[A](s: var HashSet[A], key: A) = s.excl(2) s.excl(2) assert s.len == 3 + discard exclImpl(s, key) proc excl*[A](s: var HashSet[A], other: HashSet[A]) = @@ -453,8 +306,6 @@ proc excl*[A](s: var HashSet[A], other: HashSet[A]) = assert len(numbers) == 3 ## numbers == {1, 3, 5} - assert s.isValid, "The set `s` needs to be initialized." - assert other.isValid, "The set `other` needs to be initialized." for item in other: discard exclImpl(s, item) proc missingOrExcl*[A](s: var HashSet[A], key: A): bool = @@ -474,6 +325,7 @@ proc missingOrExcl*[A](s: var HashSet[A], key: A): bool = assert s.missingOrExcl(4) == true assert s.missingOrExcl(6) == false assert s.missingOrExcl(6) == true + exclImpl(s, key) proc pop*[A](s: var HashSet[A]): A = @@ -489,7 +341,7 @@ proc pop*[A](s: var HashSet[A]): A = assert s.pop == 2 doAssertRaises(KeyError, echo s.pop) - for h in 0..high(s.data): + for h in 0 .. high(s.data): if isFilled(s.data[h].hcode): result = s.data[h].key excl(s, result) @@ -510,7 +362,7 @@ proc clear*[A](s: var HashSet[A]) = assert len(s) == 0 s.counter = 0 - for i in 0..<s.data.len: + for i in 0 ..< s.data.len: s.data[i].hcode = 0 s.data[i].key = default(type(s.data[i].key)) @@ -525,6 +377,7 @@ proc len*[A](s: HashSet[A]): int = assert len(a) == 0 let s = toHashSet([3, 5, 7]) assert len(s) == 3 + result = s.counter proc card*[A](s: HashSet[A]): int = @@ -554,8 +407,6 @@ proc union*[A](s1, s2: HashSet[A]): HashSet[A] = c = union(a, b) assert c == toHashSet(["a", "b", "c"]) - assert s1.isValid, "The set `s1` needs to be initialized." - assert s2.isValid, "The set `s2` needs to be initialized." result = s1 incl(result, s2) @@ -579,9 +430,7 @@ proc intersection*[A](s1, s2: HashSet[A]): HashSet[A] = c = intersection(a, b) assert c == toHashSet(["b"]) - assert s1.isValid, "The set `s1` needs to be initialized." - assert s2.isValid, "The set `s2` needs to be initialized." - result = initHashSet[A](min(s1.data.len, s2.data.len)) + result = initHashSet[A](max(min(s1.data.len, s2.data.len), 2)) for item in s1: if item in s2: incl(result, item) @@ -604,8 +453,6 @@ proc difference*[A](s1, s2: HashSet[A]): HashSet[A] = c = difference(a, b) assert c == toHashSet(["a"]) - assert s1.isValid, "The set `s1` needs to be initialized." - assert s2.isValid, "The set `s2` needs to be initialized." result = initHashSet[A]() for item in s1: if not contains(s2, item): @@ -631,8 +478,6 @@ proc symmetricDifference*[A](s1, s2: HashSet[A]): HashSet[A] = c = symmetricDifference(a, b) assert c == toHashSet(["a", "c"]) - assert s1.isValid, "The set `s1` needs to be initialized." - assert s2.isValid, "The set `s2` needs to be initialized." result = s1 for item in s2: if containsOrIncl(result, item): excl(result, item) @@ -663,8 +508,6 @@ proc disjoint*[A](s1, s2: HashSet[A]): bool = assert disjoint(a, b) == false assert disjoint(a, b - a) == true - assert s1.isValid, "The set `s1` needs to be initialized." - assert s2.isValid, "The set `s2` needs to be initialized." for item in s1: if item in s2: return false return true @@ -681,6 +524,7 @@ proc `<`*[A](s, t: HashSet[A]): bool = c = intersection(a, b) assert c < a and c < b assert(not (a < a)) + s.counter != t.counter and s <= t proc `<=`*[A](s, t: HashSet[A]): bool = @@ -711,6 +555,7 @@ proc `==`*[A](s, t: HashSet[A]): bool = a = toHashSet([1, 2]) b = toHashSet([2, 1]) assert a == b + s.counter == t.counter and s <= t proc map*[A, B](data: HashSet[A], op: proc (x: A): B {.closure.}): HashSet[B] = @@ -729,8 +574,7 @@ proc map*[A, B](data: HashSet[A], op: proc (x: A): B {.closure.}): HashSet[B] = proc hash*[A](s: HashSet[A]): Hash = ## Hashing of HashSet. - assert s.isValid, "The set needs to be initialized." - for h in 0..high(s.data): + for h in 0 .. high(s.data): result = result xor s.data[h].hcode result = !$result @@ -747,7 +591,6 @@ proc `$`*[A](s: HashSet[A]): string = ## # --> {2, 4, 5} ## echo toHashSet(["no", "esc'aping", "is \" provided"]) ## # --> {no, esc'aping, is " provided} - assert s.isValid, "The set needs to be initialized." dollarImpl() proc rightSize*(count: Natural): int {.inline.} = @@ -760,8 +603,28 @@ proc rightSize*(count: Natural): int {.inline.} = result = nextPowerOfTwo(count * 3 div 2 + 4) +proc initSet*[A](initialSize = defaultInitialSize): HashSet[A] {.deprecated: + "Deprecated since v0.20, use `initHashSet`"} = initHashSet[A](initialSize) + ## Deprecated since v0.20, use `initHashSet <#initHashSet,int>`_. +proc toSet*[A](keys: openArray[A]): HashSet[A] {.deprecated: + "Deprecated since v0.20, use `toHashSet`"} = toHashSet[A](keys) + ## Deprecated since v0.20, use `toHashSet <#toHashSet,openArray[A]>`_. +proc isValid*[A](s: HashSet[A]): bool {.deprecated: + "Deprecated since v0.20; sets are initialized by default"} = + ## **Deprecated since v0.20; sets are initialized by default** + ## + ## Returns `true` if the set has been initialized (with `initHashSet proc + ## <#initHashSet,int>`_ or `init proc <#init,HashSet[A],int>`_). + ## + ## **Examples:** + ## + ## .. code-block :: + ## proc savePreferences(options: HashSet[string]) = + ## assert options.isValid, "Pass an initialized set!" + ## # Do stuff here, may crash in release builds! + result = s.data.len > 0 @@ -769,89 +632,19 @@ proc rightSize*(count: Natural): int {.inline.} = # --------------------------- OrderedSet ------------------------------ # --------------------------------------------------------------------- -type - OrderedKeyValuePair[A] = tuple[ - hcode: Hash, next: int, key: A] - OrderedKeyValuePairSeq[A] = seq[OrderedKeyValuePair[A]] - OrderedSet* {.myShallow.} [A] = object ## \ - ## A generic hash set that remembers insertion order. - ## - ## Use `init proc <#init,OrderedSet[A],int>`_ or `initOrderedSet proc - ## <#initOrderedSet,int>`_ before calling other procs on it. - data: OrderedKeyValuePairSeq[A] - counter, first, last: int - - -# ---------------------- helpers ----------------------------------- - template forAllOrderedPairs(yieldStmt: untyped) {.dirty.} = - var h = s.first - var idx = 0 - while h >= 0: - var nxt = s.data[h].next - if isFilled(s.data[h].hcode): - yieldStmt - inc(idx) - h = nxt - -proc rawGetKnownHC[A](s: OrderedSet[A], key: A, hc: Hash): int {.inline.} = - rawGetKnownHCImpl() - -proc rawGet[A](s: OrderedSet[A], key: A, hc: var Hash): int {.inline.} = - rawGetImpl() - -proc rawInsert[A](s: var OrderedSet[A], data: var OrderedKeyValuePairSeq[A], - key: A, hc: Hash, h: Hash) = - rawInsertImpl() - data[h].next = -1 - if s.first < 0: s.first = h - if s.last >= 0: data[s.last].next = h - s.last = h - -proc enlarge[A](s: var OrderedSet[A]) = - var n: OrderedKeyValuePairSeq[A] - newSeq(n, len(s.data) * growthFactor) - var h = s.first - s.first = -1 - s.last = -1 - swap(s.data, n) - while h >= 0: - var nxt = n[h].next - if isFilled(n[h].hcode): - var j = -1 - rawGetKnownHC(s, n[h].key, n[h].hcode) - rawInsert(s, s.data, n[h].key, n[h].hcode, j) - h = nxt - -proc isValid*[A](s: OrderedSet[A]): bool - -proc exclImpl[A](s: var OrderedSet[A], key: A) : bool {. inline .} = - assert s.isValid, "The set needs to be initialized." - var n: OrderedKeyValuePairSeq[A] - newSeq(n, len(s.data)) - var h = s.first - s.first = -1 - s.last = -1 - swap(s.data, n) - let hc = genHash(key) - result = true - while h >= 0: - var nxt = n[h].next - if isFilled(n[h].hcode): - if n[h].hcode == hc and n[h].key == key: - dec s.counter - result = false - else: - var j = -1 - rawGetKnownHC(s, n[h].key, n[h].hcode) - rawInsert(s, s.data, n[h].key, n[h].hcode, j) - h = nxt - - - -# ----------------------------------------------------------------------- - - - -proc init*[A](s: var OrderedSet[A], initialSize=64) = + if s.data.len > 0: + var h = s.first + var idx = 0 + while h >= 0: + var nxt = s.data[h].next + if isFilled(s.data[h].hcode): + yieldStmt + inc(idx) + h = nxt + + +proc init*[A](s: var OrderedSet[A], initialSize = defaultInitialSize) = ## Initializes an ordered hash set. ## ## The `initialSize` parameter needs to be a power of two (default: 64). @@ -859,9 +652,8 @@ proc init*[A](s: var OrderedSet[A], initialSize=64) = ## `math.nextPowerOfTwo proc <math.html#nextPowerOfTwo>`_ or `rightSize proc ## <#rightSize,Natural>`_ from this module. ## - ## All set variables must be initialized before - ## use with other procs from this module, with the exception of `isValid proc - ## <#isValid,HashSet[A]>`_ and `len() <#len,HashSet[A]>`_. + ## Starting from Nim v0.20, sets are initialized by default and it is + ## not necessary to call this function explicitly. ## ## You can call this proc on a previously initialized hash set, which will ## discard all its values. This might be more convenient than iterating over @@ -876,26 +668,25 @@ proc init*[A](s: var OrderedSet[A], initialSize=64) = init(a) assert a.isValid - assert isPowerOfTwo(initialSize) - s.counter = 0 - s.first = -1 - s.last = -1 - newSeq(s.data, initialSize) + initImpl(s, initialSize) -proc initOrderedSet*[A](initialSize=64): OrderedSet[A] = +proc initOrderedSet*[A](initialSize = defaultInitialSize): OrderedSet[A] = ## Wrapper around `init proc <#init,OrderedSet[A],int>`_ for initialization of ## ordered hash sets. ## ## Returns an empty ordered hash set you can assign directly in ``var`` blocks ## in a single line. ## + ## Starting from Nim v0.20, sets are initialized by default and it is + ## not necessary to call this function explicitly. + ## ## See also: ## * `toOrderedSet proc <#toOrderedSet,openArray[A]>`_ runnableExamples: var a = initOrderedSet[int]() - assert a.isValid a.incl(3) assert len(a) == 1 + result.init(initialSize) proc toOrderedSet*[A](keys: openArray[A]): OrderedSet[A] = @@ -918,23 +709,6 @@ proc toOrderedSet*[A](keys: openArray[A]): OrderedSet[A] = result = initOrderedSet[A](rightSize(keys.len)) for key in items(keys): result.incl(key) -proc isValid*[A](s: OrderedSet[A]): bool = - ## Returns `true` if the set has been initialized (with `initHashSet proc - ## <#initOrderedSet,int>`_ or `init proc <#init,OrderedSet[A],int>`_). - ## - ## Most operations over an uninitialized set will crash at runtime and - ## `assert <system.html#assert>`_ in debug builds. You can use this proc in - ## your own procs to verify that sets passed to your procs are correctly - ## initialized. - ## - ## **Examples:** - ## - ## .. code-block :: - ## proc savePreferences(options: OrderedSet[string]) = - ## assert options.isValid, "Pass an initialized set!" - ## # Do stuff here, may crash in release builds! - result = s.data.len > 0 - proc contains*[A](s: OrderedSet[A], key: A): bool = ## Returns true if `key` is in `s`. ## @@ -952,7 +726,6 @@ proc contains*[A](s: OrderedSet[A], key: A): bool = assert values.contains(2) assert 2 in values - assert s.isValid, "The set needs to be initialized." var hc: Hash var index = rawGet(s, key, hc) result = index >= 0 @@ -972,7 +745,6 @@ proc incl*[A](s: var OrderedSet[A], key: A) = values.incl(2) assert values.len == 1 - assert s.isValid, "The set needs to be initialized." inclImpl() proc incl*[A](s: var HashSet[A], other: OrderedSet[A]) = @@ -988,8 +760,7 @@ proc incl*[A](s: var HashSet[A], other: OrderedSet[A]) = others = toOrderedSet([3, 4, 5]) values.incl(others) assert values.len == 5 - assert s.isValid, "The set `s` needs to be initialized." - assert other.isValid, "The set `other` needs to be initialized." + for item in items(other): incl(s, item) proc containsOrIncl*[A](s: var OrderedSet[A], key: A): bool = @@ -1009,7 +780,6 @@ proc containsOrIncl*[A](s: var OrderedSet[A], key: A): bool = assert values.containsOrIncl(2) == true assert values.containsOrIncl(3) == false - assert s.isValid, "The set needs to be initialized." containsOrInclImpl() proc excl*[A](s: var OrderedSet[A], key: A) = @@ -1025,6 +795,7 @@ proc excl*[A](s: var OrderedSet[A], key: A) = s.excl(2) s.excl(2) assert s.len == 3 + discard exclImpl(s, key) proc missingOrExcl*[A](s: var OrderedSet[A], key: A): bool = @@ -1044,6 +815,7 @@ proc missingOrExcl*[A](s: var OrderedSet[A], key: A): bool = assert s.missingOrExcl(4) == true assert s.missingOrExcl(6) == false assert s.missingOrExcl(6) == true + exclImpl(s, key) proc clear*[A](s: var OrderedSet[A]) = @@ -1059,7 +831,7 @@ proc clear*[A](s: var OrderedSet[A]) = s.counter = 0 s.first = -1 s.last = -1 - for i in 0..<s.data.len: + for i in 0 ..< s.data.len: s.data[i].hcode = 0 s.data[i].next = 0 s.data[i].key = default(type(s.data[i].key)) @@ -1075,6 +847,7 @@ proc len*[A](s: OrderedSet[A]): int {.inline.} = assert len(a) == 0 let s = toHashSet([3, 5, 7]) assert len(s) == 3 + result = s.counter proc card*[A](s: OrderedSet[A]): int {.inline.} = @@ -1110,7 +883,6 @@ proc `==`*[A](s, t: OrderedSet[A]): bool = proc hash*[A](s: OrderedSet[A]): Hash = ## Hashing of OrderedSet. - assert s.isValid, "The set needs to be initialized." forAllOrderedPairs: result = result !& s.data[h].hcode result = !$result @@ -1129,7 +901,6 @@ proc `$`*[A](s: OrderedSet[A]): string = ## # --> {2, 4, 5} ## echo toOrderedSet(["no", "esc'aping", "is \" provided"]) ## # --> {no, esc'aping, is " provided} - assert s.isValid, "The set needs to be initialized." dollarImpl() @@ -1152,11 +923,9 @@ iterator items*[A](s: OrderedSet[A]): A = ## # --> Got 5 ## # --> Got 8 ## # --> Got 4 - assert s.isValid, "The set needs to be initialized." forAllOrderedPairs: yield s.data[h].key - iterator pairs*[A](s: OrderedSet[A]): tuple[a: int, b: A] = ## Iterates through (position, value) tuples of OrderedSet `s`. runnableExamples: @@ -1166,12 +935,27 @@ iterator pairs*[A](s: OrderedSet[A]): tuple[a: int, b: A] = p.add(x) assert p == @[(0, 'a'), (1, 'b'), (2, 'r'), (3, 'c'), (4, 'd')] - assert s.isValid, "The set needs to be initialized" forAllOrderedPairs: yield (idx, s.data[h].key) +proc isValid*[A](s: OrderedSet[A]): bool {.deprecated: + "Deprecated since v0.20; sets are initialized by default"} = + ## **Deprecated since v0.20; sets are initialized by default** + ## + ## Returns `true` if the set has been initialized (with `initHashSet proc + ## <#initOrderedSet,int>`_ or `init proc <#init,OrderedSet[A],int>`_). + ## + ## **Examples:** + ## + ## .. code-block :: + ## proc savePreferences(options: OrderedSet[string]) = + ## assert options.isValid, "Pass an initialized set!" + ## # Do stuff here, may crash in release builds! + result = s.data.len > 0 + + # ----------------------------------------------------------------------- @@ -1179,7 +963,7 @@ iterator pairs*[A](s: OrderedSet[A]): tuple[a: int, b: A] = when isMainModule and not defined(release): proc testModule() = ## Internal micro test to validate docstrings and such. - block isValidTest: + block isValidTest: # isValid is deprecated var options: HashSet[string] proc savePreferences(options: HashSet[string]) = assert options.isValid, "Pass an initialized set!" @@ -1280,7 +1064,7 @@ when isMainModule and not defined(release): var b = a.map(proc (x: int): string = $x) assert b == toHashSet(["1", "2", "3"]) - block isValidTest: + block isValidTest: # isValid is deprecated var cards: OrderedSet[string] proc saveTarotCards(cards: OrderedSet[string]) = assert cards.isValid, "Pass an initialized set!" @@ -1393,6 +1177,69 @@ when isMainModule and not defined(release): bb.incl(y) assert aa == bb + block setsWithoutInit: + var + a: HashSet[int] + b: HashSet[int] + c: HashSet[int] + d: HashSet[int] + e: HashSet[int] + + doAssert a.containsOrIncl(3) == false + doAssert a.contains(3) + doAssert a.len == 1 + doAssert a.containsOrIncl(3) + a.incl(3) + doAssert a.len == 1 + a.incl(6) + doAssert a.len == 2 + + b.incl(5) + doAssert b.len == 1 + b.excl(5) + b.excl(c) + doAssert b.missingOrExcl(5) + doAssert b.disjoint(c) + + d = b + c + doAssert d.len == 0 + d = b * c + doAssert d.len == 0 + d = b - c + doAssert d.len == 0 + d = b -+- c + doAssert d.len == 0 + + doAssert (d < e) == false + doAssert d <= e + doAssert d == e + + block setsWithoutInit: + var + a: OrderedSet[int] + b: OrderedSet[int] + c: OrderedSet[int] + d: HashSet[int] + + + doAssert a.containsOrIncl(3) == false + doAssert a.contains(3) + doAssert a.len == 1 + doAssert a.containsOrIncl(3) + a.incl(3) + doAssert a.len == 1 + a.incl(6) + doAssert a.len == 2 + + b.incl(5) + doAssert b.len == 1 + doAssert b.missingOrExcl(5) == false + doAssert b.missingOrExcl(5) + + doAssert c.missingOrExcl(9) + d.incl(c) + doAssert d.len == 0 + when not defined(testing): echo "Micro tests run successfully." diff --git a/lib/pure/collections/sharedtables.nim b/lib/pure/collections/sharedtables.nim index 0292a27a2..6ebb00e57 100644 --- a/lib/pure/collections/sharedtables.nim +++ b/lib/pure/collections/sharedtables.nim @@ -29,6 +29,14 @@ template maxHash(t): untyped = t.dataLen-1 include tableimpl +template st_maybeRehashPutImpl(enlarge) {.dirty.} = + if mustRehash(t.dataLen, t.counter): + enlarge(t) + index = rawGetKnownHC(t, key, hc) + index = -1 - index # important to transform for mgetOrPutImpl + rawInsert(t, t.data, key, val, hc, index) + inc(t.counter) + proc enlarge[A, B](t: var SharedTable[A, B]) = let oldSize = t.dataLen let size = oldSize * growthFactor @@ -176,7 +184,7 @@ proc withKey*[A, B](t: var SharedTable[A, B], key: A, var val: B mapper(key, val, pairExists) if pairExists: - maybeRehashPutImpl(enlarge) + st_maybeRehashPutImpl(enlarge) proc `[]=`*[A, B](t: var SharedTable[A, B], key: A, val: B) = ## puts a (key, value)-pair into `t`. diff --git a/lib/pure/collections/tableimpl.nim b/lib/pure/collections/tableimpl.nim index 3e34b1488..4f9610db3 100644 --- a/lib/pure/collections/tableimpl.nim +++ b/lib/pure/collections/tableimpl.nim @@ -9,49 +9,7 @@ # An ``include`` file for the different table implementations. -# hcode for real keys cannot be zero. hcode==0 signifies an empty slot. These -# two procs retain clarity of that encoding without the space cost of an enum. -proc isEmpty(hcode: Hash): bool {.inline.} = - result = hcode == 0 - -proc isFilled(hcode: Hash): bool {.inline.} = - result = hcode != 0 - -const - growthFactor = 2 - -proc mustRehash(length, counter: int): bool {.inline.} = - assert(length > counter) - result = (length * 2 < counter * 3) or (length - counter < 4) - -proc nextTry(h, maxHash: Hash): Hash {.inline.} = - result = (h + 1) and maxHash - -template rawGetKnownHCImpl() {.dirty.} = - var h: Hash = hc and maxHash(t) # start with real hash value - while isFilled(t.data[h].hcode): - # Compare hc THEN key with boolean short circuit. This makes the common case - # zero ==key's for missing (e.g.inserts) and exactly one ==key for present. - # It does slow down succeeding lookups by one extra Hash cmp&and..usually - # just a few clock cycles, generally worth it for any non-integer-like A. - if t.data[h].hcode == hc and t.data[h].key == key: - return h - h = nextTry(h, maxHash(t)) - result = -1 - h # < 0 => MISSING; insert idx = -1 - result - -template genHashImpl(key, hc: typed) = - hc = hash(key) - if hc == 0: # This almost never taken branch should be very predictable. - hc = 314159265 # Value doesn't matter; Any non-zero favorite is fine. - -template genHash(key: typed): Hash = - var res: Hash - genHashImpl(key, res) - res - -template rawGetImpl() {.dirty.} = - genHashImpl(key, hc) - rawGetKnownHCImpl() +include hashcommon template rawGetDeepImpl() {.dirty.} = # Search algo for unconditional add genHashImpl(key, hc) @@ -65,20 +23,16 @@ template rawInsertImpl() {.dirty.} = data[h].val = val data[h].hcode = hc -proc rawGetKnownHC[X, A](t: X, key: A, hc: Hash): int {.inline.} = - rawGetKnownHCImpl() - proc rawGetDeep[X, A](t: X, key: A, hc: var Hash): int {.inline.} = rawGetDeepImpl() -proc rawGet[X, A](t: X, key: A, hc: var Hash): int {.inline.} = - rawGetImpl() - proc rawInsert[X, A, B](t: var X, data: var KeyValuePairSeq[A, B], key: A, val: B, hc: Hash, h: Hash) = rawInsertImpl() template addImpl(enlarge) {.dirty.} = + if t.dataLen == 0: + initImpl(t, defaultInitialSize) if mustRehash(t.dataLen, t.counter): enlarge(t) var hc: Hash var j = rawGetDeep(t, key, hc) @@ -86,6 +40,8 @@ template addImpl(enlarge) {.dirty.} = inc(t.counter) template maybeRehashPutImpl(enlarge) {.dirty.} = + if t.dataLen == 0: + initImpl(t, defaultInitialSize) if mustRehash(t.dataLen, t.counter): enlarge(t) index = rawGetKnownHC(t, key, hc) @@ -94,12 +50,16 @@ template maybeRehashPutImpl(enlarge) {.dirty.} = inc(t.counter) template putImpl(enlarge) {.dirty.} = + if t.dataLen == 0: + initImpl(t, defaultInitialSize) var hc: Hash var index = rawGet(t, key, hc) if index >= 0: t.data[index].val = val else: maybeRehashPutImpl(enlarge) template mgetOrPutImpl(enlarge) {.dirty.} = + if t.dataLen == 0: + initImpl(t, defaultInitialSize) var hc: Hash var index = rawGet(t, key, hc) if index < 0: @@ -109,6 +69,8 @@ template mgetOrPutImpl(enlarge) {.dirty.} = result = t.data[index].val template hasKeyOrPutImpl(enlarge) {.dirty.} = + if t.dataLen == 0: + initImpl(t, defaultInitialSize) var hc: Hash var index = rawGet(t, key, hc) if index < 0: @@ -116,11 +78,6 @@ template hasKeyOrPutImpl(enlarge) {.dirty.} = maybeRehashPutImpl(enlarge) else: result = true -when not defined(nimHasDefault): - template default[T](t: typedesc[T]): T = - var v: T - v - template delImplIdx(t, i) = let msk = maxHash(t) if i >= 0: @@ -150,9 +107,53 @@ template delImpl() {.dirty.} = delImplIdx(t, i) template clearImpl() {.dirty.} = - for i in 0 ..< t.data.len: + for i in 0 ..< t.dataLen: when compiles(t.data[i].hcode): # CountTable records don't contain a hcode t.data[i].hcode = 0 t.data[i].key = default(type(t.data[i].key)) t.data[i].val = default(type(t.data[i].val)) t.counter = 0 + +template initImpl(result: typed, size: int) = + assert isPowerOfTwo(size) + result.counter = 0 + newSeq(result.data, size) + +template insertImpl() = # for CountTable + if t.dataLen == 0: initImpl(t, defaultInitialSize) + if mustRehash(len(t.data), t.counter): enlarge(t) + ctRawInsert(t, t.data, key, val) + inc(t.counter) + +template getOrDefaultImpl(t, key): untyped = + mixin rawGet + var hc: Hash + var index = rawGet(t, key, hc) + if index >= 0: result = t.data[index].val + +template getOrDefaultImpl(t, key, default: untyped): untyped = + mixin rawGet + var hc: Hash + var index = rawGet(t, key, hc) + result = if index >= 0: t.data[index].val else: default + +template dollarImpl(): untyped {.dirty.} = + if t.len == 0: + result = "{:}" + else: + result = "{" + for key, val in pairs(t): + if result.len > 1: result.add(", ") + result.addQuoted(key) + result.add(": ") + result.addQuoted(val) + result.add("}") + +template equalsImpl(s, t: typed): typed = + if s.counter == t.counter: + # different insertion orders mean different 'data' seqs, so we have + # to use the slow route here: + for key, val in s: + if not t.hasKey(key): return false + if t.getOrDefault(key) != val: return false + return true diff --git a/lib/pure/collections/tables.nim b/lib/pure/collections/tables.nim index c6d5cc9e2..98ece4779 100644 --- a/lib/pure/collections/tables.nim +++ b/lib/pure/collections/tables.nim @@ -19,7 +19,7 @@ ## For consistency with every other data type in Nim these have **value** ## semantics, this means that ``=`` performs a copy of the hash table. ## -## For `ref semantics<manual.html#types-ref-and-pointer-types>`_ +## For `ref semantics<manual.html#types-reference-and-pointer-types>`_ ## use their ``Ref`` variants: `TableRef<#TableRef>`_, ## `OrderedTableRef<#OrderedTableRef>`_, and `CountTableRef<#CountTableRef>`_. ## @@ -237,9 +237,13 @@ type ## For creating a new empty TableRef, use `newTable proc ## <#newTable,int>`_. +const + defaultInitialSize* = 64 # ------------------------------ helpers --------------------------------- +# Do NOT move these to tableimpl.nim, because sharedtables uses that +# file and has its own implementation. template maxHash(t): untyped = high(t.data) template dataLen(t): untyped = len(t.data) @@ -260,30 +264,6 @@ template get(t, key): untyped = else: raise newException(KeyError, "key not found") -template getOrDefaultImpl(t, key): untyped = - mixin rawGet - var hc: Hash - var index = rawGet(t, key, hc) - if index >= 0: result = t.data[index].val - -template getOrDefaultImpl(t, key, default: untyped): untyped = - mixin rawGet - var hc: Hash - var index = rawGet(t, key, hc) - result = if index >= 0: t.data[index].val else: default - -template dollarImpl(): untyped {.dirty.} = - if t.len == 0: - result = "{:}" - else: - result = "{" - for key, val in pairs(t): - if result.len > 1: result.add(", ") - result.addQuoted(key) - result.add(": ") - result.addQuoted(val) - result.add("}") - proc enlarge[A, B](t: var Table[A, B]) = var n: KeyValuePairSeq[A, B] newSeq(n, len(t.data) * growthFactor) @@ -294,16 +274,8 @@ proc enlarge[A, B](t: var Table[A, B]) = var j: Hash = eh and maxHash(t) while isFilled(t.data[j].hcode): j = nextTry(j, maxHash(t)) - rawInsert(t, t.data, n[i].key, n[i].val, eh, j) + rawInsert(t, t.data, move n[i].key, move n[i].val, eh, j) -template equalsImpl(s, t: typed): typed = - if s.counter == t.counter: - # different insertion orders mean different 'data' seqs, so we have - # to use the slow route here: - for key, val in s: - if not t.hasKey(key): return false - if t.getOrDefault(key) != val: return false - return true @@ -311,7 +283,7 @@ template equalsImpl(s, t: typed): typed = # ------------------------------ Table ------------------------------ # ------------------------------------------------------------------- -proc initTable*[A, B](initialSize=64): Table[A, B] = +proc initTable*[A, B](initialsize = defaultInitialSize): Table[A, B] = ## Creates a new hash table that is empty. ## ## ``initialSize`` must be a power of two (default: 64). @@ -320,6 +292,9 @@ proc initTable*[A, B](initialSize=64): Table[A, B] = ## `math module<math.html>`_ or the `rightSize proc<#rightSize,Natural>`_ ## from this module. ## + ## Starting from Nim v0.20, tables are initialized by default and it is + ## not necessary to call this function explicitly. + ## ## See also: ## * `toTable proc<#toTable,openArray[]>`_ ## * `newTable proc<#newTable,int>`_ for creating a `TableRef` @@ -327,9 +302,7 @@ proc initTable*[A, B](initialSize=64): Table[A, B] = let a = initTable[int, string]() b = initTable[char, seq[int]]() - assert isPowerOfTwo(initialSize) - result.counter = 0 - newSeq(result.data, initialSize) + initImpl(result, initialSize) proc toTable*[A, B](pairs: openArray[(A, B)]): Table[A, B] = ## Creates a new hash table that contains the given ``pairs``. @@ -343,6 +316,7 @@ proc toTable*[A, B](pairs: openArray[(A, B)]): Table[A, B] = let a = [('a', 5), ('b', 9)] let b = toTable(a) assert b == {'a': 5, 'b': 9}.toTable + result = initTable[A, B](rightSize(pairs.len)) for key, val in items(pairs): result[key] = val @@ -398,6 +372,7 @@ proc `[]=`*[A, B](t: var Table[A, B], key: A, val: B) = a['x'] = 7 a['y'] = 33 doAssert a == {'x': 7, 'y': 33}.toTable + putImpl(enlarge) proc hasKey*[A, B](t: Table[A, B], key: A): bool = @@ -414,6 +389,7 @@ proc hasKey*[A, B](t: Table[A, B], key: A): bool = let a = {'a': 5, 'b': 9}.toTable doAssert a.hasKey('a') == true doAssert a.hasKey('z') == false + var hc: Hash result = rawGet(t, key, hc) >= 0 @@ -424,6 +400,7 @@ proc contains*[A, B](t: Table[A, B], key: A): bool = let a = {'a': 5, 'b': 9}.toTable doAssert 'b' in a == true doAssert a.contains('z') == false + return hasKey[A, B](t, key) proc hasKeyOrPut*[A, B](t: var Table[A, B], key: A, val: B): bool = @@ -443,6 +420,7 @@ proc hasKeyOrPut*[A, B](t: var Table[A, B], key: A, val: B): bool = if a.hasKeyOrPut('z', 50): a['z'] = 99 doAssert a == {'a': 99, 'b': 9, 'z': 50}.toTable + hasKeyOrPutImpl(enlarge) proc getOrDefault*[A, B](t: Table[A, B], key: A): B = @@ -461,6 +439,7 @@ proc getOrDefault*[A, B](t: Table[A, B], key: A): B = let a = {'a': 5, 'b': 9}.toTable doAssert a.getOrDefault('a') == 5 doAssert a.getOrDefault('z') == 0 + getOrDefaultImpl(t, key) proc getOrDefault*[A, B](t: Table[A, B], key: A, default: B): B = @@ -478,6 +457,7 @@ proc getOrDefault*[A, B](t: Table[A, B], key: A, default: B): B = let a = {'a': 5, 'b': 9}.toTable doAssert a.getOrDefault('a', 99) == 5 doAssert a.getOrDefault('z', 99) == 99 + getOrDefaultImpl(t, key, default) proc mgetOrPut*[A, B](t: var Table[A, B], key: A, val: B): var B = @@ -497,6 +477,7 @@ proc mgetOrPut*[A, B](t: var Table[A, B], key: A, val: B): var B = doAssert a.mgetOrPut('a', 99) == 5 doAssert a.mgetOrPut('z', 99) == 99 doAssert a == {'a': 5, 'b': 9, 'z': 99}.toTable + mgetOrPutImpl(enlarge) proc len*[A, B](t: Table[A, B]): int = @@ -504,6 +485,7 @@ proc len*[A, B](t: Table[A, B]): int = runnableExamples: let a = {'a': 5, 'b': 9}.toTable doAssert len(a) == 2 + result = t.counter proc add*[A, B](t: var Table[A, B], key: A, val: B) = @@ -527,6 +509,7 @@ proc del*[A, B](t: var Table[A, B], key: A) = doAssert a == {'b': 9, 'c': 13}.toTable a.del('z') doAssert a == {'b': 9, 'c': 13}.toTable + delImpl() proc take*[A, B](t: var Table[A, B], key: A, val: var B): bool = @@ -568,6 +551,7 @@ proc clear*[A, B](t: var Table[A, B]) = doAssert len(a) == 3 clear(a) doAssert len(a) == 0 + clearImpl() proc `$`*[A, B](t: Table[A, B]): string = @@ -583,6 +567,7 @@ proc `==`*[A, B](s, t: Table[A, B]): bool = a = {'a': 5, 'b': 9, 'c': 13}.toTable b = {'b': 9, 'c': 13, 'a': 5}.toTable doAssert a == b + equalsImpl(s, t) proc rightSize*(count: Natural): int {.inline.} = @@ -692,6 +677,7 @@ iterator mpairs*[A, B](t: var Table[A, B]): (A, var B) = for k, v in a.mpairs: v.add(v[0] + 10) doAssert a == {'e': @[2, 4, 6, 8, 12], 'o': @[1, 5, 7, 9, 11]}.toTable + for h in 0..high(t.data): if isFilled(t.data[h].hcode): yield (t.data[h].key, t.data[h].val) @@ -709,6 +695,7 @@ iterator keys*[A, B](t: Table[A, B]): A = for k in a.keys: a[k].add(99) doAssert a == {'e': @[2, 4, 6, 8, 99], 'o': @[1, 5, 7, 9, 99]}.toTable + for h in 0..high(t.data): if isFilled(t.data[h].hcode): yield t.data[h].key @@ -726,6 +713,7 @@ iterator values*[A, B](t: Table[A, B]): B = }.toTable for v in a.values: doAssert v.len == 4 + for h in 0..high(t.data): if isFilled(t.data[h].hcode): yield t.data[h].val @@ -744,6 +732,7 @@ iterator mvalues*[A, B](t: var Table[A, B]): var B = for v in a.mvalues: v.add(99) doAssert a == {'e': @[2, 4, 6, 8, 99], 'o': @[1, 5, 7, 9, 99]}.toTable + for h in 0..high(t.data): if isFilled(t.data[h].hcode): yield t.data[h].val @@ -779,7 +768,7 @@ iterator allValues*[A, B](t: Table[A, B]; key: A): B = # ------------------------------------------------------------------- -proc newTable*[A, B](initialSize=64): TableRef[A, B] = +proc newTable*[A, B](initialsize = defaultInitialSize): <//>TableRef[A, B] = ## Creates a new ref hash table that is empty. ## ## ``initialSize`` must be a power of two (default: 64). @@ -796,10 +785,11 @@ proc newTable*[A, B](initialSize=64): TableRef[A, B] = let a = newTable[int, string]() b = newTable[char, seq[int]]() + new(result) result[] = initTable[A, B](initialSize) -proc newTable*[A, B](pairs: openArray[(A, B)]): TableRef[A, B] = +proc newTable*[A, B](pairs: openArray[(A, B)]): <//>TableRef[A, B] = ## Creates a new ref hash table that contains the given ``pairs``. ## ## ``pairs`` is a container consisting of ``(key, value)`` tuples. @@ -811,10 +801,11 @@ proc newTable*[A, B](pairs: openArray[(A, B)]): TableRef[A, B] = let a = [('a', 5), ('b', 9)] let b = newTable(a) assert b == {'a': 5, 'b': 9}.newTable + new(result) result[] = toTable[A, B](pairs) -proc newTableFrom*[A, B, C](collection: A, index: proc(x: B): C): TableRef[C, B] = +proc newTableFrom*[A, B, C](collection: A, index: proc(x: B): C): <//>TableRef[C, B] = ## Index the collection with the proc provided. # TODO: As soon as supported, change collection: A to collection: A[B] result = newTable[C, B]() @@ -842,6 +833,7 @@ proc `[]`*[A, B](t: TableRef[A, B], key: A): var B = doAssert a['a'] == 5 doAssertRaises(KeyError): echo a['z'] + result = t[][key] proc `[]=`*[A, B](t: TableRef[A, B], key: A, val: B) = @@ -857,6 +849,7 @@ proc `[]=`*[A, B](t: TableRef[A, B], key: A, val: B) = a['x'] = 7 a['y'] = 33 doAssert a == {'x': 7, 'y': 33}.newTable + t[][key] = val proc hasKey*[A, B](t: TableRef[A, B], key: A): bool = @@ -874,6 +867,7 @@ proc hasKey*[A, B](t: TableRef[A, B], key: A): bool = let a = {'a': 5, 'b': 9}.newTable doAssert a.hasKey('a') == true doAssert a.hasKey('z') == false + result = t[].hasKey(key) proc contains*[A, B](t: TableRef[A, B], key: A): bool = @@ -883,6 +877,7 @@ proc contains*[A, B](t: TableRef[A, B], key: A): bool = let a = {'a': 5, 'b': 9}.newTable doAssert 'b' in a == true doAssert a.contains('z') == false + return hasKey[A, B](t, key) proc hasKeyOrPut*[A, B](t: var TableRef[A, B], key: A, val: B): bool = @@ -902,6 +897,7 @@ proc hasKeyOrPut*[A, B](t: var TableRef[A, B], key: A, val: B): bool = if a.hasKeyOrPut('z', 50): a['z'] = 99 doAssert a == {'a': 99, 'b': 9, 'z': 50}.newTable + t[].hasKeyOrPut(key, val) proc getOrDefault*[A, B](t: TableRef[A, B], key: A): B = @@ -920,6 +916,7 @@ proc getOrDefault*[A, B](t: TableRef[A, B], key: A): B = let a = {'a': 5, 'b': 9}.newTable doAssert a.getOrDefault('a') == 5 doAssert a.getOrDefault('z') == 0 + getOrDefault(t[], key) proc getOrDefault*[A, B](t: TableRef[A, B], key: A, default: B): B = @@ -937,6 +934,7 @@ proc getOrDefault*[A, B](t: TableRef[A, B], key: A, default: B): B = let a = {'a': 5, 'b': 9}.newTable doAssert a.getOrDefault('a', 99) == 5 doAssert a.getOrDefault('z', 99) == 99 + getOrDefault(t[], key, default) proc mgetOrPut*[A, B](t: TableRef[A, B], key: A, val: B): var B = @@ -956,6 +954,7 @@ proc mgetOrPut*[A, B](t: TableRef[A, B], key: A, val: B): var B = doAssert a.mgetOrPut('a', 99) == 5 doAssert a.mgetOrPut('z', 99) == 99 doAssert a == {'a': 5, 'b': 9, 'z': 99}.newTable + t[].mgetOrPut(key, val) proc len*[A, B](t: TableRef[A, B]): int = @@ -963,6 +962,7 @@ proc len*[A, B](t: TableRef[A, B]): int = runnableExamples: let a = {'a': 5, 'b': 9}.newTable doAssert len(a) == 2 + result = t.counter proc add*[A, B](t: TableRef[A, B], key: A, val: B) = @@ -988,6 +988,7 @@ proc del*[A, B](t: TableRef[A, B], key: A) = doAssert a == {'b': 9, 'c': 13}.newTable a.del('z') doAssert a == {'b': 9, 'c': 13}.newTable + t[].del(key) proc take*[A, B](t: TableRef[A, B], key: A, val: var B): bool = @@ -1012,6 +1013,7 @@ proc take*[A, B](t: TableRef[A, B], key: A, val: var B): bool = doAssert a.take('z', i) == false doAssert a == {'a': 5, 'c': 13}.newTable doAssert i == 0 + result = t[].take(key, val) proc clear*[A, B](t: TableRef[A, B]) = @@ -1025,6 +1027,7 @@ proc clear*[A, B](t: TableRef[A, B]) = doAssert len(a) == 3 clear(a) doAssert len(a) == 0 + clearImpl() proc `$`*[A, B](t: TableRef[A, B]): string = @@ -1041,6 +1044,7 @@ proc `==`*[A, B](s, t: TableRef[A, B]): bool = a = {'a': 5, 'b': 9, 'c': 13}.newTable b = {'b': 9, 'c': 13, 'a': 5}.newTable doAssert a == b + if isNil(s): result = isNil(t) elif isNil(t): result = false else: equalsImpl(s[], t[]) @@ -1089,6 +1093,7 @@ iterator mpairs*[A, B](t: TableRef[A, B]): (A, var B) = for k, v in a.mpairs: v.add(v[0] + 10) doAssert a == {'e': @[2, 4, 6, 8, 12], 'o': @[1, 5, 7, 9, 11]}.newTable + for h in 0..high(t.data): if isFilled(t.data[h].hcode): yield (t.data[h].key, t.data[h].val) @@ -1106,6 +1111,7 @@ iterator keys*[A, B](t: TableRef[A, B]): A = for k in a.keys: a[k].add(99) doAssert a == {'e': @[2, 4, 6, 8, 99], 'o': @[1, 5, 7, 9, 99]}.newTable + for h in 0..high(t.data): if isFilled(t.data[h].hcode): yield t.data[h].key @@ -1123,6 +1129,7 @@ iterator values*[A, B](t: TableRef[A, B]): B = }.newTable for v in a.values: doAssert v.len == 4 + for h in 0..high(t.data): if isFilled(t.data[h].hcode): yield t.data[h].val @@ -1140,6 +1147,7 @@ iterator mvalues*[A, B](t: TableRef[A, B]): var B = for v in a.mvalues: v.add(99) doAssert a == {'e': @[2, 4, 6, 8, 99], 'o': @[1, 5, 7, 9, 99]}.newTable + for h in 0..high(t.data): if isFilled(t.data[h].hcode): yield t.data[h].val @@ -1218,7 +1226,7 @@ template forAllOrderedPairs(yieldStmt: untyped): typed {.dirty.} = # ---------------------------------------------------------------------- -proc initOrderedTable*[A, B](initialSize=64): OrderedTable[A, B] = +proc initOrderedTable*[A, B](initialsize = defaultInitialSize): OrderedTable[A, B] = ## Creates a new ordered hash table that is empty. ## ## ``initialSize`` must be a power of two (default: 64). @@ -1227,6 +1235,9 @@ proc initOrderedTable*[A, B](initialSize=64): OrderedTable[A, B] = ## `math module<math.html>`_ or the `rightSize proc<#rightSize,Natural>`_ ## from this module. ## + ## Starting from Nim v0.20, tables are initialized by default and it is + ## not necessary to call this function explicitly. + ## ## See also: ## * `toOrderedTable proc<#toOrderedTable,openArray[]>`_ ## * `newOrderedTable proc<#newOrderedTable,int>`_ for creating an @@ -1235,11 +1246,9 @@ proc initOrderedTable*[A, B](initialSize=64): OrderedTable[A, B] = let a = initOrderedTable[int, string]() b = initOrderedTable[char, seq[int]]() - assert isPowerOfTwo(initialSize) - result.counter = 0 + initImpl(result, initialSize) result.first = -1 result.last = -1 - newSeq(result.data, initialSize) proc toOrderedTable*[A, B](pairs: openArray[(A, B)]): OrderedTable[A, B] = ## Creates a new ordered hash table that contains the given ``pairs``. @@ -1254,6 +1263,7 @@ proc toOrderedTable*[A, B](pairs: openArray[(A, B)]): OrderedTable[A, B] = let a = [('a', 5), ('b', 9)] let b = toOrderedTable(a) assert b == {'a': 5, 'b': 9}.toOrderedTable + result = initOrderedTable[A, B](rightSize(pairs.len)) for key, val in items(pairs): result[key] = val @@ -1278,6 +1288,7 @@ proc `[]`*[A, B](t: OrderedTable[A, B], key: A): B = doAssert a['a'] == 5 doAssertRaises(KeyError): echo a['z'] + get(t, key) proc `[]`*[A, B](t: var OrderedTable[A, B], key: A): var B= @@ -1309,6 +1320,7 @@ proc `[]=`*[A, B](t: var OrderedTable[A, B], key: A, val: B) = a['x'] = 7 a['y'] = 33 doAssert a == {'x': 7, 'y': 33}.toOrderedTable + putImpl(enlarge) proc hasKey*[A, B](t: OrderedTable[A, B], key: A): bool = @@ -1326,6 +1338,7 @@ proc hasKey*[A, B](t: OrderedTable[A, B], key: A): bool = let a = {'a': 5, 'b': 9}.toOrderedTable doAssert a.hasKey('a') == true doAssert a.hasKey('z') == false + var hc: Hash result = rawGet(t, key, hc) >= 0 @@ -1336,6 +1349,7 @@ proc contains*[A, B](t: OrderedTable[A, B], key: A): bool = let a = {'a': 5, 'b': 9}.toOrderedTable doAssert 'b' in a == true doAssert a.contains('z') == false + return hasKey[A, B](t, key) proc hasKeyOrPut*[A, B](t: var OrderedTable[A, B], key: A, val: B): bool = @@ -1355,6 +1369,7 @@ proc hasKeyOrPut*[A, B](t: var OrderedTable[A, B], key: A, val: B): bool = if a.hasKeyOrPut('z', 50): a['z'] = 99 doAssert a == {'a': 99, 'b': 9, 'z': 50}.toOrderedTable + hasKeyOrPutImpl(enlarge) proc getOrDefault*[A, B](t: OrderedTable[A, B], key: A): B = @@ -1373,6 +1388,7 @@ proc getOrDefault*[A, B](t: OrderedTable[A, B], key: A): B = let a = {'a': 5, 'b': 9}.toOrderedTable doAssert a.getOrDefault('a') == 5 doAssert a.getOrDefault('z') == 0 + getOrDefaultImpl(t, key) proc getOrDefault*[A, B](t: OrderedTable[A, B], key: A, default: B): B = @@ -1390,6 +1406,7 @@ proc getOrDefault*[A, B](t: OrderedTable[A, B], key: A, default: B): B = let a = {'a': 5, 'b': 9}.toOrderedTable doAssert a.getOrDefault('a', 99) == 5 doAssert a.getOrDefault('z', 99) == 99 + getOrDefaultImpl(t, key, default) proc mgetOrPut*[A, B](t: var OrderedTable[A, B], key: A, val: B): var B = @@ -1409,6 +1426,7 @@ proc mgetOrPut*[A, B](t: var OrderedTable[A, B], key: A, val: B): var B = doAssert a.mgetOrPut('a', 99) == 5 doAssert a.mgetOrPut('z', 99) == 99 doAssert a == {'a': 5, 'b': 9, 'z': 99}.toOrderedTable + mgetOrPutImpl(enlarge) proc len*[A, B](t: OrderedTable[A, B]): int {.inline.} = @@ -1416,6 +1434,7 @@ proc len*[A, B](t: OrderedTable[A, B]): int {.inline.} = runnableExamples: let a = {'a': 5, 'b': 9}.toOrderedTable doAssert len(a) == 2 + result = t.counter proc add*[A, B](t: var OrderedTable[A, B], key: A, val: B) = @@ -1440,6 +1459,7 @@ proc del*[A, B](t: var OrderedTable[A, B], key: A) = doAssert a == {'b': 9, 'c': 13}.toOrderedTable a.del('z') doAssert a == {'b': 9, 'c': 13}.toOrderedTable + var n: OrderedKeyValuePairSeq[A, B] newSeq(n, len(t.data)) var h = t.first @@ -1467,6 +1487,7 @@ proc clear*[A, B](t: var OrderedTable[A, B]) = doAssert len(a) == 3 clear(a) doAssert len(a) == 0 + clearImpl() t.first = -1 t.last = -1 @@ -1602,6 +1623,7 @@ iterator mpairs*[A, B](t: var OrderedTable[A, B]): (A, var B) = for k, v in a.mpairs: v.add(v[0] + 10) doAssert a == {'o': @[1, 5, 7, 9, 11], 'e': @[2, 4, 6, 8, 12]}.toOrderedTable + forAllOrderedPairs: yield (t.data[h].key, t.data[h].val) @@ -1619,6 +1641,7 @@ iterator keys*[A, B](t: OrderedTable[A, B]): A = for k in a.keys: a[k].add(99) doAssert a == {'o': @[1, 5, 7, 9, 99], 'e': @[2, 4, 6, 8, 99]}.toOrderedTable + forAllOrderedPairs: yield t.data[h].key @@ -1636,6 +1659,7 @@ iterator values*[A, B](t: OrderedTable[A, B]): B = }.toOrderedTable for v in a.values: doAssert v.len == 4 + forAllOrderedPairs: yield t.data[h].val @@ -1666,7 +1690,7 @@ iterator mvalues*[A, B](t: var OrderedTable[A, B]): var B = # --------------------------- OrderedTableRef ------------------------------- # --------------------------------------------------------------------------- -proc newOrderedTable*[A, B](initialSize=64): OrderedTableRef[A, B] = +proc newOrderedTable*[A, B](initialsize = defaultInitialSize): <//>OrderedTableRef[A, B] = ## Creates a new ordered ref hash table that is empty. ## ## ``initialSize`` must be a power of two (default: 64). @@ -1687,7 +1711,7 @@ proc newOrderedTable*[A, B](initialSize=64): OrderedTableRef[A, B] = new(result) result[] = initOrderedTable[A, B](initialSize) -proc newOrderedTable*[A, B](pairs: openArray[(A, B)]): OrderedTableRef[A, B] = +proc newOrderedTable*[A, B](pairs: openArray[(A, B)]): <//>OrderedTableRef[A, B] = ## Creates a new ordered ref hash table that contains the given ``pairs``. ## ## ``pairs`` is a container consisting of ``(key, value)`` tuples. @@ -1700,6 +1724,7 @@ proc newOrderedTable*[A, B](pairs: openArray[(A, B)]): OrderedTableRef[A, B] = let a = [('a', 5), ('b', 9)] let b = newOrderedTable(a) assert b == {'a': 5, 'b': 9}.newOrderedTable + result = newOrderedTable[A, B](rightSize(pairs.len)) for key, val in items(pairs): result.add(key, val) @@ -1740,6 +1765,7 @@ proc `[]=`*[A, B](t: OrderedTableRef[A, B], key: A, val: B) = a['x'] = 7 a['y'] = 33 doAssert a == {'x': 7, 'y': 33}.newOrderedTable + t[][key] = val proc hasKey*[A, B](t: OrderedTableRef[A, B], key: A): bool = @@ -1757,6 +1783,7 @@ proc hasKey*[A, B](t: OrderedTableRef[A, B], key: A): bool = let a = {'a': 5, 'b': 9}.newOrderedTable doAssert a.hasKey('a') == true doAssert a.hasKey('z') == false + result = t[].hasKey(key) proc contains*[A, B](t: OrderedTableRef[A, B], key: A): bool = @@ -1766,6 +1793,7 @@ proc contains*[A, B](t: OrderedTableRef[A, B], key: A): bool = let a = {'a': 5, 'b': 9}.newOrderedTable doAssert 'b' in a == true doAssert a.contains('z') == false + return hasKey[A, B](t, key) proc hasKeyOrPut*[A, B](t: var OrderedTableRef[A, B], key: A, val: B): bool = @@ -1785,6 +1813,7 @@ proc hasKeyOrPut*[A, B](t: var OrderedTableRef[A, B], key: A, val: B): bool = if a.hasKeyOrPut('z', 50): a['z'] = 99 doAssert a == {'a': 99, 'b': 9, 'z': 50}.newOrderedTable + result = t[].hasKeyOrPut(key, val) proc getOrDefault*[A, B](t: OrderedTableRef[A, B], key: A): B = @@ -1803,6 +1832,7 @@ proc getOrDefault*[A, B](t: OrderedTableRef[A, B], key: A): B = let a = {'a': 5, 'b': 9}.newOrderedTable doAssert a.getOrDefault('a') == 5 doAssert a.getOrDefault('z') == 0 + getOrDefault(t[], key) proc getOrDefault*[A, B](t: OrderedTableRef[A, B], key: A, default: B): B = @@ -1820,6 +1850,7 @@ proc getOrDefault*[A, B](t: OrderedTableRef[A, B], key: A, default: B): B = let a = {'a': 5, 'b': 9}.newOrderedTable doAssert a.getOrDefault('a', 99) == 5 doAssert a.getOrDefault('z', 99) == 99 + getOrDefault(t[], key, default) proc mgetOrPut*[A, B](t: OrderedTableRef[A, B], key: A, val: B): var B = @@ -1839,6 +1870,7 @@ proc mgetOrPut*[A, B](t: OrderedTableRef[A, B], key: A, val: B): var B = doAssert a.mgetOrPut('a', 99) == 5 doAssert a.mgetOrPut('z', 99) == 99 doAssert a == {'a': 5, 'b': 9, 'z': 99}.newOrderedTable + result = t[].mgetOrPut(key, val) proc len*[A, B](t: OrderedTableRef[A, B]): int {.inline.} = @@ -1846,6 +1878,7 @@ proc len*[A, B](t: OrderedTableRef[A, B]): int {.inline.} = runnableExamples: let a = {'a': 5, 'b': 9}.newOrderedTable doAssert len(a) == 2 + result = t.counter proc add*[A, B](t: OrderedTableRef[A, B], key: A, val: B) = @@ -1868,6 +1901,7 @@ proc del*[A, B](t: var OrderedTableRef[A, B], key: A) = doAssert a == {'b': 9, 'c': 13}.newOrderedTable a.del('z') doAssert a == {'b': 9, 'c': 13}.newOrderedTable + t[].del(key) proc clear*[A, B](t: var OrderedTableRef[A, B]) = @@ -1880,6 +1914,7 @@ proc clear*[A, B](t: var OrderedTableRef[A, B]) = doAssert len(a) == 3 clear(a) doAssert len(a) == 0 + clear(t[]) proc sort*[A, B](t: OrderedTableRef[A, B], cmp: proc (x,y: (A, B)): int, order = SortOrder.Ascending) = @@ -1899,6 +1934,7 @@ proc sort*[A, B](t: OrderedTableRef[A, B], cmp: proc (x,y: (A, B)): int, order = doAssert a == {'a': 10, 'b': 20, 'c': 0}.newOrderedTable a.sort(system.cmp, order=SortOrder.Descending) doAssert a == {'c': 0, 'b': 20, 'a': 10}.newOrderedTable + t[].sort(cmp, order=order) proc `$`*[A, B](t: OrderedTableRef[A, B]): string = @@ -1915,6 +1951,7 @@ proc `==`*[A, B](s, t: OrderedTableRef[A, B]): bool = a = {'a': 5, 'b': 9, 'c': 13}.newOrderedTable b = {'b': 9, 'c': 13, 'a': 5}.newOrderedTable doAssert a != b + if isNil(s): result = isNil(t) elif isNil(t): result = false else: result = s[] == t[] @@ -1964,6 +2001,7 @@ iterator mpairs*[A, B](t: OrderedTableRef[A, B]): (A, var B) = for k, v in a.mpairs: v.add(v[0] + 10) doAssert a == {'o': @[1, 5, 7, 9, 11], 'e': @[2, 4, 6, 8, 12]}.newOrderedTable + forAllOrderedPairs: yield (t.data[h].key, t.data[h].val) @@ -1981,6 +2019,7 @@ iterator keys*[A, B](t: OrderedTableRef[A, B]): A = for k in a.keys: a[k].add(99) doAssert a == {'o': @[1, 5, 7, 9, 99], 'e': @[2, 4, 6, 8, 99]}.newOrderedTable + forAllOrderedPairs: yield t.data[h].key @@ -1998,6 +2037,7 @@ iterator values*[A, B](t: OrderedTableRef[A, B]): B = }.newOrderedTable for v in a.values: doAssert v.len == 4 + forAllOrderedPairs: yield t.data[h].val @@ -2016,6 +2056,7 @@ iterator mvalues*[A, B](t: OrderedTableRef[A, B]): var B = for v in a.mvalues: v.add(99) doAssert a == {'o': @[1, 5, 7, 9, 99], 'e': @[2, 4, 6, 8, 99]}.newOrderedTable + forAllOrderedPairs: yield t.data[h].val @@ -2046,7 +2087,7 @@ type # ------------------------------ helpers --------------------------------- -proc rawInsert[A](t: CountTable[A], data: var seq[tuple[key: A, val: int]], +proc ctRawInsert[A](t: CountTable[A], data: var seq[tuple[key: A, val: int]], key: A, val: int) = var h: Hash = hash(key) and high(data) while data[h].val != 0: h = nextTry(h, high(data)) @@ -2057,10 +2098,12 @@ proc enlarge[A](t: var CountTable[A]) = var n: seq[tuple[key: A, val: int]] newSeq(n, len(t.data) * growthFactor) for i in countup(0, high(t.data)): - if t.data[i].val != 0: rawInsert(t, n, t.data[i].key, t.data[i].val) + if t.data[i].val != 0: ctRawInsert(t, n, t.data[i].key, t.data[i].val) swap(t.data, n) proc rawGet[A](t: CountTable[A], key: A): int = + if t.data.len == 0: + return -1 var h: Hash = hash(key) and high(t.data) # start with real hash value while t.data[h].val != 0: if t.data[h].key == key: return h @@ -2075,7 +2118,7 @@ proc inc*[A](t: var CountTable[A], key: A, val = 1) # ---------------------------------------------------------------------- -proc initCountTable*[A](initialSize=64): CountTable[A] = +proc initCountTable*[A](initialsize = defaultInitialSize): CountTable[A] = ## Creates a new count table that is empty. ## ## ``initialSize`` must be a power of two (default: 64). @@ -2084,13 +2127,14 @@ proc initCountTable*[A](initialSize=64): CountTable[A] = ## `math module<math.html>`_ or the `rightSize proc<#rightSize,Natural>`_ ## from this module. ## + ## Starting from Nim v0.20, tables are initialized by default and it is + ## not necessary to call this function explicitly. + ## ## See also: ## * `toCountTable proc<#toCountTable,openArray[A]>`_ ## * `newCountTable proc<#newCountTable,int>`_ for creating a ## `CountTableRef` - assert isPowerOfTwo(initialSize) - result.counter = 0 - newSeq(result.data, initialSize) + initImpl(result, initialSize) proc toCountTable*[A](keys: openArray[A]): CountTable[A] = ## Creates a new count table with every member of a container ``keys`` @@ -2130,9 +2174,7 @@ proc `[]=`*[A](t: var CountTable[A], key: A, val: int) = if h >= 0: t.data[h].val = val else: - if mustRehash(len(t.data), t.counter): enlarge(t) - rawInsert(t, t.data, key, val) - inc(t.counter) + insertImpl() proc inc*[A](t: var CountTable[A], key: A, val = 1) = ## Increments ``t[key]`` by ``val`` (default: 1). @@ -2141,14 +2183,13 @@ proc inc*[A](t: var CountTable[A], key: A, val = 1) = a.inc('a') a.inc('b', 10) doAssert a == toCountTable("aaabbbbbbbbbbb") + var index = rawGet(t, key) if index >= 0: inc(t.data[index].val, val) if t.data[index].val == 0: dec(t.counter) else: - if mustRehash(len(t.data), t.counter): enlarge(t) - rawInsert(t, t.data, key, val) - inc(t.counter) + insertImpl() proc smallest*[A](t: CountTable[A]): tuple[key: A, val: int] = ## Returns the ``(key, value)`` pair with the smallest ``val``. Efficiency: O(n) @@ -2229,6 +2270,7 @@ proc sort*[A](t: var CountTable[A], order = SortOrder.Descending) = doAssert toSeq(a.values) == @[5, 2, 2, 1, 1] a.sort(SortOrder.Ascending) doAssert toSeq(a.values) == @[1, 1, 2, 2, 5] + t.data.sort(cmp=ctCmp, order=order) proc merge*[A](s: var CountTable[A], t: CountTable[A]) = @@ -2238,6 +2280,7 @@ proc merge*[A](s: var CountTable[A], t: CountTable[A]) = let b = toCountTable("bcc") a.merge(b) doAssert a == toCountTable("aaabbbccc") + for key, value in t: s.inc(key, value) @@ -2248,6 +2291,7 @@ proc merge*[A](s, t: CountTable[A]): CountTable[A] = a = toCountTable("aaabbc") b = toCountTable("bcc") doAssert merge(a, b) == toCountTable("aaabbbccc") + result = initCountTable[A](nextPowerOfTwo(max(s.len, t.len))) for table in @[s, t]: for key, value in table: @@ -2306,6 +2350,7 @@ iterator mpairs*[A](t: var CountTable[A]): (A, var int) = for k, v in mpairs(a): v = 2 doAssert a == toCountTable("aabbccddrr") + for h in 0..high(t.data): if t.data[h].val != 0: yield (t.data[h].key, t.data[h].val) @@ -2320,6 +2365,7 @@ iterator keys*[A](t: CountTable[A]): A = for k in keys(a): a[k] = 2 doAssert a == toCountTable("aabbccddrr") + for h in 0..high(t.data): if t.data[h].val != 0: yield t.data[h].key @@ -2334,6 +2380,7 @@ iterator values*[A](t: CountTable[A]): int = let a = toCountTable("abracadabra") for v in values(a): assert v < 10 + for h in 0..high(t.data): if t.data[h].val != 0: yield t.data[h].val @@ -2349,6 +2396,7 @@ iterator mvalues*[A](t: var CountTable[A]): var int = for v in mvalues(a): v = 2 doAssert a == toCountTable("aabbccddrr") + for h in 0..high(t.data): if t.data[h].val != 0: yield t.data[h].val @@ -2364,7 +2412,7 @@ iterator mvalues*[A](t: var CountTable[A]): var int = proc inc*[A](t: CountTableRef[A], key: A, val = 1) -proc newCountTable*[A](initialSize=64): CountTableRef[A] = +proc newCountTable*[A](initialsize = defaultInitialSize): <//>CountTableRef[A] = ## Creates a new ref count table that is empty. ## ## ``initialSize`` must be a power of two (default: 64). @@ -2381,7 +2429,7 @@ proc newCountTable*[A](initialSize=64): CountTableRef[A] = new(result) result[] = initCountTable[A](initialSize) -proc newCountTable*[A](keys: openArray[A]): CountTableRef[A] = +proc newCountTable*[A](keys: openArray[A]): <//>CountTableRef[A] = ## Creates a new ref count table with every member of a container ``keys`` ## having a count of how many times it occurs in that container. result = newCountTable[A](rightSize(keys.len)) @@ -2493,6 +2541,7 @@ proc merge*[A](s, t: CountTableRef[A]) = b = newCountTable("bcc") a.merge(b) doAssert a == newCountTable("aaabbbccc") + s[].merge(t[]) proc `$`*[A](t: CountTableRef[A]): string = @@ -2551,6 +2600,7 @@ iterator mpairs*[A](t: CountTableRef[A]): (A, var int) = for k, v in mpairs(a): v = 2 doAssert a == newCountTable("aabbccddrr") + for h in 0..high(t.data): if t.data[h].val != 0: yield (t.data[h].key, t.data[h].val) @@ -2565,6 +2615,7 @@ iterator keys*[A](t: CountTableRef[A]): A = for k in keys(a): a[k] = 2 doAssert a == newCountTable("aabbccddrr") + for h in 0..high(t.data): if t.data[h].val != 0: yield t.data[h].key @@ -2579,6 +2630,7 @@ iterator values*[A](t: CountTableRef[A]): int = let a = newCountTable("abracadabra") for v in values(a): assert v < 10 + for h in 0..high(t.data): if t.data[h].val != 0: yield t.data[h].val @@ -2593,6 +2645,7 @@ iterator mvalues*[A](t: CountTableRef[A]): var int = for v in mvalues(a): v = 2 doAssert a == newCountTable("aabbccddrr") + for h in 0..high(t.data): if t.data[h].val != 0: yield t.data[h].val @@ -2813,3 +2866,99 @@ when isMainModule: doAssert "test1" == orf.getOrDefault("test1", "test1") orf["test2"] = "test2" doAssert "test2" == orf.getOrDefault("test2", "test1") + + block tableWithoutInit: + var + a: Table[string, int] + b: Table[string, int] + c: Table[string, int] + d: Table[string, int] + e: Table[string, int] + + a["a"] = 7 + doAssert a.hasKey("a") + doAssert a.len == 1 + doAssert a["a"] == 7 + a["a"] = 9 + doAssert a.len == 1 + doAssert a["a"] == 9 + + doAssert b.hasKeyOrPut("b", 5) == false + doAssert b.hasKey("b") + doAssert b.hasKeyOrPut("b", 8) + doAssert b["b"] == 5 + + doAssert c.getOrDefault("a") == 0 + doAssert c.getOrDefault("a", 3) == 3 + c["a"] = 6 + doAssert c.getOrDefault("a", 3) == 6 + + doAssert d.mgetOrPut("a", 3) == 3 + doAssert d.mgetOrPut("a", 6) == 3 + + var x = 99 + doAssert e.take("a", x) == false + doAssert x == 99 + e["a"] = 77 + doAssert e.take("a", x) + doAssert x == 77 + + block orderedTableWithoutInit: + var + a: OrderedTable[string, int] + b: OrderedTable[string, int] + c: OrderedTable[string, int] + d: OrderedTable[string, int] + + a["a"] = 7 + doAssert a.hasKey("a") + doAssert a.len == 1 + doAssert a["a"] == 7 + a["a"] = 9 + doAssert a.len == 1 + doAssert a["a"] == 9 + + doAssert b.hasKeyOrPut("b", 5) == false + doAssert b.hasKey("b") + doAssert b.hasKeyOrPut("b", 8) + doAssert b["b"] == 5 + + doAssert c.getOrDefault("a") == 0 + doAssert c.getOrDefault("a", 3) == 3 + c["a"] = 6 + doAssert c.getOrDefault("a", 3) == 6 + + doAssert d.mgetOrPut("a", 3) == 3 + doAssert d.mgetOrPut("a", 6) == 3 + + block countTableWithoutInit: + var + a: CountTable[string] + b: CountTable[string] + c: CountTable[string] + d: CountTable[string] + e: CountTable[string] + + a["a"] = 7 + doAssert a.hasKey("a") + doAssert a.len == 1 + doAssert a["a"] == 7 + a["a"] = 9 + doAssert a.len == 1 + doAssert a["a"] == 9 + + doAssert b["b"] == 0 + b.inc("b") + doAssert b["b"] == 1 + + doAssert c.getOrDefault("a") == 0 + doAssert c.getOrDefault("a", 3) == 3 + c["a"] = 6 + doAssert c.getOrDefault("a", 3) == 6 + + e["f"] = 3 + merge(d, e) + doAssert d.hasKey("f") + d.inc("f") + merge(d, e) + doAssert d["f"] == 7 diff --git a/lib/pure/dynlib.nim b/lib/pure/dynlib.nim index 97bc51bc5..ff12be90f 100644 --- a/lib/pure/dynlib.nim +++ b/lib/pure/dynlib.nim @@ -12,10 +12,10 @@ ## Windows ``LoadLibrary``. ## ## Examples -## -------- +## ======== ## ## Loading a simple C function -## ^^^^^^^^^^^^^^^^^^^^^^^^^^^ +## --------------------------- ## ## The following example demonstrates loading a function called 'greet' ## from a library that is determined at runtime based upon a language choice. diff --git a/lib/pure/htmlgen.nim b/lib/pure/htmlgen.nim index fca78fb0f..fd6432bae 100644 --- a/lib/pure/htmlgen.nim +++ b/lib/pure/htmlgen.nim @@ -16,7 +16,8 @@ ## generator. Each commonly used HTML tag has a corresponding macro ## that generates a string with its HTML representation. ## -## Example: +## Examples +## ======== ## ## .. code-block:: Nim ## var nim = "Nim" diff --git a/lib/pure/includes/osenv.nim b/lib/pure/includes/osenv.nim index f9c076158..bc4750121 100644 --- a/lib/pure/includes/osenv.nim +++ b/lib/pure/includes/osenv.nim @@ -1,6 +1,6 @@ # Include file that implements 'getEnv' and friends. Do not import it! -when not declared(os): +when not declared(os) and not declared(ospaths): {.error: "This is an include file for os.nim!".} from parseutils import skipIgnoreCase diff --git a/lib/pure/includes/oserr.nim b/lib/pure/includes/oserr.nim index 947bdd9a9..68ce5d95f 100644 --- a/lib/pure/includes/oserr.nim +++ b/lib/pure/includes/oserr.nim @@ -1,6 +1,6 @@ # Include file that implements 'osErrorMsg' and friends. Do not import it! -when not declared(os): +when not declared(os) and not declared(ospaths): {.error: "This is an include file for os.nim!".} when not defined(nimscript): diff --git a/lib/pure/parsecfg.nim b/lib/pure/parsecfg.nim index d043cd321..d85359953 100644 --- a/lib/pure/parsecfg.nim +++ b/lib/pure/parsecfg.nim @@ -12,48 +12,46 @@ ## format, but much more powerful, as it is not a line based parser. String ## literals, raw string literals and triple quoted string literals are supported ## as in the Nim programming language. - -## This is an example of how a configuration file may look like: +## +## Example of how a configuration file may look like: ## ## .. include:: ../../doc/mytest.cfg ## :literal: ## - -##[ Here is an example of how to use the configuration file parser: - -.. code-block:: nim - - import - os, parsecfg, strutils, streams - - var f = newFileStream(paramStr(1), fmRead) - if f != nil: - var p: CfgParser - open(p, f, paramStr(1)) - while true: - var e = next(p) - case e.kind - of cfgEof: break - of cfgSectionStart: ## a ``[section]`` has been parsed - echo("new section: " & e.section) - of cfgKeyValuePair: - echo("key-value-pair: " & e.key & ": " & e.value) - of cfgOption: - echo("command: " & e.key & ": " & e.value) - of cfgError: - echo(e.msg) - close(p) - else: - echo("cannot open: " & paramStr(1)) - -]## - +## Here is an example of how to use the configuration file parser: +## +## .. code-block:: nim +## +## import os, parsecfg, strutils, streams +## +## var f = newFileStream(paramStr(1), fmRead) +## if f != nil: +## var p: CfgParser +## open(p, f, paramStr(1)) +## while true: +## var e = next(p) +## case e.kind +## of cfgEof: break +## of cfgSectionStart: ## a ``[section]`` has been parsed +## echo("new section: " & e.section) +## of cfgKeyValuePair: +## echo("key-value-pair: " & e.key & ": " & e.value) +## of cfgOption: +## echo("command: " & e.key & ": " & e.value) +## of cfgError: +## echo(e.msg) +## close(p) +## else: +## echo("cannot open: " & paramStr(1)) +## +## ## Examples -## -------- +## ======== ## -## This is an example of a configuration file. +## Configuration file example +## -------------------------- ## -## :: +## .. code-block:: nim ## ## charset = "utf-8" ## [Package] @@ -64,8 +62,8 @@ ## qq = "10214028" ## email = "lihaifeng@wxm.com" ## -## Creating a configuration file. -## ============================== +## Creating a configuration file +## ----------------------------- ## .. code-block:: nim ## ## import parsecfg @@ -78,8 +76,8 @@ ## dict.setSectionKey("Author","email","lihaifeng@wxm.com") ## dict.writeConfig("config.ini") ## -## Reading a configuration file. -## ============================= +## Reading a configuration file +## ---------------------------- ## .. code-block:: nim ## ## import parsecfg @@ -92,8 +90,8 @@ ## var email = dict.getSectionValue("Author","email") ## echo pname & "\n" & name & "\n" & qq & "\n" & email ## -## Modifying a configuration file. -## =============================== +## Modifying a configuration file +## ------------------------------ ## .. code-block:: nim ## ## import parsecfg @@ -101,8 +99,8 @@ ## dict.setSectionKey("Author","name","lhf") ## dict.writeConfig("config.ini") ## -## Deleting a section key in a configuration file. -## =============================================== +## Deleting a section key in a configuration file +## ---------------------------------------------- ## .. code-block:: nim ## ## import parsecfg @@ -443,17 +441,17 @@ proc next*(c: var CfgParser): CfgEvent {.rtl, extern: "npc$1".} = # ---------------- Configuration file related operations ---------------- type - Config* = OrderedTableRef[string, OrderedTableRef[string, string]] + Config* = OrderedTableRef[string, <//>OrderedTableRef[string, string]] proc newConfig*(): Config = ## Create a new configuration table. ## Useful when wanting to create a configuration file. - result = newOrderedTable[string, OrderedTableRef[string, string]]() + result = newOrderedTable[string, <//>OrderedTableRef[string, string]]() -proc loadConfig*(stream: Stream, filename: string = "[stream]"): Config = +proc loadConfig*(stream: Stream, filename: string = "[stream]"): <//>Config = ## Load the specified configuration from stream into a new Config instance. ## `filename` parameter is only used for nicer error messages. - var dict = newOrderedTable[string, OrderedTableRef[string, string]]() + var dict = newOrderedTable[string, <//>OrderedTableRef[string, string]]() var curSection = "" ## Current section, ## the default value of the current section is "", ## which means that the current section is a common @@ -483,7 +481,7 @@ proc loadConfig*(stream: Stream, filename: string = "[stream]"): Config = close(p) result = dict -proc loadConfig*(filename: string): Config = +proc loadConfig*(filename: string): <//>Config = ## Load the specified configuration file into a new Config instance. let file = open(filename, fmRead) let fileStream = newFileStream(file) diff --git a/lib/pure/parseutils.nim b/lib/pure/parseutils.nim index ba09347a2..a4032f8f3 100644 --- a/lib/pure/parseutils.nim +++ b/lib/pure/parseutils.nim @@ -64,104 +64,154 @@ const proc toLower(c: char): char {.inline.} = result = if c in {'A'..'Z'}: chr(ord(c)-ord('A')+ord('a')) else: c -proc parseHex*(s: string, number: var int, start = 0; maxLen = 0): int {. - rtl, extern: "npuParseHex", noSideEffect.} = - ## Parses a hexadecimal number and stores its value in ``number``. +proc parseBin*[T: SomeInteger](s: string, number: var T, start = 0, maxLen = 0): int + {.noSideEffect.} = + ## Parses a binary number and stores its value in ``number``. ## - ## Returns the number of the parsed characters or 0 in case of an error. This - ## proc is sensitive to the already existing value of ``number`` and will - ## likely not do what you want unless you make sure ``number`` is zero. You - ## can use this feature to *chain* calls, though the result int will quickly - ## overflow. + ## Returns the number of the parsed characters or 0 in case of an error. + ## If error, the value of ``number`` is not changed. ## - ## If ``maxLen == 0`` the length of the hexadecimal number has no upper bound. - ## Else no more than ``start + maxLen`` characters are parsed, up to the - ## length of the string. + ## If ``maxLen == 0``, the parsing continues until the first non-bin character + ## or to the end of the string. Otherwise, no more than ``maxLen`` characters + ## are parsed starting from the ``start`` position. + ## + ## It does not check for overflow. If the value represented by the string is + ## too big to fit into ``number``, only the value of last fitting characters + ## will be stored in ``number`` without producing an error. runnableExamples: - var value = 0 - discard parseHex("0x38", value) - assert value == 56 - discard parseHex("0x34", value) - assert value == 56 * 256 + 52 - value = -1 - discard parseHex("0x38", value) - assert value == -200 + var num: int + doAssert parseBin("0100_1110_0110_1001_1110_1101", num) == 29 + doAssert num == 5138925 + doAssert parseBin("3", num) == 0 + var num8: int8 + doAssert parseBin("0b_0100_1110_0110_1001_1110_1101", num8) == 32 + doAssert num8 == 0b1110_1101'i8 + doAssert parseBin("0b_0100_1110_0110_1001_1110_1101", num8, 3, 9) == 9 + doAssert num8 == 0b0100_1110'i8 + var num8u: uint8 + doAssert parseBin("0b_0100_1110_0110_1001_1110_1101", num8u) == 32 + doAssert num8u == 237 + var num64: int64 + doAssert parseBin("0100111001101001111011010100111001101001", num64) == 40 + doAssert num64 == 336784608873 var i = start + var output = T(0) var foundDigit = false - # get last index based on minimum `start + maxLen` or `s.len` - let last = min(s.len, if maxLen == 0: s.len else: i+maxLen) - if i+1 < last and s[i] == '0' and (s[i+1] in {'x', 'X'}): inc(i, 2) - elif i < last and s[i] == '#': inc(i) + let last = min(s.len, if maxLen == 0: s.len else: i + maxLen) + if i + 1 < last and s[i] == '0' and (s[i+1] in {'b', 'B'}): inc(i, 2) while i < last: case s[i] of '_': discard - of '0'..'9': - number = number shl 4 or (ord(s[i]) - ord('0')) - foundDigit = true - of 'a'..'f': - number = number shl 4 or (ord(s[i]) - ord('a') + 10) - foundDigit = true - of 'A'..'F': - number = number shl 4 or (ord(s[i]) - ord('A') + 10) + of '0'..'1': + output = output shl 1 or T(ord(s[i]) - ord('0')) foundDigit = true else: break inc(i) - if foundDigit: result = i-start + if foundDigit: + number = output + result = i - start -proc parseOct*(s: string, number: var int, start = 0, maxLen = 0): int {. - rtl, extern: "npuParseOct", noSideEffect.} = - ## Parses an octal number and stores its value in ``number``. Returns - ## the number of the parsed characters or 0 in case of an error. +proc parseOct*[T: SomeInteger](s: string, number: var T, start = 0, maxLen = 0): int + {.noSideEffect.} = + ## Parses an octal number and stores its value in ``number``. + ## + ## Returns the number of the parsed characters or 0 in case of an error. + ## If error, the value of ``number`` is not changed. ## - ## If ``maxLen == 0`` the length of the octal number has no upper bound. - ## Else no more than ``start + maxLen`` characters are parsed, up to the - ## length of the string. + ## If ``maxLen == 0``, the parsing continues until the first non-oct character + ## or to the end of the string. Otherwise, no more than ``maxLen`` characters + ## are parsed starting from the ``start`` position. + ## + ## It does not check for overflow. If the value represented by the string is + ## too big to fit into ``number``, only the value of last fitting characters + ## will be stored in ``number`` without producing an error. runnableExamples: - var res: int - doAssert parseOct("12", res) == 2 - doAssert res == 10 - doAssert parseOct("9", res) == 0 + var num: int + doAssert parseOct("0o23464755", num) == 10 + doAssert num == 5138925 + doAssert parseOct("8", num) == 0 + var num8: int8 + doAssert parseOct("0o_1464_755", num8) == 11 + doAssert num8 == -19 + doAssert parseOct("0o_1464_755", num8, 3, 3) == 3 + doAssert num8 == 102 + var num8u: uint8 + doAssert parseOct("1464755", num8u) == 7 + doAssert num8u == 237 + var num64: int64 + doAssert parseOct("2346475523464755", num64) == 16 + doAssert num64 == 86216859871725 var i = start + var output = T(0) var foundDigit = false - # get last index based on minimum `start + maxLen` or `s.len` - let last = min(s.len, if maxLen == 0: s.len else: i+maxLen) - if i+1 < last and s[i] == '0' and (s[i+1] in {'o', 'O'}): inc(i, 2) + let last = min(s.len, if maxLen == 0: s.len else: i + maxLen) + if i + 1 < last and s[i] == '0' and (s[i+1] in {'o', 'O'}): inc(i, 2) while i < last: case s[i] of '_': discard of '0'..'7': - number = number shl 3 or (ord(s[i]) - ord('0')) + output = output shl 3 or T(ord(s[i]) - ord('0')) foundDigit = true else: break inc(i) - if foundDigit: result = i-start + if foundDigit: + number = output + result = i - start -proc parseBin*(s: string, number: var int, start = 0, maxLen = 0): int {. - rtl, extern: "npuParseBin", noSideEffect.} = - ## Parses an binary number and stores its value in ``number``. Returns - ## the number of the parsed characters or 0 in case of an error. +proc parseHex*[T: SomeInteger](s: string, number: var T, start = 0, maxLen = 0): int + {.noSideEffect.} = + ## Parses a hexadecimal number and stores its value in ``number``. + ## + ## Returns the number of the parsed characters or 0 in case of an error. + ## If error, the value of ``number`` is not changed. ## - ## If ``maxLen == 0`` the length of the binary number has no upper bound. - ## Else no more than ``start + maxLen`` characters are parsed, up to the - ## length of the string. + ## If ``maxLen == 0``, the parsing continues until the first non-hex character + ## or to the end of the string. Otherwise, no more than ``maxLen`` characters + ## are parsed starting from the ``start`` position. + ## + ## It does not check for overflow. If the value represented by the string is + ## too big to fit into ``number``, only the value of last fitting characters + ## will be stored in ``number`` without producing an error. runnableExamples: - var res: int - doAssert parseBin("010011100110100101101101", res) == 24 - doAssert parseBin("3", res) == 0 + var num: int + doAssert parseHex("4E_69_ED", num) == 8 + doAssert num == 5138925 + doAssert parseHex("X", num) == 0 + doAssert parseHex("#ABC", num) == 4 + var num8: int8 + doAssert parseHex("0x_4E_69_ED", num8) == 11 + doAssert num8 == 0xED'i8 + doAssert parseHex("0x_4E_69_ED", num8, 3, 2) == 2 + doAssert num8 == 0x4E'i8 + var num8u: uint8 + doAssert parseHex("0x_4E_69_ED", num8u) == 11 + doAssert num8u == 237 + var num64: int64 + doAssert parseHex("4E69ED4E69ED", num64) == 12 + doAssert num64 == 86216859871725 var i = start + var output = T(0) var foundDigit = false - # get last index based on minimum `start + maxLen` or `s.len` - let last = min(s.len, if maxLen == 0: s.len else: i+maxLen) - if i+1 < last and s[i] == '0' and (s[i+1] in {'b', 'B'}): inc(i, 2) + let last = min(s.len, if maxLen == 0: s.len else: i + maxLen) + if i + 1 < last and s[i] == '0' and (s[i+1] in {'x', 'X'}): inc(i, 2) + elif i < last and s[i] == '#': inc(i) while i < last: case s[i] of '_': discard - of '0'..'1': - number = number shl 1 or (ord(s[i]) - ord('0')) + of '0'..'9': + output = output shl 4 or T(ord(s[i]) - ord('0')) + foundDigit = true + of 'a'..'f': + output = output shl 4 or T(ord(s[i]) - ord('a') + 10) + foundDigit = true + of 'A'..'F': + output = output shl 4 or T(ord(s[i]) - ord('A') + 10) foundDigit = true else: break inc(i) - if foundDigit: result = i-start + if foundDigit: + number = output + result = i - start proc parseIdent*(s: string, ident: var string, start = 0): int = ## Parses an identifier and stores it in ``ident``. Returns @@ -605,11 +655,6 @@ when isMainModule: var value = 0 discard parseHex("0x38", value) doAssert value == 56 - discard parseHex("0x34", value) - doAssert value == 56 * 256 + 52 - value = -1 - discard parseHex("0x38", value) - doAssert value == -200 value = -1 doAssert(parseSaturatedNatural("848", value) == 3) diff --git a/lib/pure/random.nim b/lib/pure/random.nim index e29ad7955..dddbd9d58 100644 --- a/lib/pure/random.nim +++ b/lib/pure/random.nim @@ -129,7 +129,7 @@ proc next*(r: var Rand): uint64 = ## a given upper bound ## * `rand proc<#rand,Rand,range[]>`_ that returns a float ## * `rand proc<#rand,Rand,HSlice[T,T]>`_ that accepts a slice - ## * `rand proc<#rand,typedesc[T]>`_ that accepts an integer type + ## * `rand proc<#rand,typedesc[T]>`_ that accepts an integer or range type ## * `skipRandomNumbers proc<#skipRandomNumbers,Rand>`_ runnableExamples: var r = initRand(2019) @@ -242,7 +242,7 @@ proc rand*(r: var Rand; max: Natural): int {.benign.} = ## random number generator ## * `rand proc<#rand,Rand,range[]>`_ that returns a float ## * `rand proc<#rand,Rand,HSlice[T,T]>`_ that accepts a slice - ## * `rand proc<#rand,typedesc[T]>`_ that accepts an integer type + ## * `rand proc<#rand,typedesc[T]>`_ that accepts an integer or range type runnableExamples: var r = initRand(123) doAssert r.rand(100) == 0 @@ -268,7 +268,7 @@ proc rand*(max: int): int {.benign.} = ## provided state ## * `rand proc<#rand,float>`_ that returns a float ## * `rand proc<#rand,HSlice[T,T]>`_ that accepts a slice - ## * `rand proc<#rand,typedesc[T]>`_ that accepts an integer type + ## * `rand proc<#rand,typedesc[T]>`_ that accepts an integer or range type runnableExamples: randomize(123) doAssert rand(100) == 0 @@ -285,7 +285,7 @@ proc rand*(r: var Rand; max: range[0.0 .. high(float)]): float {.benign.} = ## random number generator ## * `rand proc<#rand,Rand,Natural>`_ that returns an integer ## * `rand proc<#rand,Rand,HSlice[T,T]>`_ that accepts a slice - ## * `rand proc<#rand,typedesc[T]>`_ that accepts an integer type + ## * `rand proc<#rand,typedesc[T]>`_ that accepts an integer or range type runnableExamples: var r = initRand(234) let f = r.rand(1.0) @@ -311,7 +311,7 @@ proc rand*(max: float): float {.benign.} = ## provided state ## * `rand proc<#rand,int>`_ that returns an integer ## * `rand proc<#rand,HSlice[T,T]>`_ that accepts a slice - ## * `rand proc<#rand,typedesc[T]>`_ that accepts an integer type + ## * `rand proc<#rand,typedesc[T]>`_ that accepts an integer or range type runnableExamples: randomize(234) let f = rand(1.0) @@ -327,7 +327,7 @@ proc rand*[T: Ordinal](r: var Rand; x: HSlice[T, T]): T = ## default random number generator ## * `rand proc<#rand,Rand,Natural>`_ that returns an integer ## * `rand proc<#rand,Rand,range[]>`_ that returns a float - ## * `rand proc<#rand,typedesc[T]>`_ that accepts an integer type + ## * `rand proc<#rand,typedesc[T]>`_ that accepts an integer or range type runnableExamples: var r = initRand(345) doAssert r.rand(1..6) == 4 @@ -349,7 +349,7 @@ proc rand*[T: Ordinal](x: HSlice[T, T]): T = ## a provided state ## * `rand proc<#rand,int>`_ that returns an integer ## * `rand proc<#rand,float>`_ that returns a floating point number - ## * `rand proc<#rand,typedesc[T]>`_ that accepts an integer type + ## * `rand proc<#rand,typedesc[T]>`_ that accepts an integer or range type runnableExamples: randomize(345) doAssert rand(1..6) == 4 @@ -383,7 +383,13 @@ proc rand*[T: SomeInteger](t: typedesc[T]): T = doAssert rand(uint32) == 578980729'u32 doAssert rand(uint32) == 4052940463'u32 doAssert rand(uint32) == 2163872389'u32 - result = cast[T](state.next) + doAssert rand(range[1..16]) == 11 + doAssert rand(range[1..16]) == 4 + doAssert rand(range[1..16]) == 16 + when T is range: + result = rand(state, low(T)..high(T)) + else: + result = cast[T](state.next) proc rand*[T](a: openArray[T]): T {.deprecated.} = ## **Deprecated since version 0.20.0:** diff --git a/lib/pure/streams.nim b/lib/pure/streams.nim index ff2b72725..83c8c71ec 100644 --- a/lib/pure/streams.nim +++ b/lib/pure/streams.nim @@ -8,29 +8,91 @@ # ## This module provides a stream interface and two implementations thereof: -## the `FileStream` and the `StringStream` which implement the stream -## interface for Nim file objects (`File`) and strings. Other modules -## may provide other implementations for this standard stream interface. +## the `FileStream <#FileStream>`_ and the `StringStream <#StringStream>`_ +## which implement the stream interface for Nim file objects (`File`) and +## strings. ## -## Examples: +## Other modules may provide other implementations for this standard +## stream interface. +## +## Basic usage +## =========== +## +## The basic flow of using this module is: +## +## 1. Open input stream +## 2. Read or write stream +## 3. Close stream +## +## StringStream example +## -------------------- ## ## .. code-block:: Nim ## ## import streams -## var -## ss = newStringStream("""The first line +## +## var strm = newStringStream("""The first line ## the second line ## the third line""") -## line = "" -## while ss.readLine(line): +## +## var line = "" +## +## while strm.readLine(line): ## echo line -## ss.close() ## -## var fs = newFileStream("somefile.txt", fmRead) -## if not isNil(fs): -## while fs.readLine(line): +## # Output: +## # The first line +## # the second line +## # the third line +## +## strm.close() +## +## FileStream example +## ------------------ +## +## Write file stream example: +## +## .. code-block:: Nim +## +## import streams +## +## 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() +## +## # Output (somefile.txt): +## # The first line +## # the second line +## # the third line +## +## Read file stream example: +## +## .. code-block:: Nim +## +## import streams +## +## var strm = newFileStream("somefile.txt", fmRead) +## var line = "" +## +## if not isNil(strm): +## while strm.readLine(line): ## echo line -## fs.close() +## strm.close() +## +## # 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>`_ include "system/inclrtl" @@ -40,11 +102,14 @@ proc newEIO(msg: string): owned(ref IOError) = type Stream* = ref StreamObj - StreamObj* = object of RootObj ## Stream interface that supports - ## writing or reading. Note that these fields - ## here shouldn't be used directly. They are - ## accessible so that a stream implementation - ## can override them. + ## All procedures of this module use this type. + ## Procedures don't directly use `StreamObj <#StreamObj>`_. + StreamObj* = object of RootObj + ## Stream interface that supports writing or reading. + ## + ## **Note:** + ## * 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.} atEndImpl*: proc (s: Stream): bool @@ -68,32 +133,103 @@ type {.nimcall, raises: [Defect, IOError, OSError], tags: [WriteIOEffect], gcsafe.} proc flush*(s: Stream) = - ## flushes the buffers that the stream `s` might use. + ## Flushes the buffers that the stream `s` might use. + ## + ## This procedure causes any unwritten data for that stream to be delivered + ## to the host environment to be written to the file. + ## + ## See also: + ## * `close proc <#close,Stream>`_ + runnableExamples: + from os import removeFile + + var strm = newFileStream("somefile.txt", fmWrite) + + doAssert "Before write:" & readFile("somefile.txt") == "Before write:" + strm.write("hello") + doAssert "After write:" & readFile("somefile.txt") == "After write:" + + strm.flush() + doAssert "After flush:" & readFile("somefile.txt") == "After flush:hello" + strm.write("HELLO") + strm.flush() + doAssert "After flush:" & readFile("somefile.txt") == "After flush:helloHELLO" + + strm.close() + doAssert "After close:" & readFile("somefile.txt") == "After close:helloHELLO" + removeFile("somefile.txt") + if not isNil(s.flushImpl): s.flushImpl(s) proc close*(s: Stream) = - ## closes the stream `s`. + ## Closes the stream `s`. + ## + ## 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) proc atEnd*(s: Stream): bool = - ## checks if more data can be read from `f`. Returns true if all data has + ## Checks if more data can be read from `s`. Returns ``true`` if all data has ## been read. + runnableExamples: + var strm = newStringStream("The first line\nthe second line\nthe third line") + var line = "" + doAssert strm.atEnd() == false + while strm.readLine(line): + discard + doAssert strm.atEnd() == true + strm.close() + result = s.atEndImpl(s) proc setPosition*(s: Stream, pos: int) = - ## sets the position `pos` of the stream `s`. + ## Sets the position `pos` of the stream `s`. + runnableExamples: + var strm = newStringStream("The first line\nthe second line\nthe third line") + strm.setPosition(4) + doAssert strm.readLine() == "first line" + strm.setPosition(0) + doAssert strm.readLine() == "The first line" + strm.close() + s.setPositionImpl(s, pos) proc getPosition*(s: Stream): int = - ## retrieves the current position in the stream `s`. + ## Retrieves the current position in the stream `s`. + runnableExamples: + var strm = newStringStream("The first line\nthe second line\nthe third line") + doAssert strm.getPosition() == 0 + discard strm.readLine() + doAssert strm.getPosition() == 15 + strm.close() + result = s.getPositionImpl(s) 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. + runnableExamples: + var strm = newStringStream("abcde") + var buffer: array[6, char] + doAssert strm.readData(addr(buffer), 1024) == 5 + doAssert buffer == ['a', 'b', 'c', 'd', 'e', '\x00'] + doAssert strm.atEnd() == true + strm.close() + result = s.readDataImpl(s, buffer, bufLen) proc readDataStr*(s: Stream, buffer: var string, slice: Slice[int]): int = - ## low level proc that reads data into a string ``buffer`` at ``slice``. + ## Low level proc that reads data into a string ``buffer`` at ``slice``. + runnableExamples: + var strm = newStringStream("abcde") + var buffer = "12345" + doAssert strm.readDataStr(buffer, 0..3) == 4 + doAssert buffer == "abcd5" + strm.close() + if s.readDataStrImpl != nil: result = s.readDataStrImpl(s, buffer, slice) else: @@ -103,6 +239,14 @@ proc readDataStr*(s: Stream, buffer: var string, slice: Slice[int]): int = when 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" + doAssert strm.atEnd() == true + strm.close() + const bufferSize = 1024 var buffer {.noinit.}: array[bufferSize, char] while true: @@ -116,173 +260,611 @@ when not defined(js): break 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 + ## Low level proc that reads data into an untyped `buffer` of `bufLen` size + ## without moving stream position. + runnableExamples: + var strm = newStringStream("abcde") + var buffer: array[6, char] + doAssert strm.peekData(addr(buffer), 1024) == 5 + doAssert buffer == ['a', 'b', 'c', 'd', 'e', '\x00'] + doAssert strm.atEnd() == false + strm.close() + result = s.peekDataImpl(s, buffer, bufLen) 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`. + runnableExamples: + ## writeData + var strm = newStringStream("") + var buffer = ['a', 'b', 'c', 'd', 'e'] + strm.writeData(addr(buffer), sizeof(buffer)) + doAssert strm.atEnd() == true + ## readData + strm.setPosition(0) + var buffer2: array[6, char] + doAssert strm.readData(addr(buffer2), sizeof(buffer2)) == 5 + doAssert buffer2 == ['a', 'b', 'c', 'd', 'e', '\x00'] + strm.close() + s.writeDataImpl(s, buffer, bufLen) 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: ## ## .. code-block:: Nim ## ## s.writeData(s, addr(x), sizeof(x)) + runnableExamples: + var strm = newStringStream("") + strm.write("abcde") + strm.setPosition(0) + doAssert strm.readAll() == "abcde" + strm.close() + var y: T shallowCopy(y, x) writeData(s, addr(y), sizeof(y)) 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 the stream `s`. No length field or ## terminating zero is written. + runnableExamples: + var strm = newStringStream("") + strm.write("THE FIRST LINE") + strm.setPosition(0) + doAssert strm.readLine() == "THE FIRST LINE" + strm.close() + when nimvm: writeData(s, cstring(x), x.len) else: if x.len > 0: 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 + ## Writes one or more strings to the the stream. No length fields or ## terminating zeros are written. + runnableExamples: + var strm = newStringStream("") + strm.write(1, 2, 3, 4) + strm.setPosition(0) + doAssert strm.readLine() == "1234" + strm.close() + for str in args: write(s, str) proc writeLine*(s: Stream, args: varargs[string, `$`]) = - ## writes one or more strings to the the stream `s` followed + ## Writes one or more strings to the the stream `s` followed ## by a new line. No length field or terminating zero is written. + runnableExamples: + var strm = newStringStream("") + strm.writeLine(1, 2) + strm.writeLine(3, 4) + strm.setPosition(0) + doAssert strm.readAll() == "12\n34\n" + strm.close() + for str in args: write(s, str) write(s, "\n") 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`. + runnableExamples: + var strm = newStringStream("012") + ## readInt + var i: int8 + strm.read(i) + doAssert i == 48 + ## readData + var buffer: array[2, char] + strm.read(buffer) + doAssert buffer == ['1', '2'] + strm.close() + if readData(s, addr(result), sizeof(T)) != sizeof(T): raise newEIO("cannot read from stream") 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`. + runnableExamples: + var strm = newStringStream("012") + ## peekInt + var i: int8 + strm.peek(i) + doAssert i == 48 + ## peekData + var buffer: array[2, char] + strm.peek(buffer) + doAssert buffer == ['0', '1'] + strm.close() + if peekData(s, addr(result), sizeof(T)) != sizeof(T): raise newEIO("cannot read from stream") proc readChar*(s: Stream): char = - ## reads a char from the stream `s`. Raises `IOError` if an error occurred. + ## Reads a char from the stream `s`. + ## + ## Raises `IOError` if an error occurred. ## Returns '\\0' as an EOF marker. + runnableExamples: + var strm = newStringStream("12\n3") + doAssert strm.readChar() == '1' + doAssert strm.readChar() == '2' + doAssert strm.readChar() == '\n' + doAssert strm.readChar() == '3' + doAssert strm.readChar() == '\x00' + strm.close() + if readData(s, addr(result), sizeof(result)) != 1: result = '\0' proc peekChar*(s: Stream): char = - ## peeks a char from the stream `s`. Raises `IOError` if an error occurred. + ## Peeks a char from the stream `s`. Raises `IOError` if an error occurred. ## Returns '\\0' as an EOF marker. + runnableExamples: + var strm = newStringStream("12\n3") + doAssert strm.peekChar() == '1' + doAssert strm.peekChar() == '1' + discard strm.readAll() + doAssert strm.peekChar() == '\x00' + strm.close() + if peekData(s, addr(result), sizeof(result)) != 1: result = '\0' proc readBool*(s: Stream): bool = - ## reads a bool from the stream `s`. Raises `IOError` if an error occurred. + ## Reads a bool from the stream `s`. Raises `IOError` if an error occurred. + runnableExamples: + var strm = newStringStream() + ## setup for reading data + strm.write(true) + strm.write(false) + strm.flush() + strm.setPosition(0) + ## get data + doAssert strm.readBool() == true + doAssert strm.readBool() == false + doAssertRaises(IOError): discard strm.readBool() + strm.close() + read(s, result) proc peekBool*(s: Stream): bool = - ## peeks a bool from the stream `s`. Raises `IOError` if an error occurred. + ## Peeks a bool from the stream `s`. Raises `IOError` if an error occurred. + runnableExamples: + var strm = newStringStream() + ## setup for reading data + strm.write(true) + strm.write(false) + strm.flush() + strm.setPosition(0) + ## get data + doAssert strm.peekBool() == true + ## not false + doAssert strm.peekBool() == true + doAssert strm.readBool() == true + doAssert strm.peekBool() == false + strm.close() + peek(s, result) 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. + runnableExamples: + var strm = newStringStream() + ## setup for reading data + strm.write(1'i8) + strm.write(2'i8) + strm.flush() + strm.setPosition(0) + ## get data + doAssert strm.readInt8() == 1'i8 + doAssert strm.readInt8() == 2'i8 + doAssertRaises(IOError): discard strm.readInt8() + strm.close() + read(s, result) 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. + runnableExamples: + var strm = newStringStream() + ## setup for reading data + strm.write(1'i8) + strm.write(2'i8) + strm.flush() + strm.setPosition(0) + ## get data + doAssert strm.peekInt8() == 1'i8 + ## not 2'i8 + doAssert strm.peekInt8() == 1'i8 + doAssert strm.readInt8() == 1'i8 + doAssert strm.peekInt8() == 2'i8 + strm.close() + peek(s, result) 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. + runnableExamples: + var strm = newStringStream() + ## setup for reading data + strm.write(1'i16) + strm.write(2'i16) + strm.flush() + strm.setPosition(0) + ## get data + doAssert strm.readInt16() == 1'i16 + doAssert strm.readInt16() == 2'i16 + doAssertRaises(IOError): discard strm.readInt16() + strm.close() + read(s, result) 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. + runnableExamples: + var strm = newStringStream() + ## setup for reading data + strm.write(1'i16) + strm.write(2'i16) + strm.flush() + strm.setPosition(0) + ## get data + doAssert strm.peekInt16() == 1'i16 + ## not 2'i16 + doAssert strm.peekInt16() == 1'i16 + doAssert strm.readInt16() == 1'i16 + doAssert strm.peekInt16() == 2'i16 + strm.close() + peek(s, result) 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. + runnableExamples: + var strm = newStringStream() + ## setup for reading data + strm.write(1'i32) + strm.write(2'i32) + strm.flush() + strm.setPosition(0) + ## get data + doAssert strm.readInt32() == 1'i32 + doAssert strm.readInt32() == 2'i32 + doAssertRaises(IOError): discard strm.readInt32() + strm.close() + read(s, result) 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. + runnableExamples: + var strm = newStringStream() + ## setup for reading data + strm.write(1'i32) + strm.write(2'i32) + strm.flush() + strm.setPosition(0) + ## get data + doAssert strm.peekInt32() == 1'i32 + ## not 2'i32 + doAssert strm.peekInt32() == 1'i32 + doAssert strm.readInt32() == 1'i32 + doAssert strm.peekInt32() == 2'i32 + strm.close() + peek(s, result) 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. + runnableExamples: + var strm = newStringStream() + ## setup for reading data + strm.write(1'i64) + strm.write(2'i64) + strm.flush() + strm.setPosition(0) + ## get data + doAssert strm.readInt64() == 1'i64 + doAssert strm.readInt64() == 2'i64 + doAssertRaises(IOError): discard strm.readInt64() + strm.close() + read(s, result) 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. + runnableExamples: + var strm = newStringStream() + ## setup for reading data + strm.write(1'i64) + strm.write(2'i64) + strm.flush() + strm.setPosition(0) + ## get data + doAssert strm.peekInt64() == 1'i64 + ## not 2'i64 + doAssert strm.peekInt64() == 1'i64 + doAssert strm.readInt64() == 1'i64 + doAssert strm.peekInt64() == 2'i64 + strm.close() + peek(s, result) 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. + runnableExamples: + var strm = newStringStream() + ## setup for reading data + strm.write(1'u8) + strm.write(2'u8) + strm.flush() + strm.setPosition(0) + ## get data + doAssert strm.readUint8() == 1'u8 + doAssert strm.readUint8() == 2'u8 + doAssertRaises(IOError): discard strm.readUint8() + strm.close() + read(s, result) 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. + runnableExamples: + var strm = newStringStream() + ## setup for reading data + strm.write(1'u8) + strm.write(2'u8) + strm.flush() + strm.setPosition(0) + ## get data + doAssert strm.peekUint8() == 1'u8 + ## not 2'u8 + doAssert strm.peekUint8() == 1'u8 + doAssert strm.readUint8() == 1'u8 + doAssert strm.peekUint8() == 2'u8 + strm.close() + peek(s, result) 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. + runnableExamples: + var strm = newStringStream() + ## setup for reading data + strm.write(1'u16) + strm.write(2'u16) + strm.flush() + strm.setPosition(0) + ## get data + doAssert strm.readUint16() == 1'u16 + doAssert strm.readUint16() == 2'u16 + doAssertRaises(IOError): discard strm.readUint16() + strm.close() + read(s, result) 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. + runnableExamples: + var strm = newStringStream() + ## setup for reading data + strm.write(1'u16) + strm.write(2'u16) + strm.flush() + strm.setPosition(0) + ## get data + doAssert strm.peekUint16() == 1'u16 + ## not 2'u16 + doAssert strm.peekUint16() == 1'u16 + doAssert strm.readUint16() == 1'u16 + doAssert strm.peekUint16() == 2'u16 + strm.close() + peek(s, result) 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. + runnableExamples: + var strm = newStringStream() + ## setup for reading data + strm.write(1'u32) + strm.write(2'u32) + strm.flush() + strm.setPosition(0) + + ## get data + doAssert strm.readUint32() == 1'u32 + doAssert strm.readUint32() == 2'u32 + doAssertRaises(IOError): discard strm.readUint32() + strm.close() + read(s, result) 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. + runnableExamples: + var strm = newStringStream() + ## setup for reading data + strm.write(1'u32) + strm.write(2'u32) + strm.flush() + strm.setPosition(0) + ## get data + doAssert strm.peekUint32() == 1'u32 + ## not 2'u32 + doAssert strm.peekUint32() == 1'u32 + doAssert strm.readUint32() == 1'u32 + doAssert strm.peekUint32() == 2'u32 + strm.close() + peek(s, result) 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. + runnableExamples: + var strm = newStringStream() + ## setup for reading data + strm.write(1'u64) + strm.write(2'u64) + strm.flush() + strm.setPosition(0) + ## get data + doAssert strm.readUint64() == 1'u64 + doAssert strm.readUint64() == 2'u64 + doAssertRaises(IOError): discard strm.readUint64() + strm.close() + read(s, result) 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. + runnableExamples: + var strm = newStringStream() + ## setup for reading data + strm.write(1'u64) + strm.write(2'u64) + strm.flush() + strm.setPosition(0) + ## get data + doAssert strm.peekUint64() == 1'u64 + ## not 2'u64 + doAssert strm.peekUint64() == 1'u64 + doAssert strm.readUint64() == 1'u64 + doAssert strm.peekUint64() == 2'u64 + strm.close() + peek(s, result) 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. + runnableExamples: + var strm = newStringStream() + ## setup for reading data + strm.write(1'f32) + strm.write(2'f32) + strm.flush() + strm.setPosition(0) + ## get data + doAssert strm.readFloat32() == 1'f32 + doAssert strm.readFloat32() == 2'f32 + doAssertRaises(IOError): discard strm.readFloat32() + strm.close() + read(s, result) 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. + runnableExamples: + var strm = newStringStream() + ## setup for reading data + strm.write(1'f32) + strm.write(2'f32) + strm.flush() + strm.setPosition(0) + ## get data + doAssert strm.peekFloat32() == 1'f32 + ## not 2'f32 + doAssert strm.peekFloat32() == 1'f32 + doAssert strm.readFloat32() == 1'f32 + doAssert strm.peekFloat32() == 2'f32 + strm.close() + peek(s, result) 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. + runnableExamples: + var strm = newStringStream() + ## setup for reading data + strm.write(1'f64) + strm.write(2'f64) + strm.flush() + strm.setPosition(0) + ## get data + doAssert strm.readFloat64() == 1'f64 + doAssert strm.readFloat64() == 2'f64 + doAssertRaises(IOError): discard strm.readFloat64() + strm.close() + read(s, result) 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. + runnableExamples: + var strm = newStringStream() + ## setup for reading data + strm.write(1'f64) + strm.write(2'f64) + strm.flush() + strm.setPosition(0) + ## get data + doAssert strm.peekFloat64() == 1'f64 + ## not 2'f64 + doAssert strm.peekFloat64() == 1'f64 + doAssert strm.readFloat64() == 1'f64 + doAssert strm.peekFloat64() == 2'f64 + strm.close() + peek(s, result) proc readStr*(s: Stream, length: int): TaintedString = - ## 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 ## an error occurred. + runnableExamples: + var strm = newStringStream("abcde") + doAssert strm.readStr(2) == "ab" + doAssert strm.readStr(2) == "cd" + doAssert strm.readStr(2) == "e" + doAssert strm.readStr(2) == "" + strm.close() + result = newString(length).TaintedString var L = readData(s, cstring(result), length) if L != length: setLen(result.string, L) proc peekStr*(s: Stream, length: int): TaintedString = - ## 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 ## an error occurred. + runnableExamples: + var strm = newStringStream("abcde") + doAssert strm.peekStr(2) == "ab" + ## not "cd + doAssert strm.peekStr(2) == "ab" + doAssert strm.readStr(2) == "ab" + doAssert strm.peekStr(2) == "cd" + strm.close() + 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 = - ## reads a line of text from the stream `s` into `line`. `line` must not be + ## Reads a line of text from the stream `s` into `line`. `line` must not be ## ``nil``! May throw an IO exception. + ## ## A line of text may be delimited by ``LF`` or ``CRLF``. ## The newline character(s) are not part of the returned string. ## Returns ``false`` if the end of the file has been reached, ``true`` ## otherwise. If ``false`` is returned `line` contains no new data. + ## + ## See also: + ## * `readLine(Stream) proc <#readLine,Stream>`_ + ## * `peekLine(Stream) proc <#peekLine,Stream>`_ + ## * `peekLine(Stream, TaintedString) proc <#peekLine,Stream,TaintedString>`_ + runnableExamples: + var strm = newStringStream("The first line\nthe second line\nthe third line") + var line = "" + doAssert strm.readLine(line) == true + doAssert line == "The first line" + doAssert strm.readLine(line) == true + doAssert line == "the second line" + doAssert strm.readLine(line) == true + doAssert line == "the third line" + doAssert strm.readLine(line) == false + doAssert line == "" + strm.close() + line.string.setLen(0) while true: var c = readChar(s) @@ -297,19 +879,53 @@ proc readLine*(s: Stream, line: var TaintedString): bool = result = true proc peekLine*(s: Stream, line: var TaintedString): bool = - ## peeks a line of text from the stream `s` into `line`. `line` must not be + ## Peeks a line of text from the stream `s` into `line`. `line` must not be ## ``nil``! May throw an IO exception. + ## ## A line of text may be delimited by ``CR``, ``LF`` or ## ``CRLF``. The newline character(s) are not part of the returned string. ## Returns ``false`` if the end of the file has been reached, ``true`` ## otherwise. If ``false`` is returned `line` contains no new data. + ## + ## See also: + ## * `readLine(Stream) proc <#readLine,Stream>`_ + ## * `readLine(Stream, TaintedString) proc <#readLine,Stream,TaintedString>`_ + ## * `peekLine(Stream) proc <#peekLine,Stream>`_ + runnableExamples: + var strm = newStringStream("The first line\nthe second line\nthe third line") + var line = "" + doAssert strm.peekLine(line) == true + doAssert line == "The first line" + doAssert strm.peekLine(line) == true + ## not "the second line" + doAssert line == "The first line" + doAssert strm.readLine(line) == true + doAssert line == "The first line" + doAssert strm.peekLine(line) == true + doAssert line == "the second line" + strm.close() + let pos = getPosition(s) defer: setPosition(s, pos) result = readLine(s, line) proc readLine*(s: Stream): TaintedString = - ## Reads a line from a stream `s`. Note: This is not very efficient. Raises - ## `IOError` if an error occurred. + ## 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>`_ + ## * `peekLine(Stream) proc <#peekLine,Stream>`_ + ## * `peekLine(Stream, TaintedString) proc <#peekLine,Stream,TaintedString>`_ + runnableExamples: + var strm = newStringStream("The first line\nthe second line\nthe third line") + doAssert strm.readLine() == "The first line" + doAssert strm.readLine() == "the second line" + doAssert strm.readLine() == "the third line" + doAssertRaises(IOError): discard strm.readLine() + strm.close() + result = TaintedString"" if s.atEnd: raise newEIO("cannot read from stream") @@ -324,8 +940,23 @@ proc readLine*(s: Stream): TaintedString = result.string.add(c) proc peekLine*(s: Stream): TaintedString = - ## Peeks a line from a stream `s`. Note: This is not very efficient. Raises - ## `IOError` if an error occurred. + ## 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>`_ + runnableExamples: + var strm = newStringStream("The first line\nthe second line\nthe third line") + doAssert strm.peekLine() == "The first line" + ## not "the second line" + doAssert strm.peekLine() == "The first line" + doAssert strm.readLine() == "The first line" + doAssert strm.peekLine() == "the second line" + strm.close() + let pos = getPosition(s) defer: setPosition(s, pos) result = readLine(s) @@ -333,6 +964,18 @@ proc peekLine*(s: Stream): TaintedString = iterator lines*(s: Stream): TaintedString = ## 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>`_ + runnableExamples: + var strm = newStringStream("The first line\nthe second line\nthe third line") + var lines: seq[string] + for line in strm.lines(): + lines.add line + doAssert lines == @["The first line", "the second line", "the third line"] + strm.close() + var line: TaintedString while s.readLine(line): yield line @@ -340,9 +983,16 @@ iterator lines*(s: Stream): TaintedString = when not defined(js): type - StringStream* = ref StringStreamObj ## a stream that encapsulates a string + StringStream* = ref StringStreamObj + ## A stream that encapsulates a string. + ## + ## **Note:** Not available for JS backend. StringStreamObj* = object of StreamObj - data*: string + ## A string stream object. + ## + ## **Note:** Not available for JS backend. + data*: string ## A string data. + ## This is updated when called `writeLine` etc. pos: int proc ssAtEnd(s: Stream): bool = @@ -404,7 +1054,24 @@ when not defined(js): s.data = nil 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: + ## * `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. + ## * `openFileStream proc <#openFileStream,string,FileMode,int>`_ creates a + ## file stream from the file name and the mode. + runnableExamples: + var strm = newStringStream("The first line\nthe second line\nthe third line") + doAssert strm.readLine() == "The first line" + doAssert strm.readLine() == "the second line" + doAssert strm.readLine() == "the third line" + strm.close() + new(result) result.data = s result.pos = 0 @@ -418,8 +1085,14 @@ when not defined(js): result.readDataStrImpl = ssReadDataStr type - FileStream* = ref FileStreamObj ## a stream that encapsulates a `File` + 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) = @@ -447,7 +1120,35 @@ when not defined(js): raise newEIO("cannot write to stream") proc newFileStream*(f: File): owned FileStream = - ## creates a new stream from the file `f`. + ## 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 <system.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 @@ -461,26 +1162,76 @@ when not defined(js): 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 `system - ## <system.html>`_ module for a list of available FileMode enums. - ## **This function returns nil in case of failure. To prevent unexpected - ## behavior and ensure proper error handling, use openFileStream instead.** + ## 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`. + ## 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") + raise newEIO("cannot open file\n") -when true: - discard -else: +when false: type FileHandleStream* = ref FileHandleStreamObj FileHandleStreamObj* = object of Stream diff --git a/lib/pure/strformat.nim b/lib/pure/strformat.nim index 19fbf8ab3..fdf1753a8 100644 --- a/lib/pure/strformat.nim +++ b/lib/pure/strformat.nim @@ -419,6 +419,9 @@ proc formatValue*(result: var string; value: SomeInteger; specifier: string) = ## Standard format implementation for ``SomeInteger``. It makes little ## sense to call this directly, but it is required to exist ## by the ``&`` macro. + if specifier.len == 0: + result.add $value + return let spec = parseStandardFormatSpecifier(specifier) var radix = 10 case spec.typ @@ -432,10 +435,13 @@ proc formatValue*(result: var string; value: SomeInteger; specifier: string) = " of 'x', 'X', 'b', 'd', 'o' but got: " & spec.typ) result.add formatInt(value, radix, spec) -proc formatValue*(result: var string; value: SomeFloat; specifier: string): void = +proc formatValue*(result: var string; value: SomeFloat; specifier: string) = ## Standard format implementation for ``SomeFloat``. It makes little ## sense to call this directly, but it is required to exist ## by the ``&`` macro. + if specifier.len == 0: + result.add $value + return let spec = parseStandardFormatSpecifier(specifier) var fmode = ffDefault @@ -457,7 +463,7 @@ proc formatValue*(result: var string; value: SomeFloat; specifier: string): void if value >= 0.0: if spec.sign != '-': sign = true - if value == 0.0: + if value == 0.0: if 1.0 / value == Inf: # only insert the sign if value != negZero f.insert($spec.sign, 0) @@ -467,16 +473,16 @@ proc formatValue*(result: var string; value: SomeFloat; specifier: string): void sign = true if spec.padWithZero: - var sign_str = "" + var signStr = "" if sign: - sign_str = $f[0] + signStr = $f[0] f = f[1..^1] let toFill = spec.minimumWidth - f.len - ord(sign) if toFill > 0: f = repeat('0', toFill) & f if sign: - f = sign_str & f + f = signStr & f # the default for numbers is right-alignment: let align = if spec.align == '\0': '>' else: spec.align @@ -712,6 +718,13 @@ when isMainModule: for s in invalidUtf8: check &"{s:>5}", repeat(" ", 5-s.len) & s + # bug #11089 + let flfoo: float = 1.0 + check &"{flfoo}", "1.0" + + # bug #11092 + check &"{high(int64)}", "9223372036854775807" + check &"{low(int64)}", "-9223372036854775808" import json diff --git a/lib/pure/times.nim b/lib/pure/times.nim index 8074c09e8..dc79877fd 100644 --- a/lib/pure/times.nim +++ b/lib/pure/times.nim @@ -17,7 +17,8 @@ resolution used by ``getTime()`` depends on the platform and backend (JS is limited to millisecond precision). - Examples: + Examples + ======== .. code-block:: nim import times, os @@ -38,7 +39,7 @@ echo "One month from now : ", now() + 1.months Parsing and Formatting Dates - ---------------------------- + ============================ The ``DateTime`` type can be parsed and formatted using the different ``parse`` and ``format`` procedures. @@ -128,7 +129,7 @@ only for years in the range 1..9999). Duration vs TimeInterval - ---------------------------- + ============================ The ``times`` module exports two similiar types that are both used to represent some amount of time: `Duration <#Duration>`_ and `TimeInterval <#TimeInterval>`_. @@ -137,7 +138,7 @@ needed). Duration - ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + ---------------------------- A ``Duration`` represents a duration of time stored as seconds and nanoseconds. A ``Duration`` is always fully normalized, so ``initDuration(hours = 1)`` and ``initDuration(minutes = 60)`` are equivilant. @@ -147,7 +148,7 @@ is more performant and easier to understand it should generally prefered. TimeInterval - ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + ---------------------------- A ``TimeInterval`` represents some amount of time expressed in calendar units, for example "1 year and 2 days". Since some units cannot be normalized (the length of a year is different for leap years for example), @@ -164,7 +165,7 @@ ``Duration`` doesn't have. How long is a day? - ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + ---------------------------- It should be especially noted that the handling of days differs between ``TimeInterval`` and ``Duration``. The ``Duration`` type always treats a day as exactly 86400 seconds. For ``TimeInterval``, it's more complex. @@ -2651,7 +2652,7 @@ proc microseconds*(dur: Duration): int {.inline, deprecated.} = doAssert dur.microseconds == 7008 result = convert(Nanoseconds, Microseconds, dur.nanosecond) -proc nanoseconds*(dur: Duration): NanosecondRange {.inline.} = +proc nanoseconds*(dur: Duration): NanosecondRange {.inline, deprecated.} = ## Number of whole microseconds represented by the **fractional** ## part of the duration. ## diff --git a/lib/pure/unittest.nim b/lib/pure/unittest.nim index ce147cccc..60b20c5b8 100644 --- a/lib/pure/unittest.nim +++ b/lib/pure/unittest.nim @@ -57,8 +57,8 @@ ## # Run suites starting with 'bug #' and standalone tests starting with '#' ## nim c -r test 'bug #*::' '::#*' ## -## Example -## ------- +## Examples +## ======== ## ## .. code:: nim ## @@ -177,13 +177,13 @@ proc addOutputFormatter*(formatter: OutputFormatter) = formatters.add(formatter) proc newConsoleOutputFormatter*(outputLevel: OutputLevel = PRINT_ALL, - colorOutput = true): ConsoleOutputFormatter = + colorOutput = true): <//>ConsoleOutputFormatter = ConsoleOutputFormatter( outputLevel: outputLevel, colorOutput: colorOutput ) -proc defaultConsoleFormatter*(): ConsoleOutputFormatter = +proc defaultConsoleFormatter*(): <//>ConsoleOutputFormatter = when declared(stdout): # Reading settings # On a terminal this branch is executed @@ -263,7 +263,7 @@ proc xmlEscape(s: string): string = else: result.add(c) -proc newJUnitOutputFormatter*(stream: Stream): JUnitOutputFormatter = +proc newJUnitOutputFormatter*(stream: Stream): <//>JUnitOutputFormatter = ## Creates a formatter that writes report to the specified stream in ## JUnit format. ## The ``stream`` is NOT closed automatically when the test are finished, diff --git a/lib/system.nim b/lib/system.nim index a04433530..bf2e156b4 100644 --- a/lib/system.nim +++ b/lib/system.nim @@ -1619,7 +1619,7 @@ template `isnot`*(x, y: untyped): untyped = not (x is y) ## assert @[1, 2] isnot enum when defined(nimV2) and not defined(nimscript): - type owned*{.magic: "BuiltinType".}[T] + type owned*{.magic: "BuiltinType".}[T] ## type constructor to mark a ref/ptr or a closure as `owned`. proc new*[T](a: var owned(ref T)) {.magic: "New", noSideEffect.} ## Creates a new object of type ``T`` and returns a safe (traced) @@ -1637,8 +1637,16 @@ when defined(nimV2) and not defined(nimscript): var r: owned(ref t) new(r) return r + + proc unown*[T](x: T): T {.magic: "Unown", noSideEffect.} + ## Use the expression ``x`` ignoring its ownership attribute. + + # This is only required to make 0.20 compile with the 0.19 line. + template `<//>`*(t: untyped): untyped = owned(t) + else: template owned*(t: typeDesc): typedesc = t + template unown*(x: typed): typed = x proc new*[T](a: var ref T) {.magic: "New", noSideEffect.} ## Creates a new object of type ``T`` and returns a safe (traced) @@ -1657,6 +1665,9 @@ else: new(r) return r + # This is only required to make 0.20 compile with the 0.19 line. + template `<//>`*(t: untyped): untyped = t + template disarm*(x: typed) = ## Useful for ``disarming`` dangling pointers explicitly for the ## --newruntime. Regardless of whether --newruntime is used or not @@ -1926,13 +1937,11 @@ const ## failure. when defined(nodejs) and not defined(nimscript): - var programResult* {.importc: "process.exitCode".}: int + var programResult* {.importc: "process.exitCode", deprecated.}: int programResult = 0 -else: - var programResult* {.compilerproc, exportc: "nim_program_result".}: int - ## Modify this variable to specify the exit code of the program - ## under normal circumstances. When the program is terminated - ## prematurely using `quit proc <#quit,int>`_, this value is ignored. +elif hostOS != "standalone": + var programResult* {.compilerproc, exportc: "nim_program_result", deprecated.}: int + ## deprecated, prefer ``quit`` when defined(nimdoc): proc quit*(errorcode: int = QuitSuccess) {.magic: "Exit", noreturn.} @@ -1994,18 +2003,7 @@ when not defined(JS) and not defined(nimscript) and hostOS != "standalone": when not defined(JS) and not defined(nimscript) and hasAlloc and not defined(gcDestructors): proc addChar(s: NimString, c: char): NimString {.compilerProc, benign.} -when defined(gcDestructors): - proc add*[T](x: var seq[T], y: sink T) {.magic: "AppendSeqElem", noSideEffect.} = - ## Generic proc for adding a data item `y` to a container `x`. - ## - ## For containers that have an order, `add` means *append*. New generic - ## containers should also call their adding proc `add` for consistency. - ## Generic code becomes much easier to write if the Nim naming scheme is - ## respected. - let xl = x.len - setLen(x, xl + 1) - x[xl] = y -else: +when not defined(gcDestructors): proc add*[T](x: var seq[T], y: T) {.magic: "AppendSeqElem", noSideEffect.} ## Generic proc for adding a data item `y` to a container `x`. ## @@ -2239,14 +2237,15 @@ proc addQuitProc*(quitProc: proc() {.noconv.}) {. # not be called explicitly! The user may decide to do this manually though. when not defined(nimscript) and not defined(JS): - proc zeroMem*(p: pointer, size: Natural) {.inline, benign.} + proc zeroMem*(p: pointer, size: Natural) {.inline, noSideEffect, + tags: [], locks: 0, raises: [].} ## Overwrites the contents of the memory at ``p`` with the value 0. ## ## Exactly ``size`` bytes will be overwritten. Like any procedure ## dealing with raw memory this is **unsafe**. proc copyMem*(dest, source: pointer, size: Natural) {.inline, benign, - tags: [], locks: 0.} + tags: [], locks: 0, raises: [].} ## Copies the contents from the memory at ``source`` to the memory ## at ``dest``. ## Exactly ``size`` bytes will be copied. The memory @@ -2254,7 +2253,7 @@ when not defined(nimscript) and not defined(JS): ## memory this is **unsafe**. proc moveMem*(dest, source: pointer, size: Natural) {.inline, benign, - tags: [], locks: 0.} + tags: [], locks: 0, raises: [].} ## Copies the contents from the memory at ``source`` to the memory ## at ``dest``. ## @@ -2263,7 +2262,8 @@ when not defined(nimscript) and not defined(JS): ## and is thus somewhat more safe than ``copyMem``. Like any procedure ## dealing with raw memory this is still **unsafe**, though. - proc equalMem*(a, b: pointer, size: Natural): bool {.inline, noSideEffect, tags: [], locks: 0.} + proc equalMem*(a, b: pointer, size: Natural): bool {.inline, noSideEffect, + tags: [], locks: 0, raises: [].} ## Compares the memory blocks ``a`` and ``b``. ``size`` bytes will ## be compared. ## diff --git a/lib/system/ansi_c.nim b/lib/system/ansi_c.nim index 23828af91..99f66961d 100644 --- a/lib/system/ansi_c.nim +++ b/lib/system/ansi_c.nim @@ -148,4 +148,4 @@ proc rawWrite*(f: CFilePtr, s: cstring) {.compilerproc, nonreloadable, inline.} # we cannot throw an exception here! discard c_fwrite(s, 1, s.len, f) -{.pop} +{.pop.} diff --git a/lib/system/fatal.nim b/lib/system/fatal.nim index 100b6d482..82704a2e7 100644 --- a/lib/system/fatal.nim +++ b/lib/system/fatal.nim @@ -10,6 +10,8 @@ when hostOS == "standalone": panic(arg) elif defined(nimQuirky) and not defined(nimscript): + import ansi_c + proc name(t: typedesc): string {.magic: "TypeTrait".} proc sysFatal(exceptn: typedesc, message, arg: string) {.inline, noReturn.} = diff --git a/lib/system/strmantle.nim b/lib/system/strmantle.nim index 727c08da3..4aa1b705f 100644 --- a/lib/system/strmantle.nim +++ b/lib/system/strmantle.nim @@ -147,58 +147,58 @@ proc nimParseBiggestFloat(s: string, number: var BiggestFloat, has_sign = false # Sign? - if s[i] == '+' or s[i] == '-': + if i < s.len and (s[i] == '+' or s[i] == '-'): has_sign = true if s[i] == '-': sign = -1.0 inc(i) # NaN? - if s[i] == 'N' or s[i] == 'n': + if i+2 < s.len and (s[i] == 'N' or s[i] == 'n'): if s[i+1] == 'A' or s[i+1] == 'a': if s[i+2] == 'N' or s[i+2] == 'n': - if s[i+3] notin IdentChars: + if i+3 >= s.len or s[i+3] notin IdentChars: number = NaN return i+3 - start return 0 # Inf? - if s[i] == 'I' or s[i] == 'i': + if i+2 < s.len and (s[i] == 'I' or s[i] == 'i'): if s[i+1] == 'N' or s[i+1] == 'n': if s[i+2] == 'F' or s[i+2] == 'f': - if s[i+3] notin IdentChars: + if i+3 >= s.len or s[i+3] notin IdentChars: number = Inf*sign return i+3 - start return 0 - if s[i] in {'0'..'9'}: + if i < s.len and s[i] in {'0'..'9'}: first_digit = (s[i].ord - '0'.ord) # Integer part? - while s[i] in {'0'..'9'}: + while i < s.len and s[i] in {'0'..'9'}: inc(kdigits) integer = integer * 10'u64 + (s[i].ord - '0'.ord).uint64 inc(i) - while s[i] == '_': inc(i) + while i < s.len and s[i] == '_': inc(i) # Fractional part? - if s[i] == '.': + if i < s.len and s[i] == '.': inc(i) # if no integer part, Skip leading zeros if kdigits <= 0: - while s[i] == '0': + while i < s.len and s[i] == '0': inc(frac_exponent) inc(i) - while s[i] == '_': inc(i) + while i < s.len and s[i] == '_': inc(i) - if first_digit == -1 and s[i] in {'0'..'9'}: + if first_digit == -1 and i < s.len and s[i] in {'0'..'9'}: first_digit = (s[i].ord - '0'.ord) # get fractional part - while s[i] in {'0'..'9'}: + while i < s.len and s[i] in {'0'..'9'}: inc(fdigits) inc(frac_exponent) integer = integer * 10'u64 + (s[i].ord - '0'.ord).uint64 inc(i) - while s[i] == '_': inc(i) + while i < s.len and s[i] == '_': inc(i) # if has no digits: return error if kdigits + fdigits <= 0 and @@ -206,7 +206,7 @@ proc nimParseBiggestFloat(s: string, number: var BiggestFloat, (i == start + 1 and has_sign)): # or only '+' or '- return 0 - if s[i] in {'e', 'E'}: + if i+1 < s.len and s[i] in {'e', 'E'}: inc(i) if s[i] == '+' or s[i] == '-': if s[i] == '-': @@ -215,10 +215,10 @@ proc nimParseBiggestFloat(s: string, number: var BiggestFloat, inc(i) if s[i] notin {'0'..'9'}: return 0 - while s[i] in {'0'..'9'}: + while i < s.len and s[i] in {'0'..'9'}: exponent = exponent * 10 + (ord(s[i]) - ord('0')) inc(i) - while s[i] == '_': inc(i) # underscores are allowed and ignored + while i < s.len and s[i] == '_': inc(i) # underscores are allowed and ignored var real_exponent = exp_sign*exponent - frac_exponent let exp_negative = real_exponent < 0 @@ -259,12 +259,12 @@ proc nimParseBiggestFloat(s: string, number: var BiggestFloat, result = i - start i = start # re-parse without error checking, any error should be handled by the code above. - if s[i] == '.': i.inc - while s[i] in {'0'..'9','+','-'}: + if i < s.len and s[i] == '.': i.inc + while i < s.len and s[i] in {'0'..'9','+','-'}: if ti < maxlen: t[ti] = s[i]; inc(ti) inc(i) - while s[i] in {'.', '_'}: # skip underscore and decimal point + while i < s.len and s[i] in {'.', '_'}: # skip underscore and decimal point inc(i) # insert exponent diff --git a/lib/system/threads.nim b/lib/system/threads.nim index 18ceb900b..2c2fd4325 100644 --- a/lib/system/threads.nim +++ b/lib/system/threads.nim @@ -19,7 +19,8 @@ ## to prevent race conditions and improves efficiency. See `the manual for ## details of this memory model <manual.html#threads>`_. ## -## Example: +## Examples +## ======== ## ## .. code-block:: Nim ## @@ -328,7 +329,7 @@ else: proc createThread*(t: var Thread[void], tp: proc () {.thread, nimcall.}) = createThread[void](t, tp) -## we need to cache current threadId to not perform syscall all the time +# we need to cache current threadId to not perform syscall all the time var threadId {.threadvar.}: int when defined(windows): |