diff options
Diffstat (limited to 'lib')
97 files changed, 7226 insertions, 5449 deletions
diff --git a/lib/core/macros.nim b/lib/core/macros.nim index f45ca3f82..3a85324ba 100644 --- a/lib/core/macros.nim +++ b/lib/core/macros.nim @@ -151,7 +151,7 @@ proc `==`*(a, b: NimNode): bool {.magic: "EqNimrodNode", noSideEffect.} proc `==`*(a, b: NimSym): bool {.magic: "EqNimrodNode", noSideEffect, deprecated.} ## compares two Nim symbols - ## **Deprecated since version 0.18.1**; Use ```==`(NimNode,NimNode)`` instead. + ## **Deprecated since version 0.18.1**; Use ``==(NimNode, NimNode)`` instead. proc sameType*(a, b: NimNode): bool {.magic: "SameNodeType", noSideEffect.} = @@ -277,9 +277,9 @@ when defined(nimHasSymOwnerInMacro): when defined(nimHasInstantiationOfInMacro): proc isInstantiationOf*(instanceProcSym, genProcSym: NimNode): bool {.magic: "SymIsInstantiationOf", noSideEffect.} ## check if proc symbol is instance of the generic proc symbol - ## useful to check proc symbols against generic symbols + ## useful to check proc symbols against generic symbols ## returned by `bindSym` - + proc getType*(n: NimNode): NimNode {.magic: "NGetType", noSideEffect.} ## with 'getType' you can access the node's `type`:idx:. A Nim type is ## mapped to a Nim AST too, so it's slightly confusing but it means the same @@ -377,7 +377,9 @@ proc copyNimNode*(n: NimNode): NimNode {.magic: "NCopyNimNode", noSideEffect.} proc copyNimTree*(n: NimNode): NimNode {.magic: "NCopyNimTree", noSideEffect.} proc error*(msg: string, n: NimNode = nil) {.magic: "NError", benign.} - ## writes an error message at compile time + ## writes an error message at compile time. The optional ``n: NimNode`` + ## parameter is used as the source for file and line number information in + ## the compilation error message. proc warning*(msg: string, n: NimNode = nil) {.magic: "NWarning", benign.} ## writes a warning message at compile time @@ -1402,8 +1404,14 @@ proc customPragmaNode(n: NimNode): NimNode = let impl = n.getImpl() if impl.kind in RoutineNodes: return impl.pragma + elif impl.kind == nnkIdentDefs and impl[0].kind == nnkPragmaExpr: + return impl[0][1] else: - return typ.getImpl()[0][1] + let timpl = typ.getImpl() + if timpl.len>0 and timpl[0].len>1: + return timpl[0][1] + else: + return timpl if n.kind in {nnkDotExpr, nnkCheckedFieldExpr}: let name = $(if n.kind == nnkCheckedFieldExpr: n[0][1] else: n[1]) @@ -1492,9 +1500,18 @@ macro getCustomPragmaVal*(n: typed, cp: typed{nkSym}): untyped = let pragmaNode = customPragmaNode(n) for p in pragmaNode: if p.kind in nnkPragmaCallKinds and p.len > 0 and p[0].kind == nnkSym and p[0] == cp: - return p[1] - - error(n.repr & " doesn't have a pragma named " & cp.repr()) # returning an empty node results in most cases in a cryptic error, + if p.len == 2: + result = p[1] + else: + let def = p[0].getImpl[3] + result = newTree(nnkPar) + for i in 1..<p.len: + let key = def[i][0] + let val = p[i] + result.add newTree(nnkExprColonExpr, key, val) + break + if result.kind == nnkEmpty: + error(n.repr & " doesn't have a pragma named " & cp.repr()) # returning an empty node results in most cases in a cryptic error, when not defined(booting): diff --git a/lib/core/seqs.nim b/lib/core/seqs.nim index a41ef10ab..977b23b26 100644 --- a/lib/core/seqs.nim +++ b/lib/core/seqs.nim @@ -70,12 +70,6 @@ proc `=sink`[T](x: var seq[T]; y: seq[T]) = a.len = b.len a.p = b.p -when false: - proc incrSeqV3(s: PGenericSeq, typ: PNimType): PGenericSeq {.compilerProc.} - proc setLengthSeqV2(s: PGenericSeq, typ: PNimType, newLen: int): PGenericSeq {. - compilerRtl.} - proc newSeq(typ: PNimType, len: int): pointer {.compilerRtl.} - type PayloadBase = object diff --git a/lib/deprecated/core/unsigned.nim b/lib/deprecated/core/unsigned.nim deleted file mode 100644 index 93a29e1c9..000000000 --- a/lib/deprecated/core/unsigned.nim +++ /dev/null @@ -1,18 +0,0 @@ -# -# -# Nim's Runtime Library -# (c) Copyright 2012 Andreas Rumpf -# -# See the file "copying.txt", included in this -# distribution, for details about the copyright. -# - -## **Warning:** Since version 0.11.4 this module is deprecated. -## -## This module implemented basic arithmetic operators for unsigned integers. -## These operators are now available in the ``system`` module directly. - -{.deprecated.} - -export `shr`, `shl`, `and`, `or`, `xor`, `==`, `+`, `-`, `*`, `div`, `mod`, - `<=`, `<` diff --git a/lib/deprecated/pure/actors.nim b/lib/deprecated/pure/actors.nim deleted file mode 100644 index 77c67a3e4..000000000 --- a/lib/deprecated/pure/actors.nim +++ /dev/null @@ -1,239 +0,0 @@ -# -# -# Nim's Runtime Library -# (c) Copyright 2012 Andreas Rumpf -# -# See the file "copying.txt", included in this -# distribution, for details about the copyright. -# - -## `Actor`:idx: support for Nim. An actor is implemented as a thread with -## a channel as its inbox. This module requires the ``--threads:on`` -## command line switch. -## -## Example: -## -## .. code-block:: nim -## -## var -## a: ActorPool[int, void] -## createActorPool(a) -## for i in 0 ..< 300: -## a.spawn(i, proc (x: int) {.thread.} = echo x) -## a.join() -## -## **Note**: This whole module is deprecated. Use `threadpool` and ``spawn`` -## instead. - -{.deprecated.} - -from os import sleep - -type - Task*[In, Out] = object{.pure, final.} ## a task - when Out isnot void: - receiver*: ptr Channel[Out] ## the receiver channel of the response - action*: proc (x: In): Out {.thread.} ## action to execute; - ## sometimes useful - shutDown*: bool ## set to tell an actor to shut-down - data*: In ## the data to process - - Actor[In, Out] = object{.pure, final.} - i: Channel[Task[In, Out]] - t: Thread[ptr Actor[In, Out]] - - PActor*[In, Out] = ptr Actor[In, Out] ## an actor - -proc spawn*[In, Out](action: proc( - self: PActor[In, Out]){.thread.}): PActor[In, Out] = - ## creates an actor; that is a thread with an inbox. The caller MUST call - ## ``join`` because that also frees the actor's associated resources. - result = cast[PActor[In, Out]](allocShared0(sizeof(result[]))) - open(result.i) - createThread(result.t, action, result) - -proc inbox*[In, Out](self: PActor[In, Out]): ptr Channel[In] = - ## gets a pointer to the associated inbox of the actor `self`. - result = addr(self.i) - -proc running*[In, Out](a: PActor[In, Out]): bool = - ## returns true if the actor `a` is running. - result = running(a.t) - -proc ready*[In, Out](a: PActor[In, Out]): bool = - ## returns true if the actor `a` is ready to process new messages. - result = ready(a.i) - -proc join*[In, Out](a: PActor[In, Out]) = - ## joins an actor. - joinThread(a.t) - close(a.i) - deallocShared(a) - -proc recv*[In, Out](a: PActor[In, Out]): Task[In, Out] = - ## receives a task from `a`'s inbox. - result = recv(a.i) - -proc send*[In, Out, X, Y](receiver: PActor[In, Out], msg: In, - sender: PActor[X, Y]) = - ## sends a message to `a`'s inbox. - var t: Task[In, Out] - t.receiver = addr(sender.i) - shallowCopy(t.data, msg) - send(receiver.i, t) - -proc send*[In, Out](receiver: PActor[In, Out], msg: In, - sender: ptr Channel[Out] = nil) = - ## sends a message to `receiver`'s inbox. - var t: Task[In, Out] - t.receiver = sender - shallowCopy(t.data, msg) - send(receiver.i, t) - -proc sendShutdown*[In, Out](receiver: PActor[In, Out]) = - ## send a shutdown message to `receiver`. - var t: Task[In, Out] - t.shutdown = true - send(receiver.i, t) - -proc reply*[In, Out](t: Task[In, Out], m: Out) = - ## sends a message to io's output message box. - when Out is void: - {.error: "you cannot reply to a void outbox".} - assert t.receiver != nil - send(t.receiver[], m) - - -# ----------------- actor pools ---------------------------------------------- - -type - ActorPool*[In, Out] = object{.pure, final.} ## an actor pool - actors: seq[PActor[In, Out]] - when Out isnot void: - outputs: Channel[Out] - -proc `^`*[T](f: ptr Channel[T]): T = - ## alias for 'recv'. - result = recv(f[]) - -proc poolWorker[In, Out](self: PActor[In, Out]) {.thread.} = - while true: - var m = self.recv - if m.shutDown: break - when Out is void: - m.action(m.data) - else: - send(m.receiver[], m.action(m.data)) - #self.reply() - -proc createActorPool*[In, Out](a: var ActorPool[In, Out], poolSize = 4) = - ## creates an actor pool. - newSeq(a.actors, poolSize) - when Out isnot void: - open(a.outputs) - for i in 0 ..< a.actors.len: - a.actors[i] = spawn(poolWorker[In, Out]) - -proc sync*[In, Out](a: var ActorPool[In, Out], polling=50) = - ## waits for every actor of `a` to finish with its work. Currently this is - ## implemented as polling every `polling` ms and has a slight chance - ## of failing since we check for every actor to be in `ready` state and not - ## for messages still in ether. This will change in a later - ## version, however. - var allReadyCount = 0 - while true: - var wait = false - for i in 0..high(a.actors): - if not a.actors[i].i.ready: - wait = true - allReadyCount = 0 - break - if not wait: - # it's possible that some actor sent a message to some other actor but - # both appeared to be non-working as the message takes some time to - # arrive. We assume that this won't take longer than `polling` and - # simply attempt a second time and declare victory then. ;-) - inc allReadyCount - if allReadyCount > 1: break - sleep(polling) - -proc terminate*[In, Out](a: var ActorPool[In, Out]) = - ## terminates each actor in the actor pool `a` and frees the - ## resources attached to `a`. - var t: Task[In, Out] - t.shutdown = true - for i in 0..<a.actors.len: send(a.actors[i].i, t) - for i in 0..<a.actors.len: join(a.actors[i]) - when Out isnot void: - close(a.outputs) - a.actors = @[] - -proc join*[In, Out](a: var ActorPool[In, Out]) = - ## short-cut for `sync` and then `terminate`. - sync(a) - terminate(a) - -template setupTask = - t.action = action - shallowCopy(t.data, input) - -template schedule = - # extremely simple scheduler: We always try the first thread first, so that - # it remains 'hot' ;-). Round-robin hurts for keeping threads hot. - for i in 0..high(p.actors): - if p.actors[i].i.ready: - p.actors[i].i.send(t) - return - # no thread ready :-( --> send message to the thread which has the least - # messages pending: - var minIdx = -1 - var minVal = high(int) - for i in 0..high(p.actors): - var curr = p.actors[i].i.peek - if curr == 0: - # ok, is ready now: - p.actors[i].i.send(t) - return - if curr < minVal and curr >= 0: - minVal = curr - minIdx = i - if minIdx >= 0: - p.actors[minIdx].i.send(t) - else: - raise newException(DeadThreadError, "cannot send message; thread died") - -proc spawn*[In, Out](p: var ActorPool[In, Out], input: In, - action: proc (input: In): Out {.thread.} - ): ptr Channel[Out] = - ## uses the actor pool to run ``action(input)`` concurrently. - ## `spawn` is guaranteed to not block. - var t: Task[In, Out] - setupTask() - result = addr(p.outputs) - t.receiver = result - schedule() - -proc spawn*[In](p: var ActorPool[In, void], input: In, - action: proc (input: In) {.thread.}) = - ## uses the actor pool to run ``action(input)`` concurrently. - ## `spawn` is guaranteed to not block. - var t: Task[In, void] - setupTask() - schedule() - -when not defined(testing) and isMainModule: - var - a: ActorPool[int, void] - createActorPool(a) - for i in 0 ..< 300: - a.spawn(i, proc (x: int) {.thread.} = echo x) - - when false: - proc treeDepth(n: PNode): int {.thread.} = - var x = a.spawn(treeDepth, n.le) - var y = a.spawn(treeDepth, n.ri) - result = max(^x, ^y) + 1 - - a.join() - - diff --git a/lib/deprecated/pure/actors.nim.cfg b/lib/deprecated/pure/actors.nim.cfg deleted file mode 100644 index c6bb9c545..000000000 --- a/lib/deprecated/pure/actors.nim.cfg +++ /dev/null @@ -1,3 +0,0 @@ -# to shut up the tester: ---threads:on - diff --git a/lib/deprecated/pure/ftpclient.nim b/lib/deprecated/pure/ftpclient.nim index d70f9556a..206c21f27 100644 --- a/lib/deprecated/pure/ftpclient.nim +++ b/lib/deprecated/pure/ftpclient.nim @@ -81,7 +81,6 @@ type oneSecond: BiggestInt # Bytes transferred in one second. lastProgressReport: float # Time toStore: string # Data left to upload (Only used with async) - else: nil FtpClientObj* = FtpBaseObj[Socket] FtpClient* = ref FtpClientObj diff --git a/lib/deprecated/pure/parseurl.nim b/lib/deprecated/pure/parseurl.nim deleted file mode 100644 index b19f6dc85..000000000 --- a/lib/deprecated/pure/parseurl.nim +++ /dev/null @@ -1,112 +0,0 @@ -# -# -# Nim's Runtime Library -# (c) Copyright 2015 Dominik Picheta -# -# See the file "copying.txt", included in this -# distribution, for details about the copyright. -# - -## **Warnings:** This module is deprecated since version 0.10.2. -## Use the `uri <uri.html>`_ module instead. -## -## Parses & constructs URLs. - -{.deprecated.} - -import strutils - -type - Url* = tuple[ ## represents a *Uniform Resource Locator* (URL) - ## any optional component is "" if it does not exist - scheme, username, password, - hostname, port, path, query, anchor: string] - -proc parseUrl*(url: string): Url {.deprecated.} = - var i = 0 - - var scheme, username, password: string = "" - var hostname, port, path, query, anchor: string = "" - - var temp = "" - - if url[i] != '/': # url isn't a relative path - while true: - # Scheme - if url[i] == ':': - if url[i+1] == '/' and url[i+2] == '/': - scheme = temp - temp.setLen(0) - inc(i, 3) # Skip the // - # Authority(username, password) - if url[i] == '@': - username = temp - let colon = username.find(':') - if colon >= 0: - password = username.substr(colon+1) - username = username.substr(0, colon-1) - temp.setLen(0) - inc(i) #Skip the @ - # hostname(subdomain, domain, port) - if url[i] == '/' or url[i] == '\0': - hostname = temp - let colon = hostname.find(':') - if colon >= 0: - port = hostname.substr(colon+1) - hostname = hostname.substr(0, colon-1) - - temp.setLen(0) - break - - temp.add(url[i]) - inc(i) - - if url[i] == '/': inc(i) # Skip the '/' - # Path - while true: - if url[i] == '?': - path = temp - temp.setLen(0) - if url[i] == '#': - if temp[0] == '?': - query = temp - else: - path = temp - temp.setLen(0) - - if url[i] == '\0': - if temp[0] == '?': - query = temp - elif temp[0] == '#': - anchor = temp - else: - path = temp - break - - temp.add(url[i]) - inc(i) - - return (scheme, username, password, hostname, port, path, query, anchor) - -proc `$`*(u: Url): string {.deprecated.} = - ## turns the URL `u` into its string representation. - result = "" - if u.scheme.len > 0: - result.add(u.scheme) - result.add("://") - if u.username.len > 0: - result.add(u.username) - if u.password.len > 0: - result.add(":") - result.add(u.password) - result.add("@") - result.add(u.hostname) - if u.port.len > 0: - result.add(":") - result.add(u.port) - if u.path.len > 0: - result.add("/") - result.add(u.path) - result.add(u.query) - result.add(u.anchor) - diff --git a/lib/deprecated/pure/rawsockets.nim b/lib/deprecated/pure/rawsockets.nim deleted file mode 100644 index 876334f9e..000000000 --- a/lib/deprecated/pure/rawsockets.nim +++ /dev/null @@ -1,14 +0,0 @@ -import nativesockets -export nativesockets - -{.warning: "rawsockets module is deprecated, use nativesockets instead".} - -template newRawSocket*(domain, sockType, protocol: cint): untyped = - {.warning: "newRawSocket is deprecated, use newNativeSocket instead".} - newNativeSocket(domain, sockType, protocol) - -template newRawSocket*(domain: Domain = AF_INET, - sockType: SockType = SOCK_STREAM, - protocol: Protocol = IPPROTO_TCP): untyped = - {.warning: "newRawSocket is deprecated, use newNativeSocket instead".} - newNativeSocket(domain, sockType, protocol) diff --git a/lib/pure/securehash.nim b/lib/deprecated/pure/securehash.nim index c6cde599a..c6cde599a 100644 --- a/lib/pure/securehash.nim +++ b/lib/deprecated/pure/securehash.nim diff --git a/lib/experimental/diff.nim b/lib/experimental/diff.nim index bffce2803..253355707 100644 --- a/lib/experimental/diff.nim +++ b/lib/experimental/diff.nim @@ -252,8 +252,11 @@ proc createDiffs(dataA, dataB: DiffData): seq[Item] = proc diffInt*(arrayA, arrayB: openArray[int]): seq[Item] = ## Find the difference in 2 arrays of integers. + ## ## ``arrayA`` A-version of the numbers (usualy the old one) + ## ## ``arrayB`` B-version of the numbers (usualy the new one) + ## ## Returns a array of Items that describe the differences. # The A-Version of the data (original data) to be compared. @@ -273,15 +276,16 @@ proc diffInt*(arrayA, arrayB: openArray[int]): seq[Item] = proc diffText*(textA, textB: string): seq[Item] = ## Find the difference in 2 text documents, comparing by textlines. + ## ## The algorithm itself is comparing 2 arrays of numbers so when comparing 2 text documents ## each line is converted into a (hash) number. This hash-value is computed by storing all ## textlines into a common hashtable so i can find dublicates in there, and generating a ## new number each time a new textline is inserted. - ## ``TextA`` A-version of the text (usualy the old one) - ## ``TextB`` B-version of the text (usualy the new one) - ## ``trimSpace`` When set to true, all leading and trailing whitespace characters are stripped out before the comparation is done. - ## ``ignoreSpace`` When set to true, all whitespace characters are converted to a single space character before the comparation is done. - ## ``ignoreCase`` When set to true, all characters are converted to their lowercase equivivalence before the comparation is done. + ## + ## ``textA`` A-version of the text (usually the old one) + ## + ## ``textB`` B-version of the text (usually the new one) + ## ## Returns a seq of Items that describe the differences. # prepare the input-text and convert to comparable numbers. diff --git a/lib/impure/db_odbc.nim b/lib/impure/db_odbc.nim index 72d7aa800..b7af5128a 100644 --- a/lib/impure/db_odbc.nim +++ b/lib/impure/db_odbc.nim @@ -131,7 +131,7 @@ proc getErrInfo(db: var DbConn): tuple[res: int, ss, ne, msg: string] {. 511.TSqlSmallInt, retSz.addr.PSQLSMALLINT) except: discard - return (res.int, $sqlState, $nativeErr, $errMsg) + return (res.int, $(addr sqlState), $(addr nativeErr), $(addr errMsg)) proc dbError*(db: var DbConn) {. tags: [ReadDbEffect, WriteDbEffect], raises: [DbError] .} = diff --git a/lib/impure/db_sqlite.nim b/lib/impure/db_sqlite.nim index c7e373098..3910992bb 100644 --- a/lib/impure/db_sqlite.nim +++ b/lib/impure/db_sqlite.nim @@ -166,10 +166,12 @@ iterator fastRows*(db: DbConn, query: SqlQuery, var stmt = setupQuery(db, query, args) var L = (column_count(stmt)) var result = newRow(L) - while step(stmt) == SQLITE_ROW: - setRow(stmt, result, L) - yield result - if finalize(stmt) != SQLITE_OK: dbError(db) + try: + while step(stmt) == SQLITE_ROW: + setRow(stmt, result, L) + yield result + finally: + if finalize(stmt) != SQLITE_OK: dbError(db) iterator instantRows*(db: DbConn, query: SqlQuery, args: varargs[string, `$`]): InstantRow @@ -177,9 +179,11 @@ iterator instantRows*(db: DbConn, query: SqlQuery, ## same as fastRows but returns a handle that can be used to get column text ## on demand using []. Returned handle is valid only within the iterator body. var stmt = setupQuery(db, query, args) - while step(stmt) == SQLITE_ROW: - yield stmt - if finalize(stmt) != SQLITE_OK: dbError(db) + try: + while step(stmt) == SQLITE_ROW: + yield stmt + finally: + if finalize(stmt) != SQLITE_OK: dbError(db) proc toTypeKind(t: var DbType; x: int32) = case x @@ -210,9 +214,11 @@ iterator instantRows*(db: DbConn; columns: var DbColumns; query: SqlQuery, ## on demand using []. Returned handle is valid only within the iterator body. var stmt = setupQuery(db, query, args) setColumns(columns, stmt) - while step(stmt) == SQLITE_ROW: - yield stmt - if finalize(stmt) != SQLITE_OK: dbError(db) + try: + while step(stmt) == SQLITE_ROW: + yield stmt + finally: + if finalize(stmt) != SQLITE_OK: dbError(db) proc `[]`*(row: InstantRow, col: int32): string {.inline.} = ## returns text for given column of the row diff --git a/lib/impure/nre.nim b/lib/impure/nre.nim index 94dd89db5..5c5125ba1 100644 --- a/lib/impure/nre.nim +++ b/lib/impure/nre.nim @@ -540,7 +540,7 @@ proc matchImpl(str: string, pattern: Regex, start, endpos: int, flags: int): Opt raise RegexInternalError(msg : "Unknown internal error: " & $execRet) proc match*(str: string, pattern: Regex, start = 0, endpos = int.high): Option[RegexMatch] = - ## Like ```find(...)`` <#proc-find>`_, but anchored to the start of the + ## Like ` ``find(...)`` <#proc-find>`_, but anchored to the start of the ## string. ## runnableExamples: @@ -550,11 +550,11 @@ proc match*(str: string, pattern: Regex, start = 0, endpos = int.high): Option[R return str.matchImpl(pattern, start, endpos, pcre.ANCHORED) iterator findIter*(str: string, pattern: Regex, start = 0, endpos = int.high): RegexMatch = - ## Works the same as ```find(...)`` <#proc-find>`_, but finds every + ## Works the same as ` ``find(...)`` <#proc-find>`_, but finds every ## non-overlapping match. ``"2222".find(re"22")`` is ``"22", "22"``, not ## ``"22", "22", "22"``. ## - ## Arguments are the same as ```find(...)`` <#proc-find>`_ + ## Arguments are the same as ` ``find(...)`` <#proc-find>`_ ## ## Variants: ## @@ -633,7 +633,7 @@ proc split*(str: string, pattern: Regex, maxSplit = -1, start = 0): seq[string] ## Splits the string with the given regex. This works according to the ## rules that Perl and Javascript use. ## - ## ``start`` behaves the same as in ```find(...)`` <#proc-find>`_. + ## ``start`` behaves the same as in ` ``find(...)`` <#proc-find>`_. ## runnableExamples: # - If the match is zero-width, then the string is still split: diff --git a/lib/impure/re.nim b/lib/impure/re.nim index dc4ee326f..42be4a3c2 100644 --- a/lib/impure/re.nim +++ b/lib/impure/re.nim @@ -561,31 +561,6 @@ proc escapeRe*(s: string): string = result.add("\\x") result.add(toHex(ord(c), 2)) -const ## common regular expressions - reIdentifier* {.deprecated.} = r"\b[a-zA-Z_]+[a-zA-Z_0-9]*\b" - ## describes an identifier - reNatural* {.deprecated.} = r"\b\d+\b" - ## describes a natural number - reInteger* {.deprecated.} = r"\b[-+]?\d+\b" - ## describes an integer - reHex* {.deprecated.} = r"\b0[xX][0-9a-fA-F]+\b" - ## describes a hexadecimal number - reBinary* {.deprecated.} = r"\b0[bB][01]+\b" - ## describes a binary number (example: 0b11101) - reOctal* {.deprecated.} = r"\b0[oO][0-7]+\b" - ## describes an octal number (example: 0o777) - reFloat* {.deprecated.} = r"\b[-+]?[0-9]*\.?[0-9]+([eE][-+]?[0-9]+)?\b" - ## describes a floating point number - reEmail* {.deprecated.} = r"\b[a-zA-Z0-9!#$%&'*+/=?^_`{|}~\-]+(?:\. &" & - r"[a-zA-Z0-9!#$%&'*+/=?^_`{|}~-]+)*@" & - r"(?:[a-zA-Z0-9](?:[a-zA-Z0-9-]*[a-zA-Z0-9])?\.)+" & - r"(?:[a-zA-Z]{2}|com|org|net|gov|mil|biz|" & - r"info|mobi|name|aero|jobs|museum)\b" - ## describes a common email address - reURL* {.deprecated.} = r"\b(http(s)?|ftp|gopher|telnet|file|notes|ms-help)" & - r":((//)|(\\\\))+[\w\d:#@%/;$()~_?\+\-\=\\\.\&]*\b" - ## describes an URL - when isMainModule: doAssert match("(a b c)", rex"\( .* \)") doAssert match("WHiLe", re("while", {reIgnoreCase})) @@ -595,7 +570,7 @@ when isMainModule: doAssert "ABC".match(rex"\d+ | \w+") {.push warnings:off.} - doAssert matchLen("key", re(reIdentifier)) == 3 + doAssert matchLen("key", re"\b[a-zA-Z_]+[a-zA-Z_0-9]*\b") == 3 {.pop.} var pattern = re"[a-z0-9]+\s*=\s*[a-z0-9]+" diff --git a/lib/impure/ssl.nim b/lib/impure/ssl.nim deleted file mode 100644 index 0bcb3f217..000000000 --- a/lib/impure/ssl.nim +++ /dev/null @@ -1,99 +0,0 @@ -# -# -# Nim's Runtime Library -# (c) Copyright 2012 Dominik Picheta -# -# See the file "copying.txt", included in this -# distribution, for details about the copyright. -# - -## This module provides an easy to use sockets-style -## nim interface to the OpenSSL library. -## -## **Warning:** This module is deprecated, use the SSL procedures defined in -## the ``net`` module instead. - -{.deprecated.} - -import openssl, strutils, os - -type - SecureSocket* = object - ssl: SslPtr - bio: BIO - -proc connect*(sock: var SecureSocket, address: string, - port: int): int = - ## Connects to the specified `address` on the specified `port`. - ## Returns the result of the certificate validation. - SslLoadErrorStrings() - ERR_load_BIO_strings() - - if SSL_library_init() != 1: - raiseOSError(osLastError()) - - var ctx = SSL_CTX_new(SSLv23_client_method()) - if ctx == nil: - ERR_print_errors_fp(stderr) - raiseOSError(osLastError()) - - #if SSL_CTX_load_verify_locations(ctx, - # "/tmp/openssl-0.9.8e/certs/vsign1.pem", NIL) == 0: - # echo("Failed load verify locations") - # ERR_print_errors_fp(stderr) - - sock.bio = BIO_new_ssl_connect(ctx) - if BIO_get_ssl(sock.bio, addr(sock.ssl)) == 0: - raiseOSError(osLastError()) - - if BIO_set_conn_hostname(sock.bio, address & ":" & $port) != 1: - raiseOSError(osLastError()) - - if BIO_do_connect(sock.bio) <= 0: - ERR_print_errors_fp(stderr) - raiseOSError(osLastError()) - - result = SSL_get_verify_result(sock.ssl) - -proc recvLine*(sock: SecureSocket, line: var TaintedString): bool = - ## Acts in a similar fashion to the `recvLine` in the sockets module. - ## Returns false when no data is available to be read. - ## `Line` must be initialized and not nil! - setLen(line.string, 0) - while true: - var c: array[0..0, char] - var n = BIO_read(sock.bio, addr c, c.len.cint) - if n <= 0: return false - if c[0] == '\r': - n = BIO_read(sock.bio, addr c, c.len.cint) - if n > 0 and c[0] == '\L': - return true - elif n <= 0: - return false - elif c[0] == '\L': return true - add(line.string, c[0]) - - -proc send*(sock: SecureSocket, data: string) = - ## Writes `data` to the socket. - if BIO_write(sock.bio, data, data.len.cint) <= 0: - raiseOSError(osLastError()) - -proc close*(sock: SecureSocket) = - ## Closes the socket - if BIO_free(sock.bio) <= 0: - ERR_print_errors_fp(stderr) - raiseOSError(osLastError()) - -when not defined(testing) and isMainModule: - var s: SecureSocket - echo connect(s, "smtp.gmail.com", 465) - - #var buffer: array[0..255, char] - #echo BIO_read(bio, buffer, buffer.len) - var buffer: string = "" - - echo s.recvLine(buffer) - echo buffer - echo buffer.len - diff --git a/lib/js/dom.nim b/lib/js/dom.nim index cf219df3d..668dee822 100644 --- a/lib/js/dom.nim +++ b/lib/js/dom.nim @@ -416,12 +416,12 @@ type BoundingRect* {.importc.} = ref object top*, bottom*, left*, right*, x*, y*, width*, height*: float - PerformanceMemory* {.importc.} = ref object + PerformanceMemory* {.importc.} = ref object jsHeapSizeLimit*: float totalJSHeapSize*: float usedJSHeapSize*: float - PerformanceTiming* {.importc.} = ref object + PerformanceTiming* {.importc.} = ref object connectStart*: float domComplete*: float domContentLoadedEventEnd*: float @@ -459,7 +459,6 @@ proc dispatchEvent*(et: EventTarget, ev: Event) proc alert*(w: Window, msg: cstring) proc back*(w: Window) proc blur*(w: Window) -proc captureEvents*(w: Window, eventMask: int) {.deprecated.} proc clearInterval*(w: Window, interval: ref TInterval) proc clearTimeout*(w: Window, timeout: ref TTimeOut) proc close*(w: Window) @@ -478,7 +477,6 @@ proc open*(w: Window, uri, windowname: cstring, properties: cstring = nil): Window proc print*(w: Window) proc prompt*(w: Window, text, default: cstring): cstring -proc releaseEvents*(w: Window, eventMask: int) {.deprecated.} proc resizeBy*(w: Window, x, y: int) proc resizeTo*(w: Window, x, y: int) proc routeEvent*(w: Window, event: Event) @@ -513,7 +511,6 @@ proc setAttribute*(n: Node, name, value: cstring) proc setAttributeNode*(n: Node, attr: Node) # Document "methods" -proc captureEvents*(d: Document, eventMask: int) {.deprecated.} proc createAttribute*(d: Document, identifier: cstring): Node proc createElement*(d: Document, identifier: cstring): Element proc createTextNode*(d: Document, identifier: cstring): Node @@ -524,7 +521,6 @@ proc getElementsByClassName*(d: Document, name: cstring): seq[Element] proc getSelection*(d: Document): cstring proc handleEvent*(d: Document, event: Event) proc open*(d: Document) -proc releaseEvents*(d: Document, eventMask: int) {.deprecated.} proc routeEvent*(d: Document, event: Event) proc write*(d: Document, text: cstring) proc writeln*(d: Document, text: cstring) @@ -605,25 +601,3 @@ proc parseInt*(s: cstring): int {.importc, nodecl.} proc parseInt*(s: cstring, radix: int):int {.importc, nodecl.} proc newEvent*(name: cstring): Event {.importcpp: "new Event(@)", constructor.} - -type - TEventHandlers* {.deprecated.} = EventTargetObj - TWindow* {.deprecated.} = WindowObj - TFrame* {.deprecated.} = FrameObj - TNode* {.deprecated.} = NodeObj - TDocument* {.deprecated.} = DocumentObj - TElement* {.deprecated.} = ElementObj - TLink* {.deprecated.} = LinkObj - TEmbed* {.deprecated.} = EmbedObj - TAnchor* {.deprecated.} = AnchorObj - TOption* {.deprecated.} = OptionObj - TForm* {.deprecated.} = FormObj - TImage* {.deprecated.} = ImageObj - TNodeType* {.deprecated.} = NodeType - TEvent* {.deprecated.} = EventObj - TLocation* {.deprecated.} = LocationObj - THistory* {.deprecated.} = HistoryObj - TNavigator* {.deprecated.} = NavigatorObj - TStyle* {.deprecated.} = StyleObj - TScreen* {.deprecated.} = ScreenObj - TApplet* {.importc, deprecated.} = object of RootObj diff --git a/lib/js/jscore.nim b/lib/js/jscore.nim index bf64b0794..2e2bd2402 100644 --- a/lib/js/jscore.nim +++ b/lib/js/jscore.nim @@ -73,7 +73,7 @@ proc parse*(d: DateLib, s: cstring): int {.importcpp.} proc newDate*(): DateTime {. importcpp: "new Date()".} -proc newDate*(date: int|string): DateTime {. +proc newDate*(date: int|int64|string): DateTime {. importcpp: "new Date(#)".} proc newDate*(year, month, day, hours, minutes, @@ -90,6 +90,16 @@ proc getSeconds*(d: DateTime): int {.importcpp.} proc getYear*(d: DateTime): int {.importcpp.} proc getTime*(d: DateTime): int {.importcpp.} proc toString*(d: DateTime): cstring {.importcpp.} +proc getUTCDate*(d: DateTime): int {.importcpp.} +proc getUTCFullYear*(d: DateTime): int {.importcpp.} +proc getUTCHours*(d: DateTime): int {.importcpp.} +proc getUTCMilliseconds*(d: DateTime): int {.importcpp.} +proc getUTCMinutes*(d: DateTime): int {.importcpp.} +proc getUTCMonth*(d: DateTime): int {.importcpp.} +proc getUTCSeconds*(d: DateTime): int {.importcpp.} +proc getUTCDay*(d: DateTime): int {.importcpp.} +proc getTimezoneOffset*(d: DateTime): int {.importcpp.} +proc setFullYear*(d: DateTime, year: int) {.importcpp.} #JSON library proc stringify*(l: JsonLib, s: JsRoot): cstring {.importcpp.} diff --git a/lib/js/jsffi.nim b/lib/js/jsffi.nim index e1c59803d..7f0939b08 100644 --- a/lib/js/jsffi.nim +++ b/lib/js/jsffi.nim @@ -104,6 +104,12 @@ var jsFilename* {.importc: "__filename", nodecl.}: cstring ## JavaScript's __filename pseudo-variable +proc isNull*[T](x: T): bool {.noSideEffect, importcpp: "(# === null)".} + ## check if a value is exactly null + +proc isUndefined*[T](x: T): bool {.noSideEffect, importcpp: "(# === undefined)".} + ## check if a value is exactly undefined + # Exceptions type JsError* {.importc: "Error".} = object of JsRoot diff --git a/lib/nimbase.h b/lib/nimbase.h index 2cb632787..ba4273726 100644 --- a/lib/nimbase.h +++ b/lib/nimbase.h @@ -166,13 +166,13 @@ __clang__ # define N_STDCALL(rettype, name) rettype __stdcall name # define N_SYSCALL(rettype, name) rettype __syscall name # define N_FASTCALL(rettype, name) rettype __fastcall name -# define N_SAFECALL(rettype, name) rettype __safecall name +# define N_SAFECALL(rettype, name) rettype __stdcall name /* function pointers with calling convention: */ # define N_CDECL_PTR(rettype, name) rettype (__cdecl *name) # define N_STDCALL_PTR(rettype, name) rettype (__stdcall *name) # define N_SYSCALL_PTR(rettype, name) rettype (__syscall *name) # define N_FASTCALL_PTR(rettype, name) rettype (__fastcall *name) -# define N_SAFECALL_PTR(rettype, name) rettype (__safecall *name) +# define N_SAFECALL_PTR(rettype, name) rettype (__stdcall *name) # ifdef __cplusplus # define N_LIB_EXPORT extern "C" __declspec(dllexport) diff --git a/lib/packages/docutils/rst.nim b/lib/packages/docutils/rst.nim index 161509afe..551df9919 100644 --- a/lib/packages/docutils/rst.nim +++ b/lib/packages/docutils/rst.nim @@ -560,9 +560,9 @@ proc match(p: RstParser, start: int, expr: string): bool = result = (p.tok[j].kind == tkWord) or (p.tok[j].symbol == "#") if result: case p.tok[j].symbol[0] - of 'a'..'z', 'A'..'Z': result = len(p.tok[j].symbol) == 1 + of 'a'..'z', 'A'..'Z', '#': result = len(p.tok[j].symbol) == 1 of '0'..'9': result = allCharsInSet(p.tok[j].symbol, {'0'..'9'}) - else: discard + else: result = false else: var c = expr[i] var length = 0 @@ -780,6 +780,31 @@ proc parseMarkdownCodeblock(p: var RstParser): PRstNode = add(result, nil) add(result, lb) +proc parseMarkdownLink(p: var RstParser; father: PRstNode): bool = + result = true + var desc, link = "" + var i = p.idx + + template parse(endToken, dest) = + inc i # skip begin token + while true: + if p.tok[i].kind in {tkEof, tkIndent}: return false + if p.tok[i].symbol == endToken: break + dest.add p.tok[i].symbol + inc i + inc i # skip end token + + parse("]", desc) + if p.tok[i].symbol != "(": return false + parse(")", link) + let child = newRstNode(rnHyperlink) + child.add desc + child.add link + # only commit if we detected no syntax error: + father.add child + p.idx = i + result = true + proc parseInline(p: var RstParser, father: PRstNode) = case p.tok[p.idx].kind of tkPunct: @@ -811,6 +836,9 @@ proc parseInline(p: var RstParser, father: PRstNode) = var n = newRstNode(rnSubstitutionReferences) parseUntil(p, n, "|", false) add(father, n) + elif roSupportMarkdown in p.s.options and p.tok[p.idx].symbol == "[" and + parseMarkdownLink(p, father): + discard "parseMarkdownLink already processed it" else: if roSupportSmilies in p.s.options: let n = parseSmiley(p) @@ -1037,15 +1065,32 @@ proc isOptionList(p: RstParser): bool = result = match(p, p.idx, "-w") or match(p, p.idx, "--w") or match(p, p.idx, "/w") or match(p, p.idx, "//w") +proc isMarkdownHeadlinePattern(s: string): bool = + if s.len >= 1 and s.len <= 6: + for c in s: + if c != '#': return false + result = true + +proc isMarkdownHeadline(p: RstParser): bool = + if roSupportMarkdown in p.s.options: + if isMarkdownHeadlinePattern(p.tok[p.idx].symbol) and p.tok[p.idx+1].kind == tkWhite: + if p.tok[p.idx+2].kind in {tkWord, tkOther, tkPunct}: + result = true + proc whichSection(p: RstParser): RstNodeKind = case p.tok[p.idx].kind of tkAdornment: if match(p, p.idx + 1, "ii"): result = rnTransition elif match(p, p.idx + 1, " a"): result = rnTable elif match(p, p.idx + 1, "i"): result = rnOverline - else: result = rnLeaf + elif isMarkdownHeadline(p): + result = rnHeadline + else: + result = rnLeaf of tkPunct: - if match(p, tokenAfterNewline(p), "ai"): + if isMarkdownHeadline(p): + result = rnHeadline + elif match(p, tokenAfterNewline(p), "ai"): result = rnHeadline elif p.tok[p.idx].symbol == "::": result = rnLiteralBlock @@ -1060,7 +1105,7 @@ proc whichSection(p: RstParser): RstNodeKind = elif match(p, p.idx, ":w:") and predNL(p): # (p.tok[p.idx].symbol == ":") result = rnFieldList - elif match(p, p.idx, "(e) "): + elif match(p, p.idx, "(e) ") or match(p, p.idx, "e. "): result = rnEnumList elif match(p, p.idx, "+a+"): result = rnGridTable @@ -1130,12 +1175,18 @@ proc parseParagraph(p: var RstParser, result: PRstNode) = proc parseHeadline(p: var RstParser): PRstNode = result = newRstNode(rnHeadline) - parseUntilNewline(p, result) - assert(p.tok[p.idx].kind == tkIndent) - assert(p.tok[p.idx + 1].kind == tkAdornment) - var c = p.tok[p.idx + 1].symbol[0] - inc(p.idx, 2) - result.level = getLevel(p.s.underlineToLevel, p.s.uLevel, c) + if isMarkdownHeadline(p): + result.level = p.tok[p.idx].symbol.len + assert(p.tok[p.idx+1].kind == tkWhite) + inc p.idx, 2 + parseUntilNewline(p, result) + else: + parseUntilNewline(p, result) + assert(p.tok[p.idx].kind == tkIndent) + assert(p.tok[p.idx + 1].kind == tkAdornment) + var c = p.tok[p.idx + 1].symbol[0] + inc(p.idx, 2) + result.level = getLevel(p.s.underlineToLevel, p.s.uLevel, c) type IntSeq = seq[int] diff --git a/lib/packages/docutils/rstast.nim b/lib/packages/docutils/rstast.nim index 4a77b4f34..fee824b09 100644 --- a/lib/packages/docutils/rstast.nim +++ b/lib/packages/docutils/rstast.nim @@ -89,6 +89,9 @@ proc lastSon*(n: PRstNode): PRstNode = proc add*(father, son: PRstNode) = add(father.sons, son) +proc add*(father: PRstNode; s: string) = + add(father.sons, newRstNode(rnLeaf, s)) + proc addIfNotNil*(father, son: PRstNode) = if son != nil: add(father, son) diff --git a/lib/packages/docutils/rstgen.nim b/lib/packages/docutils/rstgen.nim index 232da5c93..766ce6ffd 100644 --- a/lib/packages/docutils/rstgen.nim +++ b/lib/packages/docutils/rstgen.nim @@ -1134,11 +1134,9 @@ proc renderRstToOut(d: PDoc, n: PRstNode, result: var string) = of rnTripleEmphasis: renderAux(d, n, "<strong><em>$1</em></strong>", "\\textbf{emph{$1}}", result) - of rnInterpretedText: - renderAux(d, n, "<cite>$1</cite>", "\\emph{$1}", result) of rnIdx: renderIndexTerm(d, n, result) - of rnInlineLiteral: + of rnInlineLiteral, rnInterpretedText: renderAux(d, n, "<tt class=\"docutils literal\"><span class=\"pre\">$1</span></tt>", "\\texttt{$1}", result) diff --git a/lib/posix/posix_linux_amd64_consts.nim b/lib/posix/posix_linux_amd64_consts.nim index c23005b1e..dfbfe7f64 100644 --- a/lib/posix/posix_linux_amd64_consts.nim +++ b/lib/posix/posix_linux_amd64_consts.nim @@ -295,6 +295,7 @@ const IF_NAMESIZE* = cint(16) const IPPROTO_IP* = cint(0) const IPPROTO_IPV6* = cint(41) const IPPROTO_ICMP* = cint(1) +const IPPROTO_ICMPV6* = cint(58) const IPPROTO_RAW* = cint(255) const IPPROTO_TCP* = cint(6) const IPPROTO_UDP* = cint(17) diff --git a/lib/posix/posix_nintendoswitch_consts.nim b/lib/posix/posix_nintendoswitch_consts.nim index f0c0dd717..1e782d92e 100644 --- a/lib/posix/posix_nintendoswitch_consts.nim +++ b/lib/posix/posix_nintendoswitch_consts.nim @@ -237,6 +237,7 @@ const IF_NAMESIZE* = cint(16) const IPPROTO_IP* = cint(0) const IPPROTO_IPV6* = cint(41) const IPPROTO_ICMP* = cint(1) +const IPPROTO_ICMPV6* = cint(58) const IPPROTO_RAW* = cint(255) const IPPROTO_TCP* = cint(6) const IPPROTO_UDP* = cint(17) diff --git a/lib/posix/posix_other_consts.nim b/lib/posix/posix_other_consts.nim index 2b4b70941..cd5199078 100644 --- a/lib/posix/posix_other_consts.nim +++ b/lib/posix/posix_other_consts.nim @@ -302,6 +302,7 @@ var IF_NAMESIZE* {.importc: "IF_NAMESIZE", header: "<net/if.h>".}: cint var IPPROTO_IP* {.importc: "IPPROTO_IP", header: "<netinet/in.h>".}: cint var IPPROTO_IPV6* {.importc: "IPPROTO_IPV6", header: "<netinet/in.h>".}: cint var IPPROTO_ICMP* {.importc: "IPPROTO_ICMP", header: "<netinet/in.h>".}: cint +var IPPROTO_ICMPV6* {.importc: "IPPROTO_ICMPV6", header: "<netinet/in.h>".}: cint var IPPROTO_RAW* {.importc: "IPPROTO_RAW", header: "<netinet/in.h>".}: cint var IPPROTO_TCP* {.importc: "IPPROTO_TCP", header: "<netinet/in.h>".}: cint var IPPROTO_UDP* {.importc: "IPPROTO_UDP", header: "<netinet/in.h>".}: cint diff --git a/lib/pure/asynchttpserver.nim b/lib/pure/asynchttpserver.nim index d27c2fb9c..e3fc75597 100644 --- a/lib/pure/asynchttpserver.nim +++ b/lib/pure/asynchttpserver.nim @@ -143,11 +143,14 @@ proc parseUppercaseMethod(name: string): HttpMethod = of "TRACE": HttpTrace else: raise newException(ValueError, "Invalid HTTP method " & name) -proc processRequest(server: AsyncHttpServer, req: FutureVar[Request], - client: AsyncSocket, - address: string, lineFut: FutureVar[string], - callback: proc (request: Request): - Future[void] {.closure, gcsafe.}) {.async.} = +proc processRequest( + server: AsyncHttpServer, + req: FutureVar[Request], + client: AsyncSocket, + address: string, + lineFut: FutureVar[string], + callback: proc (request: Request): Future[void] {.closure, gcsafe.}, +): Future[bool] {.async.} = # Alias `request` to `req.mget()` so we don't have to write `mget` everywhere. template request(): Request = @@ -171,12 +174,12 @@ proc processRequest(server: AsyncHttpServer, req: FutureVar[Request], if lineFut.mget == "": client.close() - return + return false if lineFut.mget.len > maxLine: await request.respondError(Http413) client.close() - return + return false if lineFut.mget != "\c\L": break @@ -189,22 +192,22 @@ proc processRequest(server: AsyncHttpServer, req: FutureVar[Request], request.reqMethod = parseUppercaseMethod(linePart) except ValueError: asyncCheck request.respondError(Http400) - return + return true # Retry processing of request of 1: try: parseUri(linePart, request.url) except ValueError: asyncCheck request.respondError(Http400) - return + return true of 2: try: request.protocol = parseProtocol(linePart) except ValueError: asyncCheck request.respondError(Http400) - return + return true else: await request.respondError(Http400) - return + return true inc i # Headers @@ -215,10 +218,10 @@ proc processRequest(server: AsyncHttpServer, req: FutureVar[Request], await client.recvLineInto(lineFut, maxLength=maxLine) if lineFut.mget == "": - client.close(); return + client.close(); return false if lineFut.mget.len > maxLine: await request.respondError(Http413) - client.close(); return + client.close(); return false if lineFut.mget == "\c\L": break let (key, value) = parseHeader(lineFut.mget) request.headers[key] = value @@ -226,7 +229,7 @@ proc processRequest(server: AsyncHttpServer, req: FutureVar[Request], if request.headers.len > headerLimit: await client.sendStatus("400 Bad Request") request.client.close() - return + return false if request.reqMethod == HttpPost: # Check for Expect header @@ -242,24 +245,24 @@ proc processRequest(server: AsyncHttpServer, req: FutureVar[Request], var contentLength = 0 if parseSaturatedNatural(request.headers["Content-Length"], contentLength) == 0: await request.respond(Http400, "Bad Request. Invalid Content-Length.") - return + return true else: if contentLength > server.maxBody: await request.respondError(Http413) - return + return false request.body = await client.recv(contentLength) if request.body.len != contentLength: await request.respond(Http400, "Bad Request. Content-Length does not match actual.") - return + return true elif request.reqMethod == HttpPost: await request.respond(Http411, "Content-Length required.") - return + return true # Call the user's callback. await callback(request) if "upgrade" in request.headers.getOrDefault("connection"): - return + return false # Persistent connections if (request.protocol == HttpVer11 and @@ -273,7 +276,7 @@ proc processRequest(server: AsyncHttpServer, req: FutureVar[Request], discard else: request.client.close() - return + return false proc processClient(server: AsyncHttpServer, client: AsyncSocket, address: string, callback: proc (request: Request): @@ -285,7 +288,10 @@ proc processClient(server: AsyncHttpServer, client: AsyncSocket, address: string lineFut.mget() = newStringOfCap(80) while not client.isClosed: - await processRequest(server, request, client, address, lineFut, callback) + let retry = await processRequest( + server, request, client, address, lineFut, callback + ) + if not retry: break proc serve*(server: AsyncHttpServer, port: Port, callback: proc (request: Request): Future[void] {.closure,gcsafe.}, diff --git a/lib/pure/base64.nim b/lib/pure/base64.nim index bfb8a1666..427f93926 100644 --- a/lib/pure/base64.nim +++ b/lib/pure/base64.nim @@ -9,37 +9,49 @@ ## This module implements a base64 encoder and decoder. ## +## Base64 is an encoding and decoding technique used to convert binary +## data to an ASCII string format. +## Each Base64 digit represents exactly 6 bits of data. Three 8-bit +## bytes (i.e., a total of 24 bits) can therefore be represented by +## four 6-bit Base64 digits. +## +## +## Basic usage +## =========== +## ## Encoding data ## ------------- ## -## In order to encode some text simply call the ``encode`` procedure: -## -## .. code-block::nim -## import base64 -## let encoded = encode("Hello World") -## echo(encoded) # SGVsbG8gV29ybGQ= +## .. code-block::nim +## import base64 +## let encoded = encode("Hello World") +## assert encoded == "SGVsbG8gV29ybGQ=" ## ## Apart from strings you can also encode lists of integers or characters: ## -## .. code-block::nim -## import base64 -## let encodedInts = encode([1,2,3]) -## echo(encodedInts) # AQID -## let encodedChars = encode(['h','e','y']) -## echo(encodedChars) # aGV5 +## .. code-block::nim +## import base64 +## let encodedInts = encode([1,2,3]) +## assert encodedInts == "AQID" +## let encodedChars = encode(['h','e','y']) +## assert encodedChars == "aGV5" ## -## The ``encode`` procedure takes an ``openarray`` so both arrays and sequences -## can be passed as parameters. ## ## Decoding data ## ------------- ## -## To decode a base64 encoded data string simply call the ``decode`` -## procedure: +## .. code-block::nim +## import base64 +## let decoded = decode("SGVsbG8gV29ybGQ=") +## assert decoded == "Hello World" ## -## .. code-block::nim -## import base64 -## echo(decode("SGVsbG8gV29ybGQ=")) # Hello World +## +## See also +## ======== +## +## * `hashes module<hashes.html>`_ for efficient computations of hash values for diverse Nim types +## * `md5 module<md5.html>`_ implements the MD5 checksum algorithm +## * `sha1 module<sha1.html>`_ implements a sha1 encoder and decoder const cb64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/" @@ -100,18 +112,33 @@ template encodeInternal(s: typed, lineLen: int, newLine: string): untyped = discard proc encode*[T:SomeInteger|char](s: openarray[T], lineLen = 75, newLine="\13\10"): string = - ## encodes `s` into base64 representation. After `lineLen` characters, a - ## `newline` is added. + ## Encodes ``s`` into base64 representation. After ``lineLen`` characters, a + ## ``newline`` is added. ## ## This procedure encodes an openarray (array or sequence) of either integers ## or characters. + ## + ## **See also:** + ## * `encode proc<#encode,string,int,string>`_ for encoding a string + ## * `decode proc<#decode,string>`_ for decoding a string + runnableExamples: + assert encode(['n', 'i', 'm']) == "bmlt" + assert encode(@['n', 'i', 'm']) == "bmlt" + assert encode([1, 2, 3, 4, 5]) == "AQIDBAU=" encodeInternal(s, lineLen, newLine) proc encode*(s: string, lineLen = 75, newLine="\13\10"): string = - ## encodes `s` into base64 representation. After `lineLen` characters, a - ## `newline` is added. + ## Encodes ``s`` into base64 representation. After ``lineLen`` characters, a + ## ``newline`` is added. ## ## This procedure encodes a string. + ## + ## **See also:** + ## * `encode proc<#encode,openArray[T],int,string>`_ for encoding an openarray + ## * `decode proc<#decode,string>`_ for decoding a string + runnableExamples: + assert encode("Hello World") == "SGVsbG8gV29ybGQ=" + assert encode("Hello World", 3, "\n") == "SGVs\nbG8g\nV29ybGQ=" encodeInternal(s, lineLen, newLine) proc decodeByte(b: char): int {.inline.} = @@ -123,8 +150,15 @@ proc decodeByte(b: char): int {.inline.} = else: result = 63 proc decode*(s: string): string = - ## decodes a string in base64 representation back into its original form. - ## Whitespace is skipped. + ## Decodes string ``s`` in base64 representation back into its original form. + ## The initial whitespace is skipped. + ## + ## **See also:** + ## * `encode proc<#encode,openArray[T],int,string>`_ for encoding an openarray + ## * `encode proc<#encode,string,int,string>`_ for encoding a string + runnableExamples: + assert decode("SGVsbG8gV29ybGQ=") == "Hello World" + assert decode(" SGVsbG8gV29ybGQ=") == "Hello World" const Whitespace = {' ', '\t', '\v', '\r', '\l', '\f'} var total = ((len(s) + 3) div 4) * 3 # total is an upper bound, as we will skip arbitrary whitespace: diff --git a/lib/pure/cgi.nim b/lib/pure/cgi.nim index 869abc9cc..ec3562c35 100644 --- a/lib/pure/cgi.nim +++ b/lib/pure/cgi.nim @@ -337,11 +337,6 @@ proc setStackTraceStdout*() = ## Makes Nim output stacktraces to stdout, instead of server log. errorMessageWriter = writeErrorMessage -proc setStackTraceNewLine*() {.deprecated.} = - ## Makes Nim output stacktraces to stdout, instead of server log. - ## Depracated alias for setStackTraceStdout. - setStackTraceStdout() - proc setCookie*(name, value: string) = ## Sets a cookie. write(stdout, "Set-Cookie: ", name, "=", value, "\n") diff --git a/lib/pure/collections/critbits.nim b/lib/pure/collections/critbits.nim index 32e0299ba..dd91fdb12 100644 --- a/lib/pure/collections/critbits.nim +++ b/lib/pure/collections/critbits.nim @@ -202,12 +202,6 @@ proc `[]`*[T](c: var CritBitTree[T], key: string): var T {.inline, ## If `key` is not in `t`, the ``KeyError`` exception is raised. get(c, key) -proc mget*[T](c: var CritBitTree[T], key: string): var T {.inline, deprecated.} = - ## retrieves the value at ``c[key]``. The value can be modified. - ## If `key` is not in `t`, the ``KeyError`` exception is raised. - ## Use ```[]``` instead. - get(c, key) - iterator leaves[T](n: Node[T]): Node[T] = if n != nil: # XXX actually we could compute the necessary stack size in advance: diff --git a/lib/pure/collections/deques.nim b/lib/pure/collections/deques.nim index e8342e208..cb05e5112 100644 --- a/lib/pure/collections/deques.nim +++ b/lib/pure/collections/deques.nim @@ -20,41 +20,59 @@ ## access, unless your program logic guarantees it indirectly. ## ## .. code-block:: Nim -## proc foo(a, b: Positive) = # assume random positive values for `a` and `b` -## var deq = initDeque[int]() # initializes the object -## for i in 1 ..< a: deq.addLast i # populates the deque +## import deques ## -## if b < deq.len: # checking before indexed access -## echo "The element at index position ", b, " is ", deq[b] +## var a = initDeque[int]() ## -## # The following two lines don't need any checking on access due to the -## # logic of the program, but that would not be the case if `a` could be 0. -## assert deq.peekFirst == 1 -## assert deq.peekLast == a +## doAssertRaises(IndexError, echo a[0]) ## -## while deq.len > 0: # checking if the deque is empty -## echo deq.popLast() +## for i in 1 .. 5: +## a.addLast(10*i) +## assert $a == "[10, 20, 30, 40, 50]" ## -## Note: For inter thread communication use -## a `Channel <channels.html>`_ instead. +## assert a.peekFirst == 10 +## assert a.peekLast == 50 +## assert len(a) == 5 +## +## assert a.popFirst == 10 +## assert a.popLast == 50 +## assert len(a) == 3 +## +## a.addFirst(11) +## a.addFirst(22) +## a.addFirst(33) +## assert $a == "[33, 22, 11, 20, 30, 40]" +## +## a.shrink(fromFirst = 1, fromLast = 2) +## assert $a == "[22, 11, 20]" +## +## +## **See also:** +## * `lists module <lists.html>`_ for singly and doubly linked lists and rings +## * `channels module <channels.html>`_ for inter-thread communication + import math, typetraits type Deque*[T] = object ## A double-ended queue backed with a ringed seq buffer. + ## + ## To initialize an empty deque use `initDeque proc <#initDeque,int>`_. data: seq[T] head, tail, count, mask: int proc initDeque*[T](initialSize: int = 4): Deque[T] = - ## Create a new deque. - ## Optionally, the initial capacity can be reserved via `initialSize` as a - ## performance optimization. The length of a newly created deque will still - ## be 0. + ## Create a new empty deque. ## - ## `initialSize` needs to be a power of two. If you need to accept runtime - ## values for this you could use the ``nextPowerOfTwo`` proc from the - ## `math <math.html>`_ module. + ## Optionally, the initial capacity can be reserved via `initialSize` + ## as a performance optimization. + ## The length of a newly created deque will still be 0. + ## + ## ``initialSize`` must be a power of two (default: 4). + ## If you need to accept runtime values for this you could use the + ## `nextPowerOfTwo proc<math.html#nextPowerOfTwo,int>`_ from the + ## `math module<math.html>`_. assert isPowerOfTwo(initialSize) result.mask = initialSize-1 newSeq(result.data, initialSize) @@ -75,33 +93,128 @@ template xBoundsCheck(deq, i) = if unlikely(i >= deq.count): # x < deq.low is taken care by the Natural parameter raise newException(IndexError, "Out of bounds: " & $i & " > " & $(deq.count - 1)) + if unlikely(i < 0): # when used with BackwardsIndex + raise newException(IndexError, + "Out of bounds: " & $i & " < 0") proc `[]`*[T](deq: Deque[T], i: Natural) : T {.inline.} = - ## Access the i-th element of `deq` by order from first to last. - ## deq[0] is the first, deq[^1] is the last. + ## Access the i-th element of `deq`. + runnableExamples: + var a = initDeque[int]() + for i in 1 .. 5: + a.addLast(10*i) + assert a[0] == 10 + assert a[3] == 40 + doAssertRaises(IndexError, echo a[8]) + xBoundsCheck(deq, i) return deq.data[(deq.head + i) and deq.mask] proc `[]`*[T](deq: var Deque[T], i: Natural): var T {.inline.} = - ## Access the i-th element of `deq` and returns a mutable + ## Access the i-th element of `deq` and return a mutable ## reference to it. + runnableExamples: + var a = initDeque[int]() + for i in 1 .. 5: + a.addLast(10*i) + assert a[0] == 10 + assert a[3] == 40 + doAssertRaises(IndexError, echo a[8]) + xBoundsCheck(deq, i) return deq.data[(deq.head + i) and deq.mask] -proc `[]=`* [T] (deq: var Deque[T], i: Natural, val : T) {.inline.} = +proc `[]=`*[T](deq: var Deque[T], i: Natural, val : T) {.inline.} = ## Change the i-th element of `deq`. + runnableExamples: + var a = initDeque[int]() + for i in 1 .. 5: + a.addLast(10*i) + a[0] = 99 + a[3] = 66 + assert $a == "[99, 20, 30, 66, 50]" + xBoundsCheck(deq, i) deq.data[(deq.head + i) and deq.mask] = val +proc `[]`*[T](deq: Deque[T], i: BackwardsIndex): T {.inline.} = + ## Access the backwards indexed i-th element. + ## + ## `deq[^1]` is the last element. + runnableExamples: + var a = initDeque[int]() + for i in 1 .. 5: + a.addLast(10*i) + assert a[^1] == 50 + assert a[^4] == 20 + doAssertRaises(IndexError, echo a[^9]) + + xBoundsCheck(deq, deq.len - int(i)) + return deq[deq.len - int(i)] + +proc `[]`*[T](deq: var Deque[T], i: BackwardsIndex): var T {.inline.} = + ## Access the backwards indexed i-th element. + ## + ## `deq[^1]` is the last element. + runnableExamples: + var a = initDeque[int]() + for i in 1 .. 5: + a.addLast(10*i) + assert a[^1] == 50 + assert a[^4] == 20 + doAssertRaises(IndexError, echo a[^9]) + + xBoundsCheck(deq, deq.len - int(i)) + return deq[deq.len - int(i)] + +proc `[]=`*[T](deq: var Deque[T], i: BackwardsIndex, x: T) {.inline.} = + ## Change the backwards indexed i-th element. + ## + ## `deq[^1]` is the last element. + runnableExamples: + var a = initDeque[int]() + for i in 1 .. 5: + a.addLast(10*i) + a[^1] = 99 + a[^3] = 77 + assert $a == "[10, 20, 77, 40, 99]" + + xBoundsCheck(deq, deq.len - int(i)) + deq[deq.len - int(i)] = x + iterator items*[T](deq: Deque[T]): T = ## Yield every element of `deq`. + ## + ## **Examples:** + ## + ## .. code-block:: + ## var a = initDeque[int]() + ## for i in 1 .. 3: + ## a.addLast(10*i) + ## + ## for x in a: # the same as: for x in items(a): + ## echo x + ## + ## # 10 + ## # 20 + ## # 30 + ## var i = deq.head for c in 0 ..< deq.count: yield deq.data[i] i = (i + 1) and deq.mask iterator mitems*[T](deq: var Deque[T]): var T = - ## Yield every element of `deq`. + ## Yield every element of `deq`, which can be modified. + runnableExamples: + var a = initDeque[int]() + for i in 1 .. 5: + a.addLast(10*i) + assert $a == "[10, 20, 30, 40, 50]" + for x in mitems(a): + x = 5*x - 1 + assert $a == "[49, 99, 149, 199, 249]" + var i = deq.head for c in 0 ..< deq.count: yield deq.data[i] @@ -109,18 +222,35 @@ iterator mitems*[T](deq: var Deque[T]): var T = iterator pairs*[T](deq: Deque[T]): tuple[key: int, val: T] = ## Yield every (position, value) of `deq`. + ## + ## **Examples:** + ## + ## .. code-block:: + ## var a = initDeque[int]() + ## for i in 1 .. 3: + ## a.addLast(10*i) + ## + ## for k, v in pairs(a): + ## echo "key: ", k, ", value: ", v + ## + ## # key: 0, value: 10 + ## # key: 1, value: 20 + ## # key: 2, value: 30 + ## var i = deq.head for c in 0 ..< deq.count: yield (c, deq.data[i]) i = (i + 1) and deq.mask proc contains*[T](deq: Deque[T], item: T): bool {.inline.} = - ## Return true if `item` is in `deq` or false if not found. Usually used - ## via the ``in`` operator. It is the equivalent of ``deq.find(item) >= 0``. + ## Return true if `item` is in `deq` or false if not found. + ## + ## Usually used via the ``in`` operator. + ## It is the equivalent of ``deq.find(item) >= 0``. ## ## .. code-block:: Nim ## if x in q: - ## assert q.contains x + ## assert q.contains(x) for e in deq: if e == item: return true return false @@ -138,6 +268,19 @@ proc expandIfNeeded[T](deq: var Deque[T]) = proc addFirst*[T](deq: var Deque[T], item: T) = ## Add an `item` to the beginning of the `deq`. + ## + ## See also: + ## * `addLast proc <#addLast,Deque[T],T>`_ + ## * `peekFirst proc <#peekFirst,Deque[T]>`_ + ## * `peekLast proc <#peekLast,Deque[T]>`_ + ## * `popFirst proc <#popFirst,Deque[T]>`_ + ## * `popLast proc <#popLast,Deque[T]>`_ + runnableExamples: + var a = initDeque[int]() + for i in 1 .. 5: + a.addFirst(10*i) + assert $a == "[50, 40, 30, 20, 10]" + expandIfNeeded(deq) inc deq.count deq.head = (deq.head - 1) and deq.mask @@ -145,6 +288,19 @@ proc addFirst*[T](deq: var Deque[T], item: T) = proc addLast*[T](deq: var Deque[T], item: T) = ## Add an `item` to the end of the `deq`. + ## + ## See also: + ## * `addFirst proc <#addFirst,Deque[T],T>`_ + ## * `peekFirst proc <#peekFirst,Deque[T]>`_ + ## * `peekLast proc <#peekLast,Deque[T]>`_ + ## * `popFirst proc <#popFirst,Deque[T]>`_ + ## * `popLast proc <#popLast,Deque[T]>`_ + runnableExamples: + var a = initDeque[int]() + for i in 1 .. 5: + a.addLast(10*i) + assert $a == "[10, 20, 30, 40, 50]" + expandIfNeeded(deq) inc deq.count deq.data[deq.tail] = item @@ -152,11 +308,41 @@ proc addLast*[T](deq: var Deque[T], item: T) = proc peekFirst*[T](deq: Deque[T]): T {.inline.}= ## Returns the first element of `deq`, but does not remove it from the deque. + ## + ## See also: + ## * `addFirst proc <#addFirst,Deque[T],T>`_ + ## * `addLast proc <#addLast,Deque[T],T>`_ + ## * `peekLast proc <#peekLast,Deque[T]>`_ + ## * `popFirst proc <#popFirst,Deque[T]>`_ + ## * `popLast proc <#popLast,Deque[T]>`_ + runnableExamples: + var a = initDeque[int]() + for i in 1 .. 5: + a.addLast(10*i) + assert $a == "[10, 20, 30, 40, 50]" + assert a.peekFirst == 10 + assert len(a) == 5 + emptyCheck(deq) result = deq.data[deq.head] proc peekLast*[T](deq: Deque[T]): T {.inline.} = ## Returns the last element of `deq`, but does not remove it from the deque. + ## + ## See also: + ## * `addFirst proc <#addFirst,Deque[T],T>`_ + ## * `addLast proc <#addLast,Deque[T],T>`_ + ## * `peekFirst proc <#peekFirst,Deque[T]>`_ + ## * `popFirst proc <#popFirst,Deque[T]>`_ + ## * `popLast proc <#popLast,Deque[T]>`_ + runnableExamples: + var a = initDeque[int]() + for i in 1 .. 5: + a.addLast(10*i) + assert $a == "[10, 20, 30, 40, 50]" + assert a.peekLast == 50 + assert len(a) == 5 + emptyCheck(deq) result = deq.data[(deq.tail - 1) and deq.mask] @@ -165,6 +351,23 @@ template destroy(x: untyped) = proc popFirst*[T](deq: var Deque[T]): T {.inline, discardable.} = ## Remove and returns the first element of the `deq`. + ## + ## See also: + ## * `addFirst proc <#addFirst,Deque[T],T>`_ + ## * `addLast proc <#addLast,Deque[T],T>`_ + ## * `peekFirst proc <#peekFirst,Deque[T]>`_ + ## * `peekLast proc <#peekLast,Deque[T]>`_ + ## * `popLast proc <#popLast,Deque[T]>`_ + ## * `clear proc <#clear,Deque[T]>`_ + ## * `shrink proc <#shrink,Deque[T],int,int>`_ + runnableExamples: + var a = initDeque[int]() + for i in 1 .. 5: + a.addLast(10*i) + assert $a == "[10, 20, 30, 40, 50]" + assert a.popFirst == 10 + assert $a == "[20, 30, 40, 50]" + emptyCheck(deq) dec deq.count result = deq.data[deq.head] @@ -173,6 +376,23 @@ proc popFirst*[T](deq: var Deque[T]): T {.inline, discardable.} = proc popLast*[T](deq: var Deque[T]): T {.inline, discardable.} = ## Remove and returns the last element of the `deq`. + ## + ## See also: + ## * `addFirst proc <#addFirst,Deque[T],T>`_ + ## * `addLast proc <#addLast,Deque[T],T>`_ + ## * `peekFirst proc <#peekFirst,Deque[T]>`_ + ## * `peekLast proc <#peekLast,Deque[T]>`_ + ## * `popFirst proc <#popFirst,Deque[T]>`_ + ## * `clear proc <#clear,Deque[T]>`_ + ## * `shrink proc <#shrink,Deque[T],int,int>`_ + runnableExamples: + var a = initDeque[int]() + for i in 1 .. 5: + a.addLast(10*i) + assert $a == "[10, 20, 30, 40, 50]" + assert a.popLast == 50 + assert $a == "[10, 20, 30, 40]" + emptyCheck(deq) dec deq.count deq.tail = (deq.tail - 1) and deq.mask @@ -181,17 +401,39 @@ proc popLast*[T](deq: var Deque[T]): T {.inline, discardable.} = proc clear*[T](deq: var Deque[T]) {.inline.} = ## Resets the deque so that it is empty. + ## + ## See also: + ## * `clear proc <#clear,Deque[T]>`_ + ## * `shrink proc <#shrink,Deque[T],int,int>`_ + runnableExamples: + var a = initDeque[int]() + for i in 1 .. 5: + a.addFirst(10*i) + assert $a == "[50, 40, 30, 20, 10]" + clear(a) + assert len(a) == 0 + for el in mitems(deq): destroy(el) deq.count = 0 deq.tail = deq.head proc shrink*[T](deq: var Deque[T], fromFirst = 0, fromLast = 0) = ## Remove `fromFirst` elements from the front of the deque and - ## `fromLast` elements from the back. If the supplied number of - ## elements exceeds the total number of elements in the deque, - ## the deque will remain empty. + ## `fromLast` elements from the back. + ## + ## If the supplied number of elements exceeds the total number of elements + ## in the deque, the deque will remain empty. ## - ## Any user defined destructors + ## See also: + ## * `clear proc <#clear,Deque[T]>`_ + runnableExamples: + var a = initDeque[int]() + for i in 1 .. 5: + a.addFirst(10*i) + assert $a == "[50, 40, 30, 20, 10]" + a.shrink(fromFirst = 2, fromLast = 1) + assert $a == "[30, 20]" + if fromFirst + fromLast > deq.count: clear(deq) return @@ -214,6 +456,8 @@ proc `$`*[T](deq: Deque[T]): string = result.addQuoted(x) result.add("]") + + when isMainModule: var deq = initDeque[int](1) deq.addLast(4) diff --git a/lib/pure/collections/intsets.nim b/lib/pure/collections/intsets.nim index def96b8f7..9d7950ea6 100644 --- a/lib/pure/collections/intsets.nim +++ b/lib/pure/collections/intsets.nim @@ -9,9 +9,12 @@ ## The ``intsets`` module implements an efficient int set implemented as a ## `sparse bit set`:idx:. -## **Note**: Since Nim currently does not allow the assignment operator to -## be overloaded, ``=`` for int sets performs some rather meaningless shallow -## copy; use ``assign`` to get a deep copy. + +## **Note**: Currently the assignment operator ``=`` for ``intsets`` +## performs some rather meaningless shallow copy. Since Nim currently does +## not allow the assignment operator to be overloaded, use ``assign`` to +## get a deep copy. + import hashes, math @@ -94,7 +97,7 @@ proc intSetPut(t: var IntSet, key: int): PTrunk = t.data[h] = result proc contains*(s: IntSet, key: int): bool = - ## returns true iff `key` is in `s`. + ## Returns true iff `key` is in `s`. if s.elems <= s.a.len: for i in 0..<s.elems: if s.a[i] == key: return true @@ -107,7 +110,7 @@ proc contains*(s: IntSet, key: int): bool = result = false iterator items*(s: IntSet): int {.inline.} = - ## iterates over any included element of `s`. + ## Iterates over any included element of `s`. if s.elems <= s.a.len: for i in 0..<s.elems: yield s.a[i] @@ -135,7 +138,7 @@ proc bitincl(s: var IntSet, key: int) {.inline.} = `shl`(1, u and IntMask) proc incl*(s: var IntSet, key: int) = - ## includes an element `key` in `s`. + ## Includes an element `key` in `s`. if s.elems <= s.a.len: for i in 0..<s.elems: if s.a[i] == key: return @@ -170,7 +173,7 @@ proc exclImpl(s: var IntSet, key: int) = not `shl`(1, u and IntMask) proc excl*(s: var IntSet, key: int) = - ## excludes `key` from the set `s`. + ## Excludes `key` from the set `s`. exclImpl(s, key) proc excl*(s: var IntSet, other: IntSet) = @@ -178,14 +181,14 @@ proc excl*(s: var IntSet, other: IntSet) = for item in other: excl(s, item) proc missingOrExcl*(s: var IntSet, key: int) : bool = - ## returns true if `s` does not contain `key`, otherwise + ## Returns true if `s` does not contain `key`, otherwise ## `key` is removed from `s` and false is returned. var count = s.elems exclImpl(s, key) result = count == s.elems proc containsOrIncl*(s: var IntSet, key: int): bool = - ## returns true if `s` contains `key`, otherwise `key` is included in `s` + ## Returns true if `s` contains `key`, otherwise `key` is included in `s` ## and false is returned. if s.elems <= s.a.len: for i in 0..<s.elems: @@ -206,23 +209,28 @@ proc containsOrIncl*(s: var IntSet, key: int): bool = result = false proc initIntSet*: IntSet = - ## creates a new int set that is empty. - - #newSeq(result.data, InitIntSetSize) - #result.max = InitIntSetSize-1 - when defined(nimNoNilSeqs): - result.data = @[] - else: - result.data = nil - result.max = 0 - result.counter = 0 - result.head = nil - result.elems = 0 + ## Returns an empty IntSet. Example: + ## + ## .. code-block :: + ## var a = initIntSet() + ## a.incl(2) + + # newSeq(result.data, InitIntSetSize) + # result.max = InitIntSetSize-1 + result = IntSet( + elems: 0, + counter: 0, + max: 0, + head: nil, + data: when defined(nimNoNilSeqs): @[] else: nil) + # a: array[0..33, int] # profiling shows that 34 elements are enough proc clear*(result: var IntSet) = - #setLen(result.data, InitIntSetSize) - #for i in 0..InitIntSetSize-1: result.data[i] = nil - #result.max = InitIntSetSize-1 + ## Clears the IntSet back to an empty state. + + # setLen(result.data, InitIntSetSize) + # for i in 0..InitIntSetSize-1: result.data[i] = nil + # result.max = InitIntSetSize-1 when defined(nimNoNilSeqs): result.data = @[] else: @@ -304,7 +312,7 @@ proc `-`*(s1, s2: IntSet): IntSet {.inline.} = result = difference(s1, s2) proc disjoint*(s1, s2: IntSet): bool = - ## Returns true iff the sets `s1` and `s2` have no items in common. + ## Returns true if the sets `s1` and `s2` have no items in common. for item in s1: if contains(s2, item): return false @@ -320,7 +328,7 @@ proc len*(s: IntSet): int {.inline.} = inc(result) proc card*(s: IntSet): int {.inline.} = - ## alias for `len() <#len>` _. + ## Alias for `len() <#len>` _. result = s.len() proc `<=`*(s1, s2: IntSet): bool = @@ -349,12 +357,6 @@ proc `$`*(s: IntSet): string = ## The `$` operator for int sets. dollarImpl() -proc empty*(s: IntSet): bool {.inline, deprecated.} = - ## returns true if `s` is empty. This is safe to call even before - ## the set has been initialized with `initIntSet`. Note this never - ## worked reliably and so is deprecated. - result = s.counter == 0 - when isMainModule: import sequtils, algorithm diff --git a/lib/pure/collections/lists.nim b/lib/pure/collections/lists.nim index 15ce5d074..182eb8442 100644 --- a/lib/pure/collections/lists.nim +++ b/lib/pure/collections/lists.nim @@ -7,34 +7,112 @@ # distribution, for details about the copyright. # -## Implementation of singly and doubly linked lists. Because it makes no sense -## to do so, the 'next' and 'prev' pointers are not hidden from you and can -## be manipulated directly for efficiency. +## Implementation of: +## * `singly linked lists <#SinglyLinkedList>`_ +## * `doubly linked lists <#DoublyLinkedList>`_ +## * `singly linked rings <#SinglyLinkedRing>`_ (circular lists) +## * `doubly linked rings <#DoublyLinkedRing>`_ (circular lists) +## +## +## Basic Usage +## =========== +## +## Because it makes no sense to do otherwise, the `next` and `prev` pointers +## are not hidden from you and can be manipulated directly for efficiency. +## +## Lists +## ----- +## +## .. code-block:: +## import lists +## +## var +## l = initDoublyLinkedList[int]() +## a = newDoublyLinkedNode[int](3) +## b = newDoublyLinkedNode[int](7) +## c = newDoublyLinkedNode[int](9) +## +## l.append(a) +## l.append(b) +## l.prepend(c) +## +## assert a.next == b +## assert a.prev == c +## assert c.next == a +## assert c.next.next == b +## assert c.prev == nil +## assert b.next == nil +## +## +## Rings +## ----- +## +## .. code-block:: +## import lists +## +## var +## l = initSinglyLinkedRing[int]() +## a = newSinglyLinkedNode[int](3) +## b = newSinglyLinkedNode[int](7) +## c = newSinglyLinkedNode[int](9) +## +## l.append(a) +## l.append(b) +## l.prepend(c) +## +## assert c.next == a +## assert a.next == b +## assert c.next.next == b +## assert b.next == c +## assert c.next.next.next == c +## +## See also +## ======== +## +## * `deques module <#deques.html>`_ for double-ended queues +## * `sharedlist module <#sharedlist.html>`_ for shared singly-linked lists + when not defined(nimhygiene): {.pragma: dirty.} type - DoublyLinkedNodeObj*[T] = object ## a node a doubly linked list consists of + 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] value*: T DoublyLinkedNode*[T] = ref DoublyLinkedNodeObj[T] - SinglyLinkedNodeObj*[T] = object ## a node a singly linked list consists of + 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] value*: T SinglyLinkedNode*[T] = ref SinglyLinkedNodeObj[T] - SinglyLinkedList*[T] = object ## a singly linked list + SinglyLinkedList*[T] = object ## A singly linked list. + ## + ## Use `initSinglyLinkedList proc <#initSinglyLinkedList,>`_ to create + ## a new empty list. head*, tail*: SinglyLinkedNode[T] - DoublyLinkedList*[T] = object ## a doubly linked list + DoublyLinkedList*[T] = object ## A doubly linked list. + ## + ## Use `initDoublyLinkedList proc <#initDoublyLinkedList,>`_ to create + ## a new empty list. head*, tail*: DoublyLinkedNode[T] - SinglyLinkedRing*[T] = object ## a singly linked ring + SinglyLinkedRing*[T] = object ## A singly linked ring. + ## + ## Use `initSinglyLinkedRing proc <#initSinglyLinkedRing,>`_ to create + ## a new empty ring. head*, tail*: SinglyLinkedNode[T] - DoublyLinkedRing*[T] = object ## a doubly linked ring + DoublyLinkedRing*[T] = object ## A doubly linked ring. + ## + ## Use `initDoublyLinkedRing proc <#initDoublyLinkedRing,>`_ to create + ## a new empty ring. head*: DoublyLinkedNode[T] SomeLinkedList*[T] = SinglyLinkedList[T] | DoublyLinkedList[T] @@ -46,28 +124,44 @@ type SomeLinkedNode*[T] = SinglyLinkedNode[T] | DoublyLinkedNode[T] proc initSinglyLinkedList*[T](): SinglyLinkedList[T] = - ## creates a new singly linked list that is empty. + ## Creates a new singly linked list that is empty. + runnableExamples: + var a = initSinglyLinkedList[int]() discard proc initDoublyLinkedList*[T](): DoublyLinkedList[T] = - ## creates a new doubly linked list that is empty. + ## Creates a new doubly linked list that is empty. + runnableExamples: + var a = initDoublyLinkedList[int]() discard proc initSinglyLinkedRing*[T](): SinglyLinkedRing[T] = - ## creates a new singly linked ring that is empty. + ## Creates a new singly linked ring that is empty. + runnableExamples: + var a = initSinglyLinkedRing[int]() discard proc initDoublyLinkedRing*[T](): DoublyLinkedRing[T] = - ## creates a new doubly linked ring that is empty. + ## Creates a new doubly linked ring that is empty. + runnableExamples: + var a = initDoublyLinkedRing[int]() discard proc newDoublyLinkedNode*[T](value: T): DoublyLinkedNode[T] = - ## creates a new doubly linked node with the given `value`. + ## Creates a new doubly linked node with the given `value`. + runnableExamples: + var n = newDoublyLinkedNode[int](5) + assert n.value == 5 + new(result) result.value = value proc newSinglyLinkedNode*[T](value: T): SinglyLinkedNode[T] = - ## creates a new singly linked node with the given `value`. + ## Creates a new singly linked node with the given `value`. + runnableExamples: + var n = newSinglyLinkedNode[int](5) + assert n.value == 5 + new(result) result.value = value @@ -86,24 +180,100 @@ template itemsRingImpl() {.dirty.} = if it == L.head: break iterator items*[T](L: SomeLinkedList[T]): T = - ## yields every value of `L`. + ## Yields every value of `L`. + ## + ## See also: + ## * `mitems iterator <#mitems.i,SomeLinkedList[T]>`_ + ## * `nodes iterator <#nodes.i,SomeLinkedList[T]>`_ + ## + ## **Examples:** + ## + ## .. code-block:: + ## var a = initSinglyLinkedList[int]() + ## for i in 1 .. 3: + ## a.append(10*i) + ## + ## for x in a: # the same as: for x in items(a): + ## echo x + ## + ## # 10 + ## # 20 + ## # 30 itemsListImpl() iterator items*[T](L: SomeLinkedRing[T]): T = - ## yields every value of `L`. + ## Yields every value of `L`. + ## + ## See also: + ## * `mitems iterator <#mitems.i,SomeLinkedRing[T]>`_ + ## * `nodes iterator <#nodes.i,SomeLinkedRing[T]>`_ + ## + ## **Examples:** + ## + ## .. code-block:: + ## var a = initSinglyLinkedRing[int]() + ## for i in 1 .. 3: + ## a.append(10*i) + ## + ## for x in a: # the same as: for x in items(a): + ## echo x + ## + ## # 10 + ## # 20 + ## # 30 itemsRingImpl() iterator mitems*[T](L: var SomeLinkedList[T]): var T = - ## yields every value of `L` so that you can modify it. + ## Yields every value of `L` so that you can modify it. + ## + ## See also: + ## * `items iterator <#items.i,SomeLinkedList[T]>`_ + ## * `nodes iterator <#nodes.i,SomeLinkedList[T]>`_ + runnableExamples: + var a = initSinglyLinkedList[int]() + for i in 1 .. 5: + a.append(10*i) + assert $a == "[10, 20, 30, 40, 50]" + for x in mitems(a): + x = 5*x - 1 + assert $a == "[49, 99, 149, 199, 249]" itemsListImpl() iterator mitems*[T](L: var SomeLinkedRing[T]): var T = - ## yields every value of `L` so that you can modify it. + ## Yields every value of `L` so that you can modify it. + ## + ## See also: + ## * `items iterator <#items.i,SomeLinkedRing[T]>`_ + ## * `nodes iterator <#nodes.i,SomeLinkedRing[T]>`_ + runnableExamples: + var a = initSinglyLinkedRing[int]() + for i in 1 .. 5: + a.append(10*i) + assert $a == "[10, 20, 30, 40, 50]" + for x in mitems(a): + x = 5*x - 1 + assert $a == "[49, 99, 149, 199, 249]" itemsRingImpl() iterator nodes*[T](L: SomeLinkedList[T]): SomeLinkedNode[T] = - ## iterates over every node of `x`. Removing the current node from the + ## Iterates over every node of `x`. Removing the current node from the ## list during traversal is supported. + ## + ## See also: + ## * `items iterator <#items.i,SomeLinkedList[T]>`_ + ## * `mitems iterator <#mitems.i,SomeLinkedList[T]>`_ + runnableExamples: + var a = initDoublyLinkedList[int]() + for i in 1 .. 5: + a.append(10*i) + assert $a == "[10, 20, 30, 40, 50]" + for x in nodes(a): + if x.value == 30: + a.remove(x) + else: + x.value = 5*x.value - 1 + assert $a == "[49, 99, 199, 249]" + var it = L.head while it != nil: var nxt = it.next @@ -111,8 +281,24 @@ iterator nodes*[T](L: SomeLinkedList[T]): SomeLinkedNode[T] = it = nxt iterator nodes*[T](L: SomeLinkedRing[T]): SomeLinkedNode[T] = - ## iterates over every node of `x`. Removing the current node from the + ## Iterates over every node of `x`. Removing the current node from the ## list during traversal is supported. + ## + ## See also: + ## * `items iterator <#items.i,SomeLinkedRing[T]>`_ + ## * `mitems iterator <#mitems.i,SomeLinkedRing[T]>`_ + runnableExamples: + var a = initDoublyLinkedRing[int]() + for i in 1 .. 5: + a.append(10*i) + assert $a == "[10, 20, 30, 40, 50]" + for x in nodes(a): + if x.value == 30: + a.remove(x) + else: + x.value = 5*x.value - 1 + assert $a == "[49, 99, 199, 249]" + var it = L.head if it != nil: while true: @@ -122,7 +308,7 @@ iterator nodes*[T](L: SomeLinkedRing[T]): SomeLinkedNode[T] = if it == L.head: break proc `$`*[T](L: SomeLinkedCollection[T]): string = - ## turns a list into its string representation. + ## Turns a list into its string representation for logging and printing. result = "[" for x in nodes(L): if result.len > 1: result.add(", ") @@ -130,19 +316,54 @@ proc `$`*[T](L: SomeLinkedCollection[T]): string = result.add("]") proc find*[T](L: SomeLinkedCollection[T], value: T): SomeLinkedNode[T] = - ## searches in the list for a value. Returns nil if the value does not + ## Searches in the list for a value. Returns `nil` if the value does not ## exist. + ## + ## See also: + ## * `contains proc <#contains,SomeLinkedCollection[T],T>`_ + runnableExamples: + var a = initSinglyLinkedList[int]() + a.append(9) + a.append(8) + assert a.find(9).value == 9 + assert a.find(1) == nil + for x in nodes(L): if x.value == value: return x proc contains*[T](L: SomeLinkedCollection[T], value: T): bool {.inline.} = - ## searches in the list for a value. Returns false if the value does not - ## exist, true otherwise. + ## Searches in the list for a value. Returns `false` if the value does not + ## exist, `true` otherwise. + ## + ## See also: + ## * `find proc <#find,SomeLinkedCollection[T],T>`_ + runnableExamples: + var a = initSinglyLinkedList[int]() + a.append(9) + a.append(8) + assert a.contains(9) + assert 8 in a + assert(not a.contains(1)) + assert 2 notin a + result = find(L, value) != nil proc append*[T](L: var SinglyLinkedList[T], n: SinglyLinkedNode[T]) {.inline.} = - ## appends a node `n` to `L`. Efficiency: O(1). + ## Appends (adds to the end) a node `n` to `L`. Efficiency: O(1). + ## + ## See also: + ## * `append proc <#append,SinglyLinkedList[T],T>`_ for appending a value + ## * `prepend proc <#prepend,SinglyLinkedList[T],SinglyLinkedNode[T]>`_ + ## for prepending a node + ## * `prepend proc <#prepend,SinglyLinkedList[T],T>`_ for prepending a value + runnableExamples: + var + a = initSinglyLinkedList[int]() + n = newSinglyLinkedNode[int](9) + a.append(n) + assert a.contains(9) + n.next = nil if L.tail != nil: assert(L.tail.next == nil) @@ -151,22 +372,75 @@ proc append*[T](L: var SinglyLinkedList[T], if L.head == nil: L.head = n proc append*[T](L: var SinglyLinkedList[T], value: T) {.inline.} = - ## appends a value to `L`. Efficiency: O(1). + ## Appends (adds to the end) a value to `L`. Efficiency: O(1). + ## + ## See also: + ## * `append proc <#append,SinglyLinkedList[T],T>`_ for appending a value + ## * `prepend proc <#prepend,SinglyLinkedList[T],SinglyLinkedNode[T]>`_ + ## for prepending a node + ## * `prepend proc <#prepend,SinglyLinkedList[T],T>`_ for prepending a value + runnableExamples: + var a = initSinglyLinkedList[int]() + a.append(9) + a.append(8) + assert a.contains(9) append(L, newSinglyLinkedNode(value)) proc prepend*[T](L: var SinglyLinkedList[T], n: SinglyLinkedNode[T]) {.inline.} = - ## prepends a node to `L`. Efficiency: O(1). + ## Prepends (adds to the beginning) a node to `L`. Efficiency: O(1). + ## + ## See also: + ## * `append proc <#append,SinglyLinkedList[T],SinglyLinkedNode[T]>`_ + ## for appending a node + ## * `append proc <#append,SinglyLinkedList[T],T>`_ for appending a value + ## * `prepend proc <#prepend,SinglyLinkedList[T],T>`_ for prepending a value + runnableExamples: + var + a = initSinglyLinkedList[int]() + n = newSinglyLinkedNode[int](9) + a.prepend(n) + assert a.contains(9) + n.next = L.head L.head = n if L.tail == nil: L.tail = n proc prepend*[T](L: var SinglyLinkedList[T], value: T) {.inline.} = - ## prepends a node to `L`. Efficiency: O(1). + ## Prepends (adds to the beginning) a node to `L`. Efficiency: O(1). + ## + ## See also: + ## * `append proc <#append,SinglyLinkedList[T],SinglyLinkedNode[T]>`_ + ## for appending a node + ## * `append proc <#append,SinglyLinkedList[T],T>`_ for appending a value + ## * `prepend proc <#prepend,SinglyLinkedList[T],SinglyLinkedNode[T]>`_ + ## for prepending a node + runnableExamples: + var a = initSinglyLinkedList[int]() + a.prepend(9) + a.prepend(8) + assert a.contains(9) prepend(L, newSinglyLinkedNode(value)) + + proc append*[T](L: var DoublyLinkedList[T], n: DoublyLinkedNode[T]) = - ## appends a node `n` to `L`. Efficiency: O(1). + ## Appends (adds to the end) a node `n` to `L`. Efficiency: O(1). + ## + ## See also: + ## * `append proc <#append,DoublyLinkedList[T],T>`_ for appending a value + ## * `prepend proc <#prepend,DoublyLinkedList[T],DoublyLinkedNode[T]>`_ + ## for prepending a node + ## * `prepend proc <#prepend,DoublyLinkedList[T],T>`_ for prepending a value + ## * `remove proc <#remove,DoublyLinkedList[T],DoublyLinkedNode[T]>`_ + ## for removing a node + runnableExamples: + var + a = initDoublyLinkedList[int]() + n = newDoublyLinkedNode[int](9) + a.append(n) + assert a.contains(9) + n.next = nil n.prev = L.tail if L.tail != nil: @@ -176,11 +450,40 @@ proc append*[T](L: var DoublyLinkedList[T], n: DoublyLinkedNode[T]) = if L.head == nil: L.head = n proc append*[T](L: var DoublyLinkedList[T], value: T) = - ## appends a value to `L`. Efficiency: O(1). + ## Appends (adds to the end) a value to `L`. Efficiency: O(1). + ## + ## See also: + ## * `append proc <#append,DoublyLinkedList[T],DoublyLinkedNode[T]>`_ + ## for appending a node + ## * `prepend proc <#prepend,DoublyLinkedList[T],DoublyLinkedNode[T]>`_ + ## for prepending a node + ## * `prepend proc <#prepend,DoublyLinkedList[T],T>`_ for prepending a value + ## * `remove proc <#remove,DoublyLinkedList[T],DoublyLinkedNode[T]>`_ + ## for removing a node + runnableExamples: + var a = initDoublyLinkedList[int]() + a.append(9) + a.append(8) + assert a.contains(9) append(L, newDoublyLinkedNode(value)) proc prepend*[T](L: var DoublyLinkedList[T], n: DoublyLinkedNode[T]) = - ## prepends a node `n` to `L`. Efficiency: O(1). + ## Prepends (adds to the beginning) a node `n` to `L`. Efficiency: O(1). + ## + ## See also: + ## * `append proc <#append,DoublyLinkedList[T],DoublyLinkedNode[T]>`_ + ## for appending a node + ## * `append proc <#append,DoublyLinkedList[T],T>`_ for appending a value + ## * `prepend proc <#prepend,DoublyLinkedList[T],T>`_ for prepending a value + ## * `remove proc <#remove,DoublyLinkedList[T],DoublyLinkedNode[T]>`_ + ## for removing a node + runnableExamples: + var + a = initDoublyLinkedList[int]() + n = newDoublyLinkedNode[int](9) + a.prepend(n) + assert a.contains(9) + n.prev = nil n.next = L.head if L.head != nil: @@ -190,18 +493,56 @@ proc prepend*[T](L: var DoublyLinkedList[T], n: DoublyLinkedNode[T]) = if L.tail == nil: L.tail = n proc prepend*[T](L: var DoublyLinkedList[T], value: T) = - ## prepends a value to `L`. Efficiency: O(1). + ## Prepends (adds to the beginning) a value to `L`. Efficiency: O(1). + ## + ## See also: + ## * `append proc <#append,DoublyLinkedList[T],DoublyLinkedNode[T]>`_ + ## for appending a node + ## * `append proc <#append,DoublyLinkedList[T],T>`_ for appending a value + ## * `prepend proc <#prepend,DoublyLinkedList[T],DoublyLinkedNode[T]>`_ + ## for prepending a node + ## * `remove proc <#remove,DoublyLinkedList[T],DoublyLinkedNode[T]>`_ + ## for removing a node + runnableExamples: + var a = initDoublyLinkedList[int]() + a.prepend(9) + a.prepend(8) + assert a.contains(9) prepend(L, newDoublyLinkedNode(value)) proc remove*[T](L: var DoublyLinkedList[T], n: DoublyLinkedNode[T]) = - ## removes `n` from `L`. Efficiency: O(1). + ## Removes a node `n` from `L`. Efficiency: O(1). + runnableExamples: + var + a = initDoublyLinkedList[int]() + n = newDoublyLinkedNode[int](5) + a.append(n) + assert 5 in a + a.remove(n) + assert 5 notin a + if n == L.tail: L.tail = n.prev if n == L.head: L.head = n.next if n.next != nil: n.next.prev = n.prev if n.prev != nil: n.prev.next = n.next + + proc append*[T](L: var SinglyLinkedRing[T], n: SinglyLinkedNode[T]) = - ## appends a node `n` to `L`. Efficiency: O(1). + ## Appends (adds to the end) a node `n` to `L`. Efficiency: O(1). + ## + ## See also: + ## * `append proc <#append,SinglyLinkedRing[T],T>`_ for appending a value + ## * `prepend proc <#prepend,SinglyLinkedRing[T],SinglyLinkedNode[T]>`_ + ## for prepending a node + ## * `prepend proc <#prepend,SinglyLinkedRing[T],T>`_ for prepending a value + runnableExamples: + var + a = initSinglyLinkedRing[int]() + n = newSinglyLinkedNode[int](9) + a.append(n) + assert a.contains(9) + if L.head != nil: n.next = L.head assert(L.tail != nil) @@ -213,11 +554,36 @@ proc append*[T](L: var SinglyLinkedRing[T], n: SinglyLinkedNode[T]) = L.tail = n proc append*[T](L: var SinglyLinkedRing[T], value: T) = - ## appends a value to `L`. Efficiency: O(1). + ## Appends (adds to the end) a value to `L`. Efficiency: O(1). + ## + ## See also: + ## * `append proc <#append,SinglyLinkedRing[T],SinglyLinkedNode[T]>`_ + ## for appending a node + ## * `prepend proc <#prepend,SinglyLinkedRing[T],SinglyLinkedNode[T]>`_ + ## for prepending a node + ## * `prepend proc <#prepend,SinglyLinkedRing[T],T>`_ for prepending a value + runnableExamples: + var a = initSinglyLinkedRing[int]() + a.append(9) + a.append(8) + assert a.contains(9) append(L, newSinglyLinkedNode(value)) proc prepend*[T](L: var SinglyLinkedRing[T], n: SinglyLinkedNode[T]) = - ## prepends a node `n` to `L`. Efficiency: O(1). + ## Prepends (adds to the beginning) a node `n` to `L`. Efficiency: O(1). + ## + ## See also: + ## * `append proc <#append,SinglyLinkedRing[T],SinglyLinkedNode[T]>`_ + ## for appending a node + ## * `append proc <#append,SinglyLinkedRing[T],T>`_ for appending a value + ## * `prepend proc <#prepend,SinglyLinkedRing[T],T>`_ for prepending a value + runnableExamples: + var + a = initSinglyLinkedRing[int]() + n = newSinglyLinkedNode[int](9) + a.prepend(n) + assert a.contains(9) + if L.head != nil: n.next = L.head assert(L.tail != nil) @@ -228,11 +594,40 @@ proc prepend*[T](L: var SinglyLinkedRing[T], n: SinglyLinkedNode[T]) = L.head = n proc prepend*[T](L: var SinglyLinkedRing[T], value: T) = - ## prepends a value to `L`. Efficiency: O(1). + ## Prepends (adds to the beginning) a value to `L`. Efficiency: O(1). + ## + ## See also: + ## * `append proc <#append,SinglyLinkedRing[T],SinglyLinkedNode[T]>`_ + ## for appending a node + ## * `append proc <#append,SinglyLinkedRing[T],T>`_ for appending a value + ## * `prepend proc <#prepend,SinglyLinkedRing[T],SinglyLinkedNode[T]>`_ + ## for prepending a node + runnableExamples: + var a = initSinglyLinkedRing[int]() + a.prepend(9) + a.prepend(8) + assert a.contains(9) prepend(L, newSinglyLinkedNode(value)) + + proc append*[T](L: var DoublyLinkedRing[T], n: DoublyLinkedNode[T]) = - ## appends a node `n` to `L`. Efficiency: O(1). + ## Appends (adds to the end) a node `n` to `L`. Efficiency: O(1). + ## + ## See also: + ## * `append proc <#append,DoublyLinkedRing[T],T>`_ for appending a value + ## * `prepend proc <#prepend,DoublyLinkedRing[T],DoublyLinkedNode[T]>`_ + ## for prepending a node + ## * `prepend proc <#prepend,DoublyLinkedRing[T],T>`_ for prepending a value + ## * `remove proc <#remove,DoublyLinkedRing[T],DoublyLinkedNode[T]>`_ + ## for removing a node + runnableExamples: + var + a = initDoublyLinkedRing[int]() + n = newDoublyLinkedNode[int](9) + a.append(n) + assert a.contains(9) + if L.head != nil: n.next = L.head n.prev = L.head.prev @@ -244,11 +639,40 @@ proc append*[T](L: var DoublyLinkedRing[T], n: DoublyLinkedNode[T]) = L.head = n proc append*[T](L: var DoublyLinkedRing[T], value: T) = - ## appends a value to `L`. Efficiency: O(1). + ## Appends (adds to the end) a value to `L`. Efficiency: O(1). + ## + ## See also: + ## * `append proc <#append,DoublyLinkedRing[T],DoublyLinkedNode[T]>`_ + ## for appending a node + ## * `prepend proc <#prepend,DoublyLinkedRing[T],DoublyLinkedNode[T]>`_ + ## for prepending a node + ## * `prepend proc <#prepend,DoublyLinkedRing[T],T>`_ for prepending a value + ## * `remove proc <#remove,DoublyLinkedRing[T],DoublyLinkedNode[T]>`_ + ## for removing a node + runnableExamples: + var a = initDoublyLinkedRing[int]() + a.append(9) + a.append(8) + assert a.contains(9) append(L, newDoublyLinkedNode(value)) proc prepend*[T](L: var DoublyLinkedRing[T], n: DoublyLinkedNode[T]) = - ## prepends a node `n` to `L`. Efficiency: O(1). + ## Prepends (adds to the beginning) a node `n` to `L`. Efficiency: O(1). + ## + ## See also: + ## * `append proc <#append,DoublyLinkedRing[T],DoublyLinkedNode[T]>`_ + ## for appending a node + ## * `append proc <#append,DoublyLinkedRing[T],T>`_ for appending a value + ## * `prepend proc <#prepend,DoublyLinkedRing[T],T>`_ for prepending a value + ## * `remove proc <#remove,DoublyLinkedRing[T],DoublyLinkedNode[T]>`_ + ## for removing a node + runnableExamples: + var + a = initDoublyLinkedRing[int]() + n = newDoublyLinkedNode[int](9) + a.prepend(n) + assert a.contains(9) + if L.head != nil: n.next = L.head n.prev = L.head.prev @@ -260,11 +684,34 @@ proc prepend*[T](L: var DoublyLinkedRing[T], n: DoublyLinkedNode[T]) = L.head = n proc prepend*[T](L: var DoublyLinkedRing[T], value: T) = - ## prepends a value to `L`. Efficiency: O(1). + ## Prepends (adds to the beginning) a value to `L`. Efficiency: O(1). + ## + ## See also: + ## * `append proc <#append,DoublyLinkedRing[T],DoublyLinkedNode[T]>`_ + ## for appending a node + ## * `append proc <#append,DoublyLinkedRing[T],T>`_ for appending a value + ## * `prepend proc <#prepend,DoublyLinkedRing[T],DoublyLinkedNode[T]>`_ + ## for prepending a node + ## * `remove proc <#remove,DoublyLinkedRing[T],DoublyLinkedNode[T]>`_ + ## for removing a node + runnableExamples: + var a = initDoublyLinkedRing[int]() + a.prepend(9) + a.prepend(8) + assert a.contains(9) prepend(L, newDoublyLinkedNode(value)) proc remove*[T](L: var DoublyLinkedRing[T], n: DoublyLinkedNode[T]) = - ## removes `n` from `L`. Efficiency: O(1). + ## Removes `n` from `L`. Efficiency: O(1). + runnableExamples: + var + a = initDoublyLinkedRing[int]() + n = newDoublyLinkedNode[int](5) + a.append(n) + assert 5 in a + a.remove(n) + assert 5 notin a + n.next.prev = n.prev n.prev.next = n.next if n == L.head: diff --git a/lib/pure/collections/queues.nim b/lib/pure/collections/queues.nim deleted file mode 100644 index 9a1d169fb..000000000 --- a/lib/pure/collections/queues.nim +++ /dev/null @@ -1,257 +0,0 @@ -# -# -# Nim's Runtime Library -# (c) Copyright 2012 Andreas Rumpf -# -# See the file "copying.txt", included in this -# distribution, for details about the copyright. -# - -## Implementation of a `queue`:idx:. The underlying implementation uses a ``seq``. -## -## None of the procs that get an individual value from the queue can be used -## on an empty queue. -## If compiled with `boundChecks` option, those procs will raise an `IndexError` -## on such access. This should not be relied upon, as `-d:release` will -## disable those checks and may return garbage or crash the program. -## -## As such, a check to see if the queue is empty is needed before any -## access, unless your program logic guarantees it indirectly. -## -## .. code-block:: Nim -## proc foo(a, b: Positive) = # assume random positive values for `a` and `b` -## var q = initQueue[int]() # initializes the object -## for i in 1 ..< a: q.add i # populates the queue -## -## if b < q.len: # checking before indexed access -## echo "The element at index position ", b, " is ", q[b] -## -## # The following two lines don't need any checking on access due to the -## # logic of the program, but that would not be the case if `a` could be 0. -## assert q.front == 1 -## assert q.back == a -## -## while q.len > 0: # checking if the queue is empty -## echo q.pop() -## -## Note: For inter thread communication use -## a `Channel <channels.html>`_ instead. - -import math - -{.warning: "`queues` module is deprecated - use `deques` instead".} - -type - Queue* {.deprecated.} [T] = object ## A queue. - data: seq[T] - rd, wr, count, mask: int - -proc initQueue*[T](initialSize: int = 4): Queue[T] = - ## Create a new queue. - ## Optionally, the initial capacity can be reserved via `initialSize` as a - ## performance optimization. The length of a newly created queue will still - ## be 0. - ## - ## `initialSize` needs to be a power of two. If you need to accept runtime - ## values for this you could use the ``nextPowerOfTwo`` proc from the - ## `math <math.html>`_ module. - assert isPowerOfTwo(initialSize) - result.mask = initialSize-1 - newSeq(result.data, initialSize) - -proc len*[T](q: Queue[T]): int {.inline.}= - ## Return the number of elements of `q`. - result = q.count - -template emptyCheck(q) = - # Bounds check for the regular queue access. - when compileOption("boundChecks"): - if unlikely(q.count < 1): - raise newException(IndexError, "Empty queue.") - -template xBoundsCheck(q, i) = - # Bounds check for the array like accesses. - when compileOption("boundChecks"): # d:release should disable this. - if unlikely(i >= q.count): # x < q.low is taken care by the Natural parameter - raise newException(IndexError, - "Out of bounds: " & $i & " > " & $(q.count - 1)) - -proc front*[T](q: Queue[T]): T {.inline.}= - ## Return the oldest element of `q`. Equivalent to `q.pop()` but does not - ## remove it from the queue. - emptyCheck(q) - result = q.data[q.rd] - -proc back*[T](q: Queue[T]): T {.inline.} = - ## Return the newest element of `q` but does not remove it from the queue. - emptyCheck(q) - result = q.data[q.wr - 1 and q.mask] - -proc `[]`*[T](q: Queue[T], i: Natural) : T {.inline.} = - ## Access the i-th element of `q` by order of insertion. - ## q[0] is the oldest (the next one q.pop() will extract), - ## q[^1] is the newest (last one added to the queue). - xBoundsCheck(q, i) - return q.data[q.rd + i and q.mask] - -proc `[]`*[T](q: var Queue[T], i: Natural): var T {.inline.} = - ## Access the i-th element of `q` and returns a mutable - ## reference to it. - xBoundsCheck(q, i) - return q.data[q.rd + i and q.mask] - -proc `[]=`* [T] (q: var Queue[T], i: Natural, val : T) {.inline.} = - ## Change the i-th element of `q`. - xBoundsCheck(q, i) - q.data[q.rd + i and q.mask] = val - -iterator items*[T](q: Queue[T]): T = - ## Yield every element of `q`. - var i = q.rd - for c in 0 ..< q.count: - yield q.data[i] - i = (i + 1) and q.mask - -iterator mitems*[T](q: var Queue[T]): var T = - ## Yield every element of `q`. - var i = q.rd - for c in 0 ..< q.count: - yield q.data[i] - i = (i + 1) and q.mask - -iterator pairs*[T](q: Queue[T]): tuple[key: int, val: T] = - ## Yield every (position, value) of `q`. - var i = q.rd - for c in 0 ..< q.count: - yield (c, q.data[i]) - i = (i + 1) and q.mask - -proc contains*[T](q: Queue[T], item: T): bool {.inline.} = - ## Return true if `item` is in `q` or false if not found. Usually used - ## via the ``in`` operator. It is the equivalent of ``q.find(item) >= 0``. - ## - ## .. code-block:: Nim - ## if x in q: - ## assert q.contains x - for e in q: - if e == item: return true - return false - -proc add*[T](q: var Queue[T], item: T) = - ## Add an `item` to the end of the queue `q`. - var cap = q.mask+1 - if unlikely(q.count >= cap): - var n = newSeq[T](cap*2) - for i, x in pairs(q): # don't use copyMem because the GC and because it's slower. - shallowCopy(n[i], x) - shallowCopy(q.data, n) - q.mask = cap*2 - 1 - q.wr = q.count - q.rd = 0 - inc q.count - q.data[q.wr] = item - q.wr = (q.wr + 1) and q.mask - -template default[T](t: typedesc[T]): T = - var v: T - v - -proc pop*[T](q: var Queue[T]): T {.inline, discardable.} = - ## Remove and returns the first (oldest) element of the queue `q`. - emptyCheck(q) - dec q.count - result = q.data[q.rd] - q.data[q.rd] = default(type(result)) - q.rd = (q.rd + 1) and q.mask - -proc enqueue*[T](q: var Queue[T], item: T) = - ## Alias for the ``add`` operation. - q.add(item) - -proc dequeue*[T](q: var Queue[T]): T = - ## Alias for the ``pop`` operation. - q.pop() - -proc `$`*[T](q: Queue[T]): string = - ## Turn a queue into its string representation. - result = "[" - for x in items(q): # Don't remove the items here for reasons that don't fit in this margin. - if result.len > 1: result.add(", ") - result.add($x) - result.add("]") - -when isMainModule: - var q = initQueue[int](1) - q.add(123) - q.add(9) - q.enqueue(4) - var first = q.dequeue() - q.add(56) - q.add(6) - var second = q.pop() - q.add(789) - - assert first == 123 - assert second == 9 - assert($q == "[4, 56, 6, 789]") - - assert q[0] == q.front and q.front == 4 - q[0] = 42 - q[q.len - 1] = 7 - - assert 6 in q and 789 notin q - assert q.find(6) >= 0 - assert q.find(789) < 0 - - for i in -2 .. 10: - if i in q: - assert q.contains(i) and q.find(i) >= 0 - else: - assert(not q.contains(i) and q.find(i) < 0) - - when compileOption("boundChecks"): - try: - echo q[99] - assert false - except IndexError: - discard - - try: - assert q.len == 4 - for i in 0 ..< 5: q.pop() - assert false - except IndexError: - discard - - # grabs some types of resize error. - q = initQueue[int]() - for i in 1 .. 4: q.add i - q.pop() - q.pop() - for i in 5 .. 8: q.add i - assert $q == "[3, 4, 5, 6, 7, 8]" - - # Similar to proc from the documentation example - proc foo(a, b: Positive) = # assume random positive values for `a` and `b`. - var q = initQueue[int]() - assert q.len == 0 - for i in 1 .. a: q.add i - - if b < q.len: # checking before indexed access. - assert q[b] == b + 1 - - # The following two lines don't need any checking on access due to the logic - # of the program, but that would not be the case if `a` could be 0. - assert q.front == 1 - assert q.back == a - - while q.len > 0: # checking if the queue is empty - assert q.pop() > 0 - - #foo(0,0) - foo(8,5) - foo(10,9) - foo(1,1) - foo(2,1) - foo(1,5) - foo(3,2) diff --git a/lib/pure/collections/sequtils.nim b/lib/pure/collections/sequtils.nim index 39ba6df49..b0d50bce2 100644 --- a/lib/pure/collections/sequtils.nim +++ b/lib/pure/collections/sequtils.nim @@ -7,16 +7,71 @@ # distribution, for details about the copyright. # -## :Author: Alexander Mitchell-Robinson (Amrykid) +## Although this module has ``seq`` in its name, it implements operations +## not only for `seq`:idx: type, but for three built-in container types under +## the ``openArray`` umbrella: +## * sequences +## * strings +## * array ## -## This module implements operations for the built-in `seq`:idx: type which -## were inspired by functional programming languages. +## The system module defines several common functions, such as: +## * ``newseq[T]`` for creating new sequences of type ``T`` +## * ``@`` for converting arrays and strings to sequences +## * ``add`` for adding new elements to strings and sequences +## * ``&`` for string and seq concatenation +## * ``in`` (alias for ``contains``) and ``notin`` for checking if an item is +## in a container ## -## For functional style programming you may want to pass `anonymous procs -## <manual.html#procedures-anonymous-procs>`_ to procs like ``filter`` to -## reduce typing. Anonymous procs can use `the special do notation -## <manual.html#procedures-do-notation>`_ -## which is more convenient in certain situations. +## This module builds upon that, providing additional functionality in form of +## procs, iterators and templates inspired by functional programming +## languages. +## +## For functional style programming you have different options at your disposal: +## * pass `anonymous proc<manual.html#procedures-anonymous-procs>`_ +## * import `sugar module<sugar.html>`_ and use +## `=> macro<sugar.html#%3D>.m,untyped,untyped>`_ +## * use `...It templates<#18>`_ +## (`mapIt<#mapIt.t,typed,untyped>`_, +## `filterIt<#filterIt.t,untyped,untyped>`_, etc.) +## +## The chaining of functions is possible thanks to the +## `method call syntax<manual.html#procs-method-call-syntax>`_. +## +## .. code-block:: +## import sequtils, sugar +## +## # Creating a sequence from 1 to 10, multiplying each member by 2, +## # keeping only the members which are not divisible by 6. +## let +## foo = toSeq(1..10).map(x => x*2).filter(x => x mod 6 != 0) +## bar = toSeq(1..10).mapIt(it*2).filterIt(it mod 6 != 0) +## +## doAssert foo == bar +## echo foo # @[2, 4, 8, 10, 14, 16, 20] +## +## echo foo.any(x => x > 17) # true +## echo bar.allIt(it < 20) # false +## echo foo.foldl(a + b) # 74; sum of all members +## +## .. code-block:: +## import sequtils +## from strutils import join +## +## let +## vowels = @"aeiou" # creates a sequence @['a', 'e', 'i', 'o', 'u'] +## foo = "sequtils is an awesome module" +## +## echo foo.filterIt(it notin vowels).join # "sqtls s n wsm mdl" +## +## ---- +## +## **See also**: +## * `strutils module<strutils.html>`_ for common string functions +## * `sugar module<sugar.html>`_ for syntactic sugar macros +## * `algorithm module<algorithm.html>`_ for common generic algorithms +## * `json module<json.html>`_ for a structure which allows +## heterogeneous members + include "system/inclrtl" @@ -31,7 +86,7 @@ macro evalOnceAs(expAlias, exp: untyped, letAssigneable: static[bool]): untyped ## substitution in macro arguments such as ## https://github.com/nim-lang/Nim/issues/7187 ## ``evalOnceAs(myAlias, myExp)`` will behave as ``let myAlias = myExp`` - ## except when ``letAssigneable`` is false (eg to handle openArray) where + ## except when ``letAssigneable`` is false (e.g. to handle openArray) where ## it just forwards ``exp`` unchanged expectKind(expAlias, nnkIdent) var val = exp @@ -49,16 +104,20 @@ macro evalOnceAs(expAlias, exp: untyped, letAssigneable: static[bool]): untyped proc concat*[T](seqs: varargs[seq[T]]): seq[T] = ## Takes several sequences' items and returns them inside a new sequence. + ## All sequences must be of the same type. ## - ## Example: + ## See also: + ## * `distribute proc<#distribute,seq[T],Positive>`_ for a reverse + ## operation ## - ## .. code-block:: - ## let - ## s1 = @[1, 2, 3] - ## s2 = @[4, 5] - ## s3 = @[6, 7] - ## total = concat(s1, s2, s3) - ## assert total == @[1, 2, 3, 4, 5, 6, 7] + runnableExamples: + let + s1 = @[1, 2, 3] + s2 = @[4, 5] + s3 = @[6, 7] + total = concat(s1, s2, s3) + assert total == @[1, 2, 3, 4, 5, 6, 7] + var L = 0 for seqitm in items(seqs): inc(L, len(seqitm)) newSeq(result, L) @@ -71,13 +130,14 @@ proc concat*[T](seqs: varargs[seq[T]]): seq[T] = proc count*[T](s: openArray[T], x: T): int = ## Returns the number of occurrences of the item `x` in the container `s`. ## - ## Example: - ## - ## .. code-block:: - ## let - ## s = @[1, 2, 2, 3, 2, 4, 2] - ## c = count(s, 2) - ## assert c == 4 + runnableExamples: + let + a = @[1, 2, 2, 3, 2, 4, 2] + b = "abracadabra" + assert count(a, 2) == 4 + assert count(a, 99) == 0 + assert count(b, 'r') == 2 + for itm in items(s): if itm == x: inc result @@ -85,15 +145,14 @@ proc count*[T](s: openArray[T], x: T): int = proc cycle*[T](s: openArray[T], n: Natural): seq[T] = ## Returns a new sequence with the items of the container `s` repeated ## `n` times. + ## `n` must be a non-negative number (zero or more). ## - ## Example: - ## - ## .. code-block:: - ## - ## let - ## s = @[1, 2, 3] - ## total = s.cycle(3) - ## assert total == @[1, 2, 3, 1, 2, 3, 1, 2, 3] + runnableExamples: + let + s = @[1, 2, 3] + total = s.cycle(3) + assert total == @[1, 2, 3, 1, 2, 3, 1, 2, 3] + result = newSeq[T](n * s.len) var o = 0 for x in 0 ..< n: @@ -103,14 +162,13 @@ proc cycle*[T](s: openArray[T], n: Natural): seq[T] = proc repeat*[T](x: T, n: Natural): seq[T] = ## Returns a new sequence with the item `x` repeated `n` times. + ## `n` must be a non-negative number (zero or more). ## - ## Example: - ## - ## .. code-block:: - ## - ## let - ## total = repeat(5, 3) - ## assert total == @[5, 5, 5] + runnableExamples: + let + total = repeat(5, 3) + assert total == @[5, 5, 5] + result = newSeq[T](n) for i in 0 ..< n: result[i] = x @@ -118,16 +176,18 @@ proc repeat*[T](x: T, n: Natural): seq[T] = proc deduplicate*[T](s: openArray[T], isSorted: bool = false): seq[T] = ## Returns a new sequence without duplicates. ## - ## Example: + ## Setting the optional argument ``isSorted`` to ``true`` (default: false) + ## uses a faster algorithm for deduplication. ## - ## .. code-block:: - ## let - ## dup1 = @[1, 1, 3, 4, 2, 2, 8, 1, 4] - ## dup2 = @["a", "a", "c", "d", "d"] - ## unique1 = deduplicate(dup1) - ## unique2 = deduplicate(dup2) - ## assert unique1 == @[1, 3, 4, 2, 8] - ## assert unique2 == @["a", "c", "d"] + runnableExamples: + let + dup1 = @[1, 1, 3, 4, 2, 2, 8, 1, 4] + dup2 = @["a", "a", "c", "d", "d"] + unique1 = deduplicate(dup1) + unique2 = deduplicate(dup2, isSorted = true) + assert unique1 == @[1, 3, 4, 2, 8] + assert unique2 == @["a", "c", "d"] + result = @[] if s.len > 0: if isSorted: @@ -144,39 +204,44 @@ proc deduplicate*[T](s: openArray[T], isSorted: bool = false): seq[T] = proc zip*[S, T](s1: openArray[S], s2: openArray[T]): seq[tuple[a: S, b: T]] = ## Returns a new sequence with a combination of the two input containers. ## - ## For convenience you can access the returned tuples through the named - ## fields `a` and `b`. If one container is shorter, the remaining items in - ## the longer container are discarded. + ## The input containers can be of different types. + ## If one container is shorter, the remaining items in the longer container + ## are discarded. ## - ## Example: + ## For convenience you can access the returned tuples through the named + ## fields `a` and `b`. ## - ## .. code-block:: - ## let - ## short = @[1, 2, 3] - ## long = @[6, 5, 4, 3, 2, 1] - ## words = @["one", "two", "three"] - ## zip1 = zip(short, long) - ## zip2 = zip(short, words) - ## assert zip1 == @[(1, 6), (2, 5), (3, 4)] - ## assert zip2 == @[(1, "one"), (2, "two"), (3, "three")] - ## assert zip1[2].b == 4 - ## assert zip2[2].b == "three" + runnableExamples: + let + short = @[1, 2, 3] + long = @[6, 5, 4, 3, 2, 1] + words = @["one", "two", "three"] + letters = "abcd" + zip1 = zip(short, long) + zip2 = zip(short, words) + zip3 = zip(long, letters) + assert zip1 == @[(1, 6), (2, 5), (3, 4)] + assert zip2 == @[(1, "one"), (2, "two"), (3, "three")] + assert zip3 == @[(a: 6, b: 'a'), (a: 5, b: 'b'), (a: 4, b: 'c'), + (a: 3, b: 'd')] + assert zip1[2].b == 4 + assert zip2[2].b == "three" + var m = min(s1.len, s2.len) newSeq(result, m) for i in 0 ..< m: result[i] = (s1[i], s2[i]) proc distribute*[T](s: seq[T], num: Positive, spread = true): seq[seq[T]] = - ## Splits and distributes a sequence `s` into `num` sub sequences. + ## Splits and distributes a sequence `s` into `num` sub-sequences. ## - ## Returns a sequence of `num` sequences. For some input values this is the - ## inverse of the `concat <#concat>`_ proc. The proc will assert in debug - ## builds if `s` is nil or `num` is less than one, and will likely crash on - ## release builds. The input sequence `s` can be empty, which will produce + ## Returns a sequence of `num` sequences. For *some* input values this is the + ## inverse of the `concat <#concat,varargs[seq[T]]>`_ proc. + ## The input sequence `s` can be empty, which will produce ## `num` empty sequences. ## ## If `spread` is false and the length of `s` is not a multiple of `num`, the - ## proc will max out the first sub sequences with ``1 + len(s) div num`` + ## proc will max out the first sub-sequence with ``1 + len(s) div num`` ## entries, leaving the remainder of elements to the last sequence. ## ## On the other hand, if `spread` is true, the proc will distribute evenly @@ -184,18 +249,16 @@ proc distribute*[T](s: seq[T], num: Positive, spread = true): seq[seq[T]] = ## more suited to multithreading where you are passing equal sized work units ## to a thread pool and want to maximize core usage. ## - ## Example: - ## - ## .. code-block:: - ## let numbers = @[1, 2, 3, 4, 5, 6, 7] - ## assert numbers.distribute(3) == @[@[1, 2, 3], @[4, 5], @[6, 7]] - ## assert numbers.distribute(3, false) == @[@[1, 2, 3], @[4, 5, 6], @[7]] - ## assert numbers.distribute(6)[0] == @[1, 2] - ## assert numbers.distribute(6)[5] == @[7] + runnableExamples: + let numbers = @[1, 2, 3, 4, 5, 6, 7] + assert numbers.distribute(3) == @[@[1, 2, 3], @[4, 5], @[6, 7]] + assert numbers.distribute(3, false) == @[@[1, 2, 3], @[4, 5, 6], @[7]] + assert numbers.distribute(6)[0] == @[1, 2] + assert numbers.distribute(6)[1] == @[3] + if num < 2: result = @[s] return - let num = int(num) # XXX probably only needed because of .. bug # Create the result and calculate the stride size and the remainder if any. @@ -209,13 +272,11 @@ proc distribute*[T](s: seq[T], num: Positive, spread = true): seq[seq[T]] = if extra == 0 or spread == false: # Use an algorithm which overcounts the stride and minimizes reading limits. if extra > 0: inc(stride) - for i in 0 ..< num: result[i] = newSeq[T]() for g in first ..< min(s.len, first + stride): result[i].add(s[g]) first += stride - else: # Use an undercounting algorithm which *adds* the remainder each iteration. for i in 0 ..< num: @@ -223,7 +284,6 @@ proc distribute*[T](s: seq[T], num: Positive, spread = true): seq[seq[T]] = if extra > 0: extra -= 1 inc(last) - result[i] = newSeq[T]() for g in first ..< last: result[i].add(s[g]) @@ -231,110 +291,103 @@ proc distribute*[T](s: seq[T], num: Positive, spread = true): seq[seq[T]] = proc map*[T, S](s: openArray[T], op: proc (x: T): S {.closure.}): seq[S]{.inline.} = - ## Returns a new sequence with the results of `op` applied to every item in - ## the container `s`. + ## Returns a new sequence with the results of `op` proc applied to every + ## item in the container `s`. ## - ## Since the input is not modified you can use this version of ``map`` to + ## Since the input is not modified you can use it to ## transform the type of the elements in the input container. ## - ## Example: + ## See also: + ## * `mapIt template<#mapIt.t,typed,untyped>`_ + ## * `apply proc<#apply,openArray[T],proc(T)_2>`_ for the in-place version ## - ## .. code-block:: nim - ## let - ## a = @[1, 2, 3, 4] - ## b = map(a, proc(x: int): string = $x) - ## assert b == @["1", "2", "3", "4"] + runnableExamples: + let + a = @[1, 2, 3, 4] + b = map(a, proc(x: int): string = $x) + assert b == @["1", "2", "3", "4"] + newSeq(result, s.len) for i in 0 ..< s.len: result[i] = op(s[i]) -proc map*[T](s: var openArray[T], op: proc (x: var T) {.closure.}) - {.deprecated.} = - ## Applies `op` to every item in `s` modifying it directly. - ## - ## Note that this version of ``map`` requires your input and output types to - ## be the same, since they are modified in-place. - ## - ## Example: - ## - ## .. code-block:: nim - ## var a = @["1", "2", "3", "4"] - ## echo repr(a) - ## # --> ["1", "2", "3", "4"] - ## map(a, proc(x: var string) = x &= "42") - ## echo repr(a) - ## # --> ["142", "242", "342", "442"] - ## **Deprecated since version 0.12.0:** Use the ``apply`` proc instead. - for i in 0 ..< s.len: op(s[i]) - proc apply*[T](s: var openArray[T], op: proc (x: var T) {.closure.}) {.inline.} = ## Applies `op` to every item in `s` modifying it directly. ## - ## Note that this requires your input and output types to - ## be the same, since they are modified in-place. + ## Note that container `s` must be declared as a ``var`` + ## and it is required for your input and output types to + ## be the same, since `s` is modified in-place. ## The parameter function takes a ``var T`` type parameter. ## - ## Example: - ## - ## .. code-block:: nim - ## var a = @["1", "2", "3", "4"] - ## echo repr(a) - ## # --> ["1", "2", "3", "4"] - ## apply(a, proc(x: var string) = x &= "42") - ## echo repr(a) - ## # --> ["142", "242", "342", "442"] + ## See also: + ## * `applyIt template<#applyIt.t,untyped,untyped>`_ + ## * `map proc<#map,openArray[T],proc(T)>`_ ## + runnableExamples: + var a = @["1", "2", "3", "4"] + apply(a, proc(x: var string) = x &= "42") + assert a == @["142", "242", "342", "442"] + for i in 0 ..< s.len: op(s[i]) proc apply*[T](s: var openArray[T], op: proc (x: T): T {.closure.}) {.inline.} = ## Applies `op` to every item in `s` modifying it directly. ## - ## Note that this requires your input and output types to - ## be the same, since they are modified in-place. + ## Note that container `s` must be declared as a ``var`` + ## and it is required for your input and output types to + ## be the same, since `s` is modified in-place. ## The parameter function takes and returns a ``T`` type variable. ## - ## Example: - ## - ## .. code-block:: nim - ## var a = @["1", "2", "3", "4"] - ## echo repr(a) - ## # --> ["1", "2", "3", "4"] - ## apply(a, proc(x: string): string = x & "42") - ## echo repr(a) - ## # --> ["142", "242", "342", "442"] + ## See also: + ## * `applyIt template<#applyIt.t,untyped,untyped>`_ + ## * `map proc<#map,openArray[T],proc(T)>`_ ## + runnableExamples: + var a = @["1", "2", "3", "4"] + apply(a, proc(x: string): string = x & "42") + assert a == @["142", "242", "342", "442"] + for i in 0 ..< s.len: s[i] = op(s[i]) iterator filter*[T](s: openArray[T], pred: proc(x: T): bool {.closure.}): T = - ## Iterates through a container and yields every item that fulfills the - ## predicate. + ## Iterates through a container `s` and yields every item that fulfills the + ## predicate `pred` (function that returns a `bool`). ## - ## Example: + ## See also: + ## * `fliter proc<#filter,openArray[T],proc(T)>`_ + ## * `filterIt template<#filterIt.t,untyped,untyped>`_ ## - ## .. code-block:: - ## let numbers = @[1, 4, 5, 8, 9, 7, 4] - ## for n in filter(numbers, proc (x: int): bool = x mod 2 == 0): - ## echo($n) - ## # echoes 4, 8, 4 in separate lines + runnableExamples: + let numbers = @[1, 4, 5, 8, 9, 7, 4] + var evens = newSeq[int]() + for n in filter(numbers, proc (x: int): bool = x mod 2 == 0): + evens.add(n) + assert evens == @[4, 8, 4] + for i in 0 ..< s.len: if pred(s[i]): yield s[i] proc filter*[T](s: openArray[T], pred: proc(x: T): bool {.closure.}): seq[T] {.inline.} = - ## Returns a new sequence with all the items that fulfilled the predicate. + ## Returns a new sequence with all the items of `s` that fulfilled the + ## predicate `pred` (function that returns a `bool`). ## - ## Example: + ## See also: + ## * `filterIt template<#filterIt.t,untyped,untyped>`_ + ## * `filter iterator<#filter.i,openArray[T],proc(T)>`_ + ## * `keepIf proc<#keepIf,seq[T],proc(T)>`_ for the in-place version ## - ## .. code-block:: - ## let - ## colors = @["red", "yellow", "black"] - ## f1 = filter(colors, proc(x: string): bool = x.len < 6) - ## f2 = filter(colors) do (x: string) -> bool : x.len > 5 - ## assert f1 == @["red", "black"] - ## assert f2 == @["yellow"] + runnableExamples: + let + colors = @["red", "yellow", "black"] + f1 = filter(colors, proc(x: string): bool = x.len < 6) + f2 = filter(colors, proc(x: string): bool = x.contains('y')) + assert f1 == @["red", "black"] + assert f2 == @["yellow"] + result = newSeq[T]() for i in 0 ..< s.len: if pred(s[i]): @@ -342,15 +395,23 @@ proc filter*[T](s: openArray[T], pred: proc(x: T): bool {.closure.}): seq[T] proc keepIf*[T](s: var seq[T], pred: proc(x: T): bool {.closure.}) {.inline.} = - ## Keeps the items in the passed sequence if they fulfilled the predicate. - ## Same as the ``filter`` proc, but modifies the sequence directly. + ## Keeps the items in the passed sequence `s` if they fulfilled the + ## predicate `pred` (function that returns a `bool`). ## - ## Example: + ## Note that `s` must be declared as a ``var``. ## - ## .. code-block:: - ## var floats = @[13.0, 12.5, 5.8, 2.0, 6.1, 9.9, 10.1] - ## keepIf(floats, proc(x: float): bool = x > 10) - ## assert floats == @[13.0, 12.5, 10.1] + ## Similar to the `filter proc<#filter,openArray[T],proc(T)>`_, + ## but modifies the sequence directly. + ## + ## See also: + ## * `keepItIf template<#keepItIf.t,seq,untyped>`_ + ## * `filter proc<#filter,openArray[T],proc(T)>`_ + ## + runnableExamples: + var floats = @[13.0, 12.5, 5.8, 2.0, 6.1, 9.9, 10.1] + keepIf(floats, proc(x: float): bool = x > 10) + assert floats == @[13.0, 12.5, 10.1] + var pos = 0 for i in 0 ..< len(s): if pred(s[i]): @@ -360,16 +421,15 @@ proc keepIf*[T](s: var seq[T], pred: proc(x: T): bool {.closure.}) setLen(s, pos) proc delete*[T](s: var seq[T]; first, last: Natural) = - ## Deletes in `s` the items at position `first` .. `last`. This modifies - ## `s` itself, it does not return a copy. - ## - ## Example: + ## Deletes in the items of a sequence `s` at positions ``first..last`` + ## (including both ends of a range). + ## This modifies `s` itself, it does not return a copy. ## - ##.. code-block:: - ## let outcome = @[1,1,1,1,1,1,1,1] - ## var dest = @[1,1,1,2,2,2,2,2,2,1,1,1,1,1] - ## dest.delete(3, 8) - ## assert outcome == dest + runnableExamples: + let outcome = @[1,1,1,1,1,1,1,1] + var dest = @[1,1,1,2,2,2,2,2,2,1,1,1,1,1] + dest.delete(3, 8) + assert outcome == dest var i = first var j = last+1 @@ -384,15 +444,15 @@ proc insert*[T](dest: var seq[T], src: openArray[T], pos=0) = ## Inserts items from `src` into `dest` at position `pos`. This modifies ## `dest` itself, it does not return a copy. ## - ## Example: + ## Notice that `src` and `dest` must be of the same type. ## - ##.. code-block:: - ## var dest = @[1,1,1,1,1,1,1,1] - ## let - ## src = @[2,2,2,2,2,2] - ## outcome = @[1,1,1,2,2,2,2,2,2,1,1,1,1,1] - ## dest.insert(src, 3) - ## assert dest == outcome + runnableExamples: + var dest = @[1,1,1,1,1,1,1,1] + let + src = @[2,2,2,2,2,2] + outcome = @[1,1,1,2,2,2,2,2,2,1,1,1,1,1] + dest.insert(src, 3) + assert dest == outcome var j = len(dest) - 1 var i = len(dest) + len(src) - 1 @@ -411,37 +471,48 @@ proc insert*[T](dest: var seq[T], src: openArray[T], pos=0) = template filterIt*(s, pred: untyped): untyped = - ## Returns a new sequence with all the items that fulfilled the predicate. + ## Returns a new sequence with all the items of `s` that fulfilled the + ## predicate `pred`. ## - ## Unlike the `proc` version, the predicate needs to be an expression using - ## the ``it`` variable for testing, like: ``filterIt("abcxyz", it == 'x')``. + ## Unlike the `filter proc<#filter,openArray[T],proc(T)>`_ and + ## `filter iterator<#filter.i,openArray[T],proc(T)>`_, + ## the predicate needs to be an expression using the ``it`` variable + ## for testing, like: ``filterIt("abcxyz", it == 'x')``. ## - ## Example: + ## See also: + ## * `fliter proc<#filter,openArray[T],proc(T)>`_ + ## * `filter iterator<#filter.i,openArray[T],proc(T)>`_ ## - ## .. code-block:: - ## let - ## temperatures = @[-272.15, -2.0, 24.5, 44.31, 99.9, -113.44] - ## acceptable = filterIt(temperatures, it < 50 and it > -10) - ## notAcceptable = filterIt(temperatures, it > 50 or it < -10) - ## assert acceptable == @[-2.0, 24.5, 44.31] - ## assert notAcceptable == @[-272.15, 99.9, -113.44] + runnableExamples: + let + temperatures = @[-272.15, -2.0, 24.5, 44.31, 99.9, -113.44] + acceptable = temperatures.filterIt(it < 50 and it > -10) + notAcceptable = temperatures.filterIt(it > 50 or it < -10) + assert acceptable == @[-2.0, 24.5, 44.31] + assert notAcceptable == @[-272.15, 99.9, -113.44] + var result = newSeq[type(s[0])]() for it {.inject.} in items(s): if pred: result.add(it) result template keepItIf*(varSeq: seq, pred: untyped) = - ## Convenience template around the ``keepIf`` proc to reduce typing. + ## Keeps the items in the passed sequence (must be declared as a ``var``) + ## if they fulfilled the predicate. ## - ## Unlike the `proc` version, the predicate needs to be an expression using + ## Unlike the `keepIf proc<#keepIf,seq[T],proc(T)>`_, + ## the predicate needs to be an expression using ## the ``it`` variable for testing, like: ``keepItIf("abcxyz", it == 'x')``. ## - ## Example: + ## See also: + ## * `keepIf proc<#keepIf,seq[T],proc(T)>`_ + ## * `filterIt template<#filterIt.t,untyped,untyped>`_ ## - ## .. code-block:: - ## var candidates = @["foo", "bar", "baz", "foobar"] - ## keepItIf(candidates, it.len == 3 and it[0] == 'b') - ## assert candidates == @["bar", "baz"] + runnableExamples: + var candidates = @["foo", "bar", "baz", "foobar"] + candidates.keepItIf(it.len == 3 and it[0] == 'b') + assert candidates == @["bar", "baz"] + var pos = 0 for i in 0 ..< len(varSeq): let it {.inject.} = varSeq[i] @@ -455,26 +526,37 @@ proc all*[T](s: openArray[T], pred: proc(x: T): bool {.closure.}): bool = ## Iterates through a container and checks if every item fulfills the ## predicate. ## - ## Example: + ## See also: + ## * `allIt template<#allIt.t,untyped,untyped>`_ + ## * `any proc<#any,openArray[T],proc(T)>`_ ## - ## .. code-block:: - ## let numbers = @[1, 4, 5, 8, 9, 7, 4] - ## assert all(numbers, proc (x: int): bool = return x < 10) == true - ## assert all(numbers, proc (x: int): bool = return x < 9) == false + runnableExamples: + let numbers = @[1, 4, 5, 8, 9, 7, 4] + assert all(numbers, proc (x: int): bool = return x < 10) == true + assert all(numbers, proc (x: int): bool = return x < 9) == false + for i in s: if not pred(i): return false return true template allIt*(s, pred: untyped): bool = - ## Checks if every item fulfills the predicate. + ## Iterates through a container and checks if every item fulfills the + ## predicate. ## - ## Example: + ## Unlike the `all proc<#all,openArray[T],proc(T)>`_, + ## the predicate needs to be an expression using + ## the ``it`` variable for testing, like: ``allIt("abba", it == 'a')``. ## - ## .. code-block:: - ## let numbers = @[1, 4, 5, 8, 9, 7, 4] - ## assert allIt(numbers, it < 10) == true - ## assert allIt(numbers, it < 9) == false + ## See also: + ## * `all proc<#all,openArray[T],proc(T)>`_ + ## * `anyIt template<#anyIt.t,untyped,untyped>`_ + ## + runnableExamples: + let numbers = @[1, 4, 5, 8, 9, 7, 4] + assert numbers.allIt(it < 10) == true + assert numbers.allIt(it < 9) == false + var result = true for it {.inject.} in items(s): if not pred: @@ -486,26 +568,37 @@ proc any*[T](s: openArray[T], pred: proc(x: T): bool {.closure.}): bool = ## Iterates through a container and checks if some item fulfills the ## predicate. ## - ## Example: + ## See also: + ## * `anyIt template<#anyIt.t,untyped,untyped>`_ + ## * `all proc<#all,openArray[T],proc(T)>`_ ## - ## .. code-block:: - ## let numbers = @[1, 4, 5, 8, 9, 7, 4] - ## assert any(numbers, proc (x: int): bool = return x > 8) == true - ## assert any(numbers, proc (x: int): bool = return x > 9) == false + runnableExamples: + let numbers = @[1, 4, 5, 8, 9, 7, 4] + assert any(numbers, proc (x: int): bool = return x > 8) == true + assert any(numbers, proc (x: int): bool = return x > 9) == false + for i in s: if pred(i): return true return false template anyIt*(s, pred: untyped): bool = - ## Checks if some item fulfills the predicate. + ## Iterates through a container and checks if some item fulfills the + ## predicate. ## - ## Example: + ## Unlike the `any proc<#any,openArray[T],proc(T)>`_, + ## the predicate needs to be an expression using + ## the ``it`` variable for testing, like: ``anyIt("abba", it == 'a')``. ## - ## .. code-block:: - ## let numbers = @[1, 4, 5, 8, 9, 7, 4] - ## assert anyIt(numbers, it > 8) == true - ## assert anyIt(numbers, it > 9) == false + ## See also: + ## * `any proc<#any,openArray[T],proc(T)>`_ + ## * `allIt template<#allIt.t,untyped,untyped>`_ + ## + runnableExamples: + let numbers = @[1, 4, 5, 8, 9, 7, 4] + assert numbers.anyIt(it > 8) == true + assert numbers.anyIt(it > 9) == false + var result = false for it {.inject.} in items(s): if pred: @@ -555,19 +648,28 @@ template toSeq2(iter: iterator): untyped = result template toSeq*(iter: untyped): untyped = - ## Transforms any iterable into a sequence. + ## Transforms any iterable (anything that can be iterated over, e.g. with + ## a for-loop) into a sequence. + ## runnableExamples: let - numeric = @[1, 2, 3, 4, 5, 6, 7, 8, 9] - odd_numbers = toSeq(filter(numeric, proc(x: int): bool = x mod 2 == 1)) - doAssert odd_numbers == @[1, 3, 5, 7, 9] + myRange = 1..5 + mySet: set[int8] = {5'i8, 3, 1} + assert type(myRange) is HSlice[system.int, system.int] + assert type(mySet) is set[int8] + + let + mySeq1 = toSeq(myRange) + mySeq2 = toSeq(mySet) + assert mySeq1 == @[1, 2, 3, 4, 5] + assert mySeq2 == @[1'i8, 3, 5] when compiles(toSeq1(iter)): toSeq1(iter) elif compiles(toSeq2(iter)): toSeq2(iter) else: - # overload for untyped, eg: `toSeq(myInlineIterator(3))` + # overload for untyped, e.g.: `toSeq(myInlineIterator(3))` when compiles(iter.len): block: evalOnceAs(iter2, iter, true) @@ -597,20 +699,23 @@ template foldl*(sequence, operation: untyped): untyped = ## the sequence of numbers 1, 2 and 3 will be parenthesized as (((1) - 2) - ## 3). ## - ## Example: + ## See also: + ## * `foldl template<#foldl.t,,,>`_ with a starting parameter + ## * `foldr template<#foldr.t,untyped,untyped>`_ ## - ## .. code-block:: - ## let - ## numbers = @[5, 9, 11] - ## addition = foldl(numbers, a + b) - ## subtraction = foldl(numbers, a - b) - ## multiplication = foldl(numbers, a * b) - ## words = @["nim", "is", "cool"] - ## concatenation = foldl(words, a & b) - ## assert addition == 25, "Addition is (((5)+9)+11)" - ## assert subtraction == -15, "Subtraction is (((5)-9)-11)" - ## assert multiplication == 495, "Multiplication is (((5)*9)*11)" - ## assert concatenation == "nimiscool" + runnableExamples: + let + numbers = @[5, 9, 11] + addition = foldl(numbers, a + b) + subtraction = foldl(numbers, a - b) + multiplication = foldl(numbers, a * b) + words = @["nim", "is", "cool"] + concatenation = foldl(words, a & b) + assert addition == 25, "Addition is (((5)+9)+11)" + assert subtraction == -15, "Subtraction is (((5)-9)-11)" + assert multiplication == 495, "Multiplication is (((5)*9)*11)" + assert concatenation == "nimiscool" + let s = sequence assert s.len > 0, "Can't fold empty sequences" var result: type(s[0]) @@ -625,20 +730,22 @@ template foldl*(sequence, operation: untyped): untyped = template foldl*(sequence, operation, first): untyped = ## Template to fold a sequence from left to right, returning the accumulation. ## - ## This version of ``foldl`` gets a starting parameter. This makes it possible + ## This version of ``foldl`` gets a **starting parameter**. This makes it possible ## to accumulate the sequence into a different type than the sequence elements. ## ## The ``operation`` parameter should be an expression which uses the variables ## ``a`` and ``b`` for each step of the fold. The ``first`` parameter is the ## start value (the first ``a``) and therefor defines the type of the result. ## - ## Example: + ## See also: + ## * `foldr template<#foldr.t,untyped,untyped>`_ ## - ## .. code-block:: - ## let - ## numbers = @[0, 8, 1, 5] - ## digits = foldl(numbers, a & (chr(b + ord('0'))), "") - ## assert digits == "0815" + runnableExamples: + let + numbers = @[0, 8, 1, 5] + digits = foldl(numbers, a & (chr(b + ord('0'))), "") + assert digits == "0815" + var result: type(first) result = first for x in items(sequence): @@ -662,20 +769,23 @@ template foldr*(sequence, operation: untyped): untyped = ## the sequence of numbers 1, 2 and 3 will be parenthesized as (1 - (2 - ## (3))). ## - ## Example: + ## See also: + ## * `foldl template<#foldl.t,untyped,untyped>`_ + ## * `foldl template<#foldl.t,,,>`_ with a starting parameter ## - ## .. code-block:: - ## let - ## numbers = @[5, 9, 11] - ## addition = foldr(numbers, a + b) - ## subtraction = foldr(numbers, a - b) - ## multiplication = foldr(numbers, a * b) - ## words = @["nim", "is", "cool"] - ## concatenation = foldr(words, a & b) - ## assert addition == 25, "Addition is (5+(9+(11)))" - ## assert subtraction == 7, "Subtraction is (5-(9-(11)))" - ## assert multiplication == 495, "Multiplication is (5*(9*(11)))" - ## assert concatenation == "nimiscool" + runnableExamples: + let + numbers = @[5, 9, 11] + addition = foldr(numbers, a + b) + subtraction = foldr(numbers, a - b) + multiplication = foldr(numbers, a * b) + words = @["nim", "is", "cool"] + concatenation = foldr(words, a & b) + assert addition == 25, "Addition is (5+(9+(11)))" + assert subtraction == 7, "Subtraction is (5-(9-(11)))" + assert multiplication == 495, "Multiplication is (5*(9*(11)))" + assert concatenation == "nimiscool" + let s = sequence assert s.len > 0, "Can't fold empty sequences" var result: type(s[0]) @@ -687,41 +797,26 @@ template foldr*(sequence, operation: untyped): untyped = result = operation result -template mapIt*(s, typ, op: untyped): untyped = - ## Convenience template around the ``map`` proc to reduce typing. - ## - ## The template injects the ``it`` variable which you can use directly in an - ## expression. You also need to pass as `typ` the type of the expression, - ## since the new returned sequence can have a different type than the - ## original. - ## - ## Example: - ## - ## .. code-block:: - ## let - ## nums = @[1, 2, 3, 4] - ## strings = nums.mapIt(string, $(4 * it)) - ## assert strings == @["4", "8", "12", "16"] - ## **Deprecated since version 0.12.0:** Use the ``mapIt(seq1, op)`` - ## template instead. - var result: seq[typ] = @[] - for it {.inject.} in items(s): - result.add(op) - result - template mapIt*(s: typed, op: untyped): untyped = - ## Convenience template around the ``map`` proc to reduce typing. + ## Returns a new sequence with the results of `op` proc applied to every + ## item in the container `s`. + ## + ## Since the input is not modified you can use it to + ## transform the type of the elements in the input container. ## ## The template injects the ``it`` variable which you can use directly in an ## expression. ## - ## Example: + ## See also: + ## * `map proc<#map,openArray[T],proc(T)>`_ + ## * `applyIt template<#applyIt.t,untyped,untyped>`_ for the in-place version ## - ## .. code-block:: - ## let - ## nums = @[1, 2, 3, 4] - ## strings = nums.mapIt($(4 * it)) - ## assert strings == @["4", "8", "12", "16"] + runnableExamples: + let + nums = @[1, 2, 3, 4] + strings = nums.mapIt($(4 * it)) + assert strings == @["4", "8", "12", "16"] + when defined(nimHasTypeof): type outType = typeof(( block: @@ -758,31 +853,38 @@ template applyIt*(varSeq, op: untyped) = ## expression. The expression has to return the same type as the sequence you ## are mutating. ## - ## Example: + ## See also: + ## * `apply proc<#apply,openArray[T],proc(T)_2>`_ + ## * `mapIt template<#mapIt.t,typed,untyped>`_ ## - ## .. code-block:: - ## var nums = @[1, 2, 3, 4] - ## nums.applyIt(it * 3) - ## assert nums[0] + nums[3] == 15 + runnableExamples: + var nums = @[1, 2, 3, 4] + nums.applyIt(it * 3) + assert nums[0] + nums[3] == 15 + for i in low(varSeq) .. high(varSeq): let it {.inject.} = varSeq[i] varSeq[i] = op template newSeqWith*(len: int, init: untyped): untyped = - ## creates a new sequence, calling `init` to initialize each value. + ## Creates a new sequence of length `len`, calling `init` to initialize + ## each value of the sequence. ## - ## Example: + ## Useful for creating "2D" sequences - sequences containing other sequences + ## or to populate fields of the created sequence. ## - ## .. code-block:: - ## var seq2D = newSeqWith(20, newSeq[bool](10)) - ## seq2D[0][0] = true - ## seq2D[1][0] = true - ## seq2D[0][1] = true - ## - ## import random - ## var seqRand = newSeqWith(20, random(10)) - ## echo seqRand + runnableExamples: + ## Creates a seqence containing 5 bool sequences, each of length of 3. + var seq2D = newSeqWith(5, newSeq[bool](3)) + assert seq2D.len == 5 + assert seq2D[0].len == 3 + assert seq2D[4][2] == false + + ## Creates a sequence of 20 random numbers from 1 to 10 + import random + var seqRand = newSeqWith(20, random(10)) + var result = newSeq[type(init)](len) for i in 0 ..< len: result[i] = init @@ -804,7 +906,7 @@ proc mapLitsImpl(constructor: NimNode; op: NimNode; nested: bool; macro mapLiterals*(constructor, op: untyped; nested = true): untyped = - ## applies ``op`` to each of the **atomic** literals like ``3`` + ## Applies ``op`` to each of the **atomic** literals like ``3`` ## or ``"abc"`` in the specified ``constructor`` AST. This can ## be used to map every array element to some target type: ## @@ -819,16 +921,20 @@ macro mapLiterals*(constructor, op: untyped; ## .. code-block:: ## let x = [int(0.1), int(1.2), int(2.3), int(3.4)] ## - ## If ``nested`` is true, the literals are replaced everywhere - ## in the ``constructor`` AST, otherwise only the first level + ## If ``nested`` is true (which is the default), the literals are replaced + ## everywhere in the ``constructor`` AST, otherwise only the first level ## is considered: ## ## .. code-block:: - ## mapLiterals((1, ("abc"), 2), float, nested=false) - ## - ## Produces:: - ## - ## (float(1), ("abc"), float(2)) + ## let a = mapLiterals((1.2, (2.3, 3.4), 4.8), int) + ## let b = mapLiterals((1.2, (2.3, 3.4), 4.8), int, nested=false) + ## assert a == (1, (2, 3), 4) + ## assert b == (1, (2.3, 3.4), 4) + ## + ## let c = mapLiterals((1, (2, 3), 4, (5, 6)), `$`) + ## let d = mapLiterals((1, (2, 3), 4, (5, 6)), `$`, nested=false) + ## assert c == ("1", ("2", "3"), "4", ("5", "6")) + ## assert d == ("1", (2, 3), "4", (5, 6)) ## ## There are no constraints for the ``constructor`` AST, it ## works for nested tuples of arrays of sets etc. diff --git a/lib/pure/collections/sets.nim b/lib/pure/collections/sets.nim index 1273cbc33..d1f941e92 100644 --- a/lib/pure/collections/sets.nim +++ b/lib/pure/collections/sets.nim @@ -191,13 +191,6 @@ proc `[]`*[A](s: var HashSet[A], key: A): var A = else: raise newException(KeyError, "key not found") -proc mget*[A](s: var HashSet[A], key: A): var A {.deprecated.} = - ## returns the element that is actually stored in 's' which has the same - ## value as 'key' or raises the ``KeyError`` exception. This is useful - ## when one overloaded 'hash' and '==' but still needs reference semantics - ## for sharing. Use ```[]``` instead. - s[key] - proc contains*[A](s: HashSet[A], key: A): bool = ## Returns true iff `key` is in `s`. ## diff --git a/lib/pure/collections/tableimpl.nim b/lib/pure/collections/tableimpl.nim index 9a5bffcef..2cdc62996 100644 --- a/lib/pure/collections/tableimpl.nim +++ b/lib/pure/collections/tableimpl.nim @@ -7,7 +7,7 @@ # distribution, for details about the copyright. # -## An ``include`` file for the different table implementations. +# 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. diff --git a/lib/pure/collections/tables.nim b/lib/pure/collections/tables.nim index f46a368b1..84ec422d4 100644 --- a/lib/pure/collections/tables.nim +++ b/lib/pure/collections/tables.nim @@ -9,16 +9,21 @@ ## The ``tables`` module implements variants of an efficient `hash table`:idx: ## (also often named `dictionary`:idx: in other programming languages) that is -## a mapping from keys to values. ``Table`` is the usual hash table, -## ``OrderedTable`` is like ``Table`` but remembers insertion order -## and ``CountTable`` is a mapping from a key to its number of occurrences. +## a mapping from keys to values. +## +## There are several different types of hash tables available: +## * `Table<#Table>`_ is the usual hash table, +## * `OrderedTable<#OrderedTable>`_ is like ``Table`` but remembers insertion order, +## * `CountTable<#CountTable>`_ is a mapping from a key to its number of occurrences ## ## For consistency with every other data type in Nim these have **value** ## semantics, this means that ``=`` performs a copy of the hash table. -## For **reference** semantics use the ``Ref`` variant: ``TableRef``, -## ``OrderedTableRef``, ``CountTableRef``. ## -## To give an example, when ``a`` is a Table, then ``var b = a`` gives ``b`` +## For `ref semantics<manual.html#types-ref-and-pointer-types>`_ +## use their ``Ref`` variants: `TableRef<#TableRef>`_, +## `OrderedTableRef<#OrderedTableRef>`_, and `CountTableRef<#CountTableRef>`_. +## +## To give an example, when ``a`` is a ``Table``, then ``var b = a`` gives ``b`` ## as a new independent table. ``b`` is initialised with the contents of ``a``. ## Changing ``b`` does not affect ``a`` and vice versa: ## @@ -35,8 +40,8 @@ ## echo a, b # output: {1: one, 2: two}{1: one, 2: two, 3: three} ## echo a == b # output: false ## -## On the other hand, when ``a`` is a TableRef instead, then changes to ``b`` -## also affect ``a``. Both ``a`` and ``b`` reference the same data structure: +## On the other hand, when ``a`` is a ``TableRef`` instead, then changes to ``b`` +## also affect ``a``. Both ``a`` and ``b`` **ref** the same data structure: ## ## .. code-block:: ## import tables @@ -51,27 +56,111 @@ ## echo a, b # output: {1: one, 2: two, 3: three}{1: one, 2: two, 3: three} ## echo a == b # output: true ## +## ---- +## +## Basic usage +## =========== +## +## Table +## ----- +## +## .. code-block:: +## import tables +## from sequtils import zip +## +## let +## names = ["John", "Paul", "George", "Ringo"] +## years = [1940, 1942, 1943, 1940] +## +## var beatles = initTable[string, int]() +## +## for pairs in zip(names, years): +## let (name, birthYear) = pairs +## beatles[name] = birthYear +## +## echo beatles +## # {"George": 1943, "Ringo": 1940, "Paul": 1942, "John": 1940} +## +## +## var beatlesByYear = initTable[int, seq[string]]() +## +## for pairs in zip(years, names): +## let (birthYear, name) = pairs +## if not beatlesByYear.hasKey(birthYear): +## # if a key doesn't exists, we create one with an empty sequence +## # before we can add elements to it +## beatlesByYear[birthYear] = @[] +## beatlesByYear[birthYear].add(name) +## +## echo beatlesByYear +## # {1940: @["John", "Ringo"], 1942: @["Paul"], 1943: @["George"]} +## +## ## -## Here is an example of ``CountTable`` usage: +## OrderedTable +## ------------ +## +## `OrderedTable<#OrderedTable>`_ is used when it is important to preserve +## the insertion order of keys. +## +## .. code-block:: +## import tables +## +## let +## a = [('z', 1), ('y', 2), ('x', 3)] +## t = a.toTable # regular table +## ot = a.toOrderedTable # ordered tables +## +## echo t # {'x': 3, 'y': 2, 'z': 1} +## echo ot # {'z': 1, 'y': 2, 'x': 3} +## +## +## +## CountTable +## ---------- +## +## `CountTable<#CountTable>`_ is useful for counting number of items of some +## container (e.g. string, sequence or array), as it is a mapping where the +## items are the keys, and their number of occurrences are the values. +## For that purpose `toCountTable proc<#toCountTable,openArray[A]>`_ +## comes handy: +## +## .. code-block:: +## import tables ## -## .. code-block:: nim ## let myString = "abracadabra" -## var myTable = initCountTable[char]() +## let letterFrequencies = toCountTable(myString) +## echo letterFrequencies +## # 'a': 5, 'b': 2, 'c': 1, 'd': 1, 'r': 2} +## +## The same could have been achieved by manually iterating over a container +## and increasing each key's value with `inc proc<#inc,CountTable[A],A,int>`_: +## +## .. code-block:: +## import tables ## +## let myString = "abracadabra" +## var letterFrequencies = initCountTable[char]() ## for c in myString: -## myTable.inc(c) +## letterFrequencies.inc(c) +## echo letterFrequencies +## # output: {'a': 5, 'b': 2, 'c': 1, 'd': 1, 'r': 2} +## +## ---- +## ## -## echo myTable # output: {'a': 5, 'b': 2, 'c': 1, 'd': 1, 'r': 2} ## +## Hashing +## ------- ## ## If you are using simple standard types like ``int`` or ``string`` for the ## keys of the table you won't have any problems, but as soon as you try to use ## a more complex object as a key you will be greeted by a strange compiler -## error:: +## error: ## ## Error: type mismatch: got (Person) ## but expected one of: -## hashes.hash(x: openarray[A]): Hash +## hashes.hash(x: openArray[A]): Hash ## hashes.hash(x: int): Hash ## hashes.hash(x: float): Hash ## … @@ -89,6 +178,8 @@ ## example implementing only ``hash`` suffices: ## ## .. code-block:: +## import tables, hashes +## ## type ## Person = object ## firstName, lastName: string @@ -111,45 +202,50 @@ ## p2.firstName = "소진" ## p2.lastName = "박" ## salaries[p2] = 45_000 +## +## ---- +## +## See also +## ======== +## +## * `json module<json.html>`_ for table-like structure which allows +## heterogeneous members +## * `sharedtables module<sharedtables.html>`_ for shared hash table support +## * `strtabs module<strtabs.html>`_ for efficient hash tables +## mapping from strings to strings +## * `hashes module<hashes.html>`_ for helper functions for hashing -import - hashes, math + +import hashes, math include "system/inclrtl" type KeyValuePair[A, B] = tuple[hcode: Hash, key: A, val: B] KeyValuePairSeq[A, B] = seq[KeyValuePair[A, B]] - Table*[A, B] = object ## generic hash table + Table*[A, B] = object + ## Generic hash table, consisting of a key-value pair. + ## + ## `data` and `counter` are internal implementation details which + ## can't be accessed. + ## + ## For creating an empty Table, use `initTable proc<#initTable,int>`_. data: KeyValuePairSeq[A, B] counter: int - TableRef*[A,B] = ref Table[A, B] + TableRef*[A,B] = ref Table[A, B] ## Ref version of `Table<#Table>`_. + ## + ## For creating a new empty TableRef, use `newTable proc + ## <#newTable,int>`_. + + +# ------------------------------ helpers --------------------------------- template maxHash(t): untyped = high(t.data) template dataLen(t): untyped = len(t.data) include tableimpl -proc clear*[A, B](t: var Table[A, B]) = - ## resets the table so that it is empty. - clearImpl() - -proc clear*[A, B](t: TableRef[A, B]) = - ## resets the ref table so that it is empty. - clearImpl() - -proc rightSize*(count: Natural): int {.inline.} = - ## return the value of ``initialSize`` to support ``count`` items. - ## - ## If more items are expected to be added, simply add that - ## expected extra amount to the parameter before calling this. - ## - ## Internally, we want mustRehash(rightSize(x), x) == false. - result = nextPowerOfTwo(count * 3 div 2 + 4) - -proc len*[A, B](t: Table[A, B]): int = - ## returns the number of keys in ``t``. - result = t.counter +proc rightSize*(count: Natural): int {.inline.} template get(t, key): untyped = ## retrieves the value at ``t[key]``. The value can be modified. @@ -176,36 +272,340 @@ template getOrDefaultImpl(t, key, default: untyped): untyped = var index = rawGet(t, key, hc) result = if index >= 0: t.data[index].val else: default -proc `[]`*[A, B](t: Table[A, B], key: A): B {.deprecatedGet.} = - ## retrieves the value at ``t[key]``. If ``key`` is not in ``t``, the - ## ``KeyError`` exception is raised. One can check with ``hasKey`` whether - ## the key exists. - get(t, key) +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 `[]`*[A, B](t: var Table[A, B], key: A): var B {.deprecatedGet.} = - ## retrieves the value at ``t[key]``. The value can be modified. +proc enlarge[A, B](t: var Table[A, B]) = + var n: KeyValuePairSeq[A, B] + newSeq(n, len(t.data) * growthFactor) + swap(t.data, n) + for i in countup(0, high(n)): + let eh = n[i].hcode + if isFilled(eh): + 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) + +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 + + + +# ------------------------------------------------------------------- +# ------------------------------ Table ------------------------------ +# ------------------------------------------------------------------- + +proc initTable*[A, B](initialSize=64): Table[A, B] = + ## Creates a new hash table that is empty. + ## + ## ``initialSize`` must be a power of two (default: 64). + ## If you need to accept runtime values for this you could use the + ## `nextPowerOfTwo proc<math.html#nextPowerOfTwo,int>`_ from the + ## `math module<math.html>`_ or the `rightSize proc<#rightSize,Natural>`_ + ## from this module. + ## + ## See also: + ## * `toTable proc<#toTable,openArray[]>`_ + ## * `newTable proc<#newTable,int>`_ for creating a `TableRef` + runnableExamples: + let + a = initTable[int, string]() + b = initTable[char, seq[int]]() + assert isPowerOfTwo(initialSize) + result.counter = 0 + newSeq(result.data, initialSize) + +proc toTable*[A, B](pairs: openArray[(A, B)]): Table[A, B] = + ## Creates a new hash table that contains the given ``pairs``. + ## + ## ``pairs`` is a container consisting of ``(key, value)`` tuples. + ## + ## See also: + ## * `initTable proc<#initTable,int>`_ + ## * `newTable proc<#newTable,openArray[]>`_ for a `TableRef` version + runnableExamples: + 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 + +proc `[]`*[A, B](t: Table[A, B], key: A): B = + ## Retrieves the value at ``t[key]``. + ## ## If ``key`` is not in ``t``, the ``KeyError`` exception is raised. + ## One can check with `hasKey proc<#hasKey,Table[A,B],A>`_ whether + ## the key exists. + ## + ## See also: + ## * `getOrDefault proc<#getOrDefault,Table[A,B],A>`_ to return + ## a default value (e.g. zero for int) if the key doesn't exist + ## * `getOrDefault proc<#getOrDefault,Table[A,B],A,B>`_ to return + ## a custom value if the key doesn't exist + ## * `[]= proc<#[]=,Table[A,B],A,B>`_ for inserting a new + ## (key, value) pair in the table + ## * `hasKey proc<#hasKey,Table[A,B],A>`_ for checking if a key is in + ## the table + runnableExamples: + let a = {'a': 5, 'b': 9}.toTable + doAssert a['a'] == 5 + doAssertRaises(KeyError): + echo a['z'] get(t, key) -proc mget*[A, B](t: var Table[A, B], key: A): var B {.deprecated.} = - ## retrieves the value at ``t[key]``. The value can be modified. +proc `[]`*[A, B](t: var Table[A, B], key: A): var B = + ## Retrieves the value at ``t[key]``. The value can be modified. + ## ## If ``key`` is not in ``t``, the ``KeyError`` exception is raised. - ## Use ``[]`` instead. + ## + ## See also: + ## * `getOrDefault proc<#getOrDefault,Table[A,B],A>`_ to return + ## a default value (e.g. zero for int) if the key doesn't exist + ## * `getOrDefault proc<#getOrDefault,Table[A,B],A,B>`_ to return + ## a custom value if the key doesn't exist + ## * `[]= proc<#[]=,Table[A,B],A,B>`_ for inserting a new + ## (key, value) pair in the table + ## * `hasKey proc<#hasKey,Table[A,B],A>`_ for checking if a key is in + ## the table get(t, key) +proc `[]=`*[A, B](t: var Table[A, B], key: A, val: B) = + ## Inserts a ``(key, value)`` pair into ``t``. + ## + ## See also: + ## * `[] proc<#[],Table[A,B],A>`_ for retrieving a value of a key + ## * `hasKeyOrPut proc<#hasKeyOrPut,Table[A,B],A,B>`_ + ## * `mgetOrPut proc<#mgetOrPut,Table[A,B],A,B>`_ + ## * `del proc<#del,Table[A,B],A>`_ for removing a key from the table + runnableExamples: + var a = initTable[char, int]() + 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 = + ## Returns true if ``key`` is in the table ``t``. + ## + ## See also: + ## * `contains proc<#contains,Table[A,B],A>`_ for use with the `in` operator + ## * `[] proc<#[],Table[A,B],A>`_ for retrieving a value of a key + ## * `getOrDefault proc<#getOrDefault,Table[A,B],A>`_ to return + ## a default value (e.g. zero for int) if the key doesn't exist + ## * `getOrDefault proc<#getOrDefault,Table[A,B],A,B>`_ to return + ## a custom value if the key doesn't exist + runnableExamples: + 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 + +proc contains*[A, B](t: Table[A, B], key: A): bool = + ## Alias of `hasKey proc<#hasKey,Table[A,B],A>`_ for use with + ## the ``in`` operator. + runnableExamples: + 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 = + ## Returns true if ``key`` is in the table, otherwise inserts ``value``. + ## + ## See also: + ## * `hasKey proc<#hasKey,Table[A,B],A>`_ + ## * `[] proc<#[],Table[A,B],A>`_ for retrieving a value of a key + ## * `getOrDefault proc<#getOrDefault,Table[A,B],A>`_ to return + ## a default value (e.g. zero for int) if the key doesn't exist + ## * `getOrDefault proc<#getOrDefault,Table[A,B],A,B>`_ to return + ## a custom value if the key doesn't exist + runnableExamples: + var a = {'a': 5, 'b': 9}.toTable + if a.hasKeyOrPut('a', 50): + a['a'] = 99 + 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 = - ## retrieves the value at ``t[key]`` iff ``key`` is in ``t``. Otherwise, the + ## Retrieves the value at ``t[key]`` if ``key`` is in ``t``. Otherwise, the ## default initialization value for type ``B`` is returned (e.g. 0 for any ## integer type). + ## + ## See also: + ## * `[] proc<#[],Table[A,B],A>`_ for retrieving a value of a key + ## * `hasKey proc<#hasKey,Table[A,B],A>`_ + ## * `hasKeyOrPut proc<#hasKeyOrPut,Table[A,B],A,B>`_ + ## * `mgetOrPut proc<#mgetOrPut,Table[A,B],A,B>`_ + ## * `getOrDefault proc<#getOrDefault,Table[A,B],A,B>`_ to return + ## a custom value if the key doesn't exist + runnableExamples: + 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 = - ## retrieves the value at ``t[key]`` iff ``key`` is in ``t``. + ## Retrieves the value at ``t[key]`` if ``key`` is in ``t``. ## Otherwise, ``default`` is returned. + ## + ## See also: + ## * `[] proc<#[],Table[A,B],A>`_ for retrieving a value of a key + ## * `hasKey proc<#hasKey,Table[A,B],A>`_ + ## * `hasKeyOrPut proc<#hasKeyOrPut,Table[A,B],A,B>`_ + ## * `mgetOrPut proc<#mgetOrPut,Table[A,B],A,B>`_ + ## * `getOrDefault proc<#getOrDefault,Table[A,B],A>`_ to return + ## a default value (e.g. zero for int) if the key doesn't exist + runnableExamples: + 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 = + ## Retrieves value at ``t[key]`` or puts ``val`` if not present, either way + ## returning a value which can be modified. + ## + ## See also: + ## * `[] proc<#[],Table[A,B],A>`_ for retrieving a value of a key + ## * `hasKey proc<#hasKey,Table[A,B],A>`_ + ## * `hasKeyOrPut proc<#hasKeyOrPut,Table[A,B],A,B>`_ + ## * `getOrDefault proc<#getOrDefault,Table[A,B],A>`_ to return + ## a default value (e.g. zero for int) if the key doesn't exist + ## * `getOrDefault proc<#getOrDefault,Table[A,B],A,B>`_ to return + ## a custom value if the key doesn't exist + runnableExamples: + var a = {'a': 5, 'b': 9}.toTable + 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 = + ## Returns the number of keys in ``t``. + 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) = + ## Puts a new ``(key, value)`` pair into ``t`` even if ``t[key]`` already exists. + ## + ## **This can introduce duplicate keys into the table!** + ## + ## Use `[]= proc<#[]=,Table[A,B],A,B>`_ for inserting a new + ## (key, value) pair in the table without introducing duplicates. + addImpl(enlarge) + +proc del*[A, B](t: var Table[A, B], key: A) = + ## Deletes ``key`` from hash table ``t``. Does nothing if the key does not exist. + ## + ## See also: + ## * `take proc<#take,Table[A,B],A,B>`_ + ## * `clear proc<#clear,Table[A,B]>`_ to empty the whole table + runnableExamples: + var a = {'a': 5, 'b': 9, 'c': 13}.toTable + a.del('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 = + ## Deletes the ``key`` from the table. + ## Returns ``true``, if the ``key`` existed, and sets ``val`` to the + ## mapping of the key. Otherwise, returns ``false``, and the ``val`` is + ## unchanged. + ## + ## See also: + ## * `del proc<#del,Table[A,B],A>`_ + ## * `clear proc<#clear,Table[A,B]>`_ to empty the whole table + runnableExamples: + var + a = {'a': 5, 'b': 9, 'c': 13}.toTable + i: int + doAssert a.take('b', i) == true + doAssert a == {'a': 5, 'c': 13}.toTable + doAssert i == 9 + i = 0 + doAssert a.take('z', i) == false + doAssert a == {'a': 5, 'c': 13}.toTable + doAssert i == 0 + + var hc: Hash + var index = rawGet(t, key, hc) + result = index >= 0 + if result: + shallowCopy(val, t.data[index].val) + delImplIdx(t, index) + +proc clear*[A, B](t: var Table[A, B]) = + ## Resets the table so that it is empty. + ## + ## See also: + ## * `del proc<#del,Table[A,B],A>`_ + ## * `take proc<#take,Table[A,B],A,B>`_ + runnableExamples: + var a = {'a': 5, 'b': 9, 'c': 13}.toTable + doAssert len(a) == 3 + clear(a) + doAssert len(a) == 0 + clearImpl() + +proc `$`*[A, B](t: Table[A, B]): string = + ## The ``$`` operator for hash tables. Used internally when calling `echo` + ## on a table. + dollarImpl() + +proc `==`*[A, B](s, t: Table[A, B]): bool = + ## The ``==`` operator for hash tables. Returns ``true`` if the content of both + ## tables contains the same key-value pairs. Insert order does not matter. + runnableExamples: + let + 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.} = + ## Return the value of ``initialSize`` to support ``count`` items. + ## + ## If more items are expected to be added, simply add that + ## expected extra amount to the parameter before calling this. + ## + ## Internally, we want mustRehash(rightSize(x), x) == false. + result = nextPowerOfTwo(count * 3 div 2 + 4) + +proc indexBy*[A, B, C](collection: A, index: proc(x: B): C): Table[C, B] = + ## Index the collection with the proc provided. + # TODO: As soon as supported, change collection: A to collection: A[B] + result = initTable[C, B]() + for item in collection: + result[index(item)] = item + + + template withValue*[A, B](t: var Table[A, B], key: A, value, body: untyped) = - ## retrieves the value at ``t[key]``. + ## Retrieves the value at ``t[key]``. + ## ## ``value`` can be modified in the scope of the ``withValue`` call. ## ## .. code-block:: nim @@ -225,7 +625,8 @@ template withValue*[A, B](t: var Table[A, B], key: A, value, body: untyped) = template withValue*[A, B](t: var Table[A, B], key: A, value, body1, body2: untyped) = - ## retrieves the value at ``t[key]``. + ## Retrieves the value at ``t[key]``. + ## ## ``value`` can be modified in the scope of the ``withValue`` call. ## ## .. code-block:: nim @@ -248,370 +649,535 @@ template withValue*[A, B](t: var Table[A, B], key: A, else: body2 -iterator allValues*[A, B](t: Table[A, B]; key: A): B = - ## iterates over any value in the table ``t`` that belongs to the given ``key``. - var h: Hash = genHash(key) and high(t.data) - while isFilled(t.data[h].hcode): - if t.data[h].key == key: - yield t.data[h].val - h = nextTry(h, high(t.data)) - -proc hasKey*[A, B](t: Table[A, B], key: A): bool = - ## returns true iff ``key`` is in the table ``t``. - var hc: Hash - result = rawGet(t, key, hc) >= 0 - -proc contains*[A, B](t: Table[A, B], key: A): bool = - ## alias of ``hasKey`` for use with the ``in`` operator. - return hasKey[A, B](t, key) iterator pairs*[A, B](t: Table[A, B]): (A, B) = - ## iterates over any ``(key, value)`` pair in the table ``t``. + ## Iterates over any ``(key, value)`` pair in the table ``t``. + ## + ## See also: + ## * `mpairs iterator<#mpairs.i,Table[A,B]>`_ + ## * `keys iterator<#keys.i,Table[A,B]>`_ + ## * `values iterator<#values.i,Table[A,B]>`_ + ## + ## **Examples:** + ## + ## .. code-block:: + ## let a = { + ## 'o': [1, 5, 7, 9], + ## 'e': [2, 4, 6, 8] + ## }.toTable + ## + ## for k, v in a.pairs: + ## echo "key: ", k + ## echo "value: ", v + ## + ## # key: e + ## # value: [2, 4, 6, 8] + ## # key: o + ## # value: [1, 5, 7, 9] for h in 0..high(t.data): if isFilled(t.data[h].hcode): yield (t.data[h].key, t.data[h].val) iterator mpairs*[A, B](t: var Table[A, B]): (A, var B) = - ## iterates over any ``(key, value)`` pair in the table ``t``. The values - ## can be modified. + ## Iterates over any ``(key, value)`` pair in the table ``t`` (must be + ## declared as `var`). The values can be modified. + ## + ## See also: + ## * `pairs iterator<#pairs.i,Table[A,B]>`_ + ## * `mvalues iterator<#mvalues.i,Table[A,B]>`_ + runnableExamples: + var a = { + 'o': @[1, 5, 7, 9], + 'e': @[2, 4, 6, 8] + }.toTable + 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) iterator keys*[A, B](t: Table[A, B]): A = - ## iterates over any key in the table ``t``. + ## Iterates over any key in the table ``t``. + ## + ## See also: + ## * `pairs iterator<#pairs.i,Table[A,B]>`_ + ## * `values iterator<#values.i,Table[A,B]>`_ + runnableExamples: + var a = { + 'o': @[1, 5, 7, 9], + 'e': @[2, 4, 6, 8] + }.toTable + 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 iterator values*[A, B](t: Table[A, B]): B = - ## iterates over any value in the table ``t``. + ## Iterates over any value in the table ``t``. + ## + ## See also: + ## * `pairs iterator<#pairs.i,Table[A,B]>`_ + ## * `keys iterator<#keys.i,Table[A,B]>`_ + ## * `mvalues iterator<#mvalues.i,Table[A,B]>`_ + runnableExamples: + let a = { + 'o': @[1, 5, 7, 9], + 'e': @[2, 4, 6, 8] + }.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 iterator mvalues*[A, B](t: var Table[A, B]): var B = - ## iterates over any value in the table ``t``. The values can be modified. + ## Iterates over any value in the table ``t`` (must be + ## declared as `var`). The values can be modified. + ## + ## See also: + ## * `mpairs iterator<#mpairs.i,Table[A,B]>`_ + ## * `values iterator<#values.i,Table[A,B]>`_ + runnableExamples: + var a = { + 'o': @[1, 5, 7, 9], + 'e': @[2, 4, 6, 8] + }.toTable + 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 -proc del*[A, B](t: var Table[A, B], key: A) = - ## deletes ``key`` from hash table ``t``. Does nothing if the key does not exist. - delImpl() - -proc take*[A, B](t: var Table[A, B], key: A, val: var B): bool = - ## deletes the ``key`` from the table. - ## Returns ``true``, if the ``key`` existed, and sets ``val`` to the - ## mapping of the key. Otherwise, returns ``false``, and the ``val`` is - ## unchanged. - var hc: Hash - var index = rawGet(t, key, hc) - result = index >= 0 - if result: - shallowCopy(val, t.data[index].val) - delImplIdx(t, index) - -proc enlarge[A, B](t: var Table[A, B]) = - var n: KeyValuePairSeq[A, B] - newSeq(n, len(t.data) * growthFactor) - swap(t.data, n) - for i in countup(0, high(n)): - let eh = n[i].hcode - if isFilled(eh): - 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) - -proc mgetOrPut*[A, B](t: var Table[A, B], key: A, val: B): var B = - ## retrieves value at ``t[key]`` or puts ``val`` if not present, either way - ## returning a value which can be modified. - mgetOrPutImpl(enlarge) - -proc hasKeyOrPut*[A, B](t: var Table[A, B], key: A, val: B): bool = - ## returns true iff ``key`` is in the table, otherwise inserts ``value``. - hasKeyOrPutImpl(enlarge) - -proc `[]=`*[A, B](t: var Table[A, B], key: A, val: B) = - ## puts a ``(key, value)`` pair into ``t``. - putImpl(enlarge) - -proc add*[A, B](t: var Table[A, B], key: A, val: B) = - ## puts a new ``(key, value)`` pair into ``t`` even if ``t[key]`` already exists. - ## This can introduce duplicate keys into the table! - addImpl(enlarge) - -proc len*[A, B](t: TableRef[A, B]): int = - ## returns the number of keys in ``t``. - result = t.counter - -proc initTable*[A, B](initialSize=64): Table[A, B] = - ## creates a new hash table that is empty. +iterator allValues*[A, B](t: Table[A, B]; key: A): B = + ## Iterates over any value in the table ``t`` that belongs to the given ``key``. ## - ## ``initialSize`` needs to be a power of two. If you need to accept runtime - ## values for this you could use the ``nextPowerOfTwo`` proc from the - ## `math <math.html>`_ module or the ``rightSize`` proc from this module. - assert isPowerOfTwo(initialSize) - result.counter = 0 - newSeq(result.data, initialSize) + ## Used if you have a table with duplicate keys (as a result of using + ## `add proc<#add,Table[A,B],A,B>`_). + ## + ## **Examples:** + ## + ## .. code-block:: + ## var a = {'a': 3, 'b': 5}.toTable + ## for i in 1..3: + ## a.add('z', 10*i) + ## echo a # {'a': 3, 'b': 5, 'z': 10, 'z': 20, 'z': 30} + ## + ## for v in a.allValues('z'): + ## echo v + ## # 10 + ## # 20 + ## # 30 + var h: Hash = genHash(key) and high(t.data) + while isFilled(t.data[h].hcode): + if t.data[h].key == key: + yield t.data[h].val + h = nextTry(h, high(t.data)) -proc toTable*[A, B](pairs: openArray[(A, B)]): Table[A, B] = - ## creates a new hash table that contains the given ``pairs``. - result = initTable[A, B](rightSize(pairs.len)) - for key, val in items(pairs): result[key] = val -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 `$`*[A, B](t: Table[A, B]): string = - ## the ``$`` operator for hash tables. - dollarImpl() +# ------------------------------------------------------------------- +# ---------------------------- TableRef ----------------------------- +# ------------------------------------------------------------------- -proc hasKey*[A, B](t: TableRef[A, B], key: A): bool = - ## returns true iff ``key`` is in the table ``t``. - result = t[].hasKey(key) -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 +proc newTable*[A, B](initialSize=64): TableRef[A, B] = + ## Creates a new ref hash table that is empty. + ## + ## ``initialSize`` must be a power of two (default: 64). + ## If you need to accept runtime values for this you could use the + ## `nextPowerOfTwo proc<math.html#nextPowerOfTwo,int>`_ from the + ## `math module<math.html>`_ or the `rightSize proc<#rightSize,Natural>`_ + ## from this module. + ## + ## See also: + ## * `newTable proc<#newTable,openArray[]>`_ for creating a `TableRef` + ## from a collection of `(key, value)` pairs + ## * `initTable proc<#initTable,int>`_ for creating a `Table` + runnableExamples: + let + a = newTable[int, string]() + b = newTable[char, seq[int]]() + new(result) + result[] = initTable[A, B](initialSize) -proc `==`*[A, B](s, t: Table[A, B]): bool = - ## The ``==`` operator for hash tables. Returns ``true`` iff the content of both - ## tables contains the same key-value pairs. Insert order does not matter. - equalsImpl(s, t) +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. + ## + ## See also: + ## * `newTable proc<#newTable,int>`_ + ## * `toTable proc<#toTable,openArray[]>`_ for a `Table` version + runnableExamples: + 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 indexBy*[A, B, C](collection: A, index: proc(x: B): C): Table[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 = initTable[C, B]() + result = newTable[C, B]() for item in collection: result[index(item)] = item -iterator pairs*[A, B](t: TableRef[A, B]): (A, B) = - ## iterates over any ``(key, value)`` pair in the table ``t``. - for h in 0..high(t.data): - if isFilled(t.data[h].hcode): yield (t.data[h].key, t.data[h].val) - -iterator mpairs*[A, B](t: TableRef[A, B]): (A, var B) = - ## iterates over any ``(key, value)`` pair in the table ``t``. The values - ## can be modified. - for h in 0..high(t.data): - if isFilled(t.data[h].hcode): yield (t.data[h].key, t.data[h].val) - -iterator keys*[A, B](t: TableRef[A, B]): A = - ## iterates over any key in the table ``t``. - for h in 0..high(t.data): - if isFilled(t.data[h].hcode): yield t.data[h].key +proc `[]`*[A, B](t: TableRef[A, B], key: A): var B = + ## Retrieves the value at ``t[key]``. + ## + ## If ``key`` is not in ``t``, the ``KeyError`` exception is raised. + ## One can check with `hasKey proc<#hasKey,TableRef[A,B],A>`_ whether + ## the key exists. + ## + ## See also: + ## * `getOrDefault proc<#getOrDefault,TableRef[A,B],A>`_ to return + ## a default value (e.g. zero for int) if the key doesn't exist + ## * `getOrDefault proc<#getOrDefault,TableRef[A,B],A,B>`_ to return + ## a custom value if the key doesn't exist + ## * `[]= proc<#[]=,TableRef[A,B],A,B>`_ for inserting a new + ## (key, value) pair in the table + ## * `hasKey proc<#hasKey,TableRef[A,B],A>`_ for checking if a key is in + ## the table + runnableExamples: + let a = {'a': 5, 'b': 9}.newTable + doAssert a['a'] == 5 + doAssertRaises(KeyError): + echo a['z'] + result = t[][key] -iterator values*[A, B](t: TableRef[A, B]): B = - ## iterates over any value in the table ``t``. - for h in 0..high(t.data): - if isFilled(t.data[h].hcode): yield t.data[h].val +proc `[]=`*[A, B](t: TableRef[A, B], key: A, val: B) = + ## Inserts a ``(key, value)`` pair into ``t``. + ## + ## See also: + ## * `[] proc<#[],TableRef[A,B],A>`_ for retrieving a value of a key + ## * `hasKeyOrPut proc<#hasKeyOrPut,TableRef[A,B],A,B>`_ + ## * `mgetOrPut proc<#mgetOrPut,TableRef[A,B],A,B>`_ + ## * `del proc<#del,TableRef[A,B],A>`_ for removing a key from the table + runnableExamples: + var a = newTable[char, int]() + a['x'] = 7 + a['y'] = 33 + doAssert a == {'x': 7, 'y': 33}.newTable + t[][key] = val -iterator mvalues*[A, B](t: TableRef[A, B]): var B = - ## iterates over any value in the table ``t``. The values can be modified. - for h in 0..high(t.data): - if isFilled(t.data[h].hcode): yield t.data[h].val +proc hasKey*[A, B](t: TableRef[A, B], key: A): bool = + ## Returns true if ``key`` is in the table ``t``. + ## + ## See also: + ## * `contains proc<#contains,TableRef[A,B],A>`_ for use with the `in` + ## operator + ## * `[] proc<#[],TableRef[A,B],A>`_ for retrieving a value of a key + ## * `getOrDefault proc<#getOrDefault,TableRef[A,B],A>`_ to return + ## a default value (e.g. zero for int) if the key doesn't exist + ## * `getOrDefault proc<#getOrDefault,TableRef[A,B],A,B>`_ to return + ## a custom value if the key doesn't exist + runnableExamples: + let a = {'a': 5, 'b': 9}.newTable + doAssert a.hasKey('a') == true + doAssert a.hasKey('z') == false + result = t[].hasKey(key) -proc `[]`*[A, B](t: TableRef[A, B], key: A): var B {.deprecatedGet.} = - ## retrieves the value at ``t[key]``. If ``key`` is not in ``t``, the - ## ``KeyError`` exception is raised. One can check with ``hasKey`` whether - ## the key exists. - result = t[][key] +proc contains*[A, B](t: TableRef[A, B], key: A): bool = + ## Alias of `hasKey proc<#hasKey,TableRef[A,B],A>`_ for use with + ## the ``in`` operator. + runnableExamples: + let a = {'a': 5, 'b': 9}.newTable + doAssert 'b' in a == true + doAssert a.contains('z') == false + return hasKey[A, B](t, key) -proc mget*[A, B](t: TableRef[A, B], key: A): var B {.deprecated.} = - ## retrieves the value at ``t[key]``. The value can be modified. - ## If ``key`` is not in ``t``, the ``KeyError`` exception is raised. - ## Use ``[]`` instead. - t[][key] +proc hasKeyOrPut*[A, B](t: var TableRef[A, B], key: A, val: B): bool = + ## Returns true if ``key`` is in the table, otherwise inserts ``value``. + ## + ## See also: + ## * `hasKey proc<#hasKey,TableRef[A,B],A>`_ + ## * `[] proc<#[],TableRef[A,B],A>`_ for retrieving a value of a key + ## * `getOrDefault proc<#getOrDefault,TableRef[A,B],A>`_ to return + ## a default value (e.g. zero for int) if the key doesn't exist + ## * `getOrDefault proc<#getOrDefault,TableRef[A,B],A,B>`_ to return + ## a custom value if the key doesn't exist + runnableExamples: + var a = {'a': 5, 'b': 9}.newTable + if a.hasKeyOrPut('a', 50): + a['a'] = 99 + 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 = - ## retrieves the value at ``t[key]`` iff ``key`` is in ``t``. Otherwise, the + ## Retrieves the value at ``t[key]`` if ``key`` is in ``t``. Otherwise, the ## default initialization value for type ``B`` is returned (e.g. 0 for any ## integer type). + ## + ## See also: + ## * `[] proc<#[],TableRef[A,B],A>`_ for retrieving a value of a key + ## * `hasKey proc<#hasKey,TableRef[A,B],A>`_ + ## * `hasKeyOrPut proc<#hasKeyOrPut,TableRef[A,B],A,B>`_ + ## * `mgetOrPut proc<#mgetOrPut,TableRef[A,B],A,B>`_ + ## * `getOrDefault proc<#getOrDefault,TableRef[A,B],A,B>`_ to return + ## a custom value if the key doesn't exist + runnableExamples: + 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 = - ## retrieves the value at ``t[key]`` iff ``key`` is in ``t``. + ## Retrieves the value at ``t[key]`` if ``key`` is in ``t``. ## Otherwise, ``default`` is returned. + ## + ## See also: + ## * `[] proc<#[],TableRef[A,B],A>`_ for retrieving a value of a key + ## * `hasKey proc<#hasKey,TableRef[A,B],A>`_ + ## * `hasKeyOrPut proc<#hasKeyOrPut,TableRef[A,B],A,B>`_ + ## * `mgetOrPut proc<#mgetOrPut,TableRef[A,B],A,B>`_ + ## * `getOrDefault proc<#getOrDefault,TableRef[A,B],A>`_ to return + ## a default value (e.g. zero for int) if the key doesn't exist + runnableExamples: + 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 = - ## retrieves value at ``t[key]`` or puts ``val`` if not present, either way + ## Retrieves value at ``t[key]`` or puts ``val`` if not present, either way ## returning a value which can be modified. + ## + ## See also: + ## * `[] proc<#[],TableRef[A,B],A>`_ for retrieving a value of a key + ## * `hasKey proc<#hasKey,TableRef[A,B],A>`_ + ## * `hasKeyOrPut proc<#hasKeyOrPut,TableRef[A,B],A,B>`_ + ## * `getOrDefault proc<#getOrDefault,TableRef[A,B],A>`_ to return + ## a default value (e.g. zero for int) if the key doesn't exist + ## * `getOrDefault proc<#getOrDefault,TableRef[A,B],A,B>`_ to return + ## a custom value if the key doesn't exist + runnableExamples: + var a = {'a': 5, 'b': 9}.newTable + 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 hasKeyOrPut*[A, B](t: var TableRef[A, B], key: A, val: B): bool = - ## returns true iff ``key`` is in the table, otherwise inserts ``value``. - t[].hasKeyOrPut(key, val) - -proc contains*[A, B](t: TableRef[A, B], key: A): bool = - ## Alias of ``hasKey`` for use with the ``in`` operator. - return hasKey[A, B](t, key) - -proc `[]=`*[A, B](t: TableRef[A, B], key: A, val: B) = - ## puts a ``(key, value)`` pair into ``t``. - t[][key] = val +proc len*[A, B](t: TableRef[A, B]): int = + ## Returns the number of keys in ``t``. + 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) = - ## puts a new ``(key, value)`` pair into ``t`` even if ``t[key]`` already exists. - ## This can introduce duplicate keys into the table! + ## Puts a new ``(key, value)`` pair into ``t`` even if ``t[key]`` already exists. + ## + ## **This can introduce duplicate keys into the table!** + ## + ## Use `[]= proc<#[]=,TableRef[A,B],A,B>`_ for inserting a new + ## (key, value) pair in the table without introducing duplicates. t[].add(key, val) proc del*[A, B](t: TableRef[A, B], key: A) = - ## deletes ``key`` from hash table ``t``. Does nothing if the key does not exist. + ## Deletes ``key`` from hash table ``t``. Does nothing if the key does not exist. + ## + ## See also: + ## * `take proc<#take,TableRef[A,B],A,B>`_ + ## * `clear proc<#clear,TableRef[A,B]>`_ to empty the whole table + runnableExamples: + var a = {'a': 5, 'b': 9, 'c': 13}.newTable + a.del('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 = - ## deletes the ``key`` from the table. + ## Deletes the ``key`` from the table. ## Returns ``true``, if the ``key`` existed, and sets ``val`` to the ## mapping of the key. Otherwise, returns ``false``, and the ``val`` is ## unchanged. + ## + ## See also: + ## * `del proc<#del,TableRef[A,B],A>`_ + ## * `clear proc<#clear,TableRef[A,B]>`_ to empty the whole table + runnableExamples: + var + a = {'a': 5, 'b': 9, 'c': 13}.newTable + i: int + doAssert a.take('b', i) == true + doAssert a == {'a': 5, 'c': 13}.newTable + doAssert i == 9 + i = 0 + doAssert a.take('z', i) == false + doAssert a == {'a': 5, 'c': 13}.newTable + doAssert i == 0 result = t[].take(key, val) -proc newTable*[A, B](initialSize=64): TableRef[A, B] = - new(result) - result[] = initTable[A, B](initialSize) - -proc newTable*[A, B](pairs: openArray[(A, B)]): TableRef[A, B] = - ## creates a new hash table that contains the given ``pairs``. - new(result) - result[] = toTable[A, B](pairs) +proc clear*[A, B](t: TableRef[A, B]) = + ## Resets the table so that it is empty. + ## + ## See also: + ## * `del proc<#del,Table[A,B],A>`_ + ## * `take proc<#take,Table[A,B],A,B>`_ + runnableExamples: + var a = {'a': 5, 'b': 9, 'c': 13}.newTable + doAssert len(a) == 3 + clear(a) + doAssert len(a) == 0 + clearImpl() proc `$`*[A, B](t: TableRef[A, B]): string = - ## The ``$`` operator for hash tables. + ## The ``$`` operator for hash tables. Used internally when calling `echo` + ## on a table. dollarImpl() proc `==`*[A, B](s, t: TableRef[A, B]): bool = - ## The ``==`` operator for hash tables. Returns ``true`` iff either both tables - ## are ``nil`` or none is ``nil`` and the content of both tables contains the + ## The ``==`` operator for hash tables. Returns ``true`` if either both tables + ## are ``nil``, or neither is ``nil`` and the content of both tables contains the ## same key-value pairs. Insert order does not matter. + runnableExamples: + let + 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[]) -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]() - for item in collection: - result[index(item)] = item -# ------------------------------ ordered table ------------------------------ -type - OrderedKeyValuePair[A, B] = tuple[ - hcode: Hash, next: int, key: A, val: B] - OrderedKeyValuePairSeq[A, B] = seq[OrderedKeyValuePair[A, B]] - OrderedTable* [A, B] = object ## table that remembers insertion order - data: OrderedKeyValuePairSeq[A, B] - counter, first, last: int - OrderedTableRef*[A, B] = ref OrderedTable[A, B] - -proc len*[A, B](t: OrderedTable[A, B]): int {.inline.} = - ## returns the number of keys in ``t``. - result = t.counter +iterator pairs*[A, B](t: TableRef[A, B]): (A, B) = + ## Iterates over any ``(key, value)`` pair in the table ``t``. + ## + ## See also: + ## * `mpairs iterator<#mpairs.i,TableRef[A,B]>`_ + ## * `keys iterator<#keys.i,TableRef[A,B]>`_ + ## * `values iterator<#values.i,TableRef[A,B]>`_ + ## + ## **Examples:** + ## + ## .. code-block:: + ## let a = { + ## 'o': [1, 5, 7, 9], + ## 'e': [2, 4, 6, 8] + ## }.newTable + ## + ## for k, v in a.pairs: + ## echo "key: ", k + ## echo "value: ", v + ## + ## # key: e + ## # value: [2, 4, 6, 8] + ## # key: o + ## # value: [1, 5, 7, 9] + for h in 0..high(t.data): + if isFilled(t.data[h].hcode): yield (t.data[h].key, t.data[h].val) -proc clear*[A, B](t: var OrderedTable[A, B]) = - ## resets the table so that it is empty. - clearImpl() - t.first = -1 - t.last = -1 +iterator mpairs*[A, B](t: TableRef[A, B]): (A, var B) = + ## Iterates over any ``(key, value)`` pair in the table ``t``. The values + ## can be modified. + ## + ## See also: + ## * `pairs iterator<#pairs.i,TableRef[A,B]>`_ + ## * `mvalues iterator<#mvalues.i,TableRef[A,B]>`_ + runnableExamples: + let a = { + 'o': @[1, 5, 7, 9], + 'e': @[2, 4, 6, 8] + }.newTable + 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) -proc clear*[A, B](t: var OrderedTableRef[A, B]) = - ## resets the table so that is is empty. - clear(t[]) +iterator keys*[A, B](t: TableRef[A, B]): A = + ## Iterates over any key in the table ``t``. + ## + ## See also: + ## * `pairs iterator<#pairs.i,TableRef[A,B]>`_ + ## * `values iterator<#values.i,TableRef[A,B]>`_ + runnableExamples: + let a = { + 'o': @[1, 5, 7, 9], + 'e': @[2, 4, 6, 8] + }.newTable + 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 -template forAllOrderedPairs(yieldStmt: untyped): typed {.dirty.} = - var h = t.first - while h >= 0: - var nxt = t.data[h].next - if isFilled(t.data[h].hcode): yieldStmt - h = nxt +iterator values*[A, B](t: TableRef[A, B]): B = + ## Iterates over any value in the table ``t``. + ## + ## See also: + ## * `pairs iterator<#pairs.i,TableRef[A,B]>`_ + ## * `keys iterator<#keys.i,TableRef[A,B]>`_ + ## * `mvalues iterator<#mvalues.i,TableRef[A,B]>`_ + runnableExamples: + let a = { + 'o': @[1, 5, 7, 9], + 'e': @[2, 4, 6, 8] + }.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 -iterator pairs*[A, B](t: OrderedTable[A, B]): (A, B) = - ## iterates over any ``(key, value)`` pair in the table ``t`` in insertion - ## order. - forAllOrderedPairs: - yield (t.data[h].key, t.data[h].val) +iterator mvalues*[A, B](t: TableRef[A, B]): var B = + ## Iterates over any value in the table ``t``. The values can be modified. + ## + ## See also: + ## * `mpairs iterator<#mpairs.i,TableRef[A,B]>`_ + ## * `values iterator<#values.i,TableRef[A,B]>`_ + runnableExamples: + let a = { + 'o': @[1, 5, 7, 9], + 'e': @[2, 4, 6, 8] + }.newTable + 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 -iterator mpairs*[A, B](t: var OrderedTable[A, B]): (A, var B) = - ## iterates over any ``(key, value)`` pair in the table ``t`` in insertion - ## order. The values can be modified. - forAllOrderedPairs: - yield (t.data[h].key, t.data[h].val) -iterator keys*[A, B](t: OrderedTable[A, B]): A = - ## iterates over any key in the table ``t`` in insertion order. - forAllOrderedPairs: - yield t.data[h].key -iterator values*[A, B](t: OrderedTable[A, B]): B = - ## iterates over any value in the table ``t`` in insertion order. - forAllOrderedPairs: - yield t.data[h].val -iterator mvalues*[A, B](t: var OrderedTable[A, B]): var B = - ## iterates over any value in the table ``t`` in insertion order. The values - ## can be modified. - forAllOrderedPairs: - yield t.data[h].val -proc rawGetKnownHC[A, B](t: OrderedTable[A, B], key: A, hc: Hash): int = - rawGetKnownHCImpl() -proc rawGetDeep[A, B](t: OrderedTable[A, B], key: A, hc: var Hash): int {.inline.} = - rawGetDeepImpl() -proc rawGet[A, B](t: OrderedTable[A, B], key: A, hc: var Hash): int = - rawGetImpl() -proc `[]`*[A, B](t: OrderedTable[A, B], key: A): B {.deprecatedGet.} = - ## retrieves the value at ``t[key]``. If ``key`` is not in ``t``, the - ## ``KeyError`` exception is raised. One can check with ``hasKey`` whether - ## the key exists. - get(t, key) +# --------------------------------------------------------------------------- +# ------------------------------ OrderedTable ------------------------------- +# --------------------------------------------------------------------------- -proc `[]`*[A, B](t: var OrderedTable[A, B], key: A): var B{.deprecatedGet.} = - ## retrieves the value at ``t[key]``. The value can be modified. - ## If ``key`` is not in ``t``, the ``KeyError`` exception is raised. - get(t, key) +type + OrderedKeyValuePair[A, B] = tuple[ + hcode: Hash, next: int, key: A, val: B] + OrderedKeyValuePairSeq[A, B] = seq[OrderedKeyValuePair[A, B]] + OrderedTable* [A, B] = object + ## Hash table that remembers insertion order. + ## + ## For creating an empty OrderedTable, use `initOrderedTable proc + ## <#initOrderedTable,int>`_. + data: OrderedKeyValuePairSeq[A, B] + counter, first, last: int + OrderedTableRef*[A, B] = ref OrderedTable[A, B] ## Ref version of + ## `OrderedTable<#OrderedTable>`_. + ## + ## For creating a new empty OrderedTableRef, use `newOrderedTable proc + ## <#newOrderedTable,int>`_. -proc mget*[A, B](t: var OrderedTable[A, B], key: A): var B {.deprecated.} = - ## retrieves the value at ``t[key]``. The value can be modified. - ## If ``key`` is not in ``t``, the ``KeyError`` exception is raised. - ## Use ``[]`` instead. - get(t, key) -proc getOrDefault*[A, B](t: OrderedTable[A, B], key: A): B = - ## retrieves the value at ``t[key]`` iff ``key`` is in ``t``. Otherwise, the - ## default initialization value for type ``B`` is returned (e.g. 0 for any - ## integer type). - getOrDefaultImpl(t, key) +# ------------------------------ helpers --------------------------------- -proc getOrDefault*[A, B](t: OrderedTable[A, B], key: A, default: B): B = - ## retrieves the value at ``t[key]`` iff ``key`` is in ``t``. Otherwise, - ## ``default`` is returned. - getOrDefaultImpl(t, key, default) +proc rawGetKnownHC[A, B](t: OrderedTable[A, B], key: A, hc: Hash): int = + rawGetKnownHCImpl() -proc hasKey*[A, B](t: OrderedTable[A, B], key: A): bool = - ## returns true iff ``key`` is in the table ``t``. - var hc: Hash - result = rawGet(t, key, hc) >= 0 +proc rawGetDeep[A, B](t: OrderedTable[A, B], key: A, hc: var Hash): int {.inline.} = + rawGetDeepImpl() -proc contains*[A, B](t: OrderedTable[A, B], key: A): bool = - ## Alias of ``hasKey`` for use with the ``in`` operator. - return hasKey[A, B](t, key) +proc rawGet[A, B](t: OrderedTable[A, B], key: A, hc: var Hash): int = + rawGetImpl() proc rawInsert[A, B](t: var OrderedTable[A, B], data: var OrderedKeyValuePairSeq[A, B], @@ -639,30 +1205,32 @@ proc enlarge[A, B](t: var OrderedTable[A, B]) = rawInsert(t, t.data, n[h].key, n[h].val, n[h].hcode, j) h = nxt -proc `[]=`*[A, B](t: var OrderedTable[A, B], key: A, val: B) = - ## puts a ``(key, value)`` pair into ``t``. - putImpl(enlarge) - -proc add*[A, B](t: var OrderedTable[A, B], key: A, val: B) = - ## puts a new ``(key, value)`` pair into ``t`` even if ``t[key]`` already exists. - ## This can introduce duplicate keys into the table! - addImpl(enlarge) +template forAllOrderedPairs(yieldStmt: untyped): typed {.dirty.} = + var h = t.first + while h >= 0: + var nxt = t.data[h].next + if isFilled(t.data[h].hcode): yieldStmt + h = nxt -proc mgetOrPut*[A, B](t: var OrderedTable[A, B], key: A, val: B): var B = - ## retrieves value at ``t[key]`` or puts ``value`` if not present, either way - ## returning a value which can be modified. - mgetOrPutImpl(enlarge) - -proc hasKeyOrPut*[A, B](t: var OrderedTable[A, B], key: A, val: B): bool = - ## returns true iff ``key`` is in the table, otherwise inserts ``value``. - hasKeyOrPutImpl(enlarge) +# ---------------------------------------------------------------------- proc initOrderedTable*[A, B](initialSize=64): OrderedTable[A, B] = - ## creates a new ordered hash table that is empty. + ## Creates a new ordered hash table that is empty. + ## + ## ``initialSize`` must be a power of two (default: 64). + ## If you need to accept runtime values for this you could use the + ## `nextPowerOfTwo proc<math.html#nextPowerOfTwo,int>`_ from the + ## `math module<math.html>`_ or the `rightSize proc<#rightSize,Natural>`_ + ## from this module. ## - ## ``initialSize`` needs to be a power of two. If you need to accept runtime - ## values for this you could use the ``nextPowerOfTwo`` proc from the - ## `math <math.html>`_ module or the ``rightSize`` proc from this module. + ## See also: + ## * `toOrderedTable proc<#toOrderedTable,openArray[]>`_ + ## * `newOrderedTable proc<#newOrderedTable,int>`_ for creating an + ## `OrderedTableRef` + runnableExamples: + let + a = initOrderedTable[int, string]() + b = initOrderedTable[char, seq[int]]() assert isPowerOfTwo(initialSize) result.counter = 0 result.first = -1 @@ -670,36 +1238,250 @@ proc initOrderedTable*[A, B](initialSize=64): OrderedTable[A, B] = 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``. + ## Creates a new ordered hash table that contains the given ``pairs``. + ## + ## ``pairs`` is a container consisting of ``(key, value)`` tuples. + ## + ## See also: + ## * `initOrderedTable proc<#initOrderedTable,int>`_ + ## * `newOrderedTable proc<#newOrderedTable,openArray[]>`_ for an + ## `OrderedTableRef` version + runnableExamples: + 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 -proc `$`*[A, B](t: OrderedTable[A, B]): string = - ## The ``$`` operator for ordered hash tables. - dollarImpl() +proc `[]`*[A, B](t: OrderedTable[A, B], key: A): B = + ## Retrieves the value at ``t[key]``. + ## + ## If ``key`` is not in ``t``, the ``KeyError`` exception is raised. + ## One can check with `hasKey proc<#hasKey,OrderedTable[A,B],A>`_ whether + ## the key exists. + ## + ## See also: + ## * `getOrDefault proc<#getOrDefault,OrderedTable[A,B],A>`_ to return + ## a default value (e.g. zero for int) if the key doesn't exist + ## * `getOrDefault proc<#getOrDefault,OrderedTable[A,B],A,B>`_ to return + ## a custom value if the key doesn't exist + ## * `[]= proc<#[]=,OrderedTable[A,B],A,B>`_ for inserting a new + ## (key, value) pair in the table + ## * `hasKey proc<#hasKey,OrderedTable[A,B],A>`_ for checking if a + ## key is in the table + runnableExamples: + let a = {'a': 5, 'b': 9}.toOrderedTable + doAssert a['a'] == 5 + doAssertRaises(KeyError): + echo a['z'] + get(t, key) -proc `==`*[A, B](s, t: OrderedTable[A, B]): bool = - ## The ``==`` operator for ordered hash tables. Returns true iff both the - ## content and the order are equal. - if s.counter != t.counter: - return false - var ht = t.first - var hs = s.first - while ht >= 0 and hs >= 0: - var nxtt = t.data[ht].next - var nxts = s.data[hs].next - if isFilled(t.data[ht].hcode) and isFilled(s.data[hs].hcode): - if (s.data[hs].key != t.data[ht].key) or (s.data[hs].val != t.data[ht].val): - return false - ht = nxtt - hs = nxts - return true +proc `[]`*[A, B](t: var OrderedTable[A, B], key: A): var B= + ## Retrieves the value at ``t[key]``. The value can be modified. + ## + ## If ``key`` is not in ``t``, the ``KeyError`` exception is raised. + ## + ## See also: + ## * `getOrDefault proc<#getOrDefault,OrderedTable[A,B],A>`_ to return + ## a default value (e.g. zero for int) if the key doesn't exist + ## * `getOrDefault proc<#getOrDefault,OrderedTable[A,B],A,B>`_ to return + ## a custom value if the key doesn't exist + ## * `[]= proc<#[]=,OrderedTable[A,B],A,B>`_ for inserting a new + ## (key, value) pair in the table + ## * `hasKey proc<#hasKey,OrderedTable[A,B],A>`_ for checking if a + ## key is in the table + get(t, key) + +proc `[]=`*[A, B](t: var OrderedTable[A, B], key: A, val: B) = + ## Inserts a ``(key, value)`` pair into ``t``. + ## + ## See also: + ## * `[] proc<#[],OrderedTable[A,B],A>`_ for retrieving a value of a key + ## * `hasKeyOrPut proc<#hasKeyOrPut,OrderedTable[A,B],A,B>`_ + ## * `mgetOrPut proc<#mgetOrPut,OrderedTable[A,B],A,B>`_ + ## * `del proc<#del,OrderedTable[A,B],A>`_ for removing a key from the table + runnableExamples: + var a = initOrderedTable[char, int]() + 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 = + ## Returns true if ``key`` is in the table ``t``. + ## + ## See also: + ## * `contains proc<#contains,OrderedTable[A,B],A>`_ for use with the `in` + ## operator + ## * `[] proc<#[],OrderedTable[A,B],A>`_ for retrieving a value of a key + ## * `getOrDefault proc<#getOrDefault,OrderedTable[A,B],A>`_ to return + ## a default value (e.g. zero for int) if the key doesn't exist + ## * `getOrDefault proc<#getOrDefault,OrderedTable[A,B],A,B>`_ to return + ## a custom value if the key doesn't exist + runnableExamples: + 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 + +proc contains*[A, B](t: OrderedTable[A, B], key: A): bool = + ## Alias of `hasKey proc<#hasKey,OrderedTable[A,B],A>`_ for use with + ## the ``in`` operator. + runnableExamples: + 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 = + ## Returns true if ``key`` is in the table, otherwise inserts ``value``. + ## + ## See also: + ## * `hasKey proc<#hasKey,OrderedTable[A,B],A>`_ + ## * `[] proc<#[],OrderedTable[A,B],A>`_ for retrieving a value of a key + ## * `getOrDefault proc<#getOrDefault,OrderedTable[A,B],A>`_ to return + ## a default value (e.g. zero for int) if the key doesn't exist + ## * `getOrDefault proc<#getOrDefault,OrderedTable[A,B],A,B>`_ to return + ## a custom value if the key doesn't exist + runnableExamples: + var a = {'a': 5, 'b': 9}.toOrderedTable + if a.hasKeyOrPut('a', 50): + a['a'] = 99 + 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 = + ## Retrieves the value at ``t[key]`` if ``key`` is in ``t``. Otherwise, the + ## default initialization value for type ``B`` is returned (e.g. 0 for any + ## integer type). + ## + ## See also: + ## * `[] proc<#[],OrderedTable[A,B],A>`_ for retrieving a value of a key + ## * `hasKey proc<#hasKey,OrderedTable[A,B],A>`_ + ## * `hasKeyOrPut proc<#hasKeyOrPut,OrderedTable[A,B],A,B>`_ + ## * `mgetOrPut proc<#mgetOrPut,OrderedTable[A,B],A,B>`_ + ## * `getOrDefault proc<#getOrDefault,OrderedTable[A,B],A,B>`_ to return + ## a custom value if the key doesn't exist + runnableExamples: + 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 = + ## Retrieves the value at ``t[key]`` if ``key`` is in ``t``. + ## Otherwise, ``default`` is returned. + ## + ## See also: + ## * `[] proc<#[],OrderedTable[A,B],A>`_ for retrieving a value of a key + ## * `hasKey proc<#hasKey,OrderedTable[A,B],A>`_ + ## * `hasKeyOrPut proc<#hasKeyOrPut,OrderedTable[A,B],A,B>`_ + ## * `mgetOrPut proc<#mgetOrPut,OrderedTable[A,B],A,B>`_ + ## * `getOrDefault proc<#getOrDefault,OrderedTable[A,B],A>`_ to return + ## a default value (e.g. zero for int) if the key doesn't exist + runnableExamples: + 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 = + ## Retrieves value at ``t[key]`` or puts ``val`` if not present, either way + ## returning a value which can be modified. + ## + ## See also: + ## * `[] proc<#[],OrderedTable[A,B],A>`_ for retrieving a value of a key + ## * `hasKey proc<#hasKey,OrderedTable[A,B],A>`_ + ## * `hasKeyOrPut proc<#hasKeyOrPut,OrderedTable[A,B],A,B>`_ + ## * `getOrDefault proc<#getOrDefault,OrderedTable[A,B],A>`_ to return + ## a default value (e.g. zero for int) if the key doesn't exist + ## * `getOrDefault proc<#getOrDefault,OrderedTable[A,B],A,B>`_ to return + ## a custom value if the key doesn't exist + runnableExamples: + var a = {'a': 5, 'b': 9}.toOrderedTable + 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.} = + ## Returns the number of keys in ``t``. + 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) = + ## Puts a new ``(key, value)`` pair into ``t`` even if ``t[key]`` already exists. + ## + ## **This can introduce duplicate keys into the table!** + ## + ## Use `[]= proc<#[]=,OrderedTable[A,B],A,B>`_ for inserting a new + ## (key, value) pair in the table without introducing duplicates. + addImpl(enlarge) + +proc del*[A, B](t: var OrderedTable[A, B], key: A) = + ## Deletes ``key`` from hash table ``t``. Does nothing if the key does not exist. + ## + ## O(n) complexity. + ## + ## See also: + ## * `clear proc<#clear,OrderedTable[A,B]>`_ to empty the whole table + runnableExamples: + var a = {'a': 5, 'b': 9, 'c': 13}.toOrderedTable + a.del('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 + t.first = -1 + t.last = -1 + swap(t.data, n) + let hc = genHash(key) + while h >= 0: + var nxt = n[h].next + if isFilled(n[h].hcode): + if n[h].hcode == hc and n[h].key == key: + dec t.counter + else: + var j = -1 - rawGetKnownHC(t, n[h].key, n[h].hcode) + rawInsert(t, t.data, n[h].key, n[h].val, n[h].hcode, j) + h = nxt + +proc clear*[A, B](t: var OrderedTable[A, B]) = + ## Resets the table so that it is empty. + ## + ## See also: + ## * `del proc<#del,OrderedTable[A,B],A>`_ + runnableExamples: + var a = {'a': 5, 'b': 9, 'c': 13}.toOrderedTable + doAssert len(a) == 3 + clear(a) + doAssert len(a) == 0 + clearImpl() + t.first = -1 + t.last = -1 proc sort*[A, B](t: var OrderedTable[A, B], cmp: proc (x,y: (A, B)): int) = - ## sorts ``t`` according to ``cmp``. This modifies the internal list + ## Sorts ``t`` according to the function ``cmp``. + ## + ## This modifies the internal list ## that kept the insertion order, so insertion order is lost after this ## call but key lookup and insertions remain possible after ``sort`` (in - ## contrast to the ``sort`` for count tables). + ## contrast to the `sort proc<#sort,CountTable[A]>`_ for count tables). + runnableExamples: + var a = initOrderedTable[char, int]() + for i, c in "cab": + a[c] = 10*i + doAssert a == {'c': 0, 'a': 10, 'b': 20}.toOrderedTable + a.sort(system.cmp) + doAssert a == {'a': 10, 'b': 20, 'c': 0}.toOrderedTable + var list = t.first var p, q, e, tail, oldhead: int @@ -740,244 +1522,519 @@ proc sort*[A, B](t: var OrderedTable[A, B], cmp: proc (x,y: (A, B)): int) = t.first = list t.last = tail -proc len*[A, B](t: OrderedTableRef[A, B]): int {.inline.} = - ## returns the number of keys in ``t``. - result = t.counter +proc `$`*[A, B](t: OrderedTable[A, B]): string = + ## The ``$`` operator for ordered hash tables. Used internally when calling + ## `echo` on a table. + dollarImpl() -iterator pairs*[A, B](t: OrderedTableRef[A, B]): (A, B) = - ## iterates over any ``(key, value)`` pair in the table ``t`` in insertion +proc `==`*[A, B](s, t: OrderedTable[A, B]): bool = + ## The ``==`` operator for ordered hash tables. Returns ``true`` if both the + ## content and the order are equal. + runnableExamples: + let + a = {'a': 5, 'b': 9, 'c': 13}.toOrderedTable + b = {'b': 9, 'c': 13, 'a': 5}.toOrderedTable + doAssert a != b + + if s.counter != t.counter: + return false + var ht = t.first + var hs = s.first + while ht >= 0 and hs >= 0: + var nxtt = t.data[ht].next + var nxts = s.data[hs].next + if isFilled(t.data[ht].hcode) and isFilled(s.data[hs].hcode): + if (s.data[hs].key != t.data[ht].key) or (s.data[hs].val != t.data[ht].val): + return false + ht = nxtt + hs = nxts + return true + + + +iterator pairs*[A, B](t: OrderedTable[A, B]): (A, B) = + ## Iterates over any ``(key, value)`` pair in the table ``t`` in insertion ## order. + ## + ## See also: + ## * `mpairs iterator<#mpairs.i,OrderedTable[A,B]>`_ + ## * `keys iterator<#keys.i,OrderedTable[A,B]>`_ + ## * `values iterator<#values.i,OrderedTable[A,B]>`_ + ## + ## **Examples:** + ## + ## .. code-block:: + ## let a = { + ## 'o': [1, 5, 7, 9], + ## 'e': [2, 4, 6, 8] + ## }.toOrderedTable + ## + ## for k, v in a.pairs: + ## echo "key: ", k + ## echo "value: ", v + ## + ## # key: o + ## # value: [1, 5, 7, 9] + ## # key: e + ## # value: [2, 4, 6, 8] forAllOrderedPairs: yield (t.data[h].key, t.data[h].val) -iterator mpairs*[A, B](t: OrderedTableRef[A, B]): (A, var B) = - ## iterates over any ``(key, value)`` pair in the table ``t`` in insertion - ## order. The values can be modified. +iterator mpairs*[A, B](t: var OrderedTable[A, B]): (A, var B) = + ## Iterates over any ``(key, value)`` pair in the table ``t`` (must be + ## declared as `var`) in insertion order. The values can be modified. + ## + ## See also: + ## * `pairs iterator<#pairs.i,OrderedTable[A,B]>`_ + ## * `mvalues iterator<#mvalues.i,OrderedTable[A,B]>`_ + runnableExamples: + var a = { + 'o': @[1, 5, 7, 9], + 'e': @[2, 4, 6, 8] + }.toOrderedTable + 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) -iterator keys*[A, B](t: OrderedTableRef[A, B]): A = - ## iterates over any key in the table ``t`` in insertion order. +iterator keys*[A, B](t: OrderedTable[A, B]): A = + ## Iterates over any key in the table ``t`` in insertion order. + ## + ## See also: + ## * `pairs iterator<#pairs.i,OrderedTable[A,B]>`_ + ## * `values iterator<#values.i,OrderedTable[A,B]>`_ + runnableExamples: + var a = { + 'o': @[1, 5, 7, 9], + 'e': @[2, 4, 6, 8] + }.toOrderedTable + 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 -iterator values*[A, B](t: OrderedTableRef[A, B]): B = - ## iterates over any value in the table ``t`` in insertion order. +iterator values*[A, B](t: OrderedTable[A, B]): B = + ## Iterates over any value in the table ``t`` in insertion order. + ## + ## See also: + ## * `pairs iterator<#pairs.i,OrderedTable[A,B]>`_ + ## * `keys iterator<#keys.i,OrderedTable[A,B]>`_ + ## * `mvalues iterator<#mvalues.i,OrderedTable[A,B]>`_ + runnableExamples: + let a = { + 'o': @[1, 5, 7, 9], + 'e': @[2, 4, 6, 8] + }.toOrderedTable + for v in a.values: + doAssert v.len == 4 forAllOrderedPairs: yield t.data[h].val -iterator mvalues*[A, B](t: OrderedTableRef[A, B]): var B = - ## iterates over any value in the table ``t`` in insertion order. The values +iterator mvalues*[A, B](t: var OrderedTable[A, B]): var B = + ## Iterates over any value in the table ``t`` (must be + ## declared as `var`) in insertion order. The values ## can be modified. + ## + ## See also: + ## * `mpairs iterator<#mpairs.i,OrderedTable[A,B]>`_ + ## * `values iterator<#values.i,OrderedTable[A,B]>`_ + runnableExamples: + var a = { + 'o': @[1, 5, 7, 9], + 'e': @[2, 4, 6, 8] + }.toOrderedTable + for v in a.mvalues: + v.add(99) + doAssert a == {'o': @[1, 5, 7, 9, 99], 'e': @[2, 4, 6, 8, 99]}.toOrderedTable forAllOrderedPairs: yield t.data[h].val + + + + +# --------------------------------------------------------------------------- +# --------------------------- OrderedTableRef ------------------------------- +# --------------------------------------------------------------------------- + +proc newOrderedTable*[A, B](initialSize=64): OrderedTableRef[A, B] = + ## Creates a new ordered ref hash table that is empty. + ## + ## ``initialSize`` must be a power of two (default: 64). + ## If you need to accept runtime values for this you could use the + ## `nextPowerOfTwo proc<math.html#nextPowerOfTwo,int>`_ from the + ## `math module<math.html>`_ or the `rightSize proc<#rightSize,Natural>`_ + ## from this module. + ## + ## See also: + ## * `newOrderedTable proc<#newOrderedTable,openArray[]>`_ for creating + ## an `OrderedTableRef` from a collection of `(key, value)` pairs + ## * `initOrderedTable proc<#initOrderedTable,int>`_ for creating an + ## `OrderedTable` + runnableExamples: + let + a = newOrderedTable[int, string]() + b = newOrderedTable[char, seq[int]]() + new(result) + result[] = initOrderedTable[A, B](initialSize) + +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. + ## + ## See also: + ## * `newOrderedTable proc<#newOrderedTable,int>`_ + ## * `toOrderedTable proc<#toOrderedTable,openArray[]>`_ for an + ## `OrderedTable` version + runnableExamples: + 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) + + proc `[]`*[A, B](t: OrderedTableRef[A, B], key: A): var B = - ## retrieves the value at ``t[key]``. If ``key`` is not in ``t``, the - ## ``KeyError`` exception is raised. One can check with ``hasKey`` whether + ## Retrieves the value at ``t[key]``. + ## + ## If ``key`` is not in ``t``, the ``KeyError`` exception is raised. + ## One can check with `hasKey proc<#hasKey,OrderedTableRef[A,B],A>`_ whether ## the key exists. + ## + ## See also: + ## * `getOrDefault proc<#getOrDefault,OrderedTableRef[A,B],A>`_ to return + ## a default value (e.g. zero for int) if the key doesn't exist + ## * `getOrDefault proc<#getOrDefault,OrderedTableRef[A,B],A,B>`_ to return + ## a custom value if the key doesn't exist + ## * `[]= proc<#[]=,OrderedTableRef[A,B],A,B>`_ for inserting a new + ## (key, value) pair in the table + ## * `hasKey proc<#hasKey,OrderedTableRef[A,B],A>`_ for checking if + ## a key is in the table + runnableExamples: + let a = {'a': 5, 'b': 9}.newOrderedTable + doAssert a['a'] == 5 + doAssertRaises(KeyError): + echo a['z'] result = t[][key] -proc mget*[A, B](t: OrderedTableRef[A, B], key: A): var B {.deprecated.} = - ## retrieves the value at ``t[key]``. The value can be modified. - ## If ``key`` is not in ``t``, the ``KeyError`` exception is raised. - ## Use ``[]`` instead. - result = t[][key] +proc `[]=`*[A, B](t: OrderedTableRef[A, B], key: A, val: B) = + ## Inserts a ``(key, value)`` pair into ``t``. + ## + ## See also: + ## * `[] proc<#[],OrderedTableRef[A,B],A>`_ for retrieving a value of a key + ## * `hasKeyOrPut proc<#hasKeyOrPut,OrderedTableRef[A,B],A,B>`_ + ## * `mgetOrPut proc<#mgetOrPut,OrderedTableRef[A,B],A,B>`_ + ## * `del proc<#del,OrderedTableRef[A,B],A>`_ for removing a key from the table + runnableExamples: + var a = newOrderedTable[char, int]() + 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 = + ## Returns true if ``key`` is in the table ``t``. + ## + ## See also: + ## * `contains proc<#contains,OrderedTableRef[A,B],A>`_ for use with the `in` + ## operator + ## * `[] proc<#[],OrderedTableRef[A,B],A>`_ for retrieving a value of a key + ## * `getOrDefault proc<#getOrDefault,OrderedTableRef[A,B],A>`_ to return + ## a default value (e.g. zero for int) if the key doesn't exist + ## * `getOrDefault proc<#getOrDefault,OrderedTableRef[A,B],A,B>`_ to return + ## a custom value if the key doesn't exist + runnableExamples: + 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 = + ## Alias of `hasKey proc<#hasKey,OrderedTableRef[A,B],A>`_ for use with + ## the ``in`` operator. + runnableExamples: + 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 = + ## Returns true if ``key`` is in the table, otherwise inserts ``value``. + ## + ## See also: + ## * `hasKey proc<#hasKey,OrderedTableRef[A,B],A>`_ + ## * `[] proc<#[],OrderedTableRef[A,B],A>`_ for retrieving a value of a key + ## * `getOrDefault proc<#getOrDefault,OrderedTableRef[A,B],A>`_ to return + ## a default value (e.g. zero for int) if the key doesn't exist + ## * `getOrDefault proc<#getOrDefault,OrderedTableRef[A,B],A,B>`_ to return + ## a custom value if the key doesn't exist + runnableExamples: + var a = {'a': 5, 'b': 9}.newOrderedTable + if a.hasKeyOrPut('a', 50): + a['a'] = 99 + 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 = - ## retrieves the value at ``t[key]`` iff ``key`` is in ``t``. Otherwise, the + ## Retrieves the value at ``t[key]`` if ``key`` is in ``t``. Otherwise, the ## default initialization value for type ``B`` is returned (e.g. 0 for any ## integer type). + ## + ## See also: + ## * `[] proc<#[],OrderedTableRef[A,B],A>`_ for retrieving a value of a key + ## * `hasKey proc<#hasKey,OrderedTableRef[A,B],A>`_ + ## * `hasKeyOrPut proc<#hasKeyOrPut,OrderedTableRef[A,B],A,B>`_ + ## * `mgetOrPut proc<#mgetOrPut,OrderedTableRef[A,B],A,B>`_ + ## * `getOrDefault proc<#getOrDefault,OrderedTableRef[A,B],A,B>`_ to return + ## a custom value if the key doesn't exist + runnableExamples: + 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 = - ## retrieves the value at ``t[key]`` iff ``key`` is in ``t``. Otherwise, - ## ``default`` is returned. + ## Retrieves the value at ``t[key]`` if ``key`` is in ``t``. + ## Otherwise, ``default`` is returned. + ## + ## See also: + ## * `[] proc<#[],OrderedTableRef[A,B],A>`_ for retrieving a value of a key + ## * `hasKey proc<#hasKey,OrderedTableRef[A,B],A>`_ + ## * `hasKeyOrPut proc<#hasKeyOrPut,OrderedTableRef[A,B],A,B>`_ + ## * `mgetOrPut proc<#mgetOrPut,OrderedTableRef[A,B],A,B>`_ + ## * `getOrDefault proc<#getOrDefault,OrderedTableRef[A,B],A>`_ to return + ## a default value (e.g. zero for int) if the key doesn't exist + runnableExamples: + 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 = - ## retrieves value at ``t[key]`` or puts ``val`` if not present, either way + ## Retrieves value at ``t[key]`` or puts ``val`` if not present, either way ## returning a value which can be modified. + ## + ## See also: + ## * `[] proc<#[],OrderedTableRef[A,B],A>`_ for retrieving a value of a key + ## * `hasKey proc<#hasKey,OrderedTableRef[A,B],A>`_ + ## * `hasKeyOrPut proc<#hasKeyOrPut,OrderedTableRef[A,B],A,B>`_ + ## * `getOrDefault proc<#getOrDefault,OrderedTableRef[A,B],A>`_ to return + ## a default value (e.g. zero for int) if the key doesn't exist + ## * `getOrDefault proc<#getOrDefault,OrderedTableRef[A,B],A,B>`_ to return + ## a custom value if the key doesn't exist + runnableExamples: + var a = {'a': 5, 'b': 9}.newOrderedTable + 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 hasKeyOrPut*[A, B](t: var OrderedTableRef[A, B], key: A, val: B): bool = - ## returns true iff ``key`` is in the table, otherwise inserts ``val``. - result = t[].hasKeyOrPut(key, val) - -proc hasKey*[A, B](t: OrderedTableRef[A, B], key: A): bool = - ## returns true iff ``key`` is in the table ``t``. - result = t[].hasKey(key) - -proc contains*[A, B](t: OrderedTableRef[A, B], key: A): bool = - ## Alias of ``hasKey`` for use with the ``in`` operator. - return hasKey[A, B](t, key) - -proc `[]=`*[A, B](t: OrderedTableRef[A, B], key: A, val: B) = - ## puts a ``(key, value)`` pair into ``t``. - t[][key] = val +proc len*[A, B](t: OrderedTableRef[A, B]): int {.inline.} = + ## Returns the number of keys in ``t``. + 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) = - ## puts a new ``(key, value)`` pair into ``t`` even if ``t[key]`` already exists. - ## This can introduce duplicate keys into the table! + ## Puts a new ``(key, value)`` pair into ``t`` even if ``t[key]`` already exists. + ## + ## **This can introduce duplicate keys into the table!** + ## + ## Use `[]= proc<#[]=,OrderedTableRef[A,B],A,B>`_ for inserting a new + ## (key, value) pair in the table without introducing duplicates. t[].add(key, val) -proc newOrderedTable*[A, B](initialSize=64): OrderedTableRef[A, B] = - ## creates a new ordered hash table that is empty. +proc del*[A, B](t: var OrderedTableRef[A, B], key: A) = + ## Deletes ``key`` from hash table ``t``. Does nothing if the key does not exist. ## - ## ``initialSize`` needs to be a power of two. If you need to accept runtime - ## values for this you could use the ``nextPowerOfTwo`` proc from the - ## `math <math.html>`_ module or the ``rightSize`` proc from this module. - new(result) - result[] = initOrderedTable[A, B](initialSize) + ## See also: + ## * `clear proc<#clear,OrderedTableRef[A,B]>`_ to empty the whole table + runnableExamples: + var a = {'a': 5, 'b': 9, 'c': 13}.newOrderedTable + a.del('a') + doAssert a == {'b': 9, 'c': 13}.newOrderedTable + a.del('z') + doAssert a == {'b': 9, 'c': 13}.newOrderedTable + t[].del(key) -proc newOrderedTable*[A, B](pairs: openArray[(A, B)]): OrderedTableRef[A, B] = - ## creates a new ordered hash table that contains the given ``pairs``. - result = newOrderedTable[A, B](rightSize(pairs.len)) - for key, val in items(pairs): result.add(key, val) +proc clear*[A, B](t: var OrderedTableRef[A, B]) = + ## Resets the table so that it is empty. + ## + ## See also: + ## * `del proc<#del,OrderedTable[A,B],A>`_ + runnableExamples: + var a = {'a': 5, 'b': 9, 'c': 13}.newOrderedTable + 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) = + ## Sorts ``t`` according to the function ``cmp``. + ## + ## This modifies the internal list + ## that kept the insertion order, so insertion order is lost after this + ## call but key lookup and insertions remain possible after ``sort`` (in + ## contrast to the `sort proc<#sort,CountTableRef[A]>`_ for count tables). + runnableExamples: + var a = newOrderedTable[char, int]() + for i, c in "cab": + a[c] = 10*i + doAssert a == {'c': 0, 'a': 10, 'b': 20}.newOrderedTable + a.sort(system.cmp) + doAssert a == {'a': 10, 'b': 20, 'c': 0}.newOrderedTable + t[].sort(cmp) proc `$`*[A, B](t: OrderedTableRef[A, B]): string = - ## The ``$`` operator for ordered hash tables. + ## The ``$`` operator for hash tables. Used internally when calling `echo` + ## on a table. dollarImpl() proc `==`*[A, B](s, t: OrderedTableRef[A, B]): bool = - ## The ``==`` operator for ordered hash tables. Returns true iff either both - ## tables are ``nil`` or none is ``nil`` and the content and the order of + ## The ``==`` operator for ordered hash tables. Returns true if either both + ## tables are ``nil``, or neither is ``nil`` and the content and the order of ## both are equal. + runnableExamples: + let + 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[] -proc sort*[A, B](t: OrderedTableRef[A, B], cmp: proc (x,y: (A, B)): int) = - ## sorts ``t`` according to ``cmp``. This modifies the internal list - ## that kept the insertion order, so insertion order is lost after this - ## call but key lookup and insertions remain possible after ``sort`` (in - ## contrast to the ``sort`` for count tables). - t[].sort(cmp) - -proc del*[A, B](t: var OrderedTable[A, B], key: A) = - ## deletes ``key`` from ordered hash table ``t``. O(n) complexity. Does nothing - ## if the key does not exist. - var n: OrderedKeyValuePairSeq[A, B] - newSeq(n, len(t.data)) - var h = t.first - t.first = -1 - t.last = -1 - swap(t.data, n) - let hc = genHash(key) - while h >= 0: - var nxt = n[h].next - if isFilled(n[h].hcode): - if n[h].hcode == hc and n[h].key == key: - dec t.counter - else: - var j = -1 - rawGetKnownHC(t, n[h].key, n[h].hcode) - rawInsert(t, t.data, n[h].key, n[h].val, n[h].hcode, j) - h = nxt - -proc del*[A, B](t: var OrderedTableRef[A, B], key: A) = - ## deletes ``key`` from ordered hash table ``t``. O(n) complexity. Does nothing - ## if the key does not exist. - t[].del(key) - -# ------------------------------ count tables ------------------------------- - -type - CountTable* [ - A] = object ## table that counts the number of each key - data: seq[tuple[key: A, val: int]] - counter: int - CountTableRef*[A] = ref CountTable[A] - -proc len*[A](t: CountTable[A]): int = - ## returns the number of keys in ``t``. - result = t.counter -proc clear*[A](t: CountTableRef[A]) = - ## resets the table so that it is empty. - clearImpl() -proc clear*[A](t: var CountTable[A]) = - ## resets the table so that it is empty. - clearImpl() +iterator pairs*[A, B](t: OrderedTableRef[A, B]): (A, B) = + ## Iterates over any ``(key, value)`` pair in the table ``t`` in insertion + ## order. + ## + ## See also: + ## * `mpairs iterator<#mpairs.i,OrderedTableRef[A,B]>`_ + ## * `keys iterator<#keys.i,OrderedTableRef[A,B]>`_ + ## * `values iterator<#values.i,OrderedTableRef[A,B]>`_ + ## + ## **Examples:** + ## + ## .. code-block:: + ## let a = { + ## 'o': [1, 5, 7, 9], + ## 'e': [2, 4, 6, 8] + ## }.newOrderedTable + ## + ## for k, v in a.pairs: + ## echo "key: ", k + ## echo "value: ", v + ## + ## # key: o + ## # value: [1, 5, 7, 9] + ## # key: e + ## # value: [2, 4, 6, 8] + forAllOrderedPairs: + yield (t.data[h].key, t.data[h].val) -iterator pairs*[A](t: CountTable[A]): (A, int) = - ## iterates over any ``(key, value)`` pair in the table ``t``. - for h in 0..high(t.data): - if t.data[h].val != 0: yield (t.data[h].key, t.data[h].val) +iterator mpairs*[A, B](t: OrderedTableRef[A, B]): (A, var B) = + ## Iterates over any ``(key, value)`` pair in the table ``t`` in insertion + ## order. The values can be modified. + ## + ## See also: + ## * `pairs iterator<#pairs.i,OrderedTableRef[A,B]>`_ + ## * `mvalues iterator<#mvalues.i,OrderedTableRef[A,B]>`_ + runnableExamples: + let a = { + 'o': @[1, 5, 7, 9], + 'e': @[2, 4, 6, 8] + }.newOrderedTable + 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) -iterator mpairs*[A](t: var CountTable[A]): (A, var int) = - ## iterates over any ``(key, value)`` pair in the table ``t``. The values can - ## be modified. - for h in 0..high(t.data): - if t.data[h].val != 0: yield (t.data[h].key, t.data[h].val) +iterator keys*[A, B](t: OrderedTableRef[A, B]): A = + ## Iterates over any key in the table ``t`` in insertion order. + ## + ## See also: + ## * `pairs iterator<#pairs.i,OrderedTableRef[A,B]>`_ + ## * `values iterator<#values.i,OrderedTableRef[A,B]>`_ + runnableExamples: + let a = { + 'o': @[1, 5, 7, 9], + 'e': @[2, 4, 6, 8] + }.newOrderedTable + 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 -iterator keys*[A](t: CountTable[A]): A = - ## iterates over any key in the table ``t``. - for h in 0..high(t.data): - if t.data[h].val != 0: yield t.data[h].key +iterator values*[A, B](t: OrderedTableRef[A, B]): B = + ## Iterates over any value in the table ``t`` in insertion order. + ## + ## See also: + ## * `pairs iterator<#pairs.i,OrderedTableRef[A,B]>`_ + ## * `keys iterator<#keys.i,OrderedTableRef[A,B]>`_ + ## * `mvalues iterator<#mvalues.i,OrderedTableRef[A,B]>`_ + runnableExamples: + let a = { + 'o': @[1, 5, 7, 9], + 'e': @[2, 4, 6, 8] + }.newOrderedTable + for v in a.values: + doAssert v.len == 4 + forAllOrderedPairs: + yield t.data[h].val -iterator values*[A](t: CountTable[A]): int = - ## iterates over any value in the table ``t``. - for h in 0..high(t.data): - if t.data[h].val != 0: yield t.data[h].val +iterator mvalues*[A, B](t: OrderedTableRef[A, B]): var B = + ## Iterates over any value in the table ``t`` in insertion order. The values + ## can be modified. + ## + ## See also: + ## * `mpairs iterator<#mpairs.i,OrderedTableRef[A,B]>`_ + ## * `values iterator<#values.i,OrderedTableRef[A,B]>`_ + runnableExamples: + let a = { + 'o': @[1, 5, 7, 9], + 'e': @[2, 4, 6, 8] + }.newOrderedTable + 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 -iterator mvalues*[A](t: CountTable[A]): var int = - ## iterates over any value in the table ``t``. The values can be modified. - for h in 0..high(t.data): - if t.data[h].val != 0: yield t.data[h].val -proc rawGet[A](t: CountTable[A], key: A): int = - 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 - h = nextTry(h, high(t.data)) - result = -1 - h # < 0 => MISSING; insert idx = -1 - result -template ctget(t, key: untyped): untyped = - var index = rawGet(t, key) - if index >= 0: result = t.data[index].val - else: - when compiles($key): - raise newException(KeyError, "key not found: " & $key) - else: - raise newException(KeyError, "key not found") -proc `[]`*[A](t: CountTable[A], key: A): int {.deprecatedGet.} = - ## retrieves the value at ``t[key]``. If ``key`` is not in ``t``, - ## the ``KeyError`` exception is raised. One can check with ``hasKey`` - ## whether the key exists. - ctget(t, key) -proc `[]`*[A](t: var CountTable[A], key: A): var int {.deprecatedGet.} = - ## retrieves the value at ``t[key]``. The value can be modified. - ## If ``key`` is not in ``t``, the ``KeyError`` exception is raised. - ctget(t, key) -proc mget*[A](t: var CountTable[A], key: A): var int {.deprecated.} = - ## retrieves the value at ``t[key]``. The value can be modified. - ## If ``key`` is not in ``t``, the ``KeyError`` exception is raised. - ## Use ``[]`` instead. - ctget(t, key) -proc getOrDefault*[A](t: CountTable[A], key: A): int = - ## retrieves the value at ``t[key]`` iff ``key`` is in ``t``. Otherwise, 0 (the - ## default initialization value of ``int``), is returned. - var index = rawGet(t, key) - if index >= 0: result = t.data[index].val +# ------------------------------------------------------------------------- +# ------------------------------ CountTable ------------------------------- +# ------------------------------------------------------------------------- -proc getOrDefault*[A](t: CountTable[A], key: A, default: int): int = - ## retrieves the value at ``t[key]`` iff ``key`` is in ``t``. Otherwise, the - ## integer value of ``default`` is returned. - var index = rawGet(t, key) - result = if index >= 0: t.data[index].val else: default +type + CountTable* [A] = object + ## Hash table that counts the number of each key. + ## + ## For creating an empty CountTable, use `initCountTable proc + ## <#initCountTable,int>`_. + data: seq[tuple[key: A, val: int]] + counter: int + CountTableRef*[A] = ref CountTable[A] ## Ref version of + ## `CountTable<#CountTable>`_. + ## + ## For creating a new empty CountTableRef, use `newCountTable proc + ## <#newCountTable,int>`_. -proc hasKey*[A](t: CountTable[A], key: A): bool = - ## returns true iff ``key`` is in the table ``t``. - result = rawGet(t, key) >= 0 -proc contains*[A](t: CountTable[A], key: A): bool = - ## Alias of ``hasKey`` for use with the ``in`` operator. - return hasKey[A](t, key) +# ------------------------------ helpers --------------------------------- proc rawInsert[A](t: CountTable[A], data: var seq[tuple[key: A, val: int]], key: A, val: int) = @@ -993,22 +2050,87 @@ proc enlarge[A](t: var CountTable[A]) = if t.data[i].val != 0: rawInsert(t, n, t.data[i].key, t.data[i].val) swap(t.data, n) +proc rawGet[A](t: CountTable[A], key: A): int = + 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 + h = nextTry(h, high(t.data)) + result = -1 - h # < 0 => MISSING; insert idx = -1 - result + +template ctget(t, key, default: untyped): untyped = + var index = rawGet(t, key) + result = if index >= 0: t.data[index].val else: default + +proc inc*[A](t: var CountTable[A], key: A, val = 1) + +# ---------------------------------------------------------------------- + +proc initCountTable*[A](initialSize=64): CountTable[A] = + ## Creates a new count table that is empty. + ## + ## ``initialSize`` must be a power of two (default: 64). + ## If you need to accept runtime values for this you could use the + ## `nextPowerOfTwo proc<math.html#nextPowerOfTwo,int>`_ from the + ## `math module<math.html>`_ or the `rightSize proc<#rightSize,Natural>`_ + ## from this module. + ## + ## 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) + +proc toCountTable*[A](keys: openArray[A]): CountTable[A] = + ## Creates a new count table with every member of a container ``keys`` + ## having a count of how many times it occurs in that container. + result = initCountTable[A](rightSize(keys.len)) + for key in items(keys): result.inc(key) + +proc `[]`*[A](t: CountTable[A], key: A): int = + ## Retrieves the value at ``t[key]`` if ``key`` is in ``t``. + ## Otherwise ``0`` is returned. + ## + ## See also: + ## * `getOrDefault<#getOrDefault,CountTable[A],A,int>`_ to return + ## a custom value if the key doesn't exist + ## * `mget proc<#mget,CountTable[A],A>`_ + ## * `[]= proc<#[]%3D,CountTable[A],A,int>`_ for inserting a new + ## (key, value) pair in the table + ## * `hasKey proc<#hasKey,CountTable[A],A>`_ for checking if a key + ## is in the table + ctget(t, key, 0) + +proc mget*[A](t: var CountTable[A], key: A): var int = + ## Retrieves the value at ``t[key]``. The value can be modified. + ## + ## If ``key`` is not in ``t``, the ``KeyError`` exception is raised. + get(t, key) + proc `[]=`*[A](t: var CountTable[A], key: A, val: int) = - ## puts a ``(key, value)`` pair into ``t``. + ## Inserts a ``(key, value)`` pair into ``t``. + ## + ## See also: + ## * `[] proc<#[],CountTable[A],A>`_ for retrieving a value of a key + ## * `inc proc<#inc,CountTable[A],A,int>`_ for incrementing a + ## value of a key assert val >= 0 - var h = rawGet(t, key) + let h = rawGet(t, key) 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) - #h = -1 - h - #t.data[h].key = key - #t.data[h].val = val proc inc*[A](t: var CountTable[A], key: A, val = 1) = - ## increments ``t[key]`` by ``val``. + ## Increments ``t[key]`` by ``val`` (default: 1). + runnableExamples: + var a = toCountTable("aab") + 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) @@ -1018,33 +2140,11 @@ proc inc*[A](t: var CountTable[A], key: A, val = 1) = rawInsert(t, t.data, key, val) inc(t.counter) -proc initCountTable*[A](initialSize=64): CountTable[A] = - ## creates a new count table that is empty. - ## - ## ``initialSize`` needs to be a power of two. If you need to accept runtime - ## values for this you could use the ``nextPowerOfTwo`` proc from the - ## `math <math.html>`_ module or the ``rightSize`` proc in this module. - assert isPowerOfTwo(initialSize) - result.counter = 0 - newSeq(result.data, initialSize) - -proc toCountTable*[A](keys: openArray[A]): CountTable[A] = - ## creates a new count table with every key in ``keys`` having a count - ## of how many times it occurs in ``keys``. - result = initCountTable[A](rightSize(keys.len)) - for key in items(keys): result.inc(key) - -proc `$`*[A](t: CountTable[A]): string = - ## The ``$`` operator for count tables. - dollarImpl() - -proc `==`*[A](s, t: CountTable[A]): bool = - ## The ``==`` operator for count tables. Returns ``true`` iff both tables - ## contain the same keys with the same count. Insert order does not matter. - equalsImpl(s, t) - proc smallest*[A](t: CountTable[A]): tuple[key: A, val: int] = - ## returns the ``(key, value)`` pair with the smallest ``val``. Efficiency: O(n) + ## Returns the ``(key, value)`` pair with the smallest ``val``. Efficiency: O(n) + ## + ## See also: + ## * `largest proc<#largest,CountTable[A]>`_ assert t.len > 0 var minIdx = -1 for h in 0..high(t.data): @@ -1054,7 +2154,10 @@ proc smallest*[A](t: CountTable[A]): tuple[key: A, val: int] = result.val = t.data[minIdx].val proc largest*[A](t: CountTable[A]): tuple[key: A, val: int] = - ## returns the ``(key, value)`` pair with the largest ``val``. Efficiency: O(n) + ## Returns the ``(key, value)`` pair with the largest ``val``. Efficiency: O(n) + ## + ## See also: + ## * `smallest proc<#smallest,CountTable[A]>`_ assert t.len > 0 var maxIdx = 0 for h in 1..high(t.data): @@ -1062,11 +2165,49 @@ proc largest*[A](t: CountTable[A]): tuple[key: A, val: int] = result.key = t.data[maxIdx].key result.val = t.data[maxIdx].val +proc hasKey*[A](t: CountTable[A], key: A): bool = + ## Returns true if ``key`` is in the table ``t``. + ## + ## See also: + ## * `contains proc<#contains,CountTable[A],A>`_ for use with the `in` + ## operator + ## * `[] proc<#[],CountTable[A],A>`_ for retrieving a value of a key + ## * `getOrDefault proc<#getOrDefault,CountTable[A],A,int>`_ to return + ## a custom value if the key doesn't exist + result = rawGet(t, key) >= 0 + +proc contains*[A](t: CountTable[A], key: A): bool = + ## Alias of `hasKey proc<#hasKey,CountTable[A],A>`_ for use with + ## the ``in`` operator. + return hasKey[A](t, key) + +proc getOrDefault*[A](t: CountTable[A], key: A; default: int = 0): int = + ## Retrieves the value at ``t[key]`` if``key`` is in ``t``. Otherwise, the + ## integer value of ``default`` is returned. + ## + ## See also: + ## * `[] proc<#[],CountTable[A],A>`_ for retrieving a value of a key + ## * `hasKey proc<#hasKey,CountTable[A],A>`_ for checking if a key + ## is in the table + ctget(t, key, default) + +proc len*[A](t: CountTable[A]): int = + ## Returns the number of keys in ``t``. + result = t.counter + +proc clear*[A](t: var CountTable[A]) = + ## Resets the table so that it is empty. + clearImpl() + proc sort*[A](t: var CountTable[A]) = - ## sorts the count table so that the entry with the highest counter comes - ## first. This is destructive! You must not modify ``t`` afterwards! - ## You can use the iterators ``pairs``, ``keys``, and ``values`` to iterate over - ## ``t`` in the sorted order. + ## Sorts the count table so that the entry with the highest counter comes + ## first. + ## + ## **This is destructive! You must not modify ``t`` afterwards!** + ## + ## You can use the iterators `pairs<#pairs.i,CountTable[A]>`_, + ## `keys<#keys.i,CountTable[A]>`_, and `values<#values.i,CountTable[A]>`_ + ## to iterate over ``t`` in the sorted order. # we use shellsort here; fast enough and simple var h = 1 @@ -1083,131 +2224,373 @@ proc sort*[A](t: var CountTable[A]) = if j < h: break if h == 1: break -proc len*[A](t: CountTableRef[A]): int = - ## returns the number of keys in ``t``. - result = t.counter +proc merge*[A](s: var CountTable[A], t: CountTable[A]) = + ## Merges the second table into the first one (must be declared as `var`). + runnableExamples: + var a = toCountTable("aaabbc") + let b = toCountTable("bcc") + a.merge(b) + doAssert a == toCountTable("aaabbbccc") + for key, value in t: + s.inc(key, value) -iterator pairs*[A](t: CountTableRef[A]): (A, int) = - ## iterates over any ``(key, value)`` pair in the table ``t``. +proc merge*[A](s, t: CountTable[A]): CountTable[A] = + ## Merges the two tables into a new one. + runnableExamples: + let + 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: + result.inc(key, value) + +proc `$`*[A](t: CountTable[A]): string = + ## The ``$`` operator for count tables. Used internally when calling `echo` + ## on a table. + dollarImpl() + +proc `==`*[A](s, t: CountTable[A]): bool = + ## The ``==`` operator for count tables. Returns ``true`` if both tables + ## contain the same keys with the same count. Insert order does not matter. + equalsImpl(s, t) + + +iterator pairs*[A](t: CountTable[A]): (A, int) = + ## Iterates over any ``(key, value)`` pair in the table ``t``. + ## + ## See also: + ## * `mpairs iterator<#mpairs.i,CountTable[A]>`_ + ## * `keys iterator<#keys.i,CountTable[A]>`_ + ## * `values iterator<#values.i,CountTable[A]>`_ + ## + ## **Examples:** + ## + ## .. code-block:: + ## let a = toCountTable("abracadabra") + ## + ## for k, v in pairs(a): + ## echo "key: ", k + ## echo "value: ", v + ## + ## # key: a + ## # value: 5 + ## # key: b + ## # value: 2 + ## # key: c + ## # value: 1 + ## # key: d + ## # value: 1 + ## # key: r + ## # value: 2 for h in 0..high(t.data): if t.data[h].val != 0: yield (t.data[h].key, t.data[h].val) -iterator mpairs*[A](t: CountTableRef[A]): (A, var int) = - ## iterates over any ``(key, value)`` pair in the table ``t``. The values can - ## be modified. +iterator mpairs*[A](t: var CountTable[A]): (A, var int) = + ## Iterates over any ``(key, value)`` pair in the table ``t`` (must be + ## declared as `var`). The values can be modified. + ## + ## See also: + ## * `pairs iterator<#pairs.i,CountTable[A]>`_ + ## * `mvalues iterator<#mvalues.i,CountTable[A]>`_ + runnableExamples: + var a = toCountTable("abracadabra") + 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) -iterator keys*[A](t: CountTableRef[A]): A = - ## iterates over any key in the table ``t``. +iterator keys*[A](t: CountTable[A]): A = + ## Iterates over any key in the table ``t``. + ## + ## See also: + ## * `pairs iterator<#pairs.i,CountTable[A]>`_ + ## * `values iterator<#values.i,CountTable[A]>`_ + runnableExamples: + var a = toCountTable("abracadabra") + 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 -iterator values*[A](t: CountTableRef[A]): int = - ## iterates over any value in the table ``t``. +iterator values*[A](t: CountTable[A]): int = + ## Iterates over any value in the table ``t``. + ## + ## See also: + ## * `pairs iterator<#pairs.i,CountTable[A]>`_ + ## * `keys iterator<#keys.i,CountTable[A]>`_ + ## * `mvalues iterator<#mvalues.i,CountTable[A]>`_ + runnableExamples: + 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 -iterator mvalues*[A](t: CountTableRef[A]): var int = - ## iterates over any value in the table ``t``. The values can be modified. +iterator mvalues*[A](t: var CountTable[A]): var int = + ## Iterates over any value in the table ``t`` (must be + ## declared as `var`). The values can be modified. + ## + ## See also: + ## * `mpairs iterator<#mpairs.i,CountTable[A]>`_ + ## * `values iterator<#values.i,CountTable[A]>`_ + runnableExamples: + var a = toCountTable("abracadabra") + 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 -proc `[]`*[A](t: CountTableRef[A], key: A): var int {.deprecatedGet.} = - ## retrieves the value at ``t[key]``. The value can be modified. - ## If ``key`` is not in ``t``, the ``KeyError`` exception is raised. - result = t[][key] -proc mget*[A](t: CountTableRef[A], key: A): var int {.deprecated.} = - ## retrieves the value at ``t[key]``. The value can be modified. - ## If ``key`` is not in ``t``, the ``KeyError`` exception is raised. - ## Use ``[]`` instead. - result = t[][key] -proc getOrDefault*[A](t: CountTableRef[A], key: A): int = - ## retrieves the value at ``t[key]`` iff ``key`` is in ``t``. Otherwise, 0 (the - ## default initialization value of ``int``), is returned. - result = t[].getOrDefault(key) -proc getOrDefault*[A](t: CountTableRef[A], key: A, default: int): int = - ## retrieves the value at ``t[key]`` iff ``key`` is in ``t``. Otherwise, the - ## integer value of ``default`` is returned. - result = t[].getOrDefault(key, default) -proc hasKey*[A](t: CountTableRef[A], key: A): bool = - ## returns true iff ``key`` is in the table ``t``. - result = t[].hasKey(key) -proc contains*[A](t: CountTableRef[A], key: A): bool = - ## Alias of ``hasKey`` for use with the ``in`` operator. - return hasKey[A](t, key) -proc `[]=`*[A](t: CountTableRef[A], key: A, val: int) = - ## puts a ``(key, value)`` pair into ``t``. ``val`` has to be positive. - assert val > 0 - t[][key] = val +# --------------------------------------------------------------------------- +# ---------------------------- CountTableRef -------------------------------- +# --------------------------------------------------------------------------- -proc inc*[A](t: CountTableRef[A], key: A, val = 1) = - ## increments ``t[key]`` by ``val``. - t[].inc(key, val) +proc inc*[A](t: CountTableRef[A], key: A, val = 1) proc newCountTable*[A](initialSize=64): CountTableRef[A] = - ## creates a new count table that is empty. + ## Creates a new ref count table that is empty. + ## + ## ``initialSize`` must be a power of two (default: 64). + ## If you need to accept runtime values for this you could use the + ## `nextPowerOfTwo proc<math.html#nextPowerOfTwo,int>`_ from the + ## `math module<math.html>`_ or the `rightSize proc<#rightSize,Natural>`_ + ## from this module. ## - ## ``initialSize`` needs to be a power of two. If you need to accept runtime - ## values for this you could use the ``nextPowerOfTwo`` proc from the - ## `math <math.html>`_ module or the ``rightSize`` method in this module. + ## See also: + ## * `newCountTable proc<#newCountTable,openArray[A]>`_ for creating + ## a `CountTableRef` from a collection + ## * `initCountTable proc<#initCountTable,int>`_ for creating a + ## `CountTable` new(result) result[] = initCountTable[A](initialSize) proc newCountTable*[A](keys: openArray[A]): CountTableRef[A] = - ## creates a new count table with every key in ``keys`` having a count - ## of how many times it occurs in ``keys``. + ## 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)) for key in items(keys): result.inc(key) +proc `[]`*[A](t: CountTableRef[A], key: A): int = + ## Retrieves the value at ``t[key]`` if ``key`` is in ``t``. + ## Otherwise ``0`` is returned. + ## + ## See also: + ## * `getOrDefault<#getOrDefault,CountTableRef[A],A,int>`_ to return + ## a custom value if the key doesn't exist + ## * `mget proc<#mget,CountTableRef[A],A>`_ + ## * `[]= proc<#[]%3D,CountTableRef[A],A,int>`_ for inserting a new + ## (key, value) pair in the table + ## * `hasKey proc<#hasKey,CountTableRef[A],A>`_ for checking if a key + ## is in the table + result = t[][key] + +proc mget*[A](t: CountTableRef[A], key: A): var int = + ## Retrieves the value at ``t[key]``. The value can be modified. + ## + ## If ``key`` is not in ``t``, the ``KeyError`` exception is raised. + mget(t[], key) + +proc `[]=`*[A](t: CountTableRef[A], key: A, val: int) = + ## Inserts a ``(key, value)`` pair into ``t``. + ## + ## See also: + ## * `[] proc<#[],CountTableRef[A],A>`_ for retrieving a value of a key + ## * `inc proc<#inc,CountTableRef[A],A,int>`_ for incrementing a + ## value of a key + assert val > 0 + t[][key] = val + +proc inc*[A](t: CountTableRef[A], key: A, val = 1) = + ## Increments ``t[key]`` by ``val`` (default: 1). + runnableExamples: + var a = newCountTable("aab") + a.inc('a') + a.inc('b', 10) + doAssert a == newCountTable("aaabbbbbbbbbbb") + t[].inc(key, val) + +proc smallest*[A](t: CountTableRef[A]): (A, int) = + ## Returns the ``(key, value)`` pair with the smallest ``val``. Efficiency: O(n) + ## + ## See also: + ## * `largest proc<#largest,CountTableRef[A]>`_ + t[].smallest + +proc largest*[A](t: CountTableRef[A]): (A, int) = + ## Returns the ``(key, value)`` pair with the largest ``val``. Efficiency: O(n) + ## + ## See also: + ## * `smallest proc<#smallest,CountTable[A]>`_ + t[].largest + +proc hasKey*[A](t: CountTableRef[A], key: A): bool = + ## Returns true if ``key`` is in the table ``t``. + ## + ## See also: + ## * `contains proc<#contains,CountTableRef[A],A>`_ for use with the `in` + ## operator + ## * `[] proc<#[],CountTableRef[A],A>`_ for retrieving a value of a key + ## * `getOrDefault proc<#getOrDefault,CountTableRef[A],A,int>`_ to return + ## a custom value if the key doesn't exist + result = t[].hasKey(key) + +proc contains*[A](t: CountTableRef[A], key: A): bool = + ## Alias of `hasKey proc<#hasKey,CountTableRef[A],A>`_ for use with + ## the ``in`` operator. + return hasKey[A](t, key) + +proc getOrDefault*[A](t: CountTableRef[A], key: A, default: int): int = + ## Retrieves the value at ``t[key]`` if``key`` is in ``t``. Otherwise, the + ## integer value of ``default`` is returned. + ## + ## See also: + ## * `[] proc<#[],CountTableRef[A],A>`_ for retrieving a value of a key + ## * `hasKey proc<#hasKey,CountTableRef[A],A>`_ for checking if a key + ## is in the table + result = t[].getOrDefault(key, default) + +proc len*[A](t: CountTableRef[A]): int = + ## Returns the number of keys in ``t``. + result = t.counter + +proc clear*[A](t: CountTableRef[A]) = + ## Resets the table so that it is empty. + clearImpl() + +proc sort*[A](t: CountTableRef[A]) = + ## Sorts the count table so that the entry with the highest counter comes + ## first. + ## + ## **This is destructive! You must not modify `t` afterwards!** + ## + ## You can use the iterators `pairs<#pairs.i,CountTableRef[A]>`_, + ## `keys<#keys.i,CountTableRef[A]>`_, and `values<#values.i,CountTableRef[A]>`_ + ## to iterate over ``t`` in the sorted order. + t[].sort + +proc merge*[A](s, t: CountTableRef[A]) = + ## Merges the second table into the first one. + runnableExamples: + let + a = newCountTable("aaabbc") + b = newCountTable("bcc") + a.merge(b) + doAssert a == newCountTable("aaabbbccc") + s[].merge(t[]) + proc `$`*[A](t: CountTableRef[A]): string = - ## The ``$`` operator for count tables. + ## The ``$`` operator for count tables. Used internally when calling `echo` + ## on a table. dollarImpl() proc `==`*[A](s, t: CountTableRef[A]): bool = - ## The ``==`` operator for count tables. Returns ``true`` iff either both tables - ## are ``nil`` or none is ``nil`` and both contain the same keys with the same + ## The ``==`` operator for count tables. Returns ``true`` if either both tables + ## are ``nil``, or neither is ``nil`` and both contain the same keys with the same ## count. Insert order does not matter. if isNil(s): result = isNil(t) elif isNil(t): result = false else: result = s[] == t[] -proc smallest*[A](t: CountTableRef[A]): (A, int) = - ## returns the ``(key, value)`` pair with the smallest ``val``. Efficiency: O(n) - t[].smallest -proc largest*[A](t: CountTableRef[A]): (A, int) = - ## returns the ``(key, value)`` pair with the largest ``val``. Efficiency: O(n) - t[].largest +iterator pairs*[A](t: CountTableRef[A]): (A, int) = + ## Iterates over any ``(key, value)`` pair in the table ``t``. + ## + ## See also: + ## * `mpairs iterator<#mpairs.i,CountTableRef[A]>`_ + ## * `keys iterator<#keys.i,CountTableRef[A]>`_ + ## * `values iterator<#values.i,CountTableRef[A]>`_ + ## + ## **Examples:** + ## + ## .. code-block:: + ## let a = newCountTable("abracadabra") + ## + ## for k, v in pairs(a): + ## echo "key: ", k + ## echo "value: ", v + ## + ## # key: a + ## # value: 5 + ## # key: b + ## # value: 2 + ## # key: c + ## # value: 1 + ## # key: d + ## # value: 1 + ## # key: r + ## # value: 2 + for h in 0..high(t.data): + if t.data[h].val != 0: yield (t.data[h].key, t.data[h].val) -proc sort*[A](t: CountTableRef[A]) = - ## sorts the count table so that the entry with the highest counter comes - ## first. This is destructive! You must not modify ``t`` afterwards! - ## You can use the iterators ``pairs``, ``keys``, and ``values`` to iterate over - ## ``t`` in the sorted order. - t[].sort +iterator mpairs*[A](t: CountTableRef[A]): (A, var int) = + ## Iterates over any ``(key, value)`` pair in the table ``t``. The values can + ## be modified. + ## + ## See also: + ## * `pairs iterator<#pairs.i,CountTableRef[A]>`_ + ## * `mvalues iterator<#mvalues.i,CountTableRef[A]>`_ + runnableExamples: + let a = newCountTable("abracadabra") + 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) + +iterator keys*[A](t: CountTableRef[A]): A = + ## Iterates over any key in the table ``t``. + ## + ## See also: + ## * `pairs iterator<#pairs.i,CountTable[A]>`_ + ## * `values iterator<#values.i,CountTable[A]>`_ + runnableExamples: + let a = newCountTable("abracadabra") + 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 + +iterator values*[A](t: CountTableRef[A]): int = + ## Iterates over any value in the table ``t``. + ## + ## See also: + ## * `pairs iterator<#pairs.i,CountTableRef[A]>`_ + ## * `keys iterator<#keys.i,CountTableRef[A]>`_ + ## * `mvalues iterator<#mvalues.i,CountTableRef[A]>`_ + runnableExamples: + 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 + +iterator mvalues*[A](t: CountTableRef[A]): var int = + ## Iterates over any value in the table ``t``. The values can be modified. + ## + ## See also: + ## * `mpairs iterator<#mpairs.i,CountTableRef[A]>`_ + ## * `values iterator<#values.i,CountTableRef[A]>`_ + runnableExamples: + var a = newCountTable("abracadabra") + 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 -proc merge*[A](s: var CountTable[A], t: CountTable[A]) = - ## merges the second table into the first one. - for key, value in t: - s.inc(key, value) -proc merge*[A](s, t: CountTable[A]): CountTable[A] = - ## merges the two tables into a new one. - result = initCountTable[A](nextPowerOfTwo(max(s.len, t.len))) - for table in @[s, t]: - for key, value in table: - result.inc(key, value) -proc merge*[A](s, t: CountTableRef[A]) = - ## merges the second table into the first one. - s[].merge(t[]) when isMainModule: type @@ -1325,9 +2708,9 @@ when isMainModule: #test_counttable.nim(7, 43) template/generic instantiation from here #lib/pure/collections/tables.nim(117, 21) template/generic instantiation from here #lib/pure/collections/tableimpl.nim(32, 27) Error: undeclared field: 'hcode - doAssert 0 == t.getOrDefault(testKey) + doAssert 0 == t[testKey] t.inc(testKey, 3) - doAssert 3 == t.getOrDefault(testKey) + doAssert 3 == t[testKey] block: # Clear tests @@ -1394,6 +2777,18 @@ when isMainModule: let t = toCountTable([0, 0, 5, 5, 5]) doAssert t.smallest == (0, 2) + block: #10065 + let t = toCountTable("abracadabra") + doAssert t['z'] == 0 + + var t_mut = toCountTable("abracadabra") + doAssert t_mut['z'] == 0 + # the previous read may not have modified the table. + doAssert t_mut.hasKey('z') == false + t_mut['z'] = 1 + doAssert t_mut['z'] == 1 + doAssert t_mut.hasKey('z') == true + block: var tp: Table[string, string] = initTable[string, string]() doAssert "test1" == tp.getOrDefault("test1", "test1") diff --git a/lib/pure/concurrency/atomics.nim b/lib/pure/concurrency/atomics.nim new file mode 100644 index 000000000..9e716bdf4 --- /dev/null +++ b/lib/pure/concurrency/atomics.nim @@ -0,0 +1,378 @@ +# +# +# Nim's Runtime Library +# (c) Copyright 2018 Jörg Wollenschläger +# +# See the file "copying.txt", included in this +# distribution, for details about the copyright. +# + +## Types and operations for atomic operations and lockless algorithms. + +import macros + +when defined(cpp) or defined(nimdoc): + # For the C++ backend, types and operations map directly to C++11 atomics. + + {.push, header: "<atomic>".} + + type + MemoryOrder* {.importcpp: "std::memory_order".} = enum + ## Specifies how non-atomic operations can be reordered around atomic + ## operations. + + moRelaxed + ## No ordering constraints. Only the atomicity and ordering against + ## other atomic operations is guaranteed. + + moConsume + ## This ordering is currently discouraged as it's semantics are + ## being revised. Acquire operations should be preferred. + + moAcquire + ## When applied to a load operation, no reads or writes in the + ## current thread can be reordered before this operation. + + moRelease + ## When applied to a store operation, no reads or writes in the + ## current thread can be reorderd after this operation. + + moAcquireRelease + ## When applied to a read-modify-write operation, this behaves like + ## both an acquire and a release operation. + + moSequentiallyConsistent + ## Behaves like Acquire when applied to load, like Release when + ## applied to a store and like AcquireRelease when applied to a + ## read-modify-write operation. + ## Also garantees that all threads observe the same total ordering + ## with other moSequentiallyConsistent operations. + + type + Atomic* {.importcpp: "std::atomic".} [T] = object + ## An atomic object with underlying type `T`. + + AtomicFlag* {.importcpp: "std::atomic_flag".} = object + ## An atomic boolean state. + + # Access operations + + proc load*[T](location: var Atomic[T]; order: MemoryOrder = moSequentiallyConsistent): T {.importcpp: "#.load(@)".} + ## Atomically obtains the value of the atomic object. + + proc store*[T](location: var Atomic[T]; desired: T; order: MemoryOrder = moSequentiallyConsistent) {.importcpp: "#.store(@)".} + ## Atomically replaces the value of the atomic object with the `desired` + ## value. + + proc exchange*[T](location: var Atomic[T]; desired: T; order: MemoryOrder = moSequentiallyConsistent): T {.importcpp: "#.exchange(@)".} + ## Atomically replaces the value of the atomic object with the `desired` + ## value and returns the old value. + + proc compareExchange*[T](location: var Atomic[T]; expected: var T; desired: T; order: MemoryOrder = moSequentiallyConsistent): bool {.importcpp: "#.compare_exchange_strong(@)".} + ## Atomically compares the value of the atomic object with the `expected` + ## value and performs exchange with the `desired` one if equal or load if + ## not. Returns true if the exchange was successful. + + proc compareExchange*[T](location: var Atomic[T]; expected: var T; desired: T; success, failure: MemoryOrder): bool {.importcpp: "#.compare_exchange_strong(@)".} + ## Same as above, but allows for different memory orders for success and + ## failure. + + proc compareExchangeWeak*[T](location: var Atomic[T]; expected: var T; desired: T; order: MemoryOrder = moSequentiallyConsistent): bool {.importcpp: "#.compare_exchange_weak(@)".} + ## Same as above, but is allowed to fail spuriously. + + proc compareExchangeWeak*[T](location: var Atomic[T]; expected: var T; desired: T; success, failure: MemoryOrder): bool {.importcpp: "#.compare_exchange_weak(@)".} + ## Same as above, but allows for different memory orders for success and + ## failure. + + # Numerical operations + + proc fetchAdd*[T: SomeInteger](location: var Atomic[T]; value: T; order: MemoryOrder = moSequentiallyConsistent): T {.importcpp: "#.fetch_add(@)".} + ## Atomically adds a `value` to the atomic integer and returns the + ## original value. + + proc fetchSub*[T: SomeInteger](location: var Atomic[T]; value: T; order: MemoryOrder = moSequentiallyConsistent): T {.importcpp: "#.fetch_sub(@)".} + ## Atomically subtracts a `value` to the atomic integer and returns the + ## original value. + + proc fetchAnd*[T: SomeInteger](location: var Atomic[T]; value: T; order: MemoryOrder = moSequentiallyConsistent): T {.importcpp: "#.fetch_and(@)".} + ## Atomically replaces the atomic integer with it's bitwise AND + ## with the specified `value` and returns the original value. + + proc fetchOr*[T: SomeInteger](location: var Atomic[T]; value: T; order: MemoryOrder = moSequentiallyConsistent): T {.importcpp: "#.fetch_or(@)".} + ## Atomically replaces the atomic integer with it's bitwise OR + ## with the specified `value` and returns the original value. + + proc fetchXor*[T: SomeInteger](location: var Atomic[T]; value: T; order: MemoryOrder = moSequentiallyConsistent): T {.importcpp: "#.fetch_xor(@)".} + ## Atomically replaces the atomic integer with it's bitwise XOR + ## with the specified `value` and returns the original value. + + # Flag operations + + proc testAndSet*(location: var AtomicFlag; order: MemoryOrder = moSequentiallyConsistent): bool {.importcpp: "#.test_and_set(@)".} + ## Atomically sets the atomic flag to true and returns the original value. + + proc clear*(location: var AtomicFlag; order: MemoryOrder = moSequentiallyConsistent) {.importcpp: "#.clear(@)".} + ## Atomically sets the value of the atomic flag to false. + + proc fence*(order: MemoryOrder) {.importcpp: "std::atomic_thread_fence(@)".} + ## Ensures memory ordering without using atomic operations. + + proc signalFence*(order: MemoryOrder) {.importcpp: "std::atomic_signal_fence(@)".} + ## Prevents reordering of accesses by the compiler as would fence, but + ## inserts no CPU instructions for memory ordering. + + {.pop.} + +else: + # For the C backend, atomics map to C11 built-ins on GCC and Clang for + # trivial Nim types. Other types are implemented using spin locks. + # This could be overcome by supporting advanced importc-patterns. + + # Since MSVC does not implement C11, we fall back to MS intrinsics + # where available. + + type + Trivial = SomeNumber | bool | ptr | pointer + # A type that is known to be atomic and whose size is known at + # compile time to be 8 bytes or less + + template nonAtomicType(T: typedesc[Trivial]): untyped = + # Maps types to integers of the same size + when sizeof(T) == 1: int8 + elif sizeof(T) == 2: int16 + elif sizeof(T) == 4: int32 + elif sizeof(T) == 8: int64 + + when defined(vcc): + + # TODO: Trivial types should be volatile and use VC's special volatile + # semantics for store and loads. + + type + MemoryOrder* = enum + moRelaxed + moConsume + moAcquire + moRelease + moAcquireRelease + moSequentiallyConsistent + + Atomic*[T] = object + when T is Trivial: + value: T.nonAtomicType + else: + nonAtomicValue: T + guard: AtomicFlag + + AtomicFlag* = distinct int8 + + {.push header: "<intrin.h>".} + + # MSVC intrinsics + proc interlockedExchange(location: pointer; desired: int8): int8 {.importc: "_InterlockedExchange8".} + proc interlockedExchange(location: pointer; desired: int16): int16 {.importc: "_InterlockedExchange".} + proc interlockedExchange(location: pointer; desired: int32): int32 {.importc: "_InterlockedExchange16".} + proc interlockedExchange(location: pointer; desired: int64): int64 {.importc: "_InterlockedExchange64".} + + proc interlockedCompareExchange(location: pointer; desired, expected: int8): int8 {.importc: "_InterlockedCompareExchange8".} + proc interlockedCompareExchange(location: pointer; desired, expected: int16): int16 {.importc: "_InterlockedCompareExchange16".} + proc interlockedCompareExchange(location: pointer; desired, expected: int32): int32 {.importc: "_InterlockedCompareExchange".} + proc interlockedCompareExchange(location: pointer; desired, expected: int64): int64 {.importc: "_InterlockedCompareExchange64".} + + proc interlockedAnd(location: pointer; value: int8): int8 {.importc: "_InterlockedAnd8".} + proc interlockedAnd(location: pointer; value: int16): int16 {.importc: "_InterlockedAnd16".} + proc interlockedAnd(location: pointer; value: int32): int32 {.importc: "_InterlockedAnd".} + proc interlockedAnd(location: pointer; value: int64): int64 {.importc: "_InterlockedAnd64".} + + proc interlockedOr(location: pointer; value: int8): int8 {.importc: "_InterlockedOr8".} + proc interlockedOr(location: pointer; value: int16): int16 {.importc: "_InterlockedOr16".} + proc interlockedOr(location: pointer; value: int32): int32 {.importc: "_InterlockedOr".} + proc interlockedOr(location: pointer; value: int64): int64 {.importc: "_InterlockedOr64".} + + proc interlockedXor(location: pointer; value: int8): int8 {.importc: "_InterlockedXor8".} + proc interlockedXor(location: pointer; value: int16): int16 {.importc: "_InterlockedXor16".} + proc interlockedXor(location: pointer; value: int32): int32 {.importc: "_InterlockedXor".} + proc interlockedXor(location: pointer; value: int64): int64 {.importc: "_InterlockedXor64".} + + proc fence(order: MemoryOrder): int64 {.importc: "_ReadWriteBarrier()".} + proc signalFence(order: MemoryOrder): int64 {.importc: "_ReadWriteBarrier()".} + + {.pop.} + + proc testAndSet*(location: var AtomicFlag; order: MemoryOrder = moSequentiallyConsistent): bool = + interlockedOr(addr(location), 1'i8) == 1'i8 + proc clear*(location: var AtomicFlag; order: MemoryOrder = moSequentiallyConsistent) = + discard interlockedAnd(addr(location), 0'i8) + + proc load*[T: Trivial](location: var Atomic[T]; order: MemoryOrder = moSequentiallyConsistent): T {.inline.} = + cast[T](interlockedOr(addr(location.value), (nonAtomicType(T))0)) + proc store*[T: Trivial](location: var Atomic[T]; desired: T; order: MemoryOrder = moSequentiallyConsistent) {.inline.} = + discard interlockedExchange(addr(location.value), cast[nonAtomicType(T)](desired)) + + proc exchange*[T: Trivial](location: var Atomic[T]; desired: T; order: MemoryOrder = moSequentiallyConsistent): T {.inline.} = + cast[T](interlockedExchange(addr(location.value), cast[int64](desired))) + proc compareExchange*[T: Trivial](location: var Atomic[T]; expected: var T; desired: T; success, failure: MemoryOrder): bool {.inline.} = + cast[T](interlockedCompareExchange(addr(location.value), cast[nonAtomicType(T)](desired), cast[nonAtomicType(T)](expected))) == expected + proc compareExchange*[T: Trivial](location: var Atomic[T]; expected: var T; desired: T; order: MemoryOrder = moSequentiallyConsistent): bool {.inline.} = + compareExchange(location, expected, desired, order, order) + proc compareExchangeWeak*[T: Trivial](location: var Atomic[T]; expected: var T; desired: T; success, failure: MemoryOrder): bool {.inline.} = + compareExchange(location, expected, desired, success, failure) + proc compareExchangeWeak*[T: Trivial](location: var Atomic[T]; expected: var T; desired: T; order: MemoryOrder = moSequentiallyConsistent): bool {.inline.} = + compareExchangeWeak(location, expected, desired, order, order) + + proc fetchAdd*[T: SomeInteger](location: var Atomic[T]; value: T; order: MemoryOrder = moSequentiallyConsistent): T {.inline.} = + var currentValue = location.load() + while not compareExchangeWeak(location, currentValue, currentValue + value): discard + proc fetchSub*[T: SomeInteger](location: var Atomic[T]; value: T; order: MemoryOrder = moSequentiallyConsistent): T {.inline.} = + fetchAdd(location, -value, order) + proc fetchAnd*[T: SomeInteger](location: var Atomic[T]; value: T; order: MemoryOrder = moSequentiallyConsistent): T {.inline.} = + cast[T](interlockedAnd(addr(location.value), cast[nonAtomicType(T)](value))) + proc fetchOr*[T: SomeInteger](location: var Atomic[T]; value: T; order: MemoryOrder = moSequentiallyConsistent): T {.inline.} = + cast[T](interlockedOr(addr(location.value), cast[nonAtomicType(T)](value))) + proc fetchXor*[T: SomeInteger](location: var Atomic[T]; value: T; order: MemoryOrder = moSequentiallyConsistent): T {.inline.} = + cast[T](interlockedXor(addr(location.value), cast[nonAtomicType(T)](value))) + + else: + {.push, header: "<stdatomic.h>".} + + type + MemoryOrder* {.importc: "memory_order".} = enum + moRelaxed + moConsume + moAcquire + moRelease + moAcquireRelease + moSequentiallyConsistent + + type + # Atomic* {.importcpp: "_Atomic('0)".} [T] = object + + AtomicInt8 {.importc: "_Atomic NI8".} = object + AtomicInt16 {.importc: "_Atomic NI16".} = object + AtomicInt32 {.importc: "_Atomic NI32".} = object + AtomicInt64 {.importc: "_Atomic NI64".} = object + + template atomicType(T: typedesc[Trivial]): untyped = + # Maps the size of a trivial type to it's internal atomic type + when sizeof(T) == 1: AtomicInt8 + elif sizeof(T) == 2: AtomicInt16 + elif sizeof(T) == 4: AtomicInt32 + elif sizeof(T) == 8: AtomicInt64 + + type + AtomicFlag* {.importc: "atomic_flag".} = object + + Atomic*[T] = object + when T is Trivial: + value: T.atomicType + else: + nonAtomicValue: T + guard: AtomicFlag + + #proc init*[T](location: var Atomic[T]; value: T): T {.importcpp: "atomic_init(@)".} + proc atomic_load_explicit[T, A](location: ptr A; order: MemoryOrder): T {.importc.} + proc atomic_store_explicit[T, A](location: ptr A; desired: T; order: MemoryOrder = moSequentiallyConsistent) {.importc.} + proc atomic_exchange_explicit[T, A](location: ptr A; desired: T; order: MemoryOrder = moSequentiallyConsistent): T {.importc.} + proc atomic_compare_exchange_strong_explicit[T, A](location: ptr A; expected: ptr T; desired: T; success, failure: MemoryOrder): bool {.importc.} + proc atomic_compare_exchange_weak_explicit[T, A](location: ptr A; expected: ptr T; desired: T; success, failure: MemoryOrder): bool {.importc.} + + # Numerical operations + proc atomic_fetch_add_explicit[T, A](location: ptr A; value: T; order: MemoryOrder = moSequentiallyConsistent): T {.importc.} + proc atomic_fetch_sub_explicit[T, A](location: ptr A; value: T; order: MemoryOrder = moSequentiallyConsistent): T {.importc.} + proc atomic_fetch_and_explicit[T, A](location: ptr A; value: T; order: MemoryOrder = moSequentiallyConsistent): T {.importc.} + proc atomic_fetch_or_explicit[T, A](location: ptr A; value: T; order: MemoryOrder = moSequentiallyConsistent): T {.importc.} + proc atomic_fetch_xor_explicit[T, A](location: ptr A; value: T; order: MemoryOrder = moSequentiallyConsistent): T {.importc.} + + # Flag operations + # var ATOMIC_FLAG_INIT {.importc, nodecl.}: AtomicFlag + # proc init*(location: var AtomicFlag) {.inline.} = location = ATOMIC_FLAG_INIT + proc testAndSet*(location: var AtomicFlag; order: MemoryOrder = moSequentiallyConsistent): bool {.importc: "atomic_flag_test_and_set_explicit".} + proc clear*(location: var AtomicFlag; order: MemoryOrder = moSequentiallyConsistent) {.importc: "atomic_flag_clear_explicit".} + + proc fence*(order: MemoryOrder) {.importc: "atomic_thread_fence".} + proc signalFence*(order: MemoryOrder) {.importc: "atomic_signal_fence".} + + {.pop.} + + proc load*[T: Trivial](location: var Atomic[T]; order: MemoryOrder = moSequentiallyConsistent): T {.inline.} = + cast[T](atomic_load_explicit[nonAtomicType(T), type(location.value)](addr(location.value), order)) + proc store*[T: Trivial](location: var Atomic[T]; desired: T; order: MemoryOrder = moSequentiallyConsistent) {.inline.} = + atomic_store_explicit(addr(location.value), cast[nonAtomicType(T)](desired), order) + proc exchange*[T: Trivial](location: var Atomic[T]; desired: T; order: MemoryOrder = moSequentiallyConsistent): T {.inline.} = + cast[T](atomic_exchange_explicit(addr(location.value), cast[nonAtomicType(T)](desired), order)) + proc compareExchange*[T: Trivial](location: var Atomic[T]; expected: var T; desired: T; success, failure: MemoryOrder): bool {.inline.} = + atomic_compare_exchange_strong_explicit(addr(location.value), cast[ptr nonAtomicType(T)](addr(expected)), cast[nonAtomicType(T)](desired), success, failure) + proc compareExchange*[T: Trivial](location: var Atomic[T]; expected: var T; desired: T; order: MemoryOrder = moSequentiallyConsistent): bool {.inline.} = + compareExchange(location, expected, desired, order, order) + + proc compareExchangeWeak*[T: Trivial](location: var Atomic[T]; expected: var T; desired: T; success, failure: MemoryOrder): bool {.inline.} = + atomic_compare_exchange_weak_explicit(addr(location.value), cast[ptr nonAtomicType(T)](addr(expected)), cast[nonAtomicType(T)](desired), success, failure) + proc compareExchangeWeak*[T: Trivial](location: var Atomic[T]; expected: var T; desired: T; order: MemoryOrder = moSequentiallyConsistent): bool {.inline.} = + compareExchangeWeak(location, expected, desired, order, order) + + # Numerical operations + proc fetchAdd*[T: SomeInteger](location: var Atomic[T]; value: T; order: MemoryOrder = moSequentiallyConsistent): T {.inline.} = + cast[T](atomic_fetch_add_explicit(addr(location.value), cast[nonAtomicType(T)](value), order)) + proc fetchSub*[T: SomeInteger](location: var Atomic[T]; value: T; order: MemoryOrder = moSequentiallyConsistent): T {.inline.} = + cast[T](atomic_fetch_sub_explicit(addr(location.value), cast[nonAtomicType(T)](value), order)) + proc fetchAnd*[T: SomeInteger](location: var Atomic[T]; value: T; order: MemoryOrder = moSequentiallyConsistent): T {.inline.} = + cast[T](atomic_fetch_and_explicit(addr(location.value), cast[nonAtomicType(T)](value), order)) + proc fetchOr*[T: SomeInteger](location: var Atomic[T]; value: T; order: MemoryOrder = moSequentiallyConsistent): T {.inline.} = + cast[T](atomic_fetch_or_explicit(addr(location.value), cast[nonAtomicType(T)](value), order)) + proc fetchXor*[T: SomeInteger](location: var Atomic[T]; value: T; order: MemoryOrder = moSequentiallyConsistent): T {.inline.} = + cast[T](atomic_fetch_xor_explicit(addr(location.value), cast[nonAtomicType(T)](value), order)) + + template withLock[T: not Trivial](location: var Atomic[T]; order: MemoryOrder; body: untyped): untyped = + while location.guard.testAndSet(moAcquire): discard + body + location.guard.clear(moRelease) + + proc load*[T: not Trivial](location: var Atomic[T]; order: MemoryOrder = moSequentiallyConsistent): T {.inline.} = + withLock(location, order): + result = location.nonAtomicValue + + proc store*[T: not Trivial](location: var Atomic[T]; desired: T; order: MemoryOrder = moSequentiallyConsistent) {.inline.} = + withLock(location, order): + location.nonAtomicValue = desired + + proc exchange*[T: not Trivial](location: var Atomic[T]; desired: T; order: MemoryOrder = moSequentiallyConsistent): T {.inline.} = + withLock(location, order): + result = location.nonAtomicValue + location.nonAtomicValue = desired + + proc compareExchange*[T: not Trivial](location: var Atomic[T]; expected: var T; desired: T; success, failure: MemoryOrder): bool {.inline.} = + withLock(location, success): + if location.nonAtomicValue != expected: + return false + swap(location.nonAtomicValue, expected) + return true + + proc compareExchangeWeak*[T: not Trivial](location: var Atomic[T]; expected: var T; desired: T; success, failure: MemoryOrder): bool {.inline.} = + withLock(location, success): + if location.nonAtomicValue != expected: + return false + swap(location.nonAtomicValue, expected) + return true + + proc compareExchange*[T: not Trivial](location: var Atomic[T]; expected: var T; desired: T; order: MemoryOrder = moSequentiallyConsistent): bool {.inline.} = + compareExchange(location, expected, desired, order, order) + + proc compareExchangeWeak*[T: not Trivial](location: var Atomic[T]; expected: var T; desired: T; order: MemoryOrder = moSequentiallyConsistent): bool {.inline.} = + compareExchangeWeak(location, expected, desired, order, order) + +proc atomicInc*[T: SomeInteger](location: var Atomic[T]; value: T = 1) {.inline.} = + ## Atomically increments the atomic integer by some `value`. + discard location.fetchAdd(value) + +proc atomicDec*[T: SomeInteger](location: var Atomic[T]; value: T = 1) {.inline.} = + ## Atomically decrements the atomic integer by some `value`. + discard location.fetchSub(value) + +proc `+=`*[T: SomeInteger](location: var Atomic[T]; value: T) {.inline.} = + ## Atomically increments the atomic integer by some `value`. + discard location.fetchAdd(value) + +proc `-=`*[T: SomeInteger](location: var Atomic[T]; value: T) {.inline.} = + ## Atomically decrements the atomic integer by some `value`. + discard location.fetchSub(value) + \ No newline at end of file diff --git a/lib/pure/distros.nim b/lib/pure/distros.nim index 5847cfadb..0f1ffb1ab 100644 --- a/lib/pure/distros.nim +++ b/lib/pure/distros.nim @@ -133,16 +133,17 @@ const LacksDevPackages* = {Distribution.Gentoo, Distribution.Slackware, Distribution.ArchLinux} -var unameRes, releaseRes: string ## we cache the result of the 'uname -a' - ## execution for faster platform detections. +var unameRes, releaseRes, hostnamectlRes: string ## we cache the result of the 'uname -a' + ## execution for faster platform detections. template unameRelease(cmd, cache): untyped = if cache.len == 0: cache = (when defined(nimscript): gorge(cmd) else: execProcess(cmd)) cache -template uname(): untyped = unameRelease("uname -a", unameRes) -template release(): untyped = unameRelease("lsb_release -a", releaseRes) +template uname(): untyped = unameRelease("uname -o", unameRes) +template release(): untyped = unameRelease("lsb_release -d", releaseRes) +template hostnamectl(): untyped = unameRelease("hostnamectl", hostnamectlRes) proc detectOsImpl(d: Distribution): bool = case d @@ -172,7 +173,7 @@ proc detectOsImpl(d: Distribution): bool = result = defined(haiku) else: let dd = toLowerAscii($d) - result = dd in toLowerAscii(uname()) or dd in toLowerAscii(release()) + result = dd in toLowerAscii(uname()) or dd in toLowerAscii(release()) or ("operating system: " & dd) in toLowerAscii(hostnamectl()) template detectOs*(d: untyped): bool = ## Distro/OS detection. For convenience the diff --git a/lib/pure/httpclient.nim b/lib/pure/httpclient.nim index b7498b1c5..269ae476f 100644 --- a/lib/pure/httpclient.nim +++ b/lib/pure/httpclient.nim @@ -189,12 +189,6 @@ proc body*(response: Response): string = response.body = response.bodyStream.readAll() return response.body -proc `body=`*(response: Response, value: string) {.deprecated.} = - ## Setter for backward compatibility. - ## - ## **This is deprecated and should not be used**. - response.body = value - proc body*(response: AsyncResponse): Future[string] {.async.} = ## Reads the response's body and caches it. The read is performed only ## once. @@ -477,119 +471,6 @@ proc format(p: MultipartData): tuple[contentType, body: string] = result.body.add("--" & bound & "\c\L" & s) result.body.add("--" & bound & "--\c\L") -proc request*(url: string, httpMethod: string, extraHeaders = "", - body = "", sslContext = getDefaultSSL(), timeout = -1, - userAgent = defUserAgent, proxy: Proxy = nil): Response - {.deprecated: "use HttpClient.request instead".} = - ## | Requests ``url`` with the custom method string specified by the - ## | ``httpMethod`` parameter. - ## | Extra headers can be specified and must be separated by ``\c\L`` - ## | An optional timeout can be specified in milliseconds, if reading from the - ## server takes longer than specified an ETimeout exception will be raised. - ## - ## **Deprecated since version 0.15.0**: use ``HttpClient.request`` instead. - var r = if proxy == nil: parseUri(url) else: proxy.url - var hostUrl = if proxy == nil: r else: parseUri(url) - var headers = httpMethod.toUpperAscii() - # TODO: Use generateHeaders further down once it supports proxies. - - var s = newSocket() - defer: s.close() - if s == nil: raiseOSError(osLastError()) - var port = net.Port(80) - if r.scheme == "https": - when defined(ssl): - sslContext.wrapSocket(s) - port = net.Port(443) - else: - raise newException(HttpRequestError, - "SSL support is not available. Cannot connect over SSL. Compile with -d:ssl to enable.") - if r.port != "": - port = net.Port(r.port.parseInt) - - - # get the socket ready. If we are connecting through a proxy to SSL, - # send the appropriate CONNECT header. If not, simply connect to the proper - # host (which may still be the proxy, for normal HTTP) - if proxy != nil and hostUrl.scheme == "https": - when defined(ssl): - var connectHeaders = "CONNECT " - let targetPort = if hostUrl.port == "": 443 else: hostUrl.port.parseInt - connectHeaders.add(hostUrl.hostname) - connectHeaders.add(":" & $targetPort) - connectHeaders.add(" HTTP/1.1\c\L") - connectHeaders.add("Host: " & hostUrl.hostname & ":" & $targetPort & "\c\L") - if proxy.auth != "": - let auth = base64.encode(proxy.auth, newline = "") - connectHeaders.add("Proxy-Authorization: basic " & auth & "\c\L") - connectHeaders.add("\c\L") - if timeout == -1: - s.connect(r.hostname, port) - else: - s.connect(r.hostname, port, timeout) - - s.send(connectHeaders) - let connectResult = parseResponse(s, false, timeout) - if not connectResult.status.startsWith("200"): - raise newException(HttpRequestError, - "The proxy server rejected a CONNECT request, " & - "so a secure connection could not be established.") - sslContext.wrapConnectedSocket(s, handshakeAsClient, hostUrl.hostname) - else: - raise newException(HttpRequestError, "SSL support not available. Cannot " & - "connect via proxy over SSL. Compile with -d:ssl to enable.") - else: - if timeout == -1: - s.connect(r.hostname, port) - else: - s.connect(r.hostname, port, timeout) - - - # now that the socket is ready, prepare the headers - if proxy == nil: - headers.add ' ' - if r.path[0] != '/': headers.add '/' - headers.add(r.path) - if r.query.len > 0: - headers.add("?" & r.query) - else: - headers.add(" " & url) - - headers.add(" HTTP/1.1\c\L") - - if hostUrl.port == "": - add(headers, "Host: " & hostUrl.hostname & "\c\L") - else: - add(headers, "Host: " & hostUrl.hostname & ":" & hostUrl.port & "\c\L") - - if userAgent != "": - add(headers, "User-Agent: " & userAgent & "\c\L") - if proxy != nil and proxy.auth != "": - let auth = base64.encode(proxy.auth, newline = "") - add(headers, "Proxy-Authorization: basic " & auth & "\c\L") - add(headers, extraHeaders) - add(headers, "\c\L") - - # headers are ready. send them, await the result, and close the socket. - s.send(headers) - if body != "": - s.send(body) - - result = parseResponse(s, httpMethod != "HEAD", timeout) - -proc request*(url: string, httpMethod = HttpGET, extraHeaders = "", - body = "", sslContext = getDefaultSSL(), timeout = -1, - userAgent = defUserAgent, proxy: Proxy = nil): Response - {.deprecated.} = - ## | Requests ``url`` with the specified ``httpMethod``. - ## | Extra headers can be specified and must be separated by ``\c\L`` - ## | An optional timeout can be specified in milliseconds, if reading from the - ## server takes longer than specified an ETimeout exception will be raised. - ## - ## **Deprecated since version 0.15.0**: use ``HttpClient.request`` instead. - result = request(url, $httpMethod, extraHeaders, body, sslContext, timeout, - userAgent, proxy) - proc redirection(status: string): bool = const redirectionNRs = ["301", "302", "303", "307"] for i in items(redirectionNRs): @@ -608,130 +489,6 @@ proc getNewLocation(lastURL: string, headers: HttpHeaders): string = parsed.anchor = r.anchor result = $parsed -proc get*(url: string, extraHeaders = "", maxRedirects = 5, - sslContext: SSLContext = getDefaultSSL(), - timeout = -1, userAgent = defUserAgent, - proxy: Proxy = nil): Response {.deprecated.} = - ## | GETs the ``url`` and returns a ``Response`` object - ## | This proc also handles redirection - ## | Extra headers can be specified and must be separated by ``\c\L``. - ## | An optional timeout can be specified in milliseconds, if reading from the - ## server takes longer than specified an ETimeout exception will be raised. - ## - ## **Deprecated since version 0.15.0**: use ``HttpClient.get`` instead. - result = request(url, HttpGET, extraHeaders, "", sslContext, timeout, - userAgent, proxy) - var lastURL = url - for i in 1..maxRedirects: - if result.status.redirection(): - let redirectTo = getNewLocation(lastURL, result.headers) - result = request(redirectTo, HttpGET, extraHeaders, "", sslContext, - timeout, userAgent, proxy) - lastURL = redirectTo - -proc getContent*(url: string, extraHeaders = "", maxRedirects = 5, - sslContext: SSLContext = getDefaultSSL(), - timeout = -1, userAgent = defUserAgent, - proxy: Proxy = nil): string {.deprecated.} = - ## | GETs the body and returns it as a string. - ## | Raises exceptions for the status codes ``4xx`` and ``5xx`` - ## | Extra headers can be specified and must be separated by ``\c\L``. - ## | An optional timeout can be specified in milliseconds, if reading from the - ## server takes longer than specified an ETimeout exception will be raised. - ## - ## **Deprecated since version 0.15.0**: use ``HttpClient.getContent`` instead. - var r = get(url, extraHeaders, maxRedirects, sslContext, timeout, userAgent, - proxy) - if r.status[0] in {'4','5'}: - raise newException(HttpRequestError, r.status) - else: - return r.body - -proc post*(url: string, extraHeaders = "", body = "", - maxRedirects = 5, - sslContext: SSLContext = getDefaultSSL(), - timeout = -1, userAgent = defUserAgent, - proxy: Proxy = nil, - multipart: MultipartData = nil): Response {.deprecated.} = - ## | POSTs ``body`` to the ``url`` and returns a ``Response`` object. - ## | This proc adds the necessary Content-Length header. - ## | This proc also handles redirection. - ## | Extra headers can be specified and must be separated by ``\c\L``. - ## | An optional timeout can be specified in milliseconds, if reading from the - ## server takes longer than specified an ETimeout exception will be raised. - ## | The optional ``multipart`` parameter can be used to create - ## ``multipart/form-data`` POSTs comfortably. - ## - ## **Deprecated since version 0.15.0**: use ``HttpClient.post`` instead. - let (mpContentType, mpBody) = format(multipart) - - template withNewLine(x): untyped = - if x.len > 0 and not x.endsWith("\c\L"): - x & "\c\L" - else: - x - - var xb = mpBody.withNewLine() & body - - var xh = extraHeaders.withNewLine() & - withNewLine("Content-Length: " & $len(xb)) - - if not multipart.isNil: - xh.add(withNewLine("Content-Type: " & mpContentType)) - - result = request(url, HttpPOST, xh, xb, sslContext, timeout, userAgent, - proxy) - var lastURL = url - for i in 1..maxRedirects: - if result.status.redirection(): - let redirectTo = getNewLocation(lastURL, result.headers) - var meth = if result.status != "307": HttpGet else: HttpPost - result = request(redirectTo, meth, xh, xb, sslContext, timeout, - userAgent, proxy) - lastURL = redirectTo - -proc postContent*(url: string, extraHeaders = "", body = "", - maxRedirects = 5, - sslContext: SSLContext = getDefaultSSL(), - timeout = -1, userAgent = defUserAgent, - proxy: Proxy = nil, - multipart: MultipartData = nil): string - {.deprecated.} = - ## | POSTs ``body`` to ``url`` and returns the response's body as a string - ## | Raises exceptions for the status codes ``4xx`` and ``5xx`` - ## | Extra headers can be specified and must be separated by ``\c\L``. - ## | An optional timeout can be specified in milliseconds, if reading from the - ## server takes longer than specified an ETimeout exception will be raised. - ## | The optional ``multipart`` parameter can be used to create - ## ``multipart/form-data`` POSTs comfortably. - ## - ## **Deprecated since version 0.15.0**: use ``HttpClient.postContent`` - ## instead. - var r = post(url, extraHeaders, body, maxRedirects, sslContext, timeout, - userAgent, proxy, multipart) - if r.status[0] in {'4','5'}: - raise newException(HttpRequestError, r.status) - else: - return r.body - -proc downloadFile*(url: string, outputFilename: string, - sslContext: SSLContext = getDefaultSSL(), - timeout = -1, userAgent = defUserAgent, - proxy: Proxy = nil) {.deprecated.} = - ## | Downloads ``url`` and saves it to ``outputFilename`` - ## | An optional timeout can be specified in milliseconds, if reading from the - ## server takes longer than specified an ETimeout exception will be raised. - ## - ## **Deprecated since version 0.16.2**: use ``HttpClient.downloadFile`` - ## instead. - var f: File - if open(f, outputFilename, fmWrite): - f.write(getContent(url, sslContext = sslContext, timeout = timeout, - userAgent = userAgent, proxy = proxy)) - f.close() - else: - fileError("Unable to open file") - proc generateHeaders(requestUrl: Uri, httpMethod: string, headers: HttpHeaders, body: string, proxy: Proxy): string = # GET diff --git a/lib/pure/httpserver.nim b/lib/pure/httpserver.nim deleted file mode 100644 index a81e8c0a8..000000000 --- a/lib/pure/httpserver.nim +++ /dev/null @@ -1,535 +0,0 @@ -# -# -# Nim's Runtime Library -# (c) Copyright 2012 Andreas Rumpf, Dominik Picheta -# -# See the file "copying.txt", included in this -# distribution, for details about the copyright. -# - -## This module implements a simple HTTP-Server. -## -## **Warning**: This module will soon be deprecated in favour of -## the ``asyncdispatch`` module, you should use it instead. -## -## Example: -## -## .. code-block:: nim -## import strutils, sockets, httpserver -## -## var counter = 0 -## proc handleRequest(client: Socket, path, query: string): bool {.procvar.} = -## inc(counter) -## client.send("Hello for the $#th time." % $counter & wwwNL) -## return false # do not stop processing -## -## run(handleRequest, Port(80)) -## - -import parseutils, strutils, os, osproc, strtabs, streams, sockets, asyncio - -const - wwwNL* = "\r\L" - ServerSig = "Server: httpserver.nim/1.0.0" & wwwNL - -# --------------- output messages -------------------------------------------- - -proc sendTextContentType(client: Socket) = - send(client, "Content-type: text/html" & wwwNL) - send(client, wwwNL) - -proc sendStatus(client: Socket, status: string) = - send(client, "HTTP/1.1 " & status & wwwNL) - -proc badRequest(client: Socket) = - # Inform the client that a request it has made has a problem. - send(client, "HTTP/1.1 400 Bad Request" & wwwNL) - sendTextContentType(client) - send(client, "<p>Your browser sent a bad request, " & - "such as a POST without a Content-Length.</p>" & wwwNL) - -when false: - proc cannotExec(client: Socket) = - send(client, "HTTP/1.1 500 Internal Server Error" & wwwNL) - sendTextContentType(client) - send(client, "<P>Error prohibited CGI execution." & wwwNL) - -proc headers(client: Socket, filename: string) = - # XXX could use filename to determine file type - send(client, "HTTP/1.1 200 OK" & wwwNL) - send(client, ServerSig) - sendTextContentType(client) - -proc notFound(client: Socket) = - send(client, "HTTP/1.1 404 NOT FOUND" & wwwNL) - send(client, ServerSig) - sendTextContentType(client) - send(client, "<html><title>Not Found</title>" & wwwNL) - send(client, "<body><p>The server could not fulfill" & wwwNL) - send(client, "your request because the resource specified" & wwwNL) - send(client, "is unavailable or nonexistent.</p>" & wwwNL) - send(client, "</body></html>" & wwwNL) - -proc unimplemented(client: Socket) = - send(client, "HTTP/1.1 501 Method Not Implemented" & wwwNL) - send(client, ServerSig) - sendTextContentType(client) - send(client, "<html><head><title>Method Not Implemented" & - "</title></head>" & - "<body><p>HTTP request method not supported.</p>" & - "</body></HTML>" & wwwNL) - -# ----------------- file serving --------------------------------------------- - -when false: - proc discardHeaders(client: Socket) = skip(client) - -proc serveFile*(client: Socket, filename: string) = - ## serves a file to the client. - var f: File - if open(f, filename): - headers(client, filename) - const bufSize = 8000 # != 8K might be good for memory manager - var buf = alloc(bufsize) - while true: - var bytesread = readBuffer(f, buf, bufsize) - if bytesread > 0: - var byteswritten = send(client, buf, bytesread) - if bytesread != bytesWritten: - dealloc(buf) - close(f) - raiseOSError(osLastError()) - if bytesread != bufSize: break - dealloc(buf) - close(f) - else: - notFound(client) - -# ------------------ CGI execution ------------------------------------------- -when false: - # TODO: Fix this, or get rid of it. - type - RequestMethod = enum reqGet, reqPost - - proc executeCgi(client: Socket, path, query: string, meth: RequestMethod) = - var env = newStringTable(modeCaseInsensitive) - var contentLength = -1 - case meth - of reqGet: - discardHeaders(client) - - env["REQUEST_METHOD"] = "GET" - env["QUERY_STRING"] = query - of reqPost: - var buf = TaintedString"" - var dataAvail = false - while dataAvail: - dataAvail = recvLine(client, buf) # TODO: This is incorrect. - var L = toLowerAscii(buf.string) - if L.startsWith("content-length:"): - var i = len("content-length:") - while L[i] in Whitespace: inc(i) - contentLength = parseInt(substr(L, i)) - - if contentLength < 0: - badRequest(client) - return - - env["REQUEST_METHOD"] = "POST" - env["CONTENT_LENGTH"] = $contentLength - - send(client, "HTTP/1.0 200 OK" & wwwNL) - - var process = startProcess(command=path, env=env) - if meth == reqPost: - # get from client and post to CGI program: - var buf = alloc(contentLength) - if recv(client, buf, contentLength) != contentLength: - dealloc(buf) - raiseOSError() - var inp = process.inputStream - inp.writeData(buf, contentLength) - dealloc(buf) - - var outp = process.outputStream - var line = newStringOfCap(120).TaintedString - while true: - if outp.readLine(line): - send(client, line.string) - send(client, wwwNL) - elif not running(process): break - - # --------------- Server Setup ----------------------------------------------- - - proc acceptRequest(client: Socket) = - var cgi = false - var query = "" - var buf = TaintedString"" - discard recvLine(client, buf) - var path = "" - var data = buf.string.split() - var meth = reqGet - - var q = find(data[1], '?') - - # extract path - if q >= 0: - # strip "?..." from path, this may be found in both POST and GET - path = "." & data[1].substr(0, q-1) - else: - path = "." & data[1] - # path starts with "/", by adding "." in front of it we serve files from cwd - - if cmpIgnoreCase(data[0], "GET") == 0: - if q >= 0: - cgi = true - query = data[1].substr(q+1) - elif cmpIgnoreCase(data[0], "POST") == 0: - cgi = true - meth = reqPost - else: - unimplemented(client) - - if path[path.len-1] == '/' or existsDir(path): - path = path / "index.html" - - if not existsFile(path): - discardHeaders(client) - notFound(client) - else: - when defined(Windows): - var ext = splitFile(path).ext.toLowerAscii - if ext == ".exe" or ext == ".cgi": - # XXX: extract interpreter information here? - cgi = true - else: - if {fpUserExec, fpGroupExec, fpOthersExec} * path.getFilePermissions != {}: - cgi = true - if not cgi: - serveFile(client, path) - else: - executeCgi(client, path, query, meth) - -type - Server* = object of RootObj ## contains the current server state - socket: Socket - port: Port - client*: Socket ## the socket to write the file data to - reqMethod*: string ## Request method. GET or POST. - path*, query*: string ## path and query the client requested - headers*: StringTableRef ## headers with which the client made the request - body*: string ## only set with POST requests - ip*: string ## ip address of the requesting client - - PAsyncHTTPServer* = ref AsyncHTTPServer - AsyncHTTPServer = object of Server - asyncSocket: AsyncSocket - -proc open*(s: var Server, port = Port(80), reuseAddr = false) = - ## creates a new server at port `port`. If ``port == 0`` a free port is - ## acquired that can be accessed later by the ``port`` proc. - s.socket = socket(AF_INET) - if s.socket == invalidSocket: raiseOSError(osLastError()) - if reuseAddr: - s.socket.setSockOpt(OptReuseAddr, true) - bindAddr(s.socket, port) - listen(s.socket) - - if port == Port(0): - s.port = getSockName(s.socket) - else: - s.port = port - s.client = invalidSocket - s.reqMethod = "" - s.body = "" - s.path = "" - s.query = "" - s.headers = {:}.newStringTable() - -proc port*(s: var Server): Port = - ## get the port number the server has acquired. - result = s.port - -proc next*(s: var Server) = - ## proceed to the first/next request. - var client: Socket - new(client) - var ip: string - acceptAddr(s.socket, client, ip) - s.client = client - s.ip = ip - s.headers = newStringTable(modeCaseInsensitive) - #headers(s.client, "") - var data = "" - s.client.readLine(data) - if data == "": - # Socket disconnected - s.client.close() - next(s) - return - var header = "" - while true: - s.client.readLine(header) - if header == "\c\L": break - if header != "": - var i = 0 - var key = "" - var value = "" - i = header.parseUntil(key, ':') - inc(i) # skip : - i += header.skipWhiteSpace(i) - i += header.parseUntil(value, {'\c', '\L'}, i) - s.headers[key] = value - else: - s.client.close() - next(s) - return - - var i = skipWhitespace(data) - if skipIgnoreCase(data, "GET") > 0: - s.reqMethod = "GET" - inc(i, 3) - elif skipIgnoreCase(data, "POST") > 0: - s.reqMethod = "POST" - inc(i, 4) - else: - unimplemented(s.client) - s.client.close() - next(s) - return - - if s.reqMethod == "POST": - # Check for Expect header - if s.headers.hasKey("Expect"): - if s.headers["Expect"].toLowerAscii == "100-continue": - s.client.sendStatus("100 Continue") - else: - s.client.sendStatus("417 Expectation Failed") - - # Read the body - # - Check for Content-length header - if s.headers.hasKey("Content-Length"): - var contentLength = 0 - if parseInt(s.headers["Content-Length"], contentLength) == 0: - badRequest(s.client) - s.client.close() - next(s) - return - else: - var totalRead = 0 - var totalBody = "" - while totalRead < contentLength: - var chunkSize = 8000 - if (contentLength - totalRead) < 8000: - chunkSize = (contentLength - totalRead) - var bodyData = newString(chunkSize) - var octetsRead = s.client.recv(cstring(bodyData), chunkSize) - if octetsRead <= 0: - s.client.close() - next(s) - return - totalRead += octetsRead - totalBody.add(bodyData) - if totalBody.len != contentLength: - s.client.close() - next(s) - return - - s.body = totalBody - else: - badRequest(s.client) - s.client.close() - next(s) - return - - var L = skipWhitespace(data, i) - inc(i, L) - # XXX we ignore "HTTP/1.1" etc. for now here - var query = 0 - var last = i - while last < data.len and data[last] notin Whitespace: - if data[last] == '?' and query == 0: query = last - inc(last) - if query > 0: - s.query = data.substr(query+1, last-1) - s.path = data.substr(i, query-1) - else: - s.query = "" - s.path = data.substr(i, last-1) - -proc close*(s: Server) = - ## closes the server (and the socket the server uses). - close(s.socket) - -proc run*(handleRequest: proc (client: Socket, - path, query: string): bool {.closure.}, - port = Port(80)) = - ## encapsulates the server object and main loop - var s: Server - open(s, port, reuseAddr = true) - #echo("httpserver running on port ", s.port) - while true: - next(s) - if handleRequest(s.client, s.path, s.query): break - close(s.client) - close(s) - -# -- AsyncIO begin - -proc nextAsync(s: PAsyncHTTPServer) = - ## proceed to the first/next request. - var client: Socket - new(client) - var ip: string - acceptAddr(getSocket(s.asyncSocket), client, ip) - s.client = client - s.ip = ip - s.headers = newStringTable(modeCaseInsensitive) - #headers(s.client, "") - var data = "" - s.client.readLine(data) - if data == "": - # Socket disconnected - s.client.close() - return - var header = "" - while true: - s.client.readLine(header) # TODO: Very inefficient here. Prone to DOS. - if header == "\c\L": break - if header != "": - var i = 0 - var key = "" - var value = "" - i = header.parseUntil(key, ':') - inc(i) # skip : - if i < header.len: - i += header.skipWhiteSpace(i) - i += header.parseUntil(value, {'\c', '\L'}, i) - s.headers[key] = value - else: - s.client.close() - return - - var i = skipWhitespace(data) - if skipIgnoreCase(data, "GET") > 0: - s.reqMethod = "GET" - inc(i, 3) - elif skipIgnoreCase(data, "POST") > 0: - s.reqMethod = "POST" - inc(i, 4) - else: - unimplemented(s.client) - s.client.close() - return - - if s.reqMethod == "POST": - # Check for Expect header - if s.headers.hasKey("Expect"): - if s.headers["Expect"].toLowerAscii == "100-continue": - s.client.sendStatus("100 Continue") - else: - s.client.sendStatus("417 Expectation Failed") - - # Read the body - # - Check for Content-length header - if s.headers.hasKey("Content-Length"): - var contentLength = 0 - if parseInt(s.headers["Content-Length"], contentLength) == 0: - badRequest(s.client) - s.client.close() - return - else: - var totalRead = 0 - var totalBody = "" - while totalRead < contentLength: - var chunkSize = 8000 - if (contentLength - totalRead) < 8000: - chunkSize = (contentLength - totalRead) - var bodyData = newString(chunkSize) - var octetsRead = s.client.recv(cstring(bodyData), chunkSize) - if octetsRead <= 0: - s.client.close() - return - totalRead += octetsRead - totalBody.add(bodyData) - if totalBody.len != contentLength: - s.client.close() - return - - s.body = totalBody - else: - badRequest(s.client) - s.client.close() - return - - var L = skipWhitespace(data, i) - inc(i, L) - # XXX we ignore "HTTP/1.1" etc. for now here - var query = 0 - var last = i - while last < data.len and data[last] notin Whitespace: - if data[last] == '?' and query == 0: query = last - inc(last) - if query > 0: - s.query = data.substr(query+1, last-1) - s.path = data.substr(i, query-1) - else: - s.query = "" - s.path = data.substr(i, last-1) - -proc asyncHTTPServer*(handleRequest: proc (server: PAsyncHTTPServer, client: Socket, - path, query: string): bool {.closure, gcsafe.}, - port = Port(80), address = "", - reuseAddr = false): PAsyncHTTPServer = - ## Creates an Asynchronous HTTP server at ``port``. - var capturedRet: PAsyncHTTPServer - new(capturedRet) - capturedRet.asyncSocket = asyncSocket() - capturedRet.asyncSocket.handleAccept = - proc (s: AsyncSocket) = - nextAsync(capturedRet) - let quit = handleRequest(capturedRet, capturedRet.client, capturedRet.path, - capturedRet.query) - if quit: capturedRet.asyncSocket.close() - if reuseAddr: - capturedRet.asyncSocket.setSockOpt(OptReuseAddr, true) - - capturedRet.asyncSocket.bindAddr(port, address) - capturedRet.asyncSocket.listen() - if port == Port(0): - capturedRet.port = getSockName(capturedRet.asyncSocket) - else: - capturedRet.port = port - - capturedRet.client = invalidSocket - capturedRet.reqMethod = "" - capturedRet.body = "" - capturedRet.path = "" - capturedRet.query = "" - capturedRet.headers = {:}.newStringTable() - result = capturedRet - -proc register*(d: Dispatcher, s: PAsyncHTTPServer) = - ## Registers a ``PAsyncHTTPServer`` with a ``Dispatcher``. - d.register(s.asyncSocket) - -proc close*(h: PAsyncHTTPServer) = - ## Closes the ``PAsyncHTTPServer``. - h.asyncSocket.close() - -when not defined(testing) and isMainModule: - var counter = 0 - - var s: Server - open(s, Port(0)) - echo("httpserver running on port ", s.port) - while true: - next(s) - - inc(counter) - s.client.send("Hello, Andreas, for the $#th time. $# ? $#" % [ - $counter, s.path, s.query] & wwwNL) - - close(s.client) - close(s) - diff --git a/lib/pure/includes/osenv.nim b/lib/pure/includes/osenv.nim index 4acc36b93..945555540 100644 --- a/lib/pure/includes/osenv.nim +++ b/lib/pure/includes/osenv.nim @@ -1,4 +1,4 @@ -## Include file that implements 'getEnv' and friends. Do not import it! +# Include file that implements 'getEnv' and friends. Do not import it! when not declared(os): {.error: "This is an include file for os.nim!".} diff --git a/lib/pure/includes/oserr.nim b/lib/pure/includes/oserr.nim index abd0bf501..db7d84c1e 100644 --- a/lib/pure/includes/oserr.nim +++ b/lib/pure/includes/oserr.nim @@ -1,4 +1,4 @@ -## Include file that implements 'osErrorMsg' and friends. Do not import it! +# Include file that implements 'osErrorMsg' and friends. Do not import it! when not declared(os): {.error: "This is an include file for os.nim!".} @@ -59,7 +59,7 @@ proc raiseOSError*(errorCode: OSErrorCode; additionalInfo = "") {.noinline.} = e.errorCode = errorCode.int32 e.msg = osErrorMsg(errorCode) if additionalInfo.len > 0: - if e.msg[^1] != '\n': e.msg.add '\n' + if e.msg.len > 0 and e.msg[^1] != '\n': e.msg.add '\n' e.msg.add "Additional info: " e.msg.addQuoted additionalInfo if e.msg == "": diff --git a/lib/pure/includes/osseps.nim b/lib/pure/includes/osseps.nim index 9a79fe303..944ad123e 100644 --- a/lib/pure/includes/osseps.nim +++ b/lib/pure/includes/osseps.nim @@ -85,9 +85,9 @@ elif doslikeFileSystem: const CurDir* = '.' ParDir* = ".." - DirSep* = '\\' # seperator within paths + DirSep* = '\\' # separator within paths AltSep* = '/' - PathSep* = ';' # seperator between paths + PathSep* = ';' # separator between paths FileSystemCaseSensitive* = false ExeExt* = "exe" ScriptExt* = "bat" diff --git a/lib/pure/ioselects/ioselectors_epoll.nim b/lib/pure/ioselects/ioselectors_epoll.nim index 16d901ff0..ffd60120e 100644 --- a/lib/pure/ioselects/ioselectors_epoll.nim +++ b/lib/pure/ioselects/ioselectors_epoll.nim @@ -53,6 +53,7 @@ when hasThreadSupport: SelectorImpl[T] = object epollFD: cint maxFD: int + numFD: int fds: ptr SharedArray[SelectorKey[T]] count: int Selector*[T] = ptr SelectorImpl[T] @@ -61,6 +62,7 @@ else: SelectorImpl[T] = object epollFD: cint maxFD: int + numFD: int fds: seq[SelectorKey[T]] count: int Selector*[T] = ref SelectorImpl[T] @@ -76,6 +78,8 @@ proc newSelector*[T](): Selector[T] = raiseOsError(osLastError()) var maxFD = int(a.rlim_max) doAssert(maxFD > 0) + # Start with a reasonable size, checkFd() will grow this on demand + const numFD = 1024 var epollFD = epoll_create(MAX_EPOLL_EVENTS) if epollFD < 0: @@ -85,14 +89,16 @@ proc newSelector*[T](): Selector[T] = result = cast[Selector[T]](allocShared0(sizeof(SelectorImpl[T]))) result.epollFD = epollFD result.maxFD = maxFD - result.fds = allocSharedArray[SelectorKey[T]](maxFD) + result.numFD = numFD + result.fds = allocSharedArray[SelectorKey[T]](numFD) else: result = Selector[T]() result.epollFD = epollFD result.maxFD = maxFD - result.fds = newSeq[SelectorKey[T]](maxFD) + result.numFD = numFD + result.fds = newSeq[SelectorKey[T]](numFD) - for i in 0 ..< maxFD: + for i in 0 ..< numFD: result.fds[i].ident = InvalidIdent proc close*[T](s: Selector[T]) = @@ -127,6 +133,16 @@ template checkFd(s, f) = # FD if there is too many. -- DP if f >= s.maxFD: raiseIOSelectorsError("Maximum number of descriptors is exhausted!") + if f >= s.numFD: + var numFD = s.numFD + while numFD <= f: numFD *= 2 + when hasThreadSupport: + s.fds = reallocSharedArray(s.fds, numFD) + else: + s.fds.setLen(numFD) + for i in s.numFD ..< numFD: + s.fds[i].ident = InvalidIdent + s.numFD = numFD proc registerHandle*[T](s: Selector[T], fd: int | SocketHandle, events: set[Event], data: T) = diff --git a/lib/pure/json.nim b/lib/pure/json.nim index 389df4087..cf979e2d0 100644 --- a/lib/pure/json.nim +++ b/lib/pure/json.nim @@ -134,8 +134,16 @@ ## j2["details"] = %* {"age":35, "pi":3.1415} ## echo j2 +runnableExamples: + ## Note: for JObject, key ordering is preserved, unlike in some languages, + ## this is convenient for some use cases. Example: + type Foo = object + a1, a2, a0, a3, a4: int + doAssert $(%* Foo()) == """{"a1":0,"a2":0,"a0":0,"a3":0,"a4":0}""" + import - hashes, tables, strutils, lexbase, streams, unicode, macros, parsejson + hashes, tables, strutils, lexbase, streams, unicode, macros, parsejson, + typetraits export tables.`$` @@ -349,6 +357,25 @@ when false: assert false notin elements, "usage error: only empty sets allowed" assert true notin elements, "usage error: only empty sets allowed" +proc `[]=`*(obj: JsonNode, key: string, val: JsonNode) {.inline.} = + ## Sets a field from a `JObject`. + assert(obj.kind == JObject) + obj.fields[key] = val + +#[ +Note: could use simply: +proc `%`*(o: object|tuple): JsonNode +but blocked by https://github.com/nim-lang/Nim/issues/10019 +]# +proc `%`*(o: tuple): JsonNode = + ## Generic constructor for JSON data. Creates a new `JObject JsonNode` + when isNamedTuple(type(o)): + result = newJObject() + for k, v in o.fieldPairs: result[k] = %v + else: + result = newJArray() + for a in o.fields: result.add(%a) + proc `%`*(o: object): JsonNode = ## Generic constructor for JSON data. Creates a new `JObject JsonNode` result = newJObject() @@ -500,11 +527,6 @@ proc contains*(node: JsonNode, val: JsonNode): bool = proc existsKey*(node: JsonNode, key: string): bool {.deprecated: "use hasKey instead".} = node.hasKey(key) ## **Deprecated:** use `hasKey` instead. -proc `[]=`*(obj: JsonNode, key: string, val: JsonNode) {.inline.} = - ## Sets a field from a `JObject`. - assert(obj.kind == JObject) - obj.fields[key] = val - proc `{}`*(node: JsonNode, keys: varargs[string]): JsonNode = ## Traverses the node and gets the given value. If any of the ## keys do not exist, returns ``nil``. Also returns ``nil`` if one of the diff --git a/lib/pure/logging.nim b/lib/pure/logging.nim index cd13deec3..febd0b602 100644 --- a/lib/pure/logging.nim +++ b/lib/pure/logging.nim @@ -12,7 +12,7 @@ ## write your own. ## ## Format strings support the following variables which must be prefixed with -## the dollar operator (``$``): +## the dollar operator (``$``, see example below): ## ## ============ ======================= ## Operator Output @@ -43,6 +43,11 @@ ## warn("4 8 15 16 23 4-- Error") ## error("922044:16 SYSTEM FAILURE") ## fatal("SYSTEM FAILURE SYSTEM FAILURE") +## # Using the aformetioned operator +## var opL = newConsoleLogger(fmtStr = "$datetime :: ") +## addHandler(opL) +## info("Starting web server...") +## # Will print something like 2018-12-17T19:28:05 :: Starting web server... ## ## **Warning:** The global list of handlers is a thread var, this means that ## the handlers must be re-added in each thread. diff --git a/lib/pure/matchers.nim b/lib/pure/matchers.nim deleted file mode 100644 index 97223ed01..000000000 --- a/lib/pure/matchers.nim +++ /dev/null @@ -1,68 +0,0 @@ -# -# -# Nim's Runtime Library -# (c) Copyright 2015 Andreas Rumpf -# -# See the file "copying.txt", included in this -# distribution, for details about the copyright. -# - -## This module contains various string matchers for email addresses, etc. -## -## **Warning:** This module is deprecated since version 0.14.0. -{.deprecated.} - -{.deadCodeElim: on.} # dce option deprecated - -{.push debugger:off .} # the user does not want to trace a part - # of the standard library! - -include "system/inclrtl" - -import parseutils, strutils - -proc validEmailAddress*(s: string): bool {.noSideEffect, - rtl, extern: "nsuValidEmailAddress".} = - ## returns true if `s` seems to be a valid e-mail address. - ## The checking also uses a domain list. - const - chars = Letters + Digits + {'!','#','$','%','&', - '\'','*','+','/','=','?','^','_','`','{','}','|','~','-','.'} - var i = 0 - if i >= s.len or s[i] notin chars or s[i] == '.': return false - while i < s.len and s[i] in chars: - if i+1 < s.len and s[i] == '.' and s[i+1] == '.': return false - inc(i) - if i >= s.len or s[i] != '@': return false - var j = len(s)-1 - if j >= 0 and s[j] notin Letters: return false - while j >= i and s[j] in Letters: dec(j) - inc(i) # skip '@' - while i < s.len and s[i] in {'0'..'9', 'a'..'z', '-', '.'}: inc(i) - if i != s.len: return false - - var x = substr(s, j+1) - if len(x) == 2 and x[0] in Letters and x[1] in Letters: return true - case toLowerAscii(x) - of "com", "org", "net", "gov", "mil", "biz", "info", "mobi", "name", - "aero", "jobs", "museum": return true - else: return false - -proc parseInt*(s: string, value: var int, validRange: HSlice[int, int]) {. - noSideEffect, rtl, extern: "nmatchParseInt".} = - ## parses `s` into an integer in the range `validRange`. If successful, - ## `value` is modified to contain the result. Otherwise no exception is - ## raised and `value` is not touched; this way a reasonable default value - ## won't be overwritten. - var x = value - try: - discard parseutils.parseInt(s, x, 0) - except OverflowError: - discard - if x in validRange: value = x - -when isMainModule: - doAssert "wuseldusel@codehome.com".validEmailAddress - -{.pop.} - diff --git a/lib/pure/math.nim b/lib/pure/math.nim index ee32772b1..460be1cd0 100644 --- a/lib/pure/math.nim +++ b/lib/pure/math.nim @@ -7,15 +7,48 @@ # distribution, for details about the copyright. # -## Constructive mathematics is naturally typed. -- Simon Thompson +## *Constructive mathematics is naturally typed.* -- Simon Thompson ## ## Basic math routines for Nim. +## +## Note that the trigonometric functions naturally operate on radians. +## The helper functions `degToRad<#degToRad,T>`_ and `radToDeg<#radToDeg,T>`_ +## provide conversion between radians and degrees. +## +## .. code-block:: +## +## import math +## from sequtils import map +## +## let a = [0.0, PI/6, PI/4, PI/3, PI/2] +## +## echo a.map(sin) +## # @[0.0, 0.499…, 0.707…, 0.866…, 1.0] +## +## echo a.map(tan) +## # @[0.0, 0.577…, 0.999…, 1.732…, 1.633…e+16] +## +## echo cos(degToRad(180.0)) +## # -1.0 +## +## echo sqrt(-1.0) +## # nan (use `complex` module) +## ## This module is available for the `JavaScript target ## <backends.html#the-javascript-target>`_. ## -## Note that the trigonometric functions naturally operate on radians. -## The helper functions `degToRad` and `radToDeg` provide conversion -## between radians and degrees. +## **See also:** +## * `complex module<complex.html>`_ for complex numbers and their +## mathematical operations +## * `rationals module<rationals.html>`_ for rational numbers and their +## mathematical operations +## * `fenv module<fenv.html>`_ for handling of floating-point rounding +## and exceptions (overflow, zero-devide, etc.) +## * `random module<random.html>`_ for fast and tiny random number generator +## * `mersenne module<mersenne.html>`_ for Mersenne twister random number generator +## * `stats module<stats.html>`_ for statistical analysis +## * `strformat module<strformat>`_ for formatting floats for print + include "system/inclrtl" {.push debugger:off .} # the user does not want to trace a part @@ -25,9 +58,11 @@ import bitops proc binom*(n, k: int): int {.noSideEffect.} = ## Computes the `binomial coefficient <https://en.wikipedia.org/wiki/Binomial_coefficient>`_. - ## - ## .. code-block:: nim - ## echo binom(6, 2) ## 15 + runnableExamples: + doAssert binom(6, 2) == binom(6, 4) + doAssert binom(6, 2) == 15 + doAssert binom(-6, 2) == 1 + doAssert binom(6, 0) == 1 if k <= 0: return 1 if 2*k > n: return binom(n, n-k) result = n @@ -40,10 +75,15 @@ proc createFactTable[N: static[int]]: array[N, int] = result[i] = result[i - 1] * i proc fac*(n: int): int = - ## Computes the `factorial <https://en.wikipedia.org/wiki/Factorial>`_ of a non-negative integer ``n`` + ## Computes the `factorial <https://en.wikipedia.org/wiki/Factorial>`_ of + ## a non-negative integer ``n``. ## - ## .. code-block:: nim - ## echo fac(4) ## 24 + ## See also: + ## * `prod proc <#prod,openArray[T]>`_ + runnableExamples: + doAssert fac(3) == 6 + doAssert fac(4) == 24 + doAssert fac(10) == 3628800 const factTable = when sizeof(int) == 4: createFactTable[13]() @@ -59,25 +99,26 @@ when defined(Posix): {.passl: "-lm".} const - PI* = 3.1415926535897932384626433 ## the circle constant PI (Ludolph's number) - TAU* = 2.0 * PI ## the circle constant TAU (= 2 * PI) + PI* = 3.1415926535897932384626433 ## The circle constant PI (Ludolph's number) + TAU* = 2.0 * PI ## The circle constant TAU (= 2 * PI) E* = 2.71828182845904523536028747 ## Euler's number - MaxFloat64Precision* = 16 ## maximum number of meaningful digits + MaxFloat64Precision* = 16 ## Maximum number of meaningful digits ## after the decimal point for Nim's ## ``float64`` type. - MaxFloat32Precision* = 8 ## maximum number of meaningful digits + MaxFloat32Precision* = 8 ## Maximum number of meaningful digits ## after the decimal point for Nim's ## ``float32`` type. - MaxFloatPrecision* = MaxFloat64Precision ## maximum number of + MaxFloatPrecision* = MaxFloat64Precision ## Maximum number of ## meaningful digits ## after the decimal point ## for Nim's ``float`` type. - RadPerDeg = PI / 180.0 ## number of radians per degree + RadPerDeg = PI / 180.0 ## Number of radians per degree type - FloatClass* = enum ## describes the class a floating point value belongs to. - ## This is the type that is returned by `classify`. + FloatClass* = enum ## Describes the class a floating point value belongs to. + ## This is the type that is returned by + ## `classify proc <#classify,float>`_. fcNormal, ## value is an ordinary nonzero floating point value fcSubnormal, ## value is a subnormal (a very small) floating point value fcZero, ## value is zero @@ -87,13 +128,14 @@ type fcNegInf ## value is negative infinity proc classify*(x: float): FloatClass = - ## Classifies a floating point value. Returns ``x``'s class as specified by - ## `FloatClass`. + ## Classifies a floating point value. ## - ## .. code-block:: nim - ## echo classify(0.3) ## fcNormal - ## echo classify(0.0) ## fcZero - ## echo classify(0.3/0.0) ## fcInf + ## Returns ``x``'s class as specified by `FloatClass enum<#FloatClass>`_. + runnableExamples: + doAssert classify(0.3) == fcNormal + doAssert classify(0.0) == fcZero + doAssert classify(0.3/0.0) == fcInf + doAssert classify(-0.3/0.0) == fcNegInf # JavaScript and most C compilers have no classify: if x == 0.0: @@ -110,20 +152,30 @@ proc classify*(x: float): FloatClass = proc isPowerOfTwo*(x: int): bool {.noSideEffect.} = ## Returns ``true``, if ``x`` is a power of two, ``false`` otherwise. + ## ## Zero and negative numbers are not a power of two. ## - ## .. code-block:: nim - ## echo isPowerOfTwo(5) ## false - ## echo isPowerOfTwo(8) ## true + ## See also: + ## * `nextPowerOfTwo proc<#nextPowerOfTwo,int>`_ + runnableExamples: + doAssert isPowerOfTwo(16) == true + doAssert isPowerOfTwo(5) == false + doAssert isPowerOfTwo(0) == false + doAssert isPowerOfTwo(-16) == false return (x > 0) and ((x and (x - 1)) == 0) proc nextPowerOfTwo*(x: int): int {.noSideEffect.} = ## Returns ``x`` rounded up to the nearest power of two. + ## ## Zero and negative numbers get rounded up to 1. ## - ## .. code-block:: nim - ## echo nextPowerOfTwo(8) ## 8 - ## echo nextPowerOfTwo(9) ## 16 + ## See also: + ## * `isPowerOfTwo proc<#isPowerOfTwo,int>`_ + runnableExamples: + doAssert nextPowerOfTwo(16) == 16 + doAssert nextPowerOfTwo(5) == 8 + doAssert nextPowerOfTwo(0) == 1 + doAssert nextPowerOfTwo(-16) == 1 result = x - 1 when defined(cpu64): result = result or (result shr 32) @@ -138,9 +190,12 @@ proc nextPowerOfTwo*(x: int): int {.noSideEffect.} = proc countBits32*(n: int32): int {.noSideEffect.} = ## Counts the set bits in ``n``. - ## - ## .. code-block:: nim - ## echo countBits32(13'i32) ## 3 + runnableExamples: + doAssert countBits32(7) == 3 + doAssert countBits32(8) == 1 + doAssert countBits32(15) == 4 + doAssert countBits32(16) == 1 + doAssert countBits32(17) == 2 var v = n v = v -% ((v shr 1'i32) and 0x55555555'i32) v = (v and 0x33333333'i32) +% ((v shr 2'i32) and 0x33333333'i32) @@ -148,41 +203,99 @@ proc countBits32*(n: int32): int {.noSideEffect.} = proc sum*[T](x: openArray[T]): T {.noSideEffect.} = ## Computes the sum of the elements in ``x``. + ## ## If ``x`` is empty, 0 is returned. ## - ## .. code-block:: nim - ## echo sum([1.0, 2.5, -3.0, 4.3]) ## 4.8 + ## See also: + ## * `prod proc <#prod,openArray[T]>`_ + ## * `cumsum proc <#cumsum,openArray[T]>`_ + ## * `cumsummed proc <#cumsummed,openArray[T]>`_ + runnableExamples: + doAssert sum([1, 2, 3, 4]) == 10 + doAssert sum([-1.5, 2.7, -0.1]) == 1.1 for i in items(x): result = result + i proc prod*[T](x: openArray[T]): T {.noSideEffect.} = ## Computes the product of the elements in ``x``. + ## ## If ``x`` is empty, 1 is returned. ## - ## .. code-block:: nim - ## echo prod([1.0, 3.0, -0.2]) ## -0.6 + ## See also: + ## * `sum proc <#sum,openArray[T]>`_ + ## * `fac proc <#fac,int>`_ + runnableExamples: + doAssert prod([1, 2, 3, 4]) == 24 + doAssert prod([-4, 3, 5]) == -60 result = 1.T for i in items(x): result = result * i +proc cumsummed*[T](x: openArray[T]): seq[T] = + ## Return cumulative (aka prefix) summation of ``x``. + ## + ## See also: + ## * `sum proc <#sum,openArray[T]>`_ + ## * `cumsum proc <#cumsum,openArray[T]>`_ for the in-place version + runnableExamples: + let a = [1, 2, 3, 4] + doAssert cumsummed(a) == @[1, 3, 6, 10] + result.setLen(x.len) + result[0] = x[0] + for i in 1 ..< x.len: result[i] = result[i-1] + x[i] + +proc cumsum*[T](x: var openArray[T]) = + ## Transforms ``x`` in-place (must be declared as `var`) into its + ## cumulative (aka prefix) summation. + ## + ## See also: + ## * `sum proc <#sum,openArray[T]>`_ + ## * `cumsummed proc <#cumsummed,openArray[T]>`_ for a version which + ## returns cumsummed sequence + runnableExamples: + var a = [1, 2, 3, 4] + cumsum(a) + doAssert a == @[1, 3, 6, 10] + for i in 1 ..< x.len: x[i] = x[i-1] + x[i] + {.push noSideEffect.} when not defined(JS): # C proc sqrt*(x: float32): float32 {.importc: "sqrtf", header: "<math.h>".} proc sqrt*(x: float64): float64 {.importc: "sqrt", header: "<math.h>".} ## Computes the square root of ``x``. ## + ## See also: + ## * `cbrt proc <#cbrt,float64>`_ for cubic root + ## ## .. code-block:: nim + ## echo sqrt(4.0) ## 2.0 ## echo sqrt(1.44) ## 1.2 + ## echo sqrt(-4.0) ## nan proc cbrt*(x: float32): float32 {.importc: "cbrtf", header: "<math.h>".} proc cbrt*(x: float64): float64 {.importc: "cbrt", header: "<math.h>".} ## Computes the cubic root of ``x``. ## + ## See also: + ## * `sqrt proc <#sqrt,float64>`_ for square root + ## ## .. code-block:: nim + ## echo cbrt(8.0) ## 2.0 ## echo cbrt(2.197) ## 1.3 + ## echo cbrt(-27.0) ## -3.0 proc ln*(x: float32): float32 {.importc: "logf", header: "<math.h>".} proc ln*(x: float64): float64 {.importc: "log", header: "<math.h>".} - ## Computes the `natural logarithm <https://en.wikipedia.org/wiki/Natural_logarithm>`_ of ``x``. + ## Computes the `natural logarithm <https://en.wikipedia.org/wiki/Natural_logarithm>`_ + ## of ``x``. + ## + ## See also: + ## * `log proc <#log,T,T>`_ + ## * `log10 proc <#log10,float64>`_ + ## * `log2 proc <#log2,float64>`_ + ## * `exp proc <#exp,float64>`_ ## ## .. code-block:: nim ## echo ln(exp(4.0)) ## 4.0 + ## echo ln(1.0)) ## 0.0 + ## echo ln(0.0) ## -inf + ## echo ln(-7.0) ## nan else: # JS proc sqrt*(x: float32): float32 {.importc: "Math.sqrt", nodecl.} proc sqrt*(x: float64): float64 {.importc: "Math.sqrt", nodecl.} @@ -193,8 +306,18 @@ else: # JS proc log*[T: SomeFloat](x, base: T): T = ## Computes the logarithm of ``x`` to base ``base``. ## + ## See also: + ## * `ln proc <#ln,float64>`_ + ## * `log10 proc <#log10,float64>`_ + ## * `log2 proc <#log2,float64>`_ + ## * `exp proc <#exp,float64>`_ + ## ## .. code-block:: nim - ## echo log(9.0, 3.0) ## 2.0 + ## echo log(9.0, 3.0) ## 2.0 + ## echo log(32.0, 2.0) ## 5.0 + ## echo log(0.0, 2.0) ## -inf + ## echo log(-7.0, 4.0) ## nan + ## echo log(8.0, -2.0) ## nan ln(x) / ln(base) when not defined(JS): # C @@ -202,77 +325,164 @@ when not defined(JS): # C proc log10*(x: float64): float64 {.importc: "log10", header: "<math.h>".} ## Computes the common logarithm (base 10) of ``x``. ## + ## See also: + ## * `ln proc <#ln,float64>`_ + ## * `log proc <#log,T,T>`_ + ## * `log2 proc <#log2,float64>`_ + ## * `exp proc <#exp,float64>`_ + ## ## .. code-block:: nim - ## echo log10(100.0) ## 2.0 + ## echo log10(100.0) ## 2.0 + ## echo log10(0.0) ## nan + ## echo log10(-100.0) ## -inf proc exp*(x: float32): float32 {.importc: "expf", header: "<math.h>".} proc exp*(x: float64): float64 {.importc: "exp", header: "<math.h>".} - ## Computes the exponential function of ``x`` (pow(E, x)). + ## Computes the exponential function of ``x`` (e^x). + ## + ## See also: + ## * `ln proc <#ln,float64>`_ + ## * `log proc <#log,T,T>`_ + ## * `log10 proc <#log10,float64>`_ + ## * `log2 proc <#log2,float64>`_ ## ## .. code-block:: nim - ## echo exp(1.0) ## 2.718281828459045 + ## echo exp(1.0) ## 2.718281828459045 ## echo ln(exp(4.0)) ## 4.0 + ## echo exp(0.0) ## 1.0 + ## echo exp(-1.0) ## 0.3678794411714423 proc sin*(x: float32): float32 {.importc: "sinf", header: "<math.h>".} proc sin*(x: float64): float64 {.importc: "sin", header: "<math.h>".} ## Computes the sine of ``x``. ## + ## See also: + ## * `cos proc <#cos,float64>`_ + ## * `tan proc <#tan,float64>`_ + ## * `arcsin proc <#arcsin,float64>`_ + ## * `sinh proc <#sinh,float64>`_ + ## ## .. code-block:: nim - ## echo sin(PI / 6) ## 0.4999999999999999 + ## echo sin(PI / 6) ## 0.4999999999999999 ## echo sin(degToRad(90.0)) ## 1.0 proc cos*(x: float32): float32 {.importc: "cosf", header: "<math.h>".} proc cos*(x: float64): float64 {.importc: "cos", header: "<math.h>".} ## Computes the cosine of ``x``. ## + ## See also: + ## * `sin proc <#sin,float64>`_ + ## * `tan proc <#tan,float64>`_ + ## * `arccos proc <#arccos,float64>`_ + ## * `cosh proc <#cosh,float64>`_ + ## ## .. code-block:: nim - ## echo cos(2 * PI) ## 1.0 + ## echo cos(2 * PI) ## 1.0 ## echo cos(degToRad(60.0)) ## 0.5000000000000001 proc tan*(x: float32): float32 {.importc: "tanf", header: "<math.h>".} proc tan*(x: float64): float64 {.importc: "tan", header: "<math.h>".} ## Computes the tangent of ``x``. ## + ## See also: + ## * `sin proc <#sin,float64>`_ + ## * `cos proc <#cos,float64>`_ + ## * `arctan proc <#arctan,float64>`_ + ## * `tanh proc <#tanh,float64>`_ + ## ## .. code-block:: nim ## echo tan(degToRad(45.0)) ## 0.9999999999999999 - ## echo tan(PI / 4) ## 0.9999999999999999 + ## echo tan(PI / 4) ## 0.9999999999999999 proc sinh*(x: float32): float32 {.importc: "sinhf", header: "<math.h>".} proc sinh*(x: float64): float64 {.importc: "sinh", header: "<math.h>".} ## Computes the `hyperbolic sine <https://en.wikipedia.org/wiki/Hyperbolic_function#Definitions>`_ of ``x``. ## + ## See also: + ## * `cosh proc <#cosh,float64>`_ + ## * `tanh proc <#tanh,float64>`_ + ## * `arcsinh proc <#arcsinh,float64>`_ + ## * `sin proc <#sin,float64>`_ + ## ## .. code-block:: nim - ## echo sinh(1.0) ## 1.175201193643801 + ## echo sinh(0.0) ## 0.0 + ## echo sinh(1.0) ## 1.175201193643801 + ## echo sinh(degToRad(90.0)) ## 2.301298902307295 proc cosh*(x: float32): float32 {.importc: "coshf", header: "<math.h>".} proc cosh*(x: float64): float64 {.importc: "cosh", header: "<math.h>".} ## Computes the `hyperbolic cosine <https://en.wikipedia.org/wiki/Hyperbolic_function#Definitions>`_ of ``x``. ## + ## See also: + ## * `sinh proc <#sinh,float64>`_ + ## * `tanh proc <#tanh,float64>`_ + ## * `arccosh proc <#arccosh,float64>`_ + ## * `cos proc <#cos,float64>`_ + ## ## .. code-block:: nim - ## echo cosh(1.0) ## 1.543080634815244 + ## echo cosh(0.0) ## 1.0 + ## echo cosh(1.0) ## 1.543080634815244 + ## echo cosh(degToRad(90.0)) ## 2.509178478658057 proc tanh*(x: float32): float32 {.importc: "tanhf", header: "<math.h>".} proc tanh*(x: float64): float64 {.importc: "tanh", header: "<math.h>".} ## Computes the `hyperbolic tangent <https://en.wikipedia.org/wiki/Hyperbolic_function#Definitions>`_ of ``x``. ## + ## See also: + ## * `sinh proc <#sinh,float64>`_ + ## * `cosh proc <#cosh,float64>`_ + ## * `arctanh proc <#arctanh,float64>`_ + ## * `tan proc <#tan,float64>`_ + ## ## .. code-block:: nim - ## echo tanh(1.0) ## 0.7615941559557649 + ## echo tanh(0.0) ## 0.0 + ## echo tanh(1.0) ## 0.7615941559557649 + ## echo tanh(degToRad(90.0)) ## 0.9171523356672744 proc arccos*(x: float32): float32 {.importc: "acosf", header: "<math.h>".} proc arccos*(x: float64): float64 {.importc: "acos", header: "<math.h>".} ## Computes the arc cosine of ``x``. ## + ## See also: + ## * `arcsin proc <#arcsin,float64>`_ + ## * `arctan proc <#arctan,float64>`_ + ## * `arctan2 proc <#arctan2,float64,float64>`_ + ## * `cos proc <#cos,float64>`_ + ## ## .. code-block:: nim - ## echo arccos(1.0) ## 0.0 + ## echo radToDeg(arccos(0.0)) ## 90.0 + ## echo radToDeg(arccos(1.0)) ## 0.0 proc arcsin*(x: float32): float32 {.importc: "asinf", header: "<math.h>".} proc arcsin*(x: float64): float64 {.importc: "asin", header: "<math.h>".} ## Computes the arc sine of ``x``. + ## + ## See also: + ## * `arccos proc <#arccos,float64>`_ + ## * `arctan proc <#arctan,float64>`_ + ## * `arctan2 proc <#arctan2,float64,float64>`_ + ## * `sin proc <#sin,float64>`_ + ## + ## .. code-block:: nim + ## echo radToDeg(arcsin(0.0)) ## 0.0 + ## echo radToDeg(arcsin(1.0)) ## 90.0 proc arctan*(x: float32): float32 {.importc: "atanf", header: "<math.h>".} proc arctan*(x: float64): float64 {.importc: "atan", header: "<math.h>".} ## Calculate the arc tangent of ``x``. ## + ## See also: + ## * `arcsin proc <#arcsin,float64>`_ + ## * `arccos proc <#arccos,float64>`_ + ## * `arctan2 proc <#arctan2,float64,float64>`_ + ## * `tan proc <#tan,float64>`_ + ## ## .. code-block:: nim ## echo arctan(1.0) ## 0.7853981633974483 ## echo radToDeg(arctan(1.0)) ## 45.0 proc arctan2*(y, x: float32): float32 {.importc: "atan2f", header: "<math.h>".} proc arctan2*(y, x: float64): float64 {.importc: "atan2", header: "<math.h>".} ## Calculate the arc tangent of ``y`` / ``x``. - ## `arctan2` returns the arc tangent of ``y`` / ``x``; it produces correct - ## results even when the resulting angle is near pi/2 or -pi/2 - ## (``x`` near 0). + ## + ## It produces correct results even when the resulting angle is near + ## pi/2 or -pi/2 (``x`` near 0). + ## + ## See also: + ## * `arcsin proc <#arcsin,float64>`_ + ## * `arccos proc <#arccos,float64>`_ + ## * `arctan proc <#arctan,float64>`_ + ## * `tan proc <#tan,float64>`_ ## ## .. code-block:: nim ## echo arctan2(1.0, 0.0) ## 1.570796326794897 @@ -313,18 +523,18 @@ else: # JS proc arctanh*[T: float32|float64](x: T): T {.importc: "Math.atanh", nodecl.} proc cot*[T: float32|float64](x: T): T = 1.0 / tan(x) - ## Computes the cotangent of ``x``. + ## Computes the cotangent of ``x`` (1 / tan(x)). proc sec*[T: float32|float64](x: T): T = 1.0 / cos(x) - ## Computes the secant of ``x``. + ## Computes the secant of ``x`` (1 / cos(x)). proc csc*[T: float32|float64](x: T): T = 1.0 / sin(x) - ## Computes the cosecant of ``x``. + ## Computes the cosecant of ``x`` (1 / sin(x)). proc coth*[T: float32|float64](x: T): T = 1.0 / tanh(x) - ## Computes the hyperbolic cotangent of ``x``. + ## Computes the hyperbolic cotangent of ``x`` (1 / tanh(x)). proc sech*[T: float32|float64](x: T): T = 1.0 / cosh(x) - ## Computes the hyperbolic secant of ``x``. + ## Computes the hyperbolic secant of ``x`` (1 / cosh(x)). proc csch*[T: float32|float64](x: T): T = 1.0 / sinh(x) - ## Computes the hyperbolic cosecant of ``x``. + ## Computes the hyperbolic cosecant of ``x`` (1 / sinh(x)). proc arccot*[T: float32|float64](x: T): T = arctan(1.0 / x) ## Computes the inverse cotangent of ``x``. @@ -352,11 +562,17 @@ when not defined(JS): # C ## echo hypot(4.0, 3.0) ## 5.0 proc pow*(x, y: float32): float32 {.importc: "powf", header: "<math.h>".} proc pow*(x, y: float64): float64 {.importc: "pow", header: "<math.h>".} - ## computes x to power raised of y. + ## Computes x to power raised of y. + ## + ## To compute power between integers (e.g. 2^6), use `^ proc<#^,T,Natural>`_. ## - ## To compute power between integers, use ``^`` e.g. 2 ^ 6 + ## See also: + ## * `^ proc<#^,T,Natural>`_ + ## * `sqrt proc <#sqrt,float64>`_ + ## * `cbrt proc <#cbrt,float64>`_ ## ## .. code-block:: nim + ## echo pow(100, 1.5) ## 1000.0 ## echo pow(16.0, 0.5) ## 4.0 # TODO: add C89 version on windows @@ -370,6 +586,15 @@ when not defined(JS): # C proc gamma*(x: float32): float32 {.importc: "tgammaf", header: "<math.h>".} proc gamma*(x: float64): float64 {.importc: "tgamma", header: "<math.h>".} ## Computes the the `gamma function <https://en.wikipedia.org/wiki/Gamma_function>`_ for ``x``. + ## + ## See also: + ## * `lgamma proc <#lgamma,float64>`_ for a natural log of gamma function + ## + ## .. code-block:: Nim + ## echo gamma(1.0) # 1.0 + ## echo gamma(4.0) # 6.0 + ## echo gamma(11.0) # 3628800.0 + ## echo gamma(-1.0) # nan proc tgamma*(x: float32): float32 {.deprecated: "use gamma instead", importc: "tgammaf", header: "<math.h>".} proc tgamma*(x: float64): float64 @@ -379,19 +604,43 @@ when not defined(JS): # C proc lgamma*(x: float32): float32 {.importc: "lgammaf", header: "<math.h>".} proc lgamma*(x: float64): float64 {.importc: "lgamma", header: "<math.h>".} ## Computes the natural log of the gamma function for ``x``. + ## + ## See also: + ## * `gamma proc <#gamma,float64>`_ for gamma function + ## + ## .. code-block:: Nim + ## echo lgamma(1.0) # 1.0 + ## echo lgamma(4.0) # 1.791759469228055 + ## echo lgamma(11.0) # 15.10441257307552 + ## echo lgamma(-1.0) # inf proc floor*(x: float32): float32 {.importc: "floorf", header: "<math.h>".} proc floor*(x: float64): float64 {.importc: "floor", header: "<math.h>".} ## Computes the floor function (i.e., the largest integer not greater than ``x``). ## + ## See also: + ## * `ceil proc <#ceil,float64>`_ + ## * `round proc <#round,float64>`_ + ## * `trunc proc <#trunc,float64>`_ + ## ## .. code-block:: nim + ## echo floor(2.1) ## 2.0 + ## echo floor(2.9) ## 2.0 ## echo floor(-3.5) ## -4.0 proc ceil*(x: float32): float32 {.importc: "ceilf", header: "<math.h>".} proc ceil*(x: float64): float64 {.importc: "ceil", header: "<math.h>".} - ## Computes the ceiling function (i.e., the smallest integer not less than ``x``). + ## Computes the ceiling function (i.e., the smallest integer not smaller + ## than ``x``). + ## + ## See also: + ## * `floor proc <#floor,float64>`_ + ## * `round proc <#round,float64>`_ + ## * `trunc proc <#trunc,float64>`_ ## ## .. code-block:: nim + ## echo ceil(2.1) ## 3.0 + ## echo ceil(2.9) ## 3.0 ## echo ceil(-2.1) ## -2.0 when windowsCC89: @@ -452,26 +701,50 @@ when not defined(JS): # C else: proc round*(x: float32): float32 {.importc: "roundf", header: "<math.h>".} proc round*(x: float64): float64 {.importc: "round", header: "<math.h>".} - ## Rounds a float to zero decimal places. Used internally by the round - ## function when the specified number of places is 0. + ## Rounds a float to zero decimal places. + ## + ## Used internally by the `round proc <#round,T,int>`_ + ## when the specified number of places is 0. + ## + ## See also: + ## * `round proc <#round,T,int>`_ for rounding to the specific + ## number of decimal places + ## * `floor proc <#floor,float64>`_ + ## * `ceil proc <#ceil,float64>`_ + ## * `trunc proc <#trunc,float64>`_ + ## + ## .. code-block:: nim + ## echo round(3.4) ## 3.0 + ## echo round(3.5) ## 4.0 + ## echo round(4.5) ## 5.0 proc trunc*(x: float32): float32 {.importc: "truncf", header: "<math.h>".} proc trunc*(x: float64): float64 {.importc: "trunc", header: "<math.h>".} ## Truncates ``x`` to the decimal point. ## + ## See also: + ## * `floor proc <#floor,float64>`_ + ## * `ceil proc <#ceil,float64>`_ + ## * `round proc <#round,float64>`_ + ## ## .. code-block:: nim ## echo trunc(PI) # 3.0 ## echo trunc(-1.85) # -1.0 proc fmod*(x, y: float32): float32 {.deprecated: "use mod instead", importc: "fmodf", header: "<math.h>".} proc fmod*(x, y: float64): float64 {.deprecated: "use mod instead", importc: "fmod", header: "<math.h>".} + ## **Deprecated since version 0.19.0**: Use the `mod proc + ## <#mod,float64,float64>`_ instead. + ## ## Computes the remainder of ``x`` divided by ``y``. - ## **Deprecated since version 0.19.0**: Use the ``mod`` operator instead. proc `mod`*(x, y: float32): float32 {.importc: "fmodf", header: "<math.h>".} proc `mod`*(x, y: float64): float64 {.importc: "fmod", header: "<math.h>".} ## Computes the modulo operation for float values (the remainder of ``x`` divided by ``y``). ## + ## See also: + ## * `floorMod proc <#floorMod,T,T>`_ for Python-like (% operator) behavior + ## ## .. code-block:: nim ## ( 6.5 mod 2.5) == 1.5 ## (-6.5 mod 2.5) == -1.5 @@ -502,16 +775,22 @@ else: # JS ## (-6.5 mod -2.5) == -1.5 proc round*[T: float32|float64](x: T, places: int): T {.deprecated: "use format instead".} = + ## **Deprecated:** use `strformat module <strformat.html>`_ + ## ## Decimal rounding on a binary floating point number. ## ## This function is NOT reliable. Floating point numbers cannot hold - ## non integer decimals precisely. If ``places`` is 0 (or omitted), + ## non integer decimals precisely. If ``places`` is 0 (or omitted), ## round to the nearest integral value following normal mathematical - ## rounding rules (e.g. ``round(54.5) -> 55.0``). If ``places`` is + ## rounding rules (e.g. ``round(54.5) -> 55.0``). If ``places`` is ## greater than 0, round to the given number of decimal places, - ## e.g. ``round(54.346, 2) -> 54.350000000000001421...``. If ``places`` is negative, round - ## to the left of the decimal place, e.g. ``round(537.345, -1) -> + ## e.g. ``round(54.346, 2) -> 54.350000000000001421…``. If ``places`` is negative, round + ## to the left of the decimal place, e.g. ``round(537.345, -1) -> ## 540.0`` + ## + ## .. code-block:: Nim + ## echo round(PI, 2) ## 3.14 + ## echo round(PI, 4) ## 3.1416 if places == 0: result = round(x) else: @@ -520,9 +799,14 @@ proc round*[T: float32|float64](x: T, places: int): T {.deprecated: "use format proc floorDiv*[T: SomeInteger](x, y: T): T = ## Floor division is conceptually defined as ``floor(x / y)``. - ## This is different from the ``div`` operator, which is defined - ## as ``trunc(x / y)``. That is, ``div`` rounds towards ``0`` and ``floorDiv`` - ## rounds down. + ## + ## This is different from the `system.div <system.html#div,int,int>`_ + ## operator, which is defined as ``trunc(x / y)``. + ## That is, ``div`` rounds towards ``0`` and ``floorDiv`` rounds down. + ## + ## See also: + ## * `system.div proc <system.html#div,int,int>`_ for integer division + ## * `floorMod proc <#floorMod,T,T>`_ for Python-like (% operator) behavior ## ## .. code-block:: nim ## echo floorDiv( 13, 3) # 4 @@ -535,8 +819,13 @@ proc floorDiv*[T: SomeInteger](x, y: T): T = proc floorMod*[T: SomeNumber](x, y: T): T = ## Floor modulus is conceptually defined as ``x - (floorDiv(x, y) * y)``. + ## ## This proc behaves the same as the ``%`` operator in Python. ## + ## See also: + ## * `mod proc <#mod,float64,float64>`_ + ## * `floorDiv proc <#floorDiv,T,T>`_ + ## ## .. code-block:: nim ## echo floorMod( 13, 3) # 1 ## echo floorMod(-13, 3) # 2 @@ -552,6 +841,7 @@ when not defined(JS): importc: "frexp", header: "<math.h>".} proc frexp*[T, U](x: T, exponent: var U): T = ## Split a number into mantissa and exponent. + ## ## ``frexp`` calculates the mantissa m (a float greater than or equal to 0.5 ## and less than 1) and the integer value n such that ``x`` (the original ## float value) equals ``m * 2**n``. frexp stores n in `exponent` and returns @@ -584,7 +874,19 @@ when not defined(JS): else: proc log2*(x: float32): float32 {.importc: "log2f", header: "<math.h>".} proc log2*(x: float64): float64 {.importc: "log2", header: "<math.h>".} - ## Computes the binary logarithm (base 2) of ``x`` + ## Computes the binary logarithm (base 2) of ``x``. + ## + ## See also: + ## * `log proc <#log,T,T>`_ + ## * `log10 proc <#log10,float64>`_ + ## * `ln proc <#ln,float64>`_ + ## * `exp proc <#exp,float64>`_ + ## + ## .. code-block:: Nim + ## echo log2(8.0) # 3.0 + ## echo log2(1.0) # 0.0 + ## echo log2(0.0) # -inf + ## echo log2(-2.0) # nan else: proc frexp*[T: float32|float64](x: T, exponent: var int): T = @@ -613,7 +915,8 @@ proc splitDecimal*[T: float32|float64](x: T): tuple[intpart: T, floatpart: T] = ## function in C. ## ## .. code-block:: nim - ## echo splitDecimal(5.25) # (intpart: 5.0, floatpart: 0.25) + ## echo splitDecimal(5.25) # (intpart: 5.0, floatpart: 0.25) + ## echo splitDecimal(-2.73) # (intpart: -2.0, floatpart: -0.73) var absolute: T absolute = abs(x) @@ -626,26 +929,36 @@ proc splitDecimal*[T: float32|float64](x: T): tuple[intpart: T, floatpart: T] = {.pop.} proc degToRad*[T: float32|float64](d: T): T {.inline.} = - ## Convert from degrees to radians + ## Convert from degrees to radians. + ## + ## See also: + ## * `radToDeg proc <#radToDeg,T>`_ ## ## .. code-block:: nim ## echo degToRad(180.0) # 3.141592653589793 result = T(d) * RadPerDeg proc radToDeg*[T: float32|float64](d: T): T {.inline.} = - ## Convert from radians to degrees - + ## Convert from radians to degrees. + ## + ## See also: + ## * `degToRad proc <#degToRad,T>`_ + ## ## .. code-block:: nim ## echo degToRad(2 * PI) # 360.0 result = T(d) / RadPerDeg proc sgn*[T: SomeNumber](x: T): int {.inline.} = - ## Sign function. Returns -1 for negative numbers and ``NegInf``, 1 for - ## positive numbers and ``Inf``, and 0 for positive zero, negative zero and - ## ``NaN``. + ## Sign function. + ## + ## Returns: + ## * `-1` for negative numbers and ``NegInf``, + ## * `1` for positive numbers and ``Inf``, + ## * `0` for positive zero, negative zero and ``NaN`` ## ## .. code-block:: nim - ## echo sgn(-5) # 1 + ## echo sgn(5) # 1 + ## echo sgn(0) # 0 ## echo sgn(-4.1) # -1 ord(T(0) < x) - ord(x < T(0)) @@ -653,11 +966,20 @@ proc sgn*[T: SomeNumber](x: T): int {.inline.} = {.pop.} proc `^`*[T](x: T, y: Natural): T = - ## Computes ``x`` to the power ``y``. ``x`` must be non-negative, use - ## `pow <#pow,float,float>`_ for negative exponents. + ## Computes ``x`` to the power ``y``. + ## + ## Exponent ``y`` must be non-negative, use + ## `pow proc <#pow,float64,float64>`_ for negative exponents. + ## + ## See also: + ## * `pow proc <#pow,float64,float64>`_ for negative exponent or + ## floats + ## * `sqrt proc <#sqrt,float64>`_ + ## * `cbrt proc <#cbrt,float64>`_ ## ## .. code-block:: nim - ## echo 2 ^ 3 # 8 + ## echo 2^3 # 8 + ## echo -2^3 # -8 when compiles(y >= T(0)): assert y >= T(0) else: @@ -675,9 +997,16 @@ proc `^`*[T](x: T, y: Natural): T = proc gcd*[T](x, y: T): T = ## Computes the greatest common (positive) divisor of ``x`` and ``y``. + ## ## Note that for floats, the result cannot always be interpreted as ## "greatest decimal `z` such that ``z*N == x and z*M == y`` ## where N and M are positive integers." + ## + ## See also: + ## * `gcd proc <#gcd,SomeInteger,SomeInteger>`_ for integer version + ## * `lcm proc <#lcm,T,T>`_ + runnableExamples: + doAssert gcd(13.5, 9.0) == 4.5 var (x, y) = (x, y) while y != 0: x = x mod y @@ -685,11 +1014,15 @@ proc gcd*[T](x, y: T): T = abs x proc gcd*(x, y: SomeInteger): SomeInteger = - ## Computes the greatest common (positive) divisor of ``x`` and ``y``. - ## Using binary GCD (aka Stein's) algorithm. + ## Computes the greatest common (positive) divisor of ``x`` and ``y``, + ## using binary GCD (aka Stein's) algorithm. ## - ## .. code-block:: nim - ## echo gcd(24, 30) # 6 + ## See also: + ## * `gcd proc <#gcd,T,T>`_ for floats version + ## * `lcm proc <#lcm,T,T>`_ + runnableExamples: + doAssert gcd(12, 8) == 4 + doAssert gcd(17, 63) == 1 when x is SomeSignedInt: var x = abs(x) else: @@ -716,10 +1049,15 @@ proc gcd*(x, y: SomeInteger): SomeInteger = proc lcm*[T](x, y: T): T = ## Computes the least common multiple of ``x`` and ``y``. ## - ## .. code-block:: nim - ## echo lcm(24, 30) # 120 + ## See also: + ## * `gcd proc <#gcd,T,T>`_ + runnableExamples: + doAssert lcm(24, 30) == 120 + doAssert lcm(13, 39) == 39 x div gcd(x, y) * y + + when isMainModule and not defined(JS) and not windowsCC89: # Check for no side effect annotation proc mySqrt(num: float): float {.noSideEffect.} = diff --git a/lib/pure/memfiles.nim b/lib/pure/memfiles.nim index 810223d72..d1a006eee 100644 --- a/lib/pure/memfiles.nim +++ b/lib/pure/memfiles.nim @@ -42,9 +42,10 @@ type wasOpened*: bool ## **Caution**: Windows specific public field. else: handle*: cint ## **Caution**: Posix specific public field. + flags: cint ## **Caution**: Platform specific private field. proc mapMem*(m: var MemFile, mode: FileMode = fmRead, - mappedSize = -1, offset = 0): pointer = + mappedSize = -1, offset = 0, mapFlags = cint(-1)): pointer = ## returns a pointer to a mapped portion of MemFile `m` ## ## ``mappedSize`` of ``-1`` maps to the whole file, and @@ -65,11 +66,17 @@ proc mapMem*(m: var MemFile, mode: FileMode = fmRead, raiseOSError(osLastError()) else: assert mappedSize > 0 + + m.flags = if mapFlags == cint(-1): MAP_SHARED else: mapFlags + #Ensure exactly one of MAP_PRIVATE cr MAP_SHARED is set + if int(m.flags and MAP_PRIVATE) == 0: + m.flags = m.flags or MAP_SHARED + result = mmap( nil, mappedSize, if readonly: PROT_READ else: PROT_READ or PROT_WRITE, - if readonly: (MAP_PRIVATE or MAP_POPULATE) else: (MAP_SHARED or MAP_POPULATE), + m.flags, m.handle, offset) if result == cast[pointer](MAP_FAILED): raiseOSError(osLastError()) @@ -90,7 +97,7 @@ proc unmapMem*(f: var MemFile, p: pointer, size: int) = proc open*(filename: string, mode: FileMode = fmRead, mappedSize = -1, offset = 0, newFileSize = -1, - allowRemap = false): MemFile = + allowRemap = false, mapFlags = cint(-1)): MemFile = ## opens a memory mapped file. If this fails, ``OSError`` is raised. ## ## ``newFileSize`` can only be set if the file does not exist and is opened @@ -105,6 +112,10 @@ proc open*(filename: string, mode: FileMode = fmRead, ## ``allowRemap`` only needs to be true if you want to call ``mapMem`` on ## the resulting MemFile; else file handles are not kept open. ## + ## ``mapFlags`` allows callers to override default choices for memory mapping + ## flags with a bitwise mask of a variety of likely platform-specific flags + ## which may be ignored or even cause `open` to fail if misspecified. + ## ## Example: ## ## .. code-block:: nim @@ -245,11 +256,16 @@ proc open*(filename: string, mode: FileMode = fmRead, else: fail(osLastError(), "error getting file size") + result.flags = if mapFlags == cint(-1): MAP_SHARED else: mapFlags + #Ensure exactly one of MAP_PRIVATE cr MAP_SHARED is set + if int(result.flags and MAP_PRIVATE) == 0: + result.flags = result.flags or MAP_SHARED + result.mem = mmap( nil, result.size, if readonly: PROT_READ else: PROT_READ or PROT_WRITE, - if readonly: (MAP_PRIVATE or MAP_POPULATE) else: (MAP_SHARED or MAP_POPULATE), + result.flags, result.handle, offset) @@ -305,7 +321,7 @@ when defined(posix) or defined(nimdoc): if munmap(f.mem, f.size) != 0: raiseOSError(osLastError()) let newAddr = mmap(nil, newFileSize, PROT_READ or PROT_WRITE, - MAP_SHARED or MAP_POPULATE, f.handle, 0) + f.flags, f.handle, 0) if newAddr == cast[pointer](MAP_FAILED): raiseOSError(osLastError()) f.mem = newAddr diff --git a/lib/pure/nativesockets.nim b/lib/pure/nativesockets.nim index f98f9a444..6d4df1c5d 100644 --- a/lib/pure/nativesockets.nim +++ b/lib/pure/nativesockets.nim @@ -71,6 +71,7 @@ type IPPROTO_IPV6, ## Internet Protocol Version 6. Unsupported on Windows. IPPROTO_RAW, ## Raw IP Packets Protocol. Unsupported on Windows. IPPROTO_ICMP ## Control message protocol. Unsupported on Windows. + IPPROTO_ICMPV6 ## Control message protocol for IPv6. Unsupported on Windows. Servent* = object ## information about a service name*: string @@ -154,6 +155,7 @@ when not useWinVersion: of IPPROTO_IPV6: result = posix.IPPROTO_IPV6 of IPPROTO_RAW: result = posix.IPPROTO_RAW of IPPROTO_ICMP: result = posix.IPPROTO_ICMP + of IPPROTO_ICMPV6: result = posix.IPPROTO_ICMPV6 else: proc toInt(domain: Domain): cint = @@ -179,7 +181,7 @@ proc toSockType*(protocol: Protocol): SockType = SOCK_STREAM of IPPROTO_UDP: SOCK_DGRAM - of IPPROTO_IP, IPPROTO_IPV6, IPPROTO_RAW, IPPROTO_ICMP: + of IPPROTO_IP, IPPROTO_IPV6, IPPROTO_RAW, IPPROTO_ICMP, IPPROTO_ICMPV6: SOCK_RAW proc createNativeSocket*(domain: Domain = AF_INET, @@ -255,17 +257,14 @@ proc getAddrInfo*(address: string, port: Port, domain: Domain = AF_INET, when not defined(freebsd) and not defined(openbsd) and not defined(netbsd) and not defined(android) and not defined(haiku): if domain == AF_INET6: hints.ai_flags = AI_V4MAPPED - var gaiResult = getaddrinfo(address, $port, addr(hints), result) + let socket_port = if sockType == SOCK_RAW: "" else: $port + var gaiResult = getaddrinfo(address, socket_port, addr(hints), result) if gaiResult != 0'i32: when useWinVersion: raiseOSError(osLastError()) else: raiseOSError(osLastError(), $gai_strerror(gaiResult)) -proc dealloc*(ai: ptr AddrInfo) {.deprecated.} = - ## Deprecated since 0.16.2. Use ``freeAddrInfo`` instead. - freeaddrinfo(ai) - proc ntohl*(x: uint32): uint32 = ## Converts 32-bit unsigned integers from network to host byte order. ## On machines where the host byte order is the same as network byte order, @@ -276,15 +275,6 @@ proc ntohl*(x: uint32): uint32 = (x shl 8'u32 and 0xff0000'u32) or (x shl 24'u32) -template ntohl*(x: int32): untyped {.deprecated.} = - ## Converts 32-bit integers from network to host byte order. - ## On machines where the host byte order is the same as network byte order, - ## this is a no-op; otherwise, it performs a 4-byte swap operation. - ## **Warning**: This template is deprecated since 0.14.0, IPv4 - ## addresses are now treated as unsigned integers. Please use the unsigned - ## version of this template. - cast[int32](nativesockets.ntohl(cast[uint32](x))) - proc ntohs*(x: uint16): uint16 = ## Converts 16-bit unsigned integers from network to host byte order. On ## machines where the host byte order is the same as network byte order, @@ -292,39 +282,12 @@ proc ntohs*(x: uint16): uint16 = when cpuEndian == bigEndian: result = x else: result = (x shr 8'u16) or (x shl 8'u16) -template ntohs*(x: int16): untyped {.deprecated.} = - ## Converts 16-bit integers from network to host byte order. On - ## machines where the host byte order is the same as network byte order, - ## this is a no-op; otherwise, it performs a 2-byte swap operation. - ## **Warning**: This template is deprecated since 0.14.0, where port - ## numbers became unsigned integers. Please use the unsigned version of - ## this template. - cast[int16](nativesockets.ntohs(cast[uint16](x))) - -template htonl*(x: int32): untyped {.deprecated.} = - ## Converts 32-bit integers from host to network byte order. On machines - ## where the host byte order is the same as network byte order, this is - ## a no-op; otherwise, it performs a 4-byte swap operation. - ## **Warning**: This template is deprecated since 0.14.0, IPv4 - ## addresses are now treated as unsigned integers. Please use the unsigned - ## version of this template. - nativesockets.ntohl(x) - template htonl*(x: uint32): untyped = ## Converts 32-bit unsigned integers from host to network byte order. On ## machines where the host byte order is the same as network byte order, ## this is a no-op; otherwise, it performs a 4-byte swap operation. nativesockets.ntohl(x) -template htons*(x: int16): untyped {.deprecated.} = - ## Converts 16-bit integers from host to network byte order. - ## On machines where the host byte order is the same as network byte - ## order, this is a no-op; otherwise, it performs a 2-byte swap operation. - ## **Warning**: This template is deprecated since 0.14.0, where port - ## numbers became unsigned integers. Please use the unsigned version of - ## this template. - nativesockets.ntohs(x) - template htons*(x: uint16): untyped = ## Converts 16-bit unsigned integers from host to network byte order. ## On machines where the host byte order is the same as network byte @@ -646,29 +609,6 @@ proc pruneSocketSet(s: var seq[SocketHandle], fd: var TFdSet) = inc(i) setLen(s, L) -proc select*(readfds: var seq[SocketHandle], timeout = 500): int {.deprecated: "use selectRead instead".} = - ## When a socket in ``readfds`` is ready to be read from then a non-zero - ## value will be returned specifying the count of the sockets which can be - ## read from. The sockets which can be read from will also be removed - ## from ``readfds``. - ## - ## ``timeout`` is specified in milliseconds and ``-1`` can be specified for - ## an unlimited time. - ## **Warning:** This is deprecated since version 0.16.2. - ## Use the ``selectRead`` procedure instead. - var tv {.noInit.}: Timeval = timeValFromMilliseconds(timeout) - - var rd: TFdSet - var m = 0 - createFdSet((rd), readfds, m) - - if timeout != -1: - result = int(select(cint(m+1), addr(rd), nil, nil, addr(tv))) - else: - result = int(select(cint(m+1), addr(rd), nil, nil, nil)) - - pruneSocketSet(readfds, (rd)) - proc selectRead*(readfds: var seq[SocketHandle], timeout = 500): int = ## When a socket in ``readfds`` is ready to be read from then a non-zero ## value will be returned specifying the count of the sockets which can be diff --git a/lib/pure/net.nim b/lib/pure/net.nim index 840a81f17..a5bdf3ce6 100644 --- a/lib/pure/net.nim +++ b/lib/pure/net.nim @@ -233,7 +233,7 @@ proc parseIPv4Address(addressStr: string): IpAddress = var byteCount = 0 currentByte:uint16 = 0 - seperatorValid = false + separatorValid = false result.family = IpAddressFamily.IPv4 @@ -244,20 +244,20 @@ proc parseIPv4Address(addressStr: string): IpAddress = if currentByte > 255'u16: raise newException(ValueError, "Invalid IP Address. Value is out of range") - seperatorValid = true + separatorValid = true elif addressStr[i] == '.': # IPv4 address separator - if not seperatorValid or byteCount >= 3: + if not separatorValid or byteCount >= 3: raise newException(ValueError, "Invalid IP Address. The address consists of too many groups") result.address_v4[byteCount] = cast[uint8](currentByte) currentByte = 0 byteCount.inc - seperatorValid = false + separatorValid = false else: raise newException(ValueError, "Invalid IP Address. Address contains an invalid character") - if byteCount != 3 or not seperatorValid: + if byteCount != 3 or not separatorValid: raise newException(ValueError, "Invalid IP Address") result.address_v4[byteCount] = cast[uint8](currentByte) @@ -272,7 +272,7 @@ proc parseIPv6Address(addressStr: string): IpAddress = groupCount = 0 currentGroupStart = 0 currentShort:uint32 = 0 - seperatorValid = true + separatorValid = true dualColonGroup = -1 lastWasColon = false v4StartPos = -1 @@ -280,15 +280,15 @@ proc parseIPv6Address(addressStr: string): IpAddress = for i,c in addressStr: if c == ':': - if not seperatorValid: + if not separatorValid: raise newException(ValueError, - "Invalid IP Address. Address contains an invalid seperator") + "Invalid IP Address. Address contains an invalid separator") if lastWasColon: if dualColonGroup != -1: raise newException(ValueError, - "Invalid IP Address. Address contains more than one \"::\" seperator") + "Invalid IP Address. Address contains more than one \"::\" separator") dualColonGroup = groupCount - seperatorValid = false + separatorValid = false elif i != 0 and i != high(addressStr): if groupCount >= 8: raise newException(ValueError, @@ -297,7 +297,7 @@ proc parseIPv6Address(addressStr: string): IpAddress = result.address_v6[groupCount*2+1] = cast[uint8](currentShort and 0xFF) currentShort = 0 groupCount.inc() - if dualColonGroup != -1: seperatorValid = false + if dualColonGroup != -1: separatorValid = false elif i == 0: # only valid if address starts with :: if addressStr[1] != ':': raise newException(ValueError, @@ -309,11 +309,11 @@ proc parseIPv6Address(addressStr: string): IpAddress = lastWasColon = true currentGroupStart = i + 1 elif c == '.': # Switch to parse IPv4 mode - if i < 3 or not seperatorValid or groupCount >= 7: + if i < 3 or not separatorValid or groupCount >= 7: raise newException(ValueError, "Invalid IP Address") v4StartPos = currentGroupStart currentShort = 0 - seperatorValid = false + separatorValid = false break elif c in strutils.HexDigits: if c in strutils.Digits: # Normal digit @@ -326,14 +326,14 @@ proc parseIPv6Address(addressStr: string): IpAddress = raise newException(ValueError, "Invalid IP Address. Value is out of range") lastWasColon = false - seperatorValid = true + separatorValid = true else: raise newException(ValueError, "Invalid IP Address. Address contains an invalid character") if v4StartPos == -1: # Don't parse v4. Copy the remaining v6 stuff - if seperatorValid: # Copy remaining data + if separatorValid: # Copy remaining data if groupCount >= 8: raise newException(ValueError, "Invalid IP Address. The address consists of too many groups") @@ -347,19 +347,19 @@ proc parseIPv6Address(addressStr: string): IpAddress = if currentShort > 255'u32: raise newException(ValueError, "Invalid IP Address. Value is out of range") - seperatorValid = true + separatorValid = true elif c == '.': # IPv4 address separator - if not seperatorValid or byteCount >= 3: + if not separatorValid or byteCount >= 3: raise newException(ValueError, "Invalid IP Address") result.address_v6[groupCount*2 + byteCount] = cast[uint8](currentShort) currentShort = 0 byteCount.inc() - seperatorValid = false + separatorValid = false else: # Invalid character raise newException(ValueError, "Invalid IP Address. Address contains an invalid character") - if byteCount != 3 or not seperatorValid: + if byteCount != 3 or not separatorValid: raise newException(ValueError, "Invalid IP Address") result.address_v6[groupCount*2 + byteCount] = cast[uint8](currentShort) groupCount += 2 @@ -967,39 +967,6 @@ when defined(posix) or defined(nimdoc): raiseOSError(osLastError()) when defined(ssl): - proc handshake*(socket: Socket): bool - {.tags: [ReadIOEffect, WriteIOEffect], deprecated.} = - ## This proc needs to be called on a socket after it connects. This is - ## only applicable when using ``connectAsync``. - ## This proc performs the SSL handshake. - ## - ## Returns ``False`` whenever the socket is not yet ready for a handshake, - ## ``True`` whenever handshake completed successfully. - ## - ## A SslError error is raised on any other errors. - ## - ## **Note:** This procedure is deprecated since version 0.14.0. - result = true - if socket.isSSL: - var ret = SSLConnect(socket.sslHandle) - if ret <= 0: - var errret = SSLGetError(socket.sslHandle, ret) - case errret - of SSL_ERROR_ZERO_RETURN: - raiseSSLError("TLS/SSL connection failed to initiate, socket closed prematurely.") - of SSL_ERROR_WANT_CONNECT, SSL_ERROR_WANT_ACCEPT, - SSL_ERROR_WANT_READ, SSL_ERROR_WANT_WRITE: - return false - of SSL_ERROR_WANT_X509_LOOKUP: - raiseSSLError("Function for x509 lookup has been called.") - of SSL_ERROR_SYSCALL, SSL_ERROR_SSL: - raiseSSLError() - else: - raiseSSLError("Unknown Error") - socket.sslNoHandshake = false - else: - raiseSSLError("Socket is not an SSL socket.") - proc gotHandshake*(socket: Socket): bool = ## Determines whether a handshake has occurred between a client (``socket``) ## and the server that ``socket`` is connected to. @@ -1026,7 +993,7 @@ proc select(readfd: Socket, timeout = 500): int = return 1 var fds = @[readfd.fd] - result = select(fds, timeout) + result = selectRead(fds, timeout) proc isClosed(socket: Socket): bool = socket.fd == osInvalidSocket @@ -1694,7 +1661,5 @@ proc connect*(socket: Socket, address: string, port = Port(0), when defineSsl and not defined(nimdoc): if socket.isSSL: socket.fd.setBlocking(true) - {.warning[Deprecated]: off.} - doAssert socket.handshake() - {.warning[Deprecated]: on.} + doAssert socket.gotHandshake() socket.fd.setBlocking(true) diff --git a/lib/pure/os.nim b/lib/pure/os.nim index 6521d827c..181bc5728 100644 --- a/lib/pure/os.nim +++ b/lib/pure/os.nim @@ -19,7 +19,9 @@ include "system/inclrtl" import strutils, pathnorm -when defined(nimscript): +const weirdTarget = defined(nimscript) or defined(js) + +when weirdTarget: discard elif defined(windows): import winlean, times @@ -31,7 +33,7 @@ elif defined(posix): else: {.error: "OS module not ported to your operating system!".} -when defined(nimscript) and defined(nimErrorProcCanHaveBody): +when weirdTarget and defined(nimErrorProcCanHaveBody): {.pragma: noNimScript, error: "this proc is not available on the NimScript target".} else: {.pragma: noNimScript.} @@ -334,7 +336,7 @@ proc searchExtPos*(path: string): int = proc splitFile*(path: string): tuple[dir, name, ext: string] {. noSideEffect, rtl, extern: "nos$1".} = - ## Splits a filename into (dir, filename, extension). + ## Splits a filename into (dir, name, extension). ## `dir` does not end in `DirSep`. ## `extension` includes the leading dot. ## @@ -349,19 +351,30 @@ proc splitFile*(path: string): tuple[dir, name, ext: string] {. ## If `path` has no extension, `ext` is the empty string. ## If `path` has no directory component, `dir` is the empty string. ## If `path` has no filename component, `name` and `ext` are empty strings. - if path.len == 0 or path[path.len-1] in {DirSep, AltSep}: - result = (path, "", "") + if path.len == 0: + result = ("", "", "") + elif path[^1] in {DirSep, AltSep}: + if path.len == 1: + # issue #8255 + result = ($path[0], "", "") + else: + result = (path[0 ..< ^1], "", "") else: var sepPos = -1 var dotPos = path.len for i in countdown(len(path)-1, 0): if path[i] == ExtSep: if dotPos == path.len and i > 0 and - path[i-1] notin {DirSep, AltSep}: dotPos = i + path[i-1] notin {DirSep, AltSep, ExtSep}: dotPos = i elif path[i] in {DirSep, AltSep}: sepPos = i break - result.dir = substr(path, 0, sepPos-1) + if sepPos-1 >= 0: + result.dir = substr(path, 0, sepPos-1) + elif path[0] in {DirSep, AltSep}: + # issue #8255 + result.dir = $path[0] + result.name = substr(path, sepPos+1, dotPos-1) result.ext = substr(path, dotPos) @@ -651,7 +664,7 @@ when defined(windows) or defined(posix) or defined(nintendoswitch): if i > 0: result.add " " result.add quoteShell(args[i]) -when not defined(nimscript): +when not weirdTarget: proc c_rename(oldname, newname: cstring): cint {. importc: "rename", header: "<stdio.h>".} proc c_system(cmd: cstring): cint {. @@ -662,7 +675,7 @@ when not defined(nimscript): importc: "free", header: "<stdlib.h>".} -when defined(windows) and not defined(nimscript): +when defined(windows) and not weirdTarget: when useWinUnicode: template wrapUnary(varname, winApiProc, arg: untyped) = var varname = winApiProc(newWideCString(arg)) @@ -744,7 +757,7 @@ proc dirExists*(dir: string): bool {.inline, noNimScript.} = ## Synonym for existsDir existsDir(dir) -when not defined(windows) and not defined(nimscript): +when not defined(windows) and not weirdTarget: proc checkSymlink(path: string): bool = var rawInfo: Stat if lstat(path, rawInfo) < 0'i32: result = false @@ -805,7 +818,7 @@ proc findExe*(exe: string, followSymlinks: bool = true; return x result = "" -when defined(nimscript): +when weirdTarget: const times = "fake const" template Time(x: untyped): untyped = string @@ -920,7 +933,7 @@ proc setCurrentDir*(newDir: string) {.inline, tags: [], noNimScript.} = else: if chdir(newDir) != 0'i32: raiseOSError(osLastError()) -when not defined(nimscript): +when not weirdTarget: proc absolutePath*(path: string, root = getCurrentDir()): string {.noNimScript.} = ## Returns the absolute path of `path`, rooted at `root` (which must be absolute) ## if `path` is absolute, return it, ignoring `root` @@ -932,48 +945,6 @@ when not defined(nimscript): raise newException(ValueError, "The specified root is not absolute: " & root) joinPath(root, path) -proc expandFilename*(filename: string): string {.rtl, extern: "nos$1", - tags: [ReadDirEffect], noNimScript.} = - ## Returns the full (`absolute`:idx:) path of an existing file `filename`, - ## raises OSError in case of an error. Follows symlinks. - when defined(windows): - var bufsize = MAX_PATH.int32 - when useWinUnicode: - var unused: WideCString = nil - var res = newWideCString("", bufsize) - while true: - var L = getFullPathNameW(newWideCString(filename), bufsize, res, unused) - if L == 0'i32: - raiseOSError(osLastError()) - elif L > bufsize: - res = newWideCString("", L) - bufsize = L - else: - result = res$L - break - else: - var unused: cstring = nil - result = newString(bufsize) - while true: - var L = getFullPathNameA(filename, bufsize, result, unused) - if L == 0'i32: - raiseOSError(osLastError()) - elif L > bufsize: - result = newString(L) - bufsize = L - else: - setLen(result, L) - break - else: - # according to Posix we don't need to allocate space for result pathname. - # But we need to free return value with free(3). - var r = realpath(filename, nil) - if r.isNil: - raiseOSError(osLastError()) - else: - result = $r - c_free(cast[pointer](r)) - proc normalizePath*(path: var string) {.rtl, extern: "nos$1", tags: [], noNimScript.} = ## Normalize a path. ## @@ -984,38 +955,39 @@ proc normalizePath*(path: var string) {.rtl, extern: "nos$1", tags: [], noNimScr ## ## Warning: URL-encoded and Unicode attempts at directory traversal are not detected. ## Triple dot is not handled. - let isAbs = isAbsolute(path) - var stack: seq[string] = @[] - for p in split(path, {DirSep}): - case p - of "", ".": - continue - of "..": - if stack.len == 0: - if isAbs: - discard # collapse all double dots on absoluta paths - else: + path = pathnorm.normalizePath(path) + when false: + let isAbs = isAbsolute(path) + var stack: seq[string] = @[] + for p in split(path, {DirSep}): + case p + of "", ".": + continue + of "..": + if stack.len == 0: + if isAbs: + discard # collapse all double dots on absoluta paths + else: + stack.add(p) + elif stack[^1] == "..": stack.add(p) - elif stack[^1] == "..": - stack.add(p) + else: + discard stack.pop() else: - discard stack.pop() - else: - stack.add(p) + stack.add(p) - if isAbs: - path = DirSep & join(stack, $DirSep) - elif stack.len > 0: - path = join(stack, $DirSep) - else: - path = "." + if isAbs: + path = DirSep & join(stack, $DirSep) + elif stack.len > 0: + path = join(stack, $DirSep) + else: + path = "." proc normalizedPath*(path: string): string {.rtl, extern: "nos$1", tags: [], noNimScript.} = ## Returns a normalized path for the current OS. See `<#normalizePath>`_ - result = path - normalizePath(result) + result = pathnorm.normalizePath(path) -when defined(Windows) and not defined(nimscript): +when defined(Windows) and not weirdTarget: proc openHandle(path: string, followSymlink=true, writeAccess=false): Handle = var flags = FILE_FLAG_BACKUP_SEMANTICS or FILE_ATTRIBUTE_NORMAL if not followSymlink: @@ -1238,7 +1210,7 @@ when not declared(ENOENT) and not defined(Windows): else: var ENOENT {.importc, header: "<errno.h>".}: cint -when defined(Windows) and not defined(nimscript): +when defined(Windows) and not weirdTarget: when useWinUnicode: template deleteFile(file: untyped): untyped = deleteFileW(file) template setFileAttributes(file, attrs: untyped): untyped = @@ -1315,6 +1287,17 @@ proc moveFile*(source, dest: string) {.rtl, extern: "nos$1", discard tryRemoveFile(dest) raise +proc exitStatusLikeShell*(status: cint): cint = + ## converts exit code from `c_system` into a shell exit code + when defined(posix) and not weirdTarget: + if WIFSIGNALED(status): + # like the shell! + 128 + WTERMSIG(status) + else: + WEXITSTATUS(status) + else: + status + proc execShellCmd*(command: string): int {.rtl, extern: "nos$1", tags: [ExecIOEffect], noNimScript.} = ## Executes a `shell command`:idx:. @@ -1323,23 +1306,19 @@ proc execShellCmd*(command: string): int {.rtl, extern: "nos$1", ## line arguments given to program. The proc returns the error code ## of the shell when it has finished. The proc does not return until ## the process has finished. To execute a program without having a - ## shell involved, use the `execProcess` proc of the `osproc` - ## module. - when defined(posix): - result = c_system(command) shr 8 - else: - result = c_system(command) + ## shell involved, use `osproc.execProcess`. + result = exitStatusLikeShell(c_system(command)) # Templates for filtering directories and files -when defined(windows) and not defined(nimscript): +when defined(windows) and not weirdTarget: template isDir(f: WIN32_FIND_DATA): bool = (f.dwFileAttributes and FILE_ATTRIBUTE_DIRECTORY) != 0'i32 template isFile(f: WIN32_FIND_DATA): bool = not isDir(f) else: - template isDir(f: string): bool = + template isDir(f: string): bool {.dirty.} = dirExists(f) - template isFile(f: string): bool = + template isFile(f: string): bool {.dirty.} = fileExists(f) template defaultWalkFilter(item): bool = @@ -1411,6 +1390,54 @@ iterator walkDirs*(pattern: string): string {.tags: [ReadDirEffect], noNimScript ## notation is supported. walkCommon(pattern, isDir) +proc expandFilename*(filename: string): string {.rtl, extern: "nos$1", + tags: [ReadDirEffect], noNimScript.} = + ## Returns the full (`absolute`:idx:) path of an existing file `filename`, + ## raises OSError in case of an error. Follows symlinks. + when defined(windows): + var bufsize = MAX_PATH.int32 + when useWinUnicode: + var unused: WideCString = nil + var res = newWideCString("", bufsize) + while true: + var L = getFullPathNameW(newWideCString(filename), bufsize, res, unused) + if L == 0'i32: + raiseOSError(osLastError()) + elif L > bufsize: + res = newWideCString("", L) + bufsize = L + else: + result = res$L + break + else: + var unused: cstring = nil + result = newString(bufsize) + while true: + var L = getFullPathNameA(filename, bufsize, result, unused) + if L == 0'i32: + raiseOSError(osLastError()) + elif L > bufsize: + result = newString(L) + bufsize = L + else: + setLen(result, L) + break + # getFullPathName doesn't do case corrections, so we have to use this convoluted + # way of retrieving the true filename + for x in walkFiles(result.string): + result = x + if not existsFile(result) and not existsDir(result): + raise newException(OSError, "file '" & result & "' does not exist") + else: + # according to Posix we don't need to allocate space for result pathname. + # But we need to free return value with free(3). + var r = realpath(filename, nil) + if r.isNil: + raiseOSError(osLastError()) + else: + result = $r + c_free(cast[pointer](r)) + type PathComponent* = enum ## Enumeration specifying a path component. pcFile, ## path refers to a file @@ -1418,7 +1445,13 @@ type pcDir, ## path refers to a directory pcLinkToDir ## path refers to a symbolic link to a directory -when defined(posix) and not defined(nimscript): +proc getCurrentCompilerExe*(): string {.compileTime.} = discard + ## `getAppFilename` at CT; can be used to retrive the currently executing + ## Nim compiler from a Nim or nimscript program, or the nimble binary + ## inside a nimble program (likewise with other binaries built from + ## compiler API). + +when defined(posix) and not weirdTarget: proc getSymlinkFileKind(path: string): PathComponent = # Helper function. var s: Stat @@ -1459,7 +1492,7 @@ iterator walkDir*(dir: string; relative=false): tuple[kind: PathComponent, path: for k, v in items(staticWalkDir(dir, relative)): yield (k, v) else: - when defined(nimscript): + when weirdTarget: for k, v in items(staticWalkDir(dir, relative)): yield (k, v) elif defined(windows): @@ -1494,8 +1527,9 @@ iterator walkDir*(dir: string; relative=false): tuple[kind: PathComponent, path: var y = $x.d_name.cstring if y != "." and y != "..": var s: Stat + let path = dir / y if not relative: - y = dir / y + y = path var k = pcFile when defined(linux) or defined(macosx) or @@ -1503,16 +1537,16 @@ iterator walkDir*(dir: string; relative=false): tuple[kind: PathComponent, path: if x.d_type != DT_UNKNOWN: if x.d_type == DT_DIR: k = pcDir if x.d_type == DT_LNK: - if dirExists(y): k = pcLinkToDir + if dirExists(path): k = pcLinkToDir else: k = pcLinkToFile yield (k, y) continue - if lstat(y, s) < 0'i32: break + if lstat(path, s) < 0'i32: break if S_ISDIR(s.st_mode): k = pcDir elif S_ISLNK(s.st_mode): - k = getSymlinkFileKind(y) + k = getSymlinkFileKind(path) yield (k, y) iterator walkDirRec*(dir: string, @@ -1953,7 +1987,7 @@ when defined(nimdoc): ## else: ## # Do something else! -elif defined(nintendoswitch) or defined(nimscript): +elif defined(nintendoswitch) or weirdTarget: proc paramStr*(i: int): TaintedString {.tags: [ReadIOEffect].} = raise newException(OSError, "paramStr is not implemented on Nintendo Switch") @@ -2030,7 +2064,7 @@ when declared(paramCount) or defined(nimdoc): for i in 1..paramCount(): result.add(paramStr(i)) -when not defined(nimscript) and (defined(freebsd) or defined(dragonfly)): +when not weirdTarget and (defined(freebsd) or defined(dragonfly)): proc sysctl(name: ptr cint, namelen: cuint, oldp: pointer, oldplen: ptr csize, newp: pointer, newplen: csize): cint {.importc: "sysctl",header: """#include <sys/types.h> @@ -2063,7 +2097,7 @@ when not defined(nimscript) and (defined(freebsd) or defined(dragonfly)): result.setLen(pathLength) break -when not defined(nimscript) and (defined(linux) or defined(solaris) or defined(bsd) or defined(aix)): +when not weirdTarget and (defined(linux) or defined(solaris) or defined(bsd) or defined(aix)): proc getApplAux(procPath: string): string = result = newString(256) var len = readlink(procPath, result, 256) @@ -2072,7 +2106,7 @@ when not defined(nimscript) and (defined(linux) or defined(solaris) or defined(b len = readlink(procPath, result, len) setLen(result, len) -when not (defined(windows) or defined(macosx) or defined(nimscript)): +when not (defined(windows) or defined(macosx) or weirdTarget): proc getApplHeuristic(): string = when declared(paramStr): result = string(paramStr(0)) @@ -2117,7 +2151,8 @@ when defined(haiku): result = "" proc getAppFilename*(): string {.rtl, extern: "nos$1", tags: [ReadIOEffect], noNimScript.} = - ## Returns the filename of the application's executable. + ## Returns the filename of the application's executable. See also + ## `getCurrentCompilerExe`. ## ## This procedure will resolve symlinks. @@ -2207,7 +2242,7 @@ proc getFileSize*(file: string): BiggestInt {.rtl, extern: "nos$1", close(f) else: raiseOSError(osLastError()) -when defined(Windows) or defined(nimscript): +when defined(Windows) or weirdTarget: type DeviceId* = int32 FileId* = int64 @@ -2287,6 +2322,12 @@ template rawToFormalFileInfo(rawInfo, path, formalInfo): untyped = assert(path != "") # symlinks can't occur for file handles formalInfo.kind = getSymlinkFileKind(path) +when defined(js): + when not declared(FileHandle): + type FileHandle = distinct int32 + when not declared(File): + type File = object + proc getFileInfo*(handle: FileHandle): FileInfo {.noNimScript.} = ## Retrieves file information for the file object represented by the given ## handle. diff --git a/lib/pure/osproc.nim b/lib/pure/osproc.nim index b2239b9c5..e7ab395ae 100644 --- a/lib/pure/osproc.nim +++ b/lib/pure/osproc.nim @@ -60,9 +60,6 @@ type Process* = ref ProcessObj ## represents an operating system process -const poUseShell* {.deprecated.} = poUsePath - ## Deprecated alias for poUsePath. - proc execProcess*(command: string, workingDir: string = "", args: openArray[string] = [], @@ -229,7 +226,7 @@ proc execProcesses*(cmds: openArray[string], {.rtl, extern: "nosp$1", tags: [ExecIOEffect, TimeEffect, ReadEnvEffect, RootEffect]} = ## executes the commands `cmds` in parallel. Creates `n` processes - ## that execute in parallel. The highest return value of all processes + ## that execute in parallel. The highest (absolute) return value of all processes ## is returned. Runs `beforeRunEvent` before running each command. assert n > 0 @@ -335,19 +332,6 @@ proc execProcesses*(cmds: openArray[string], if afterRunEvent != nil: afterRunEvent(i, p) close(p) -proc select*(readfds: var seq[Process], timeout = 500): int - {.benign, deprecated.} - ## `select` with a sensible Nim interface. `timeout` is in milliseconds. - ## Specify -1 for no timeout. Returns the number of processes that are - ## ready to read from. The processes that are ready to be read from are - ## removed from `readfds`. - ## - ## **Warning**: This function may give unexpected or completely wrong - ## results on Windows. - ## - ## **Deprecated since version 0.17.0**: This procedure isn't cross-platform - ## and so should not be used in newly written code. - when not defined(useNimRtl): proc execProcess(command: string, workingDir: string = "", @@ -724,13 +708,6 @@ elif not defined(useNimRtl): proc isExitStatus(status: cint): bool = WIFEXITED(status) or WIFSIGNALED(status) - proc exitStatus(status: cint): cint = - if WIFSIGNALED(status): - # like the shell! - 128 + WTERMSIG(status) - else: - WEXITSTATUS(status) - proc envToCStringArray(t: StringTableRef): cstringArray = result = cast[cstringArray](alloc0((t.len + 1) * sizeof(cstring))) var i = 0 @@ -1054,7 +1031,7 @@ elif not defined(useNimRtl): proc waitForExit(p: Process, timeout: int = -1): int = if p.exitFlag: - return exitStatus(p.exitStatus) + return exitStatusLikeShell(p.exitStatus) if timeout == -1: var status: cint = 1 @@ -1109,7 +1086,7 @@ elif not defined(useNimRtl): finally: discard posix.close(kqFD) - result = exitStatus(p.exitStatus) + result = exitStatusLikeShell(p.exitStatus) else: import times @@ -1142,7 +1119,7 @@ elif not defined(useNimRtl): s.tv_nsec = b.tv_nsec if p.exitFlag: - return exitStatus(p.exitStatus) + return exitStatusLikeShell(p.exitStatus) if timeout == -1: var status: cint = 1 @@ -1220,20 +1197,20 @@ elif not defined(useNimRtl): if sigprocmask(SIG_UNBLOCK, nmask, omask) == -1: raiseOSError(osLastError()) - result = exitStatus(p.exitStatus) + result = exitStatusLikeShell(p.exitStatus) proc peekExitCode(p: Process): int = var status = cint(0) result = -1 if p.exitFlag: - return exitStatus(p.exitStatus) + return exitStatusLikeShell(p.exitStatus) var ret = waitpid(p.id, status, WNOHANG) if ret > 0: if isExitStatus(status): p.exitFlag = true p.exitStatus = status - result = exitStatus(status) + result = exitStatusLikeShell(status) proc createStream(stream: var Stream, handle: var FileHandle, fileMode: FileMode) = @@ -1265,7 +1242,7 @@ elif not defined(useNimRtl): proc execCmd(command: string): int = when defined(linux): let tmp = csystem(command) - result = if tmp == -1: tmp else: exitStatus(tmp) + result = if tmp == -1: tmp else: exitStatusLikeShell(tmp) else: result = csystem(command) diff --git a/lib/pure/parsecfg.nim b/lib/pure/parsecfg.nim index b991dd57f..106d59017 100644 --- a/lib/pure/parsecfg.nim +++ b/lib/pure/parsecfg.nim @@ -111,7 +111,7 @@ ## dict.writeConfig("config.ini") import - hashes, strutils, lexbase, streams, tables + strutils, lexbase, streams, tables include "system/inclrtl" diff --git a/lib/pure/parseopt.nim b/lib/pure/parseopt.nim index fe3d3186f..06f32f032 100644 --- a/lib/pure/parseopt.nim +++ b/lib/pure/parseopt.nim @@ -73,24 +73,6 @@ proc parseWord(s: string, i: int, w: var string, inc(result) when declared(os.paramCount): - proc quote(s: string): string = - if find(s, {' ', '\t'}) >= 0 and s.len > 0 and s[0] != '"': - if s[0] == '-': - result = newStringOfCap(s.len) - var i = parseWord(s, 0, result, {' ', '\t', ':', '='}) - if i < s.len and s[i] in {':','='}: - result.add s[i] - inc i - result.add '"' - while i < s.len: - result.add s[i] - inc i - result.add '"' - else: - result = '"' & s & '"' - else: - result = s - # we cannot provide this for NimRtl creation on Posix, because we can't # access the command line arguments then! @@ -228,11 +210,12 @@ proc next*(p: var OptParser) {.rtl, extern: "npo$1".} = when declared(os.paramCount): proc cmdLineRest*(p: OptParser): TaintedString {.rtl, extern: "npo$1".} = ## retrieves the rest of the command line that has not been parsed yet. - var res = "" - for i in p.idx..<p.cmds.len: - if i > p.idx: res.add ' ' - res.add quote(p.cmds[i]) - result = res.TaintedString + result = p.cmds[p.idx .. ^1].quoteShellCommand.TaintedString + + proc remainingArgs*(p: OptParser): seq[TaintedString] {.rtl, extern: "npo$1".} = + ## retrieves the rest of the command line that has not been parsed yet. + result = @[] + for i in p.idx..<p.cmds.len: result.add TaintedString(p.cmds[i]) iterator getopt*(p: var OptParser): tuple[kind: CmdLineKind, key, val: TaintedString] = ## This is an convenience iterator for iterating over the given OptParser object. diff --git a/lib/pure/parseopt2.nim b/lib/pure/parseopt2.nim index 51a70b6d1..9fd6cd2c7 100644 --- a/lib/pure/parseopt2.nim +++ b/lib/pure/parseopt2.nim @@ -55,15 +55,6 @@ proc initOptParser*(cmdline: seq[string]): OptParser {.rtl.} = result.cmd = @cmdline -proc initOptParser*(cmdline: string): OptParser {.rtl, deprecated.} = - ## Initalizes option parses with cmdline. Splits cmdline in on spaces - ## and calls initOptParser(openarray[string]) - ## Do not use. - if cmdline == "": # backward compatibility - return initOptParser(@[]) - else: - return initOptParser(cmdline.split) - when not defined(createNimRtl): proc initOptParser*(): OptParser = ## Initializes option parser from current command line arguments. @@ -112,10 +103,10 @@ proc next(p: var OptParser) = p.key = token p.val = "" -proc cmdLineRest*(p: OptParser): TaintedString {.rtl, extern: "npo2$1", deprecated.} = - ## Returns part of command line string that has not been parsed yet. - ## Do not use - does not correctly handle whitespace. - return p.cmd[p.pos..p.cmd.len-1].join(" ") +proc cmdLineRest*(p: OptParser): TaintedString {.rtl, extern: "npo2$1".} = + ## Returns the part of command line string that has not been parsed yet, + ## properly quoted. + return p.cmd[p.pos..p.cmd.len-1].quoteShellCommand type GetoptResult* = tuple[kind: CmdLineKind, key, val: TaintedString] diff --git a/lib/pure/parsesql.nim b/lib/pure/parsesql.nim index 20f02e815..f0961829b 100644 --- a/lib/pure/parsesql.nim +++ b/lib/pure/parsesql.nim @@ -11,7 +11,7 @@ ## parser. It parses PostgreSQL syntax and the SQL ANSI standard. import - hashes, strutils, lexbase + strutils, lexbase # ------------------- scanner ------------------------------------------------- diff --git a/lib/pure/parseutils.nim b/lib/pure/parseutils.nim index fb4bc19af..ba09347a2 100644 --- a/lib/pure/parseutils.nim +++ b/lib/pure/parseutils.nim @@ -7,9 +7,46 @@ # distribution, for details about the copyright. # -## This module contains helpers for parsing tokens, numbers, identifiers, etc. +## This module contains helpers for parsing tokens, numbers, integers, floats, +## identifiers, etc. ## ## To unpack raw bytes look at the `streams <streams.html>`_ module. +## +## +## .. code-block:: +## import parseutils +## +## let logs = @["2019-01-10: OK_", "2019-01-11: FAIL_", "2019-01: aaaa"] +## +## for log in logs: +## var res: string +## if parseUntil(log, res, ':') == 10: # YYYY-MM-DD == 10 +## echo res & " - " & captureBetween(log, ' ', '_') +## # => 2019-01-10 - OK +## +## +## .. code-block:: +## import parseutils +## from strutils import Digits, parseInt +## +## let userInput1 = "2019 school start" +## let userInput2 = "3 years back" +## +## let startYear = input1[0..skipWhile(input1, Digits)-1] # 2019 +## let yearsBack = input2[0..skipWhile(input2, Digits)-1] # 3 +## +## echo "Examination is in " & $(parseInt(startYear) + parseInt(yearsBack)) +## +## +## **See also:** +## * `strutils module<strutils.html>`_ for combined and identical parsing proc's +## * `json module<json.html>`_ for a JSON parser +## * `parsecfg module<parsecfg.html>`_ for a configuration file parser +## * `parsecsv module<parsecsv.html>`_ for a simple CSV (comma separated value) parser +## * `parseopt module<parseopt.html>`_ for a command line parser +## * `parsexml module<parsexml.html>`_ for a XML / HTML parser +## * `other parsers<lib.html#pure-libraries-parsers>`_ for other parsers + {.deadCodeElim: on.} # dce option deprecated @@ -35,21 +72,20 @@ proc parseHex*(s: string, number: var int, start = 0; maxLen = 0): int {. ## 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. Example: - ## - ## .. code-block:: nim - ## 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 + ## overflow. ## ## 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. + 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 i = start var foundDigit = false # get last index based on minimum `start + maxLen` or `s.len` @@ -80,6 +116,11 @@ proc parseOct*(s: string, number: var int, start = 0, maxLen = 0): int {. ## 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. + runnableExamples: + var res: int + doAssert parseOct("12", res) == 2 + doAssert res == 10 + doAssert parseOct("9", res) == 0 var i = start var foundDigit = false # get last index based on minimum `start + maxLen` or `s.len` @@ -95,7 +136,7 @@ proc parseOct*(s: string, number: var int, start = 0, maxLen = 0): int {. inc(i) if foundDigit: result = i-start -proc parseBin*(s: string, number: var int, start = 0, maxLen = 0): int {. +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. @@ -103,6 +144,10 @@ proc parseBin*(s: string, number: var int, start = 0, maxLen = 0): int {. ## 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. + runnableExamples: + var res: int + doAssert parseBin("010011100110100101101101", res) == 24 + doAssert parseBin("3", res) == 0 var i = start var foundDigit = false # get last index based on minimum `start + maxLen` or `s.len` @@ -119,8 +164,16 @@ proc parseBin*(s: string, number: var int, start = 0, maxLen = 0): int {. if foundDigit: result = i-start proc parseIdent*(s: string, ident: var string, start = 0): int = - ## parses an identifier and stores it in ``ident``. Returns + ## Parses an identifier and stores it in ``ident``. Returns ## the number of the parsed characters or 0 in case of an error. + runnableExamples: + var res: string + doAssert parseIdent("Hello World", res, 0) == 5 + doAssert res == "Hello" + doAssert parseIdent("Hello World", res, 1) == 4 + doAssert res == "ello" + doAssert parseIdent("Hello World", res, 6) == 5 + doAssert res == "World" var i = start if i < s.len and s[i] in IdentStartChars: inc(i) @@ -129,8 +182,13 @@ proc parseIdent*(s: string, ident: var string, start = 0): int = result = i-start proc parseIdent*(s: string, start = 0): string = - ## parses an identifier and returns it or an empty string in + ## Parses an identifier and returns it or an empty string in ## case of an error. + runnableExamples: + doAssert parseIdent("Hello World", 0) == "Hello" + doAssert parseIdent("Hello World", 1) == "ello" + doAssert parseIdent("Hello World", 5) == "" + doAssert parseIdent("Hello World", 6) == "World" result = "" var i = start if i < s.len and s[i] in IdentStartChars: @@ -138,33 +196,35 @@ proc parseIdent*(s: string, start = 0): string = while i < s.len and s[i] in IdentChars: inc(i) result = substr(s, start, i-1) -proc parseToken*(s: string, token: var string, validChars: set[char], - start = 0): int {.inline, deprecated.} = - ## parses a token and stores it in ``token``. Returns - ## the number of the parsed characters or 0 in case of an error. A token - ## consists of the characters in `validChars`. - ## - ## **Deprecated since version 0.8.12**: Use ``parseWhile`` instead. - var i = start - while i < s.len and s[i] in validChars: inc(i) - result = i-start - token = substr(s, start, i-1) - proc skipWhitespace*(s: string, start = 0): int {.inline.} = - ## skips the whitespace starting at ``s[start]``. Returns the number of + ## Skips the whitespace starting at ``s[start]``. Returns the number of ## skipped characters. + runnableExamples: + doAssert skipWhitespace("Hello World", 0) == 0 + doAssert skipWhitespace(" Hello World", 0) == 1 + doAssert skipWhitespace("Hello World", 5) == 1 + doAssert skipWhitespace("Hello World", 5) == 2 while start+result < s.len and s[start+result] in Whitespace: inc(result) proc skip*(s, token: string, start = 0): int {.inline.} = - ## skips the `token` starting at ``s[start]``. Returns the length of `token` + ## Skips the `token` starting at ``s[start]``. Returns the length of `token` ## or 0 if there was no `token` at ``s[start]``. + runnableExamples: + doAssert skip("2019-01-22", "2019", 0) == 4 + doAssert skip("2019-01-22", "19", 0) == 0 + doAssert skip("2019-01-22", "19", 2) == 2 + doAssert skip("CAPlow", "CAP", 0) == 3 + doAssert skip("CAPlow", "cap", 0) == 0 while start+result < s.len and result < token.len and s[result+start] == token[result]: inc(result) if result != token.len: result = 0 proc skipIgnoreCase*(s, token: string, start = 0): int = - ## same as `skip` but case is ignored for token matching. + ## Same as `skip` but case is ignored for token matching. + runnableExamples: + doAssert skipIgnoreCase("CAPlow", "CAP", 0) == 3 + doAssert skipIgnoreCase("CAPlow", "cap", 0) == 3 while start+result < s.len and result < token.len and toLower(s[result+start]) == toLower(token[result]): inc(result) if result != token.len: result = 0 @@ -173,24 +233,45 @@ proc skipUntil*(s: string, until: set[char], start = 0): int {.inline.} = ## Skips all characters until one char from the set `until` is found ## or the end is reached. ## Returns number of characters skipped. + runnableExamples: + doAssert skipUntil("Hello World", {'W', 'e'}, 0) == 1 + doAssert skipUntil("Hello World", {'W'}, 0) == 6 + doAssert skipUntil("Hello World", {'W', 'd'}, 0) == 6 while start+result < s.len and s[result+start] notin until: inc(result) proc skipUntil*(s: string, until: char, start = 0): int {.inline.} = ## Skips all characters until the char `until` is found ## or the end is reached. ## Returns number of characters skipped. + runnableExamples: + doAssert skipUntil("Hello World", 'o', 0) == 4 + doAssert skipUntil("Hello World", 'o', 4) == 0 + doAssert skipUntil("Hello World", 'W', 0) == 6 + doAssert skipUntil("Hello World", 'w', 0) == 11 while start+result < s.len and s[result+start] != until: inc(result) proc skipWhile*(s: string, toSkip: set[char], start = 0): int {.inline.} = ## Skips all characters while one char from the set `token` is found. ## Returns number of characters skipped. + runnableExamples: + doAssert skipWhile("Hello World", {'H', 'e'}) == 2 + doAssert skipWhile("Hello World", {'e'}) == 0 + doAssert skipWhile("Hello World", {'W', 'o', 'r'}, 6) == 3 while start+result < s.len and s[result+start] in toSkip: inc(result) proc parseUntil*(s: string, token: var string, until: set[char], start = 0): int {.inline.} = - ## parses a token and stores it in ``token``. Returns + ## Parses a token and stores it in ``token``. Returns ## the number of the parsed characters or 0 in case of an error. A token ## consists of the characters notin `until`. + runnableExamples: + var myToken: string + doAssert parseUntil("Hello World", myToken, {'W', 'o', 'r'}) == 4 + doAssert myToken == "Hell" + doAssert parseUntil("Hello World", myToken, {'W', 'r'}) == 6 + doAssert myToken == "Hello " + doAssert parseUntil("Hello World", myToken, {'W', 'r'}, 3) == 3 + doAssert myToken == "lo " var i = start while i < s.len and s[i] notin until: inc(i) result = i-start @@ -198,9 +279,17 @@ proc parseUntil*(s: string, token: var string, until: set[char], proc parseUntil*(s: string, token: var string, until: char, start = 0): int {.inline.} = - ## parses a token and stores it in ``token``. Returns + ## Parses a token and stores it in ``token``. Returns ## the number of the parsed characters or 0 in case of an error. A token ## consists of any character that is not the `until` character. + runnableExamples: + var myToken: string + doAssert parseUntil("Hello World", myToken, 'W') == 6 + doAssert myToken == "Hello " + doAssert parseUntil("Hello World", myToken, 'o') == 4 + doAssert myToken == "Hell" + doAssert parseUntil("Hello World", myToken, 'o', 2) == 2 + doAssert myToken == "ll" var i = start while i < s.len and s[i] != until: inc(i) result = i-start @@ -208,9 +297,15 @@ proc parseUntil*(s: string, token: var string, until: char, proc parseUntil*(s: string, token: var string, until: string, start = 0): int {.inline.} = - ## parses a token and stores it in ``token``. Returns + ## Parses a token and stores it in ``token``. Returns ## the number of the parsed characters or 0 in case of an error. A token ## consists of any character that comes before the `until` token. + runnableExamples: + var myToken: string + doAssert parseUntil("Hello World", myToken, "Wor") == 6 + doAssert myToken == "Hello " + doAssert parseUntil("Hello World", myToken, "Wor", 2) == 4 + doAssert myToken == "llo " if until.len == 0: token.setLen(0) return 0 @@ -227,9 +322,15 @@ proc parseUntil*(s: string, token: var string, until: string, proc parseWhile*(s: string, token: var string, validChars: set[char], start = 0): int {.inline.} = - ## parses a token and stores it in ``token``. Returns + ## Parses a token and stores it in ``token``. Returns ## the number of the parsed characters or 0 in case of an error. A token ## consists of the characters in `validChars`. + runnableExamples: + var myToken: string + doAssert parseWhile("Hello World", myToken, {'W', 'o', 'r'}, 0) == 0 + doAssert myToken.len() == 0 + doAssert parseWhile("Hello World", myToken, {'W', 'o', 'r'}, 6) == 3 + doAssert myToken == "Wor" var i = start while i < s.len and s[i] in validChars: inc(i) result = i-start @@ -238,12 +339,21 @@ proc parseWhile*(s: string, token: var string, validChars: set[char], proc captureBetween*(s: string, first: char, second = '\0', start = 0): string = ## Finds the first occurrence of ``first``, then returns everything from there ## up to ``second`` (if ``second`` is '\0', then ``first`` is used). + runnableExamples: + doAssert captureBetween("Hello World", 'e') == "llo World" + doAssert captureBetween("Hello World", 'e', 'r') == "llo Wo" + doAssert captureBetween("Hello World", 'l', start = 6) == "d" var i = skipUntil(s, first, start)+1+start result = "" discard s.parseUntil(result, if second == '\0': first else: second, i) -{.push overflowChecks: on.} -# this must be compiled with overflow checking turned on: +proc integerOutOfRangeError() {.noinline.} = + raise newException(ValueError, "Parsed integer outside of valid range") + +# See #6752 +when defined(js): + {.push overflowChecks: off.} + proc rawParseInt(s: string, b: var BiggestInt, start = 0): int = var sign: BiggestInt = -1 @@ -256,48 +366,67 @@ proc rawParseInt(s: string, b: var BiggestInt, start = 0): int = if i < s.len and s[i] in {'0'..'9'}: b = 0 while i < s.len and s[i] in {'0'..'9'}: - b = b * 10 - (ord(s[i]) - ord('0')) + let c = ord(s[i]) - ord('0') + if b >= (low(BiggestInt) + c) div 10: + b = b * 10 - c + else: + integerOutOfRangeError() inc(i) while i < s.len and s[i] == '_': inc(i) # underscores are allowed and ignored - b = b * sign - result = i - start -{.pop.} # overflowChecks + if sign == -1 and b == low(BiggestInt): + integerOutOfRangeError() + else: + b = b * sign + result = i - start + +when defined(js): + {.pop.} # overflowChecks: off proc parseBiggestInt*(s: string, number: var BiggestInt, start = 0): int {. - rtl, extern: "npuParseBiggestInt", noSideEffect.} = - ## parses an integer starting at `start` and stores the value into `number`. + rtl, extern: "npuParseBiggestInt", noSideEffect, raises: [ValueError].} = + ## Parses an integer starting at `start` and stores the value into `number`. ## Result is the number of processed chars or 0 if there is no integer. - ## `OverflowError` is raised if an overflow occurs. + ## `ValueError` is raised if the parsed integer is out of the valid range. + runnableExamples: + var res: BiggestInt + doAssert parseBiggestInt("9223372036854775807", res, 0) == 19 + doAssert res == 9223372036854775807 var res: BiggestInt # use 'res' for exception safety (don't write to 'number' in case of an # overflow exception): result = rawParseInt(s, res, start) - number = res + if result != 0: + number = res proc parseInt*(s: string, number: var int, start = 0): int {. - rtl, extern: "npuParseInt", noSideEffect.} = - ## parses an integer starting at `start` and stores the value into `number`. + rtl, extern: "npuParseInt", noSideEffect, raises: [ValueError].} = + ## Parses an integer starting at `start` and stores the value into `number`. ## Result is the number of processed chars or 0 if there is no integer. - ## `OverflowError` is raised if an overflow occurs. + ## `ValueError` is raised if the parsed integer is out of the valid range. + runnableExamples: + var res: int + doAssert parseInt("2019", res, 0) == 4 + doAssert res == 2019 + doAssert parseInt("2019", res, 2) == 2 + doAssert res == 19 var res: BiggestInt result = parseBiggestInt(s, res, start) - if (sizeof(int) <= 4) and - ((res < low(int)) or (res > high(int))): - raise newException(OverflowError, "overflow") - elif result != 0: + when sizeof(int) <= 4: + if res < low(int) or res > high(int): + integerOutOfRangeError() + if result != 0: number = int(res) -proc parseSaturatedNatural*(s: string, b: var int, start = 0): int = - ## parses a natural number into ``b``. This cannot raise an overflow +proc parseSaturatedNatural*(s: string, b: var int, start = 0): int {. + raises: [].}= + ## Parses a natural number into ``b``. This cannot raise an overflow ## error. ``high(int)`` is returned for an overflow. ## The number of processed character is returned. ## This is usually what you really want to use instead of `parseInt`:idx:. - ## Example: - ## - ## .. code-block:: nim - ## var res = 0 - ## discard parseSaturatedNatural("848", res) - ## doAssert res == 848 + runnableExamples: + var res = 0 + discard parseSaturatedNatural("848", res) + doAssert res == 848 var i = start if i < s.len and s[i] == '+': inc(i) if i < s.len and s[i] in {'0'..'9'}: @@ -312,12 +441,13 @@ proc parseSaturatedNatural*(s: string, b: var int, start = 0): int = while i < s.len and s[i] == '_': inc(i) # underscores are allowed and ignored result = i - start -# overflowChecks doesn't work with BiggestUInt proc rawParseUInt(s: string, b: var BiggestUInt, start = 0): int = var res = 0.BiggestUInt prev = 0.BiggestUInt i = start + if i < s.len - 1 and s[i] == '-' and s[i + 1] in {'0'..'9'}: + integerOutOfRangeError() if i < s.len and s[i] == '+': inc(i) # Allow if i < s.len and s[i] in {'0'..'9'}: b = 0 @@ -325,56 +455,75 @@ proc rawParseUInt(s: string, b: var BiggestUInt, start = 0): int = prev = res res = res * 10 + (ord(s[i]) - ord('0')).BiggestUInt if prev > res: - return 0 # overflowChecks emulation + integerOutOfRangeError() inc(i) while i < s.len and s[i] == '_': inc(i) # underscores are allowed and ignored b = res result = i - start proc parseBiggestUInt*(s: string, number: var BiggestUInt, start = 0): int {. - rtl, extern: "npuParseBiggestUInt", noSideEffect.} = - ## parses an unsigned integer starting at `start` and stores the value + rtl, extern: "npuParseBiggestUInt", noSideEffect, raises: [ValueError].} = + ## Parses an unsigned integer starting at `start` and stores the value ## into `number`. - ## Result is the number of processed chars or 0 if there is no integer - ## or overflow detected. + ## `ValueError` is raised if the parsed integer is out of the valid range. + runnableExamples: + var res: BiggestUInt + doAssert parseBiggestUInt("12", res, 0) == 2 + doAssert res == 12 + doAssert parseBiggestUInt("1111111111111111111", res, 0) == 19 + doAssert res == 1111111111111111111'u64 var res: BiggestUInt # use 'res' for exception safety (don't write to 'number' in case of an # overflow exception): result = rawParseUInt(s, res, start) - number = res + if result != 0: + number = res proc parseUInt*(s: string, number: var uint, start = 0): int {. - rtl, extern: "npuParseUInt", noSideEffect.} = - ## parses an unsigned integer starting at `start` and stores the value + rtl, extern: "npuParseUInt", noSideEffect, raises: [ValueError].} = + ## Parses an unsigned integer starting at `start` and stores the value ## into `number`. - ## Result is the number of processed chars or 0 if there is no integer or - ## overflow detected. + ## `ValueError` is raised if the parsed integer is out of the valid range. + runnableExamples: + var res: uint + doAssert parseUInt("3450", res) == 4 + doAssert res == 3450 + doAssert parseUInt("3450", res, 2) == 2 + doAssert res == 50 var res: BiggestUInt result = parseBiggestUInt(s, res, start) when sizeof(BiggestUInt) > sizeof(uint) and sizeof(uint) <= 4: if res > 0xFFFF_FFFF'u64: - raise newException(OverflowError, "overflow") + integerOutOfRangeError() if result != 0: number = uint(res) proc parseBiggestFloat*(s: string, number: var BiggestFloat, start = 0): int {. magic: "ParseBiggestFloat", importc: "nimParseBiggestFloat", noSideEffect.} - ## parses a float starting at `start` and stores the value into `number`. + ## Parses a float starting at `start` and stores the value into `number`. ## Result is the number of processed chars or 0 if a parsing error ## occurred. proc parseFloat*(s: string, number: var float, start = 0): int {. rtl, extern: "npuParseFloat", noSideEffect.} = - ## parses a float starting at `start` and stores the value into `number`. + ## Parses a float starting at `start` and stores the value into `number`. ## Result is the number of processed chars or 0 if there occurred a parsing ## error. + runnableExamples: + var res: float + doAssert parseFloat("32", res, 0) == 2 + doAssert res == 32.0 + doAssert parseFloat("32.57", res, 0) == 5 + doAssert res == 32.57 + doAssert parseFloat("32.57", res, 3) == 2 + doAssert res == 57.00 var bf: BiggestFloat result = parseBiggestFloat(s, bf, start) if result != 0: number = bf type - InterpolatedKind* = enum ## describes for `interpolatedFragments` + InterpolatedKind* = enum ## Describes for `interpolatedFragments` ## which part of the interpolated string is ## yielded; for example in "str$$$var${expr}" ikStr, ## ``str`` part of the interpolated string @@ -490,4 +639,8 @@ when isMainModule: doAssert(parseSaturatedNatural("1_000_000", value) == 9) doAssert value == 1_000_000 + var i64Value: int64 + discard parseBiggestInt("9223372036854775807", i64Value) + doAssert i64Value == 9223372036854775807 + {.pop.} diff --git a/lib/pure/parsexml.nim b/lib/pure/parsexml.nim index 0967f7983..953c5cdde 100644 --- a/lib/pure/parsexml.nim +++ b/lib/pure/parsexml.nim @@ -147,7 +147,7 @@ an HTML document contains. ]## import - hashes, strutils, lexbase, streams, unicode + strutils, lexbase, streams, unicode # the parser treats ``<br />`` as ``<br></br>`` diff --git a/lib/pure/pathnorm.nim b/lib/pure/pathnorm.nim index a33afefbd..ca869fd03 100644 --- a/lib/pure/pathnorm.nim +++ b/lib/pure/pathnorm.nim @@ -66,19 +66,27 @@ proc addNormalizePath*(x: string; result: var string; state: var int; dirSep = D if (state shr 1 == 0) and isSlash(x, b): result.add dirSep state = state or 1 - elif result.len > (state and 1) and isDotDot(x, b): - var d = result.len - # f/.. - while (d-1) > (state and 1) and result[d-1] notin {DirSep, AltSep}: - dec d - if d > 0: setLen(result, d-1) + elif isDotDot(x, b): + if (state shr 1) >= 1: + var d = result.len + # f/.. + while (d-1) > (state and 1) and result[d-1] notin {DirSep, AltSep}: + dec d + if d > 0: + setLen(result, d-1) + dec state, 2 + else: + if result.len > 0 and result[^1] notin {DirSep, AltSep}: + result.add dirSep + result.add substr(x, b[0], b[1]) elif isDot(x, b): discard "discard the dot" elif b[1] >= b[0]: if result.len > 0 and result[^1] notin {DirSep, AltSep}: result.add dirSep result.add substr(x, b[0], b[1]) - inc state, 2 + inc state, 2 + if result == "" and x != "": result = "." proc normalizePath*(path: string; dirSep = DirSep): string = ## Example: @@ -89,7 +97,7 @@ proc normalizePath*(path: string; dirSep = DirSep): string = ## ## - Turns multiple slashes into single slashes. ## - Resolves '/foo/../bar' to '/bar'. - ## - Removes './' from the path. + ## - Removes './' from the path (but "foo/.." becomes ".") result = newStringOfCap(path.len) var state = 0 addNormalizePath(path, result, state, dirSep) diff --git a/lib/pure/random.nim b/lib/pure/random.nim index c458d51eb..378ca6f87 100644 --- a/lib/pure/random.nim +++ b/lib/pure/random.nim @@ -14,6 +14,8 @@ ## ## **Do not use this module for cryptographic purposes!** +import algorithm #For upperBound + include "system/inclrtl" {.push debugger:off.} @@ -155,14 +157,48 @@ proc rand*[T](x: HSlice[T, T]): T = ## For a slice `a .. b` returns a value in the range `a .. b`. result = rand(state, x) -proc rand*[T](r: var Rand; a: openArray[T]): T = - ## returns a random element from the openarray `a`. +proc rand*[T](r: var Rand; a: openArray[T]): T {.deprecated.} = + ## Returns a random element from the openarray `a`. + ## **Deprecated since v0.20.0:** use ``sample`` instead. result = a[rand(r, a.low..a.high)] -proc rand*[T](a: openArray[T]): T = +proc rand*[T: SomeInteger](t: typedesc[T]): T = + ## Returns a random integer in the range `low(T)..high(T)`. + result = cast[T](state.next) + +proc rand*[T](a: openArray[T]): T {.deprecated.} = ## returns a random element from the openarray `a`. + ## **Deprecated since v0.20.0:** use ``sample`` instead. + result = a[rand(a.low..a.high)] + +proc sample*[T](r: var Rand; a: openArray[T]): T = + ## returns a random element from openArray ``a`` using state in ``r``. + result = a[r.rand(a.low..a.high)] + +proc sample*[T](a: openArray[T]): T = + ## returns a random element from openArray ``a`` using non-thread-safe state. result = a[rand(a.low..a.high)] +proc sample*[T, U](r: var Rand; a: openArray[T], cdf: openArray[U]): T = + ## Sample one element from openArray ``a`` when it has cumulative distribution + ## function (CDF) ``cdf`` (not necessarily normalized, any type of elements + ## convertible to ``float``). Uses state in ``r``. E.g.: + ## + ## .. code-block:: nim + ## let val = [ "a", "b", "c", "d" ] # some values + ## var cnt = [1, 2, 3, 4] # histogram of counts + ## echo r.sample(val, cnt.cumsummed) # echo a sample + assert(cdf.len == a.len) # Two basic sanity checks. + assert(float(cdf[^1]) > 0.0) + #While we could check cdf[i-1] <= cdf[i] for i in 1..cdf.len, that could get + #awfully expensive even in debugging modes. + let u = r.rand(float(cdf[^1])) + a[cdf.upperBound(U(u))] + +proc sample*[T, U](a: openArray[T], cdf: openArray[U]): T = + ## Like ``sample(var Rand; openArray[T], openArray[U])``, but uses default + ## non-thread-safe state. + state.sample(a, cdf) proc initRand*(seed: int64): Rand = ## Creates a new ``Rand`` state from ``seed``. @@ -191,8 +227,12 @@ when not defined(nimscript): proc randomize*() {.benign.} = ## Initializes the random number generator with a "random" ## number, i.e. a tickcount. Note: Does not work for NimScript. - let now = times.getTime() - randomize(convert(Seconds, Nanoseconds, now.toUnix) + now.nanosecond) + when defined(js): + let time = int64(times.epochTime() * 1_000_000_000) + randomize(time) + else: + let now = times.getTime() + randomize(convert(Seconds, Nanoseconds, now.toUnix) + now.nanosecond) {.pop.} diff --git a/lib/pure/scgi.nim b/lib/pure/scgi.nim deleted file mode 100644 index e36803823..000000000 --- a/lib/pure/scgi.nim +++ /dev/null @@ -1,295 +0,0 @@ -# -# -# Nim's Runtime Library -# (c) Copyright 2013 Andreas Rumpf, Dominik Picheta -# -# See the file "copying.txt", included in this -# distribution, for details about the copyright. -# - -## This module implements helper procs for SCGI applications. Example: -## -## .. code-block:: Nim -## -## import strtabs, sockets, scgi -## -## var counter = 0 -## proc handleRequest(client: Socket, input: string, -## headers: StringTableRef): bool {.procvar.} = -## inc(counter) -## client.writeStatusOkTextContent() -## client.send("Hello for the $#th time." % $counter & "\c\L") -## return false # do not stop processing -## -## run(handleRequest) -## -## **Warning:** The API of this module is unstable, and therefore is subject -## to change. -## -## **Warning:** This module only supports the old asynchronous interface. -## You may wish to use the `asynchttpserver <asynchttpserver.html>`_ -## instead for web applications. - -include "system/inclrtl" - -import sockets, strutils, os, strtabs, asyncio - -type - ScgiError* = object of IOError ## the exception that is raised, if a SCGI error occurs - -proc raiseScgiError*(msg: string) {.noreturn.} = - ## raises an ScgiError exception with message `msg`. - var e: ref ScgiError - new(e) - e.msg = msg - raise e - -proc parseWord(inp: string, outp: var string, start: int): int = - result = start - while inp[result] != '\0': inc(result) - outp = substr(inp, start, result-1) - -proc parseHeaders(s: string, L: int): StringTableRef = - result = newStringTable() - var i = 0 - while i < L: - var key, val: string - i = parseWord(s, key, i)+1 - i = parseWord(s, val, i)+1 - result[key] = val - if s[i] == ',': inc(i) - else: raiseScgiError("',' after netstring expected") - -proc recvChar(s: Socket): char = - var c: char - if recv(s, addr(c), sizeof(c)) == sizeof(c): - result = c - -type - ScgiState* = object of RootObj ## SCGI state object - server: Socket - bufLen: int - client*: Socket ## the client socket to send data to - headers*: StringTableRef ## the parsed headers - input*: string ## the input buffer - - - # Async - - ClientMode = enum - ClientReadChar, ClientReadHeaders, ClientReadContent - - AsyncClient = ref object - c: AsyncSocket - mode: ClientMode - dataLen: int - headers: StringTableRef ## the parsed headers - input: string ## the input buffer - - AsyncScgiStateObj = object - handleRequest: proc (client: AsyncSocket, - input: string, - headers: StringTableRef) {.closure, gcsafe.} - asyncServer: AsyncSocket - disp: Dispatcher - AsyncScgiState* = ref AsyncScgiStateObj - -proc recvBuffer(s: var ScgiState, L: int) = - if L > s.bufLen: - s.bufLen = L - s.input = newString(L) - if L > 0 and recv(s.client, cstring(s.input), L) != L: - raiseScgiError("could not read all data") - setLen(s.input, L) - -proc open*(s: var ScgiState, port = Port(4000), address = "127.0.0.1", - reuseAddr = false) = - ## opens a connection. - s.bufLen = 4000 - s.input = newString(s.bufLen) # will be reused - - s.server = socket() - if s.server == invalidSocket: raiseOSError(osLastError()) - new(s.client) # Initialise s.client for `next` - if s.server == invalidSocket: raiseScgiError("could not open socket") - #s.server.connect(connectionName, port) - if reuseAddr: - s.server.setSockOpt(OptReuseAddr, true) - bindAddr(s.server, port, address) - listen(s.server) - -proc close*(s: var ScgiState) = - ## closes the connection. - s.server.close() - -proc next*(s: var ScgiState, timeout: int = -1): bool = - ## proceed to the first/next request. Waits ``timeout`` milliseconds for a - ## request, if ``timeout`` is `-1` then this function will never time out. - ## Returns `true` if a new request has been processed. - var rsocks = @[s.server] - if select(rsocks, timeout) == 1 and rsocks.len == 1: - new(s.client) - accept(s.server, s.client) - var L = 0 - while true: - var d = s.client.recvChar() - if d == '\0': - s.client.close() - return false - if d notin strutils.Digits: - if d != ':': raiseScgiError("':' after length expected") - break - L = L * 10 + ord(d) - ord('0') - recvBuffer(s, L+1) - s.headers = parseHeaders(s.input, L) - if s.headers.getOrDefault("SCGI") != "1": raiseScgiError("SCGI Version 1 expected") - L = parseInt(s.headers.getOrDefault("CONTENT_LENGTH")) - recvBuffer(s, L) - return true - -proc writeStatusOkTextContent*(c: Socket, contentType = "text/html") = - ## sends the following string to the socket `c`:: - ## - ## Status: 200 OK\r\LContent-Type: text/html\r\L\r\L - ## - ## You should send this before sending your HTML page, for example. - c.send("Status: 200 OK\r\L" & - "Content-Type: $1\r\L\r\L" % contentType) - -proc run*(handleRequest: proc (client: Socket, input: string, - headers: StringTableRef): bool {.nimcall,gcsafe.}, - port = Port(4000)) = - ## encapsulates the SCGI object and main loop. - var s: ScgiState - s.open(port) - var stop = false - while not stop: - if next(s): - stop = handleRequest(s.client, s.input, s.headers) - s.client.close() - s.close() - -# -- AsyncIO start - -proc recvBufferAsync(client: AsyncClient, L: int): ReadLineResult = - result = ReadPartialLine - var data = "" - if L < 1: - raiseScgiError("Cannot read negative or zero length: " & $L) - let ret = recvAsync(client.c, data, L) - if ret == 0 and data == "": - client.c.close() - return ReadDisconnected - if ret == -1: - return ReadNone # No more data available - client.input.add(data) - if ret == L: - return ReadFullLine - -proc checkCloseSocket(client: AsyncClient) = - if not client.c.isClosed: - if client.c.isSendDataBuffered: - client.c.setHandleWrite do (s: AsyncSocket): - if not s.isClosed and not s.isSendDataBuffered: - s.close() - s.delHandleWrite() - else: client.c.close() - -proc handleClientRead(client: AsyncClient, s: AsyncScgiState) = - case client.mode - of ClientReadChar: - while true: - var d = "" - let ret = client.c.recvAsync(d, 1) - if d == "" and ret == 0: - # Disconnected - client.c.close() - return - if ret == -1: - return # No more data available - if d[0] notin strutils.Digits: - if d[0] != ':': raiseScgiError("':' after length expected") - break - client.dataLen = client.dataLen * 10 + ord(d[0]) - ord('0') - client.mode = ClientReadHeaders - handleClientRead(client, s) # Allow progression - of ClientReadHeaders: - let ret = recvBufferAsync(client, (client.dataLen+1)-client.input.len) - case ret - of ReadFullLine: - client.headers = parseHeaders(client.input, client.input.len-1) - if client.headers.getOrDefault("SCGI") != "1": raiseScgiError("SCGI Version 1 expected") - client.input = "" # For next part - - let contentLen = parseInt(client.headers.getOrDefault("CONTENT_LENGTH")) - if contentLen > 0: - client.mode = ClientReadContent - else: - s.handleRequest(client.c, client.input, client.headers) - checkCloseSocket(client) - of ReadPartialLine, ReadDisconnected, ReadNone: return - of ClientReadContent: - let L = parseInt(client.headers.getOrDefault("CONTENT_LENGTH")) - - client.input.len - if L > 0: - let ret = recvBufferAsync(client, L) - case ret - of ReadFullLine: - s.handleRequest(client.c, client.input, client.headers) - checkCloseSocket(client) - of ReadPartialLine, ReadDisconnected, ReadNone: return - else: - s.handleRequest(client.c, client.input, client.headers) - checkCloseSocket(client) - -proc handleAccept(sock: AsyncSocket, s: AsyncScgiState) = - var client: AsyncSocket - new(client) - accept(s.asyncServer, client) - var asyncClient = AsyncClient(c: client, mode: ClientReadChar, dataLen: 0, - headers: newStringTable(), input: "") - client.handleRead = - proc (sock: AsyncSocket) = - handleClientRead(asyncClient, s) - s.disp.register(client) - -proc open*(handleRequest: proc (client: AsyncSocket, - input: string, headers: StringTableRef) {. - closure, gcsafe.}, - port = Port(4000), address = "127.0.0.1", - reuseAddr = false): AsyncScgiState = - ## Creates an ``AsyncScgiState`` object which serves as a SCGI server. - ## - ## After the execution of ``handleRequest`` the client socket will be closed - ## automatically unless it has already been closed. - var cres: AsyncScgiState - new(cres) - cres.asyncServer = asyncSocket() - cres.asyncServer.handleAccept = proc (s: AsyncSocket) = handleAccept(s, cres) - if reuseAddr: - cres.asyncServer.setSockOpt(OptReuseAddr, true) - bindAddr(cres.asyncServer, port, address) - listen(cres.asyncServer) - cres.handleRequest = handleRequest - result = cres - -proc register*(d: Dispatcher, s: AsyncScgiState): Delegate {.discardable.} = - ## Registers ``s`` with dispatcher ``d``. - result = d.register(s.asyncServer) - s.disp = d - -proc close*(s: AsyncScgiState) = - ## Closes the ``AsyncScgiState``. - s.asyncServer.close() - -when false: - var counter = 0 - proc handleRequest(client: Socket, input: string, - headers: StringTableRef): bool {.procvar.} = - inc(counter) - client.writeStatusOkTextContent() - client.send("Hello for the $#th time." % $counter & "\c\L") - return false # do not stop processing - - run(handleRequest) - diff --git a/lib/pure/selectors.nim b/lib/pure/selectors.nim index e4c2b2124..b9c834127 100644 --- a/lib/pure/selectors.nim +++ b/lib/pure/selectors.nim @@ -239,6 +239,9 @@ else: proc allocSharedArray[T](nsize: int): ptr SharedArray[T] = result = cast[ptr SharedArray[T]](allocShared0(sizeof(T) * nsize)) + proc reallocSharedArray[T](sa: ptr SharedArray[T], nsize: int): ptr SharedArray[T] = + result = cast[ptr SharedArray[T]](reallocShared(sa, sizeof(T) * nsize)) + proc deallocSharedArray[T](sa: ptr SharedArray[T]) = deallocShared(cast[pointer](sa)) type diff --git a/lib/pure/streams.nim b/lib/pure/streams.nim index b0ac62525..10de86e9f 100644 --- a/lib/pure/streams.nim +++ b/lib/pure/streams.nim @@ -146,12 +146,12 @@ proc writeLine*(s: Stream, args: varargs[string, `$`]) = for str in args: write(s, str) write(s, "\n") -proc read[T](s: Stream, result: var T) = +proc read*[T](s: Stream, result: var T) = ## generic read procedure. Reads `result` from the stream `s`. if readData(s, addr(result), sizeof(T)) != sizeof(T): raise newEIO("cannot read from stream") -proc peek[T](s: Stream, result: var T) = +proc peek*[T](s: Stream, result: var T) = ## generic peek procedure. Peeks `result` from the stream `s`. if peekData(s, addr(result), sizeof(T)) != sizeof(T): raise newEIO("cannot read from stream") @@ -271,7 +271,7 @@ proc peekStr*(s: Stream, length: int): TaintedString = proc readLine*(s: Stream, line: var TaintedString): bool = ## 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``. + ## 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. diff --git a/lib/pure/strtabs.nim b/lib/pure/strtabs.nim index d8a23286a..7bafe1675 100644 --- a/lib/pure/strtabs.nim +++ b/lib/pure/strtabs.nim @@ -12,6 +12,34 @@ ## style-insensitive mode. An efficient string substitution operator ``%`` ## for the string table is also provided. +runnableExamples: + + var t = newStringTable() + t["name"] = "John" + t["city"] = "Monaco" + doAssert t.len == 2 + doAssert t.hasKey "name" + doAssert "name" in t + +## String tables can be created from a table constructor: + +runnableExamples: + var t = {"name": "John", "city": "Monaco"}.newStringTable + + +## When using the style insensitive mode ``modeStyleInsensitive``, +## all letters are compared case insensitively within the ASCII range +## and underscores are ignored. + +runnableExamples: + + var x = newStringTable(modeStyleInsensitive) + x["first_name"] = "John" + x["LastName"] = "Doe" + + doAssert x["firstName"] == "John" + doAssert x["last_name"] == "Doe" + import hashes, strutils @@ -214,6 +242,9 @@ proc newStringTable*(keyValuePairs: varargs[tuple[key, val: string]], proc `%`*(f: string, t: StringTableRef, flags: set[FormatFlag] = {}): string {. rtlFunc, extern: "nstFormat".} = ## The `%` operator for string tables. + runnableExamples: + var t = {"name": "John", "city": "Monaco"}.newStringTable + doAssert "${name} lives in ${city}" % t == "John lives in Monaco" const PatternChars = {'a'..'z', 'A'..'Z', '0'..'9', '_', '\x80'..'\xFF'} result = "" diff --git a/lib/pure/strutils.nim b/lib/pure/strutils.nim index 4d0fe800e..8385eb24e 100644 --- a/lib/pure/strutils.nim +++ b/lib/pure/strutils.nim @@ -7,11 +7,70 @@ # distribution, for details about the copyright. # -## This module contains various string utility routines. -## See the module `re <re.html>`_ for regular expression support. -## See the module `pegs <pegs.html>`_ for PEG support. +## The system module defines several common functions for working with strings, +## such as: +## * ``$`` for converting other data-types to strings +## * ``&`` for string concatenation +## * ``add`` for adding a new character or a string to the existing one +## * ``in`` (alias for ``contains``) and ``notin`` for checking if a character +## is in a string +## +## This module builds upon that, providing additional functionality in form of +## procedures, iterators and templates for strings. +## +## .. code-block:: +## import strutils +## +## let +## numbers = @[867, 5309] +## multiLineString = "first line\nsecond line\nthird line" +## +## let jenny = numbers.join("-") +## assert jenny == "867-5309" +## +## assert splitLines(multiLineString) == +## @["first line", "second line", "third line"] +## assert split(multiLineString) == @["first", "line", "second", +## "line", "third", "line"] +## assert indent(multiLineString, 4) == +## " first line\n second line\n third line" +## assert 'z'.repeat(5) == "zzzzz" +## +## The chaining of functions is possible thanks to the +## `method call syntax<manual.html#procedures-method-call-syntax>`_: +## +## .. code-block:: +## import strutils +## from sequtils import map +## +## let jenny = "867-5309" +## assert jenny.split('-').map(parseInt) == @[867, 5309] +## +## assert "Beetlejuice".indent(1).repeat(3).strip == +## "Beetlejuice Beetlejuice Beetlejuice" +## ## This module is available for the `JavaScript target ## <backends.html#the-javascript-target>`_. +## +## ---- +## +## **See also:** +## * `strformat module<strformat.html>`_ for string interpolation and formatting +## * `unicode module<unicode.html>`_ for Unicode UTF-8 handling +## * `sequtils module<collections/sequtils.html>`_ for operations on container +## types (including strings) +## * `parseutils module<parseutils.html>`_ for lower-level parsing of tokens, +## numbers, identifiers, etc. +## * `parseopt module<parseopt.html>`_ for command-line parsing +## * `strtabs module<strtabs.html>`_ for efficient hash tables +## (dictionaries, in some programming languages) mapping from strings to strings +## * `pegs module<pegs.html>`_ for PEG (Parsing Expression Grammar) support +## * `ropes module<ropes.html>`_ for rope data type, which can represent very +## long strings efficiently +## * `re module<re.html>`_ for regular expression (regex) support +## * `strscans<strscans.html>`_ for ``scanf`` and ``scanp`` macros, which offer +## easier substring extraction than regular expressions + import parseutils from math import pow, floor, log10 @@ -38,7 +97,8 @@ else: const Whitespace* = {' ', '\t', '\v', '\r', '\l', '\f'} - ## All the characters that count as whitespace. + ## All the characters that count as whitespace (space, tab, vertical tab, + ## carriage return, new line, form feed) Letters* = {'A'..'Z', 'a'..'z'} ## the set of letters @@ -56,14 +116,15 @@ const ## the set of characters an identifier can start with NewLines* = {'\13', '\10'} - ## the set of characters a newline terminator can start with + ## the set of characters a newline terminator can start with (carriage + ## return, line feed) AllChars* = {'\x00'..'\xFF'} ## A set with all the possible characters. ## ## Not very useful by its own, you can use it to create *inverted* sets to - ## make the `find() proc <#find,string,set[char],int>`_ find **invalid** - ## characters in strings. Example: + ## make the `find proc<#find,string,set[char],Natural,int>`_ + ## find **invalid** characters in strings. Example: ## ## .. code-block:: nim ## let invalid = AllChars - Digits @@ -72,9 +133,10 @@ const proc isAlphaAscii*(c: char): bool {.noSideEffect, procvar, rtl, extern: "nsuIsAlphaAsciiChar".}= - ## Checks whether or not `c` is alphabetical. + ## Checks whether or not character `c` is alphabetical. ## ## This checks a-z, A-Z ASCII characters only. + ## Use `Unicode module<unicode.html>`_ for UTF-8 support. runnableExamples: doAssert isAlphaAscii('e') == true doAssert isAlphaAscii('E') == true @@ -108,6 +170,7 @@ proc isSpaceAscii*(c: char): bool {.noSideEffect, procvar, runnableExamples: doAssert isSpaceAscii('n') == false doAssert isSpaceAscii(' ') == true + doAssert isSpaceAscii('\t') == true return c in Whitespace proc isLowerAscii*(c: char): bool {.noSideEffect, procvar, @@ -115,6 +178,10 @@ proc isLowerAscii*(c: char): bool {.noSideEffect, procvar, ## Checks whether or not `c` is a lower case character. ## ## This checks ASCII characters only. + ## Use `Unicode module<unicode.html>`_ for UTF-8 support. + ## + ## See also: + ## * `toLowerAscii proc<#toLowerAscii,char>`_ runnableExamples: doAssert isLowerAscii('e') == true doAssert isLowerAscii('E') == false @@ -126,138 +193,28 @@ proc isUpperAscii*(c: char): bool {.noSideEffect, procvar, ## Checks whether or not `c` is an upper case character. ## ## This checks ASCII characters only. + ## Use `Unicode module<unicode.html>`_ for UTF-8 support. + ## + ## See also: + ## * `toUpperAscii proc<#toUpperAscii,char>`_ runnableExamples: doAssert isUpperAscii('e') == false doAssert isUpperAscii('E') == true doAssert isUpperAscii('7') == false return c in {'A'..'Z'} -template isImpl(call) = - if s.len == 0: return false - result = true - for c in s: - if not call(c): return false - -proc isAlphaAscii*(s: string): bool {.noSideEffect, procvar, - rtl, extern: "nsuIsAlphaAsciiStr", - deprecated: "Deprecated since version 0.20 since its semantics are unclear".} = - ## Checks whether or not `s` is alphabetical. - ## - ## This checks a-z, A-Z ASCII characters only. - ## Returns true if all characters in `s` are - ## alphabetic and there is at least one character - ## in `s`. - runnableExamples: - doAssert isAlphaAscii("fooBar") == true - doAssert isAlphaAscii("fooBar1") == false - doAssert isAlphaAscii("foo Bar") == false - isImpl isAlphaAscii - -proc isAlphaNumeric*(s: string): bool {.noSideEffect, procvar, - rtl, extern: "nsuIsAlphaNumericStr", - deprecated: "Deprecated since version 0.20 since its semantics are unclear".} = - ## Checks whether or not `s` is alphanumeric. - ## - ## This checks a-z, A-Z, 0-9 ASCII characters only. - ## Returns true if all characters in `s` are - ## alpanumeric and there is at least one character - ## in `s`. - runnableExamples: - doAssert isAlphaNumeric("fooBar") == true - doAssert isAlphaNumeric("fooBar") == true - doAssert isAlphaNumeric("foo Bar") == false - isImpl isAlphaNumeric - -proc isDigit*(s: string): bool {.noSideEffect, procvar, - rtl, extern: "nsuIsDigitStr", - deprecated: "Deprecated since version 0.20 since its semantics are unclear".} = - ## Checks whether or not `s` is a numeric value. - ## - ## This checks 0-9 ASCII characters only. - ## Returns true if all characters in `s` are - ## numeric and there is at least one character - ## in `s`. - runnableExamples: - doAssert isDigit("1908") == true - doAssert isDigit("fooBar1") == false - isImpl isDigit - -proc isSpaceAscii*(s: string): bool {.noSideEffect, procvar, - rtl, extern: "nsuIsSpaceAsciiStr", - deprecated: "Deprecated since version 0.20 since its semantics are unclear".} = - ## Checks whether or not `s` is completely whitespace. - ## - ## Returns true if all characters in `s` are whitespace - ## characters and there is at least one character in `s`. - runnableExamples: - doAssert isSpaceAscii(" ") == true - doAssert isSpaceAscii("") == false - isImpl isSpaceAscii - -template isCaseImpl(s, charProc, skipNonAlpha) = - var hasAtleastOneAlphaChar = false - if s.len == 0: return false - for c in s: - if skipNonAlpha: - var charIsAlpha = c.isAlphaAscii() - if not hasAtleastOneAlphaChar: - hasAtleastOneAlphaChar = charIsAlpha - if charIsAlpha and (not charProc(c)): - return false - else: - if not charProc(c): - return false - return if skipNonAlpha: hasAtleastOneAlphaChar else: true - -proc isLowerAscii*(s: string, skipNonAlpha: bool): bool {. - deprecated: "Deprecated since version 0.20 since its semantics are unclear".} = - ## Checks whether ``s`` is lower case. - ## - ## This checks ASCII characters only. - ## - ## If ``skipNonAlpha`` is true, returns true if all alphabetical - ## characters in ``s`` are lower case. Returns false if none of the - ## characters in ``s`` are alphabetical. - ## - ## If ``skipNonAlpha`` is false, returns true only if all characters - ## in ``s`` are alphabetical and lower case. - ## - ## For either value of ``skipNonAlpha``, returns false if ``s`` is - ## an empty string. - runnableExamples: - doAssert isLowerAscii("1foobar", false) == false - doAssert isLowerAscii("1foobar", true) == true - doAssert isLowerAscii("1fooBar", true) == false - isCaseImpl(s, isLowerAscii, skipNonAlpha) - -proc isUpperAscii*(s: string, skipNonAlpha: bool): bool {. - deprecated: "Deprecated since version 0.20 since its semantics are unclear".} = - ## Checks whether ``s`` is upper case. - ## - ## This checks ASCII characters only. - ## - ## If ``skipNonAlpha`` is true, returns true if all alphabetical - ## characters in ``s`` are upper case. Returns false if none of the - ## characters in ``s`` are alphabetical. - ## - ## If ``skipNonAlpha`` is false, returns true only if all characters - ## in ``s`` are alphabetical and upper case. - ## - ## For either value of ``skipNonAlpha``, returns false if ``s`` is - ## an empty string. - runnableExamples: - doAssert isUpperAscii("1FOO", false) == false - doAssert isUpperAscii("1FOO", true) == true - doAssert isUpperAscii("1Foo", true) == false - isCaseImpl(s, isUpperAscii, skipNonAlpha) proc toLowerAscii*(c: char): char {.noSideEffect, procvar, rtl, extern: "nsuToLowerAsciiChar".} = - ## Returns the lower case version of ``c``. + ## Returns the lower case version of character ``c``. ## ## This works only for the letters ``A-Z``. See `unicode.toLower ## <unicode.html#toLower>`_ for a version that works for any Unicode ## character. + ## + ## See also: + ## * `isLowerAscii proc<#isLowerAscii,char>`_ + ## * `toLowerAscii proc<#toLowerAscii,string>`_ for converting a string runnableExamples: doAssert toLowerAscii('A') == 'a' doAssert toLowerAscii('e') == 'e' @@ -273,22 +230,30 @@ template toImpl(call) = proc toLowerAscii*(s: string): string {.noSideEffect, procvar, rtl, extern: "nsuToLowerAsciiStr".} = - ## Converts `s` into lower case. + ## Converts string `s` into lower case. ## ## This works only for the letters ``A-Z``. See `unicode.toLower ## <unicode.html#toLower>`_ for a version that works for any Unicode ## character. + ## + ## See also: + ## * `normalize proc<#normalize,string>`_ runnableExamples: doAssert toLowerAscii("FooBar!") == "foobar!" toImpl toLowerAscii proc toUpperAscii*(c: char): char {.noSideEffect, procvar, rtl, extern: "nsuToUpperAsciiChar".} = - ## Converts `c` into upper case. + ## Converts character `c` into upper case. ## ## This works only for the letters ``A-Z``. See `unicode.toUpper ## <unicode.html#toUpper>`_ for a version that works for any Unicode ## character. + ## + ## See also: + ## * `isLowerAscii proc<#isLowerAscii,char>`_ + ## * `toUpperAscii proc<#toUpperAscii,string>`_ for converting a string + ## * `capitalizeAscii proc<#capitalizeAscii,string>`_ runnableExamples: doAssert toUpperAscii('a') == 'A' doAssert toUpperAscii('E') == 'E' @@ -299,20 +264,27 @@ proc toUpperAscii*(c: char): char {.noSideEffect, procvar, proc toUpperAscii*(s: string): string {.noSideEffect, procvar, rtl, extern: "nsuToUpperAsciiStr".} = - ## Converts `s` into upper case. + ## Converts string `s` into upper case. ## ## This works only for the letters ``A-Z``. See `unicode.toUpper ## <unicode.html#toUpper>`_ for a version that works for any Unicode ## character. + ## + ## See also: + ## * `capitalizeAscii proc<#capitalizeAscii,string>`_ runnableExamples: doAssert toUpperAscii("FooBar!") == "FOOBAR!" toImpl toUpperAscii proc capitalizeAscii*(s: string): string {.noSideEffect, procvar, rtl, extern: "nsuCapitalizeAscii".} = - ## Converts the first character of `s` into upper case. + ## Converts the first character of string `s` into upper case. ## ## This works only for the letters ``A-Z``. + ## Use `Unicode module<unicode.html>`_ for UTF-8 support. + ## + ## See also: + ## * `toUpperAscii proc<#toUpperAscii,char>`_ runnableExamples: doAssert capitalizeAscii("foo") == "Foo" doAssert capitalizeAscii("-bar") == "-bar" @@ -325,6 +297,9 @@ proc normalize*(s: string): string {.noSideEffect, procvar, ## ## That means to convert it to lower case and remove any '_'. This ## should NOT be used to normalize Nim identifier names. + ## + ## See also: + ## * `toLowerAscii proc<#toLowerAscii,string>`_ runnableExamples: doAssert normalize("Foo_bar") == "foobar" doAssert normalize("Foo Bar") == "foo bar" @@ -343,9 +318,9 @@ proc cmpIgnoreCase*(a, b: string): int {.noSideEffect, rtl, extern: "nsuCmpIgnoreCase", procvar.} = ## Compares two strings in a case insensitive manner. Returns: ## - ## | 0 iff a == b - ## | < 0 iff a < b - ## | > 0 iff a > b + ## | 0 if a == b + ## | < 0 if a < b + ## | > 0 if a > b runnableExamples: doAssert cmpIgnoreCase("FooBar", "foobar") == 0 doAssert cmpIgnoreCase("bar", "Foo") < 0 @@ -365,12 +340,14 @@ proc cmpIgnoreStyle*(a, b: string): int {.noSideEffect, rtl, extern: "nsuCmpIgnoreStyle", procvar.} = ## Semantically the same as ``cmp(normalize(a), normalize(b))``. It ## is just optimized to not allocate temporary strings. This should - ## NOT be used to compare Nim identifier names. use `macros.eqIdent` - ## for that. Returns: + ## NOT be used to compare Nim identifier names. + ## Use `macros.eqIdent<macros.html#eqIdent,string,string>`_ for that. + ## + ## Returns: ## - ## | 0 iff a == b - ## | < 0 iff a < b - ## | > 0 iff a > b + ## | 0 if a == b + ## | < 0 if a < b + ## | > 0 if a > b runnableExamples: doAssert cmpIgnoreStyle("foo_bar", "FooBar") == 0 doAssert cmpIgnoreStyle("foo_bar_5", "FooBar4") > 0 @@ -394,51 +371,8 @@ proc cmpIgnoreStyle*(a, b: string): int {.noSideEffect, inc i inc j -proc strip*(s: string, leading = true, trailing = true, - chars: set[char] = Whitespace): string - {.noSideEffect, rtl, extern: "nsuStrip".} = - ## Strips leading or trailing `chars` from `s` and returns - ## the resulting string. - ## - ## If `leading` is true, leading `chars` are stripped. - ## If `trailing` is true, trailing `chars` are stripped. - ## If both are false, the string is returned unchanged. - runnableExamples: - doAssert " vhellov ".strip().strip(trailing = false, chars = {'v'}) == "hellov" - var - first = 0 - last = len(s)-1 - if leading: - while first <= last and s[first] in chars: inc(first) - if trailing: - while last >= 0 and s[last] in chars: dec(last) - result = substr(s, first, last) - -proc toOctal*(c: char): string {.noSideEffect, rtl, extern: "nsuToOctal".} = - ## Converts a character `c` to its octal representation. - ## - ## The resulting string may not have a leading zero. Its length is always - ## exactly 3. - runnableExamples: - doAssert toOctal('!') == "041" - result = newString(3) - var val = ord(c) - for i in countdown(2, 0): - result[i] = chr(val mod 8 + ord('0')) - val = val div 8 -proc isNilOrEmpty*(s: string): bool {.noSideEffect, procvar, rtl, - extern: "nsuIsNilOrEmpty", - deprecated: "use 'x.len == 0' instead".} = - ## Checks if `s` is nil or empty. - result = len(s) == 0 - -proc isNilOrWhitespace*(s: string): bool {.noSideEffect, procvar, rtl, extern: "nsuIsNilOrWhitespace".} = - ## Checks if `s` is nil or consists entirely of whitespace characters. - result = true - for c in s: - if not c.isSpaceAscii(): - return false +# --------- Private templates for different split separators ----------- proc substrEq(s: string, pos: int, substr: string): bool = var i = 0 @@ -447,8 +381,6 @@ proc substrEq(s: string, pos: int, substr: string): bool = inc i return i == length -# --------- Private templates for different split separators ----------- - template stringHasSep(s: string, index: int, seps: set[char]): bool = s[index] in seps @@ -459,7 +391,7 @@ template stringHasSep(s: string, index: int, sep: string): bool = s.substrEq(index, sep) template splitCommon(s, sep, maxsplit, sepLen) = - ## Common code for split procedures + ## Common code for split procs var last = 0 var splits = maxsplit @@ -487,6 +419,42 @@ template oldSplit(s, seps, maxsplit) = if splits == 0: break dec(splits) +template accResult(iter: untyped) = + result = @[] + for x in iter: add(result, x) + + +iterator split*(s: string, sep: char, maxsplit: int = -1): string = + ## Splits the string `s` into substrings using a single separator. + ## + ## Substrings are separated by the character `sep`. + ## The code: + ## + ## .. code-block:: nim + ## for word in split(";;this;is;an;;example;;;", ';'): + ## writeLine(stdout, word) + ## + ## Results in: + ## + ## .. code-block:: + ## "" + ## "" + ## "this" + ## "is" + ## "an" + ## "" + ## "example" + ## "" + ## "" + ## "" + ## + ## See also: + ## * `rsplit iterator<#rsplit.i,string,char,int>`_ + ## * `splitLines iterator<#splitLines.i,string>`_ + ## * `splitWhitespace iterator<#splitWhitespace.i,string,int>`_ + ## * `split proc<#split,string,char,int>`_ + splitCommon(s, sep, maxsplit, 1) + iterator split*(s: string, seps: set[char] = Whitespace, maxsplit: int = -1): string = ## Splits the string `s` into substrings using a group of separators. @@ -529,79 +497,13 @@ iterator split*(s: string, seps: set[char] = Whitespace, ## "08" ## "08.398990" ## + ## See also: + ## * `rsplit iterator<#rsplit.i,string,set[char],int>`_ + ## * `splitLines iterator<#splitLines.i,string>`_ + ## * `splitWhitespace iterator<#splitWhitespace.i,string,int>`_ + ## * `split proc<#split,string,set[char],int>`_ splitCommon(s, seps, maxsplit, 1) -iterator splitWhitespace*(s: string, maxsplit: int = -1): string = - ## Splits the string ``s`` at whitespace stripping leading and trailing - ## whitespace if necessary. If ``maxsplit`` is specified and is positive, - ## no more than ``maxsplit`` splits is made. - ## - ## The following code: - ## - ## .. code-block:: nim - ## let s = " foo \t bar baz " - ## for ms in [-1, 1, 2, 3]: - ## echo "------ maxsplit = ", ms, ":" - ## for item in s.splitWhitespace(maxsplit=ms): - ## echo '"', item, '"' - ## - ## ...results in: - ## - ## .. code-block:: - ## ------ maxsplit = -1: - ## "foo" - ## "bar" - ## "baz" - ## ------ maxsplit = 1: - ## "foo" - ## "bar baz " - ## ------ maxsplit = 2: - ## "foo" - ## "bar" - ## "baz " - ## ------ maxsplit = 3: - ## "foo" - ## "bar" - ## "baz" - ## - oldSplit(s, Whitespace, maxsplit) - -template accResult(iter: untyped) = - result = @[] - for x in iter: add(result, x) - -proc splitWhitespace*(s: string, maxsplit: int = -1): seq[string] {.noSideEffect, - rtl, extern: "nsuSplitWhitespace".} = - ## The same as the `splitWhitespace <#splitWhitespace.i,string,int>`_ - ## iterator, but is a proc that returns a sequence of substrings. - accResult(splitWhitespace(s, maxsplit)) - -iterator split*(s: string, sep: char, maxsplit: int = -1): string = - ## Splits the string `s` into substrings using a single separator. - ## - ## Substrings are separated by the character `sep`. - ## The code: - ## - ## .. code-block:: nim - ## for word in split(";;this;is;an;;example;;;", ';'): - ## writeLine(stdout, word) - ## - ## Results in: - ## - ## .. code-block:: - ## "" - ## "" - ## "this" - ## "is" - ## "an" - ## "" - ## "example" - ## "" - ## "" - ## "" - ## - splitCommon(s, sep, maxsplit, 1) - iterator split*(s: string, sep: string, maxsplit: int = -1): string = ## Splits the string `s` into substrings using a string separator. ## @@ -619,8 +521,14 @@ iterator split*(s: string, sep: string, maxsplit: int = -1): string = ## "is" ## "corrupted" ## + ## See also: + ## * `rsplit iterator<#rsplit.i,string,string,int,bool>`_ + ## * `splitLines iterator<#splitLines.i,string>`_ + ## * `splitWhitespace iterator<#splitWhitespace.i,string,int>`_ + ## * `split proc<#split,string,string,int>`_ splitCommon(s, sep, maxsplit, sep.len) + template rsplitCommon(s, sep, maxsplit, sepLen) = ## Common code for rsplit functions var @@ -645,14 +553,14 @@ template rsplitCommon(s, sep, maxsplit, sepLen) = dec(first) last = first -iterator rsplit*(s: string, seps: set[char] = Whitespace, +iterator rsplit*(s: string, sep: char, maxsplit: int = -1): string = ## Splits the string `s` into substrings from the right using a ## string separator. Works exactly the same as `split iterator ## <#split.i,string,char,int>`_ except in reverse order. ## ## .. code-block:: nim - ## for piece in "foo bar".rsplit(WhiteSpace): + ## for piece in "foo:bar".rsplit(':'): ## echo piece ## ## Results in: @@ -661,17 +569,23 @@ iterator rsplit*(s: string, seps: set[char] = Whitespace, ## "bar" ## "foo" ## - ## Substrings are separated from the right by the set of chars `seps` - rsplitCommon(s, seps, maxsplit, 1) + ## Substrings are separated from the right by the char `sep`. + ## + ## See also: + ## * `split iterator<#split.i,string,char,int>`_ + ## * `splitLines iterator<#splitLines.i,string>`_ + ## * `splitWhitespace iterator<#splitWhitespace.i,string,int>`_ + ## * `rsplit proc<#rsplit,string,char,int>`_ + rsplitCommon(s, sep, maxsplit, 1) -iterator rsplit*(s: string, sep: char, +iterator rsplit*(s: string, seps: set[char] = Whitespace, maxsplit: int = -1): string = ## Splits the string `s` into substrings from the right using a ## string separator. Works exactly the same as `split iterator ## <#split.i,string,char,int>`_ except in reverse order. ## ## .. code-block:: nim - ## for piece in "foo:bar".rsplit(':'): + ## for piece in "foo bar".rsplit(WhiteSpace): ## echo piece ## ## Results in: @@ -680,8 +594,14 @@ iterator rsplit*(s: string, sep: char, ## "bar" ## "foo" ## - ## Substrings are separated from the right by the char `sep` - rsplitCommon(s, sep, maxsplit, 1) + ## Substrings are separated from the right by the set of chars `seps` + ## + ## See also: + ## * `split iterator<#split.i,string,set[char],int>`_ + ## * `splitLines iterator<#splitLines.i,string>`_ + ## * `splitWhitespace iterator<#splitWhitespace.i,string,int>`_ + ## * `rsplit proc<#rsplit,string,set[char],int>`_ + rsplitCommon(s, seps, maxsplit, 1) iterator rsplit*(s: string, sep: string, maxsplit: int = -1, keepSeparators: bool = false): string = @@ -700,6 +620,12 @@ iterator rsplit*(s: string, sep: string, maxsplit: int = -1, ## "foo" ## ## Substrings are separated from the right by the string `sep` + ## + ## See also: + ## * `split iterator<#split.i,string,string,int>`_ + ## * `splitLines iterator<#splitLines.i,string>`_ + ## * `splitWhitespace iterator<#splitWhitespace.i,string,int>`_ + ## * `rsplit proc<#rsplit,string,string,int>`_ rsplitCommon(s, sep, maxsplit, sep.len) iterator splitLines*(s: string, keepEol = false): string = @@ -726,6 +652,10 @@ iterator splitLines*(s: string, keepEol = false): string = ## "" ## "example" ## "" + ## + ## See also: + ## * `splitWhitespace iterator<#splitWhitespace.i,string,int>`_ + ## * `splitLines proc<#splitLines,string>`_ var first = 0 var last = 0 var eolpos = 0 @@ -747,76 +677,102 @@ iterator splitLines*(s: string, keepEol = false): string = first = last -proc splitLines*(s: string, keepEol = false): seq[string] {.noSideEffect, - rtl, extern: "nsuSplitLines".} = - ## The same as the `splitLines <#splitLines.i,string>`_ iterator, but is a - ## proc that returns a sequence of substrings. - accResult(splitLines(s, keepEol=keepEol)) - -proc countLines*(s: string): int {.noSideEffect, - rtl, extern: "nsuCountLines".} = - ## Returns the number of lines in the string `s`. +iterator splitWhitespace*(s: string, maxsplit: int = -1): string = + ## Splits the string ``s`` at whitespace stripping leading and trailing + ## whitespace if necessary. If ``maxsplit`` is specified and is positive, + ## no more than ``maxsplit`` splits is made. ## - ## This is the same as ``len(splitLines(s))``, but much more efficient - ## because it doesn't modify the string creating temporal objects. Every - ## `character literal <manual.html#character-literals>`_ newline combination - ## (CR, LF, CR-LF) is supported. + ## The following code: ## - ## In this context, a line is any string seperated by a newline combination. - ## A line can be an empty string. - runnableExamples: - doAssert countLines("First line\l and second line.") == 2 - result = 1 - var i = 0 - while i < s.len: - case s[i] - of '\c': - if i+1 < s.len and s[i+1] == '\l': inc i - inc result - of '\l': inc result - else: discard - inc i + ## .. code-block:: nim + ## let s = " foo \t bar baz " + ## for ms in [-1, 1, 2, 3]: + ## echo "------ maxsplit = ", ms, ":" + ## for item in s.splitWhitespace(maxsplit=ms): + ## echo '"', item, '"' + ## + ## ...results in: + ## + ## .. code-block:: + ## ------ maxsplit = -1: + ## "foo" + ## "bar" + ## "baz" + ## ------ maxsplit = 1: + ## "foo" + ## "bar baz " + ## ------ maxsplit = 2: + ## "foo" + ## "bar" + ## "baz " + ## ------ maxsplit = 3: + ## "foo" + ## "bar" + ## "baz" + ## + ## See also: + ## * `splitLines iterator<#splitLines.i,string>`_ + ## * `splitWhitespace proc<#splitWhitespace,string,int>`_ + oldSplit(s, Whitespace, maxsplit) + -proc split*(s: string, seps: set[char] = Whitespace, maxsplit: int = -1): seq[string] {. - noSideEffect, rtl, extern: "nsuSplitCharSet".} = - ## The same as the `split iterator <#split.i,string,set[char],int>`_, but is a - ## proc that returns a sequence of substrings. - runnableExamples: - doAssert "a,b;c".split({',', ';'}) == @["a", "b", "c"] - doAssert "".split({' '}) == @[""] - accResult(split(s, seps, maxsplit)) proc split*(s: string, sep: char, maxsplit: int = -1): seq[string] {.noSideEffect, rtl, extern: "nsuSplitChar".} = - ## The same as the `split iterator <#split.i,string,char,int>`_, but is a proc - ## that returns a sequence of substrings. + ## The same as the `split iterator <#split.i,string,char,int>`_ (see its + ## documentation), but is a proc that returns a sequence of substrings. + ## + ## See also: + ## * `split iterator <#split.i,string,char,int>`_ + ## * `rsplit proc<#rsplit,string,char,int>`_ + ## * `splitLines proc<#splitLines,string>`_ + ## * `splitWhitespace proc<#splitWhitespace,string,int>`_ runnableExamples: doAssert "a,b,c".split(',') == @["a", "b", "c"] doAssert "".split(' ') == @[""] accResult(split(s, sep, maxsplit)) +proc split*(s: string, seps: set[char] = Whitespace, maxsplit: int = -1): seq[string] {. + noSideEffect, rtl, extern: "nsuSplitCharSet".} = + ## The same as the `split iterator <#split.i,string,set[char],int>`_ (see its + ## documentation), but is a proc that returns a sequence of substrings. + ## + ## See also: + ## * `split iterator <#split.i,string,set[char],int>`_ + ## * `rsplit proc<#rsplit,string,set[char],int>`_ + ## * `splitLines proc<#splitLines,string>`_ + ## * `splitWhitespace proc<#splitWhitespace,string,int>`_ + runnableExamples: + doAssert "a,b;c".split({',', ';'}) == @["a", "b", "c"] + doAssert "".split({' '}) == @[""] + accResult(split(s, seps, maxsplit)) + proc split*(s: string, sep: string, maxsplit: int = -1): seq[string] {.noSideEffect, rtl, extern: "nsuSplitString".} = ## Splits the string `s` into substrings using a string separator. ## ## Substrings are separated by the string `sep`. This is a wrapper around the ## `split iterator <#split.i,string,string,int>`_. + ## + ## See also: + ## * `split iterator <#split.i,string,string,int>`_ + ## * `rsplit proc<#rsplit,string,string,int>`_ + ## * `splitLines proc<#splitLines,string>`_ + ## * `splitWhitespace proc<#splitWhitespace,string,int>`_ runnableExamples: doAssert "a,b,c".split(",") == @["a", "b", "c"] doAssert "a man a plan a canal panama".split("a ") == @["", "man ", "plan ", "canal panama"] doAssert "".split("Elon Musk") == @[""] doAssert "a largely spaced sentence".split(" ") == @["a", "", "largely", "", "", "", "spaced", "sentence"] - doAssert "a largely spaced sentence".split(" ", maxsplit=1) == @["a", " largely spaced sentence"] doAssert(sep.len > 0) accResult(split(s, sep, maxsplit)) -proc rsplit*(s: string, seps: set[char] = Whitespace, - maxsplit: int = -1): seq[string] - {.noSideEffect, rtl, extern: "nsuRSplitCharSet".} = - ## The same as the `rsplit iterator <#rsplit.i,string,set[char],int>`_, but is a - ## proc that returns a sequence of substrings. +proc rsplit*(s: string, sep: char, maxsplit: int = -1): seq[string] + {.noSideEffect, rtl, extern: "nsuRSplitChar".} = + ## The same as the `rsplit iterator <#rsplit.i,string,char,int>`_, but is a proc + ## that returns a sequence of substrings. ## ## A possible common use case for `rsplit` is path manipulation, ## particularly on systems that don't use a common delimiter. @@ -825,20 +781,26 @@ proc rsplit*(s: string, seps: set[char] = Whitespace, ## do the following to get the tail of the path: ## ## .. code-block:: nim - ## var tailSplit = rsplit("Root#Object#Method#Index", {'#'}, maxsplit=1) + ## var tailSplit = rsplit("Root#Object#Method#Index", '#', maxsplit=1) ## ## Results in `tailSplit` containing: ## ## .. code-block:: nim ## @["Root#Object#Method", "Index"] ## - accResult(rsplit(s, seps, maxsplit)) + ## See also: + ## * `rsplit iterator <#rsplit.i,string,char,int>`_ + ## * `split proc<#split,string,char,int>`_ + ## * `splitLines proc<#splitLines,string>`_ + ## * `splitWhitespace proc<#splitWhitespace,string,int>`_ + accResult(rsplit(s, sep, maxsplit)) result.reverse() -proc rsplit*(s: string, sep: char, maxsplit: int = -1): seq[string] - {.noSideEffect, rtl, extern: "nsuRSplitChar".} = - ## The same as the `rsplit iterator <#rsplit.i,string,char,int>`_, but is a proc - ## that returns a sequence of substrings. +proc rsplit*(s: string, seps: set[char] = Whitespace, + maxsplit: int = -1): seq[string] + {.noSideEffect, rtl, extern: "nsuRSplitCharSet".} = + ## The same as the `rsplit iterator <#rsplit.i,string,set[char],int>`_, but is a + ## proc that returns a sequence of substrings. ## ## A possible common use case for `rsplit` is path manipulation, ## particularly on systems that don't use a common delimiter. @@ -847,19 +809,24 @@ proc rsplit*(s: string, sep: char, maxsplit: int = -1): seq[string] ## do the following to get the tail of the path: ## ## .. code-block:: nim - ## var tailSplit = rsplit("Root#Object#Method#Index", '#', maxsplit=1) + ## var tailSplit = rsplit("Root#Object#Method#Index", {'#'}, maxsplit=1) ## ## Results in `tailSplit` containing: ## ## .. code-block:: nim ## @["Root#Object#Method", "Index"] ## - accResult(rsplit(s, sep, maxsplit)) + ## See also: + ## * `rsplit iterator <#rsplit.i,string,set[char],int>`_ + ## * `split proc<#split,string,set[char],int>`_ + ## * `splitLines proc<#splitLines,string>`_ + ## * `splitWhitespace proc<#splitWhitespace,string,int>`_ + accResult(rsplit(s, seps, maxsplit)) result.reverse() proc rsplit*(s: string, sep: string, maxsplit: int = -1): seq[string] {.noSideEffect, rtl, extern: "nsuRSplitString".} = - ## The same as the `rsplit iterator <#rsplit.i,string,string,int>`_, but is a proc + ## The same as the `rsplit iterator <#rsplit.i,string,string,int,bool>`_, but is a proc ## that returns a sequence of substrings. ## ## A possible common use case for `rsplit` is path manipulation, @@ -876,9 +843,13 @@ proc rsplit*(s: string, sep: string, maxsplit: int = -1): seq[string] ## .. code-block:: nim ## @["Root#Object#Method", "Index"] ## + ## See also: + ## * `rsplit iterator <#rsplit.i,string,string,int,bool>`_ + ## * `split proc<#split,string,string,int>`_ + ## * `splitLines proc<#splitLines,string>`_ + ## * `splitWhitespace proc<#splitWhitespace,string,int>`_ runnableExamples: doAssert "a largely spaced sentence".rsplit(" ", maxsplit=1) == @["a largely spaced", "sentence"] - doAssert "a,b,c".rsplit(",") == @["a", "b", "c"] doAssert "a man a plan a canal panama".rsplit("a ") == @["", "man ", "plan ", "canal panama"] doAssert "".rsplit("Elon Musk") == @[""] @@ -886,6 +857,75 @@ proc rsplit*(s: string, sep: string, maxsplit: int = -1): seq[string] accResult(rsplit(s, sep, maxsplit)) result.reverse() +proc splitLines*(s: string, keepEol = false): seq[string] {.noSideEffect, + rtl, extern: "nsuSplitLines".} = + ## The same as the `splitLines iterator<#splitLines.i,string>`_ (see its + ## documentation), but is a proc that returns a sequence of substrings. + ## + ## See also: + ## * `splitLines iterator<#splitLines.i,string>`_ + ## * `splitWhitespace proc<#splitWhitespace,string,int>`_ + ## * `countLines proc<#countLines,string>`_ + accResult(splitLines(s, keepEol=keepEol)) + +proc splitWhitespace*(s: string, maxsplit: int = -1): seq[string] {.noSideEffect, + rtl, extern: "nsuSplitWhitespace".} = + ## The same as the `splitWhitespace iterator <#splitWhitespace.i,string,int>`_ + ## (see its documentation), but is a proc that returns a sequence of substrings. + ## + ## See also: + ## * `splitWhitespace iterator <#splitWhitespace.i,string,int>`_ + ## * `splitLines proc<#splitLines,string>`_ + accResult(splitWhitespace(s, maxsplit)) + +proc toBin*(x: BiggestInt, len: Positive): string {.noSideEffect, + rtl, extern: "nsuToBin".} = + ## Converts `x` into its binary representation. + ## + ## The resulting string is always `len` characters long. No leading ``0b`` + ## prefix is generated. + runnableExamples: + let + a = 29 + b = 257 + doAssert a.toBin(8) == "00011101" + doAssert b.toBin(8) == "00000001" + doAssert b.toBin(9) == "100000001" + var + mask: BiggestInt = 1 + shift: BiggestInt = 0 + assert(len > 0) + result = newString(len) + for j in countdown(len-1, 0): + result[j] = chr(int((x and mask) shr shift) + ord('0')) + shift = shift + 1 + mask = mask shl 1 + +proc toOct*(x: BiggestInt, len: Positive): string {.noSideEffect, + rtl, extern: "nsuToOct".} = + ## Converts `x` into its octal representation. + ## + ## The resulting string is always `len` characters long. No leading ``0o`` + ## prefix is generated. + ## + ## Do not confuse it with `toOctal proc<#toOctal,char>`_. + runnableExamples: + let + a = 62 + b = 513 + doAssert a.toOct(3) == "076" + doAssert b.toOct(3) == "001" + doAssert b.toOct(5) == "01001" + var + mask: BiggestInt = 7 + shift: BiggestInt = 0 + assert(len > 0) + result = newString(len) + for j in countdown(len-1, 0): + result[j] = chr(int((x and mask) shr shift) + ord('0')) + shift = shift + 3 + mask = mask shl 3 + proc toHex*(x: BiggestInt, len: Positive): string {.noSideEffect, rtl, extern: "nsuToHex".} = ## Converts `x` to its hexadecimal representation. @@ -893,8 +933,12 @@ proc toHex*(x: BiggestInt, len: Positive): string {.noSideEffect, ## The resulting string will be exactly `len` characters long. No prefix like ## ``0x`` is generated. `x` is treated as an unsigned value. runnableExamples: - doAssert toHex(1984, 6) == "0007C0" - doAssert toHex(1984, 2) == "C0" + let + a = 62 + b = 4097 + doAssert a.toHex(3) == "03E" + doAssert b.toHex(3) == "001" + doAssert b.toHex(4) == "1001" const HexChars = "0123456789ABCDEF" var @@ -917,6 +961,18 @@ proc toHex*(s: string): string {.noSideEffect, rtl.} = ## ## The output is twice the input long. No prefix like ## ``0x`` is generated. + ## + ## See also: + ## * `parseHexStr proc<#parseHexStr,string>`_ for the reverse operation + runnableExamples: + let + a = "1" + b = "A" + c = "\0\255" + doAssert a.toHex() == "31" + doAssert b.toHex() == "41" + doAssert c.toHex() == "00FF" + const HexChars = "0123456789ABCDEF" result = newString(s.len * 2) for pos, c in s: @@ -925,6 +981,25 @@ proc toHex*(s: string): string {.noSideEffect, rtl.} = n = n shr 4 result[pos * 2] = HexChars[n] +proc toOctal*(c: char): string {.noSideEffect, rtl, extern: "nsuToOctal".} = + ## Converts a character `c` to its octal representation. + ## + ## The resulting string may not have a leading zero. Its length is always + ## exactly 3. + ## + ## Do not confuse it with `toOct proc<#toOct,BiggestInt,Positive>`_. + runnableExamples: + doAssert toOctal('1') == "061" + doAssert toOctal('A') == "101" + doAssert toOctal('a') == "141" + doAssert toOctal('!') == "041" + + result = newString(3) + var val = ord(c) + for i in countdown(2, 0): + result[i] = chr(val mod 8 + ord('0')) + val = val div 8 + proc intToStr*(x: int, minchars: Positive = 1): string {.noSideEffect, rtl, extern: "nsuIntToStr".} = ## Converts `x` to its decimal representation. @@ -980,9 +1055,10 @@ proc parseBiggestUInt*(s: string): BiggestUInt {.noSideEffect, procvar, proc parseFloat*(s: string): float {.noSideEffect, procvar, rtl, extern: "nsuParseFloat".} = - ## Parses a decimal floating point value contained in `s`. If `s` is not - ## a valid floating point number, `ValueError` is raised. ``NAN``, - ## ``INF``, ``-INF`` are also supported (case insensitive comparison). + ## Parses a decimal floating point value contained in `s`. + ## + ## If `s` is not a valid floating point number, `ValueError` is raised. + ##``NAN``, ``INF``, ``-INF`` are also supported (case insensitive comparison). runnableExamples: doAssert parseFloat("3.14") == 3.14 doAssert parseFloat("inf") == 1.0/0 @@ -997,6 +1073,13 @@ proc parseBinInt*(s: string): int {.noSideEffect, procvar, ## If `s` is not a valid binary integer, `ValueError` is raised. `s` can have ## one of the following optional prefixes: ``0b``, ``0B``. Underscores within ## `s` are ignored. + runnableExamples: + let + a = "0b11_0101" + b = "111" + doAssert a.parseBinInt() == 53 + doAssert b.parseBinInt() == 7 + let L = parseutils.parseBin(s, result, 0) if L != s.len or L == 0: raise newException(ValueError, "invalid binary integer: " & s) @@ -1042,11 +1125,20 @@ proc parseHexStr*(s: string): string {.noSideEffect, procvar, rtl, extern: "nsuParseHexStr".} = ## Convert hex-encoded string to byte string, e.g.: ## - ## .. code-block:: nim - ## hexToStr("00ff") == "\0\255" - ## ## Raises ``ValueError`` for an invalid hex values. The comparison is ## case-insensitive. + ## + ## See also: + ## * `toHex proc<#toHex,string>`_ for the reverse operation + runnableExamples: + let + a = "41" + b = "3161" + c = "00ff" + doAssert parseHexStr(a) == "A" + doAssert parseHexStr(b) == "1a" + doAssert parseHexStr(c) == "\0\255" + if s.len mod 2 != 0: raise newException(ValueError, "Incorrect hex string len") result = newString(s.len div 2) @@ -1067,6 +1159,10 @@ proc parseBool*(s: string): bool = ## returns `true`. If ``s`` is one of the following values: ``n, no, false, ## 0, off``, then returns `false`. If ``s`` is something else a ## ``ValueError`` exception is raised. + runnableExamples: + let a = "n" + doAssert parseBool(a) == false + case normalize(s) of "y", "yes", "true", "1", "on": result = true of "n", "no", "false", "0", "off": result = false @@ -1095,39 +1191,41 @@ proc parseEnum*[T: enum](s: string, default: T): T = proc repeat*(c: char, count: Natural): string {.noSideEffect, rtl, extern: "nsuRepeatChar".} = ## Returns a string of length `count` consisting only of - ## the character `c`. You can use this proc to left align strings. Example: - ## - ## .. code-block:: nim - ## proc tabexpand(indent: int, text: string, tabsize: int = 4) = - ## echo '\t'.repeat(indent div tabsize), ' '.repeat(indent mod tabsize), - ## text - ## - ## tabexpand(4, "At four") - ## tabexpand(5, "At five") - ## tabexpand(6, "At six") + ## the character `c`. + runnableExamples: + let a = 'z' + doAssert a.repeat(5) == "zzzzz" result = newString(count) for i in 0..count-1: result[i] = c proc repeat*(s: string, n: Natural): string {.noSideEffect, rtl, extern: "nsuRepeatStr".} = - ## Returns String `s` concatenated `n` times. Example: - ## - ## .. code-block:: nim - ## echo "+++ STOP ".repeat(4), "+++" + ## Returns string `s` concatenated `n` times. + runnableExamples: + doAssert "+ foo +".repeat(3) == "+ foo ++ foo ++ foo +" + result = newStringOfCap(n * s.len) for i in 1..n: result.add(s) -template spaces*(n: Natural): string = repeat(' ', n) - ## Returns a String with `n` space characters. You can use this proc - ## to left align strings. Example: +proc spaces*(n: Natural): string {.inline.} = + ## Returns a string with `n` space characters. You can use this proc + ## to left align strings. ## - ## .. code-block:: nim - ## let - ## width = 15 - ## text1 = "Hello user!" - ## text2 = "This is a very long string" - ## echo text1 & spaces(max(0, width - text1.len)) & "|" - ## echo text2 & spaces(max(0, width - text2.len)) & "|" + ## See also: + ## * `align proc<#align,string,Natural,Char>`_ + ## * `alignLeft proc<#alignLeft,string,Natural,Char>`_ + ## * `indent proc<#indent,string,Natural,string>`_ + ## * `center proc<#center,string,int,char>`_ + runnableExamples: + let + width = 15 + text1 = "Hello user!" + text2 = "This is a very long string" + doAssert text1 & spaces(max(0, width - text1.len)) & "|" == + "Hello user! |" + doAssert text2 & spaces(max(0, width - text2.len)) & "|" == + "This is a very long string|" + repeat(' ', n) proc align*(s: string, count: Natural, padding = ' '): string {. noSideEffect, rtl, extern: "nsuAlignString".} = @@ -1136,13 +1234,18 @@ proc align*(s: string, count: Natural, padding = ' '): string {. ## `padding` characters (by default spaces) are added before `s` resulting in ## right alignment. If ``s.len >= count``, no spaces are added and `s` is ## returned unchanged. If you need to left align a string use the `alignLeft - ## proc <#alignLeft>`_. Example: + ## proc <#alignLeft,string,Natural,Char>`_. ## - ## .. code-block:: nim - ## assert align("abc", 4) == " abc" - ## assert align("a", 0) == "a" - ## assert align("1232", 6) == " 1232" - ## assert align("1232", 6, '#') == "##1232" + ## See also: + ## * `alignLeft proc<#alignLeft,string,Natural,Char>`_ + ## * `spaces proc<#spaces,Natural>`_ + ## * `indent proc<#indent,string,Natural,string>`_ + ## * `center proc<#center,string,int,char>`_ + runnableExamples: + assert align("abc", 4) == " abc" + assert align("a", 0) == "a" + assert align("1232", 6) == " 1232" + assert align("1232", 6, '#') == "##1232" if s.len < count: result = newString(count) let spaces = count - s.len @@ -1157,13 +1260,18 @@ proc alignLeft*(s: string, count: Natural, padding = ' '): string {.noSideEffect ## `padding` characters (by default spaces) are added after `s` resulting in ## left alignment. If ``s.len >= count``, no spaces are added and `s` is ## returned unchanged. If you need to right align a string use the `align - ## proc <#align>`_. Example: + ## proc <#align,string,Natural,Char>`_. ## - ## .. code-block:: nim - ## assert alignLeft("abc", 4) == "abc " - ## assert alignLeft("a", 0) == "a" - ## assert alignLeft("1232", 6) == "1232 " - ## assert alignLeft("1232", 6, '#') == "1232##" + ## See also: + ## * `align proc<#align,string,Natural,Char>`_ + ## * `spaces proc<#spaces,Natural>`_ + ## * `indent proc<#indent,string,Natural,string>`_ + ## * `center proc<#center,string,int,char>`_ + runnableExamples: + assert alignLeft("abc", 4) == "abc " + assert alignLeft("a", 0) == "a" + assert alignLeft("1232", 6) == "1232 " + assert alignLeft("1232", 6, '#') == "1232##" if s.len < count: result = newString(count) if s.len > 0: @@ -1173,83 +1281,55 @@ proc alignLeft*(s: string, count: Natural, padding = ' '): string {.noSideEffect else: result = s -iterator tokenize*(s: string, seps: set[char] = Whitespace): tuple[ - token: string, isSep: bool] = - ## Tokenizes the string `s` into substrings. - ## - ## Substrings are separated by a substring containing only `seps`. - ## Examples: - ## - ## .. code-block:: nim - ## for word in tokenize(" this is an example "): - ## writeLine(stdout, word) +proc center*(s: string, width: int, fillChar: char = ' '): string {. + noSideEffect, rtl, extern: "nsuCenterString".} = + ## Return the contents of `s` centered in a string `width` long using + ## `fillChar` (default: space) as padding. ## - ## Results in: + ## The original string is returned if `width` is less than or equal + ## to `s.len`. ## - ## .. code-block:: nim - ## (" ", true) - ## ("this", false) - ## (" ", true) - ## ("is", false) - ## (" ", true) - ## ("an", false) - ## (" ", true) - ## ("example", false) - ## (" ", true) - var i = 0 - while true: - var j = i - var isSep = j < s.len and s[j] in seps - while j < s.len and (s[j] in seps) == isSep: inc(j) - if j > i: - yield (substr(s, i, j-1), isSep) - else: - break - i = j - -proc wordWrap*(s: string, maxLineWidth = 80, - splitLongWords = true, - seps: set[char] = Whitespace, - newLine = "\n"): string {. - noSideEffect, rtl, extern: "nsuWordWrap", - deprecated: "use wrapWords in std/wordwrap instead".} = - ## Word wraps `s`. - result = newStringOfCap(s.len + s.len shr 6) - var spaceLeft = maxLineWidth - var lastSep = "" - for word, isSep in tokenize(s, seps): - if isSep: - lastSep = word - spaceLeft = spaceLeft - len(word) - continue - if len(word) > spaceLeft: - if splitLongWords and len(word) > maxLineWidth: - result.add(substr(word, 0, spaceLeft-1)) - var w = spaceLeft - var wordLeft = len(word) - spaceLeft - while wordLeft > 0: - result.add(newLine) - var L = min(maxLineWidth, wordLeft) - spaceLeft = maxLineWidth - L - result.add(substr(word, w, w+L-1)) - inc(w, L) - dec(wordLeft, L) - else: - spaceLeft = maxLineWidth - len(word) - result.add(newLine) - result.add(word) + ## See also: + ## * `align proc<#align,string,Natural,Char>`_ + ## * `alignLeft proc<#alignLeft,string,Natural,Char>`_ + ## * `spaces proc<#spaces,Natural>`_ + ## * `indent proc<#indent,string,Natural,string>`_ + runnableExamples: + let a = "foo" + doAssert a.center(2) == "foo" + doAssert a.center(5) == " foo " + doAssert a.center(6) == " foo " + if width <= s.len: return s + result = newString(width) + # Left padding will be one fillChar + # smaller if there are an odd number + # of characters + let + charsLeft = (width - s.len) + leftPadding = charsLeft div 2 + for i in 0 ..< width: + if i >= leftPadding and i < leftPadding + s.len: + # we are where the string should be located + result[i] = s[i-leftPadding] else: - spaceLeft = spaceLeft - len(word) - result.add(lastSep & word) - lastSep.setLen(0) + # we are either before or after where + # the string s should go + result[i] = fillChar proc indent*(s: string, count: Natural, padding: string = " "): string {.noSideEffect, rtl, extern: "nsuIndent".} = ## Indents each line in ``s`` by ``count`` amount of ``padding``. ## ## **Note:** This does not preserve the new line characters used in ``s``. + ## + ## See also: + ## * `align proc<#align,string,Natural,Char>`_ + ## * `alignLeft proc<#alignLeft,string,Natural,Char>`_ + ## * `spaces proc<#spaces,Natural>`_ + ## * `unindent proc<#unindent,string,Natural,string>`_ runnableExamples: - doAssert indent("First line\c\l and second line.", 2) == " First line\l and second line." + doAssert indent("First line\c\l and second line.", 2) == + " First line\l and second line." result = "" var i = 0 for line in s.splitLines(): @@ -1266,8 +1346,15 @@ proc unindent*(s: string, count: Natural, padding: string = " "): string ## Sometimes called `dedent`:idx: ## ## **Note:** This does not preserve the new line characters used in ``s``. + ## + ## See also: + ## * `align proc<#align,string,Natural,Char>`_ + ## * `alignLeft proc<#alignLeft,string,Natural,Char>`_ + ## * `spaces proc<#spaces,Natural>`_ + ## * `indent proc<#indent,string,Natural,string>`_ runnableExamples: - doAssert unindent(" First line\l and second line", 3) == "First line\land second line" + doAssert unindent(" First line\l and second line", 3) == + "First line\land second line" result = "" var i = 0 for line in s.splitLines(): @@ -1286,37 +1373,105 @@ proc unindent*(s: string): string {.noSideEffect, rtl, extern: "nsuUnindentAll".} = ## Removes all indentation composed of whitespace from each line in ``s``. ## - ## For example: + ## See also: + ## * `align proc<#align,string,Natural,Char>`_ + ## * `alignLeft proc<#alignLeft,string,Natural,Char>`_ + ## * `spaces proc<#spaces,Natural>`_ + ## * `indent proc<#indent,string,Natural,string>`_ + runnableExamples: + let x = """ + Hello + There + """.unindent() + + doAssert x == "Hello\nThere\n" + unindent(s, 1000) # TODO: Passing a 1000 is a bit hackish. + +proc delete*(s: var string, first, last: int) {.noSideEffect, + rtl, extern: "nsuDelete".} = + ## Deletes in `s` (must be declared as ``var``) the characters at positions + ## ``first ..last`` (both ends included). ## - ## .. code-block:: nim - ## const x = """ - ## Hello - ## There - ## """.unindent() + ## This modifies `s` itself, it does not return a copy. + runnableExamples: + var a = "abracadabra" + + a.delete(4, 5) + doAssert a == "abradabra" + + a.delete(1, 6) + doAssert a == "ara" + + var i = first + var j = last+1 + var newLen = len(s)-j+i + while i < newLen: + s[i] = s[j] + inc(i) + inc(j) + setLen(s, newLen) + + +proc startsWith*(s: string, prefix: char): bool {.noSideEffect, inline.} = + ## Returns true if ``s`` starts with character ``prefix``. ## - ## doAssert x == "Hello\nThere\n" - unindent(s, 1000) # TODO: Passing a 1000 is a bit hackish. + ## See also: + ## * `endsWith proc<#endsWith,string,char>`_ + ## * `continuesWith proc<#continuesWith,string,string,Natural>`_ + ## * `removePrefix proc<#removePrefix,string,char>`_ + runnableExamples: + let a = "abracadabra" + doAssert a.startsWith('a') == true + doAssert a.startsWith('b') == false + result = s.len > 0 and s[0] == prefix proc startsWith*(s, prefix: string): bool {.noSideEffect, rtl, extern: "nsuStartsWith".} = - ## Returns true iff ``s`` starts with ``prefix``. + ## Returns true if ``s`` starts with string ``prefix``. ## ## If ``prefix == ""`` true is returned. + ## + ## See also: + ## * `endsWith proc<#endsWith,string,string>`_ + ## * `continuesWith proc<#continuesWith,string,string,Natural>`_ + ## * `removePrefix proc<#removePrefix,string,string>`_ + runnableExamples: + let a = "abracadabra" + doAssert a.startsWith("abra") == true + doAssert a.startsWith("bra") == false var i = 0 while true: if i >= prefix.len: return true if i >= s.len or s[i] != prefix[i]: return false inc(i) -proc startsWith*(s: string, prefix: char): bool {.noSideEffect, inline.} = - ## Returns true iff ``s`` starts with ``prefix``. - result = s.len > 0 and s[0] == prefix +proc endsWith*(s: string, suffix: char): bool {.noSideEffect, inline.} = + ## Returns true if ``s`` ends with ``suffix``. + ## + ## See also: + ## * `startsWith proc<#startsWith,string,char>`_ + ## * `continuesWith proc<#continuesWith,string,string,Natural>`_ + ## * `removeSuffix proc<#removeSuffix,string,char>`_ + runnableExamples: + let a = "abracadabra" + doAssert a.endsWith('a') == true + doAssert a.endsWith('b') == false + result = s.len > 0 and s[s.high] == suffix proc endsWith*(s, suffix: string): bool {.noSideEffect, rtl, extern: "nsuEndsWith".} = - ## Returns true iff ``s`` ends with ``suffix``. + ## Returns true if ``s`` ends with ``suffix``. ## ## If ``suffix == ""`` true is returned. + ## + ## See also: + ## * `startsWith proc<#startsWith,string,string>`_ + ## * `continuesWith proc<#continuesWith,string,string,Natural>`_ + ## * `removeSuffix proc<#removeSuffix,string,string>`_ + runnableExamples: + let a = "abracadabra" + doAssert a.endsWith("abra") == true + doAssert a.endsWith("dab") == false var i = 0 var j = len(s) - len(suffix) while i+j <% s.len: @@ -1324,21 +1479,136 @@ proc endsWith*(s, suffix: string): bool {.noSideEffect, inc(i) if i >= suffix.len: return true -proc endsWith*(s: string, suffix: char): bool {.noSideEffect, inline.} = - ## Returns true iff ``s`` ends with ``suffix``. - result = s.len > 0 and s[s.high] == suffix - proc continuesWith*(s, substr: string, start: Natural): bool {.noSideEffect, rtl, extern: "nsuContinuesWith".} = - ## Returns true iff ``s`` continues with ``substr`` at position ``start``. + ## Returns true if ``s`` continues with ``substr`` at position ``start``. ## ## If ``substr == ""`` true is returned. + ## + ## See also: + ## * `startsWith proc<#startsWith,string,string>`_ + ## * `endsWith proc<#endsWith,string,string>`_ + runnableExamples: + let a = "abracadabra" + doAssert a.continuesWith("ca", 4) == true + doAssert a.continuesWith("ca", 5) == false + doAssert a.continuesWith("dab", 6) == true var i = 0 while true: if i >= substr.len: return true if i+start >= s.len or s[i+start] != substr[i]: return false inc(i) + +proc removePrefix*(s: var string, chars: set[char] = Newlines) {. + rtl, extern: "nsuRemovePrefixCharSet".} = + ## Removes all characters from `chars` from the start of the string `s` + ## (in-place). + ## + ## See also: + ## * `removeSuffix proc<#removeSuffix,string,set[char]>`_ + runnableExamples: + var userInput = "\r\n*~Hello World!" + userInput.removePrefix + doAssert userInput == "*~Hello World!" + userInput.removePrefix({'~', '*'}) + doAssert userInput == "Hello World!" + + var otherInput = "?!?Hello!?!" + otherInput.removePrefix({'!', '?'}) + doAssert otherInput == "Hello!?!" + + var start = 0 + while start < s.len and s[start] in chars: start += 1 + if start > 0: s.delete(0, start - 1) + +proc removePrefix*(s: var string, c: char) {. + rtl, extern: "nsuRemovePrefixChar".} = + ## Removes all occurrences of a single character (in-place) from the start + ## of a string. + ## + ## See also: + ## * `removeSuffix proc<#removeSuffix,string,char>`_ + ## * `startsWith proc<#startsWith,string,char>`_ + runnableExamples: + var ident = "pControl" + ident.removePrefix('p') + doAssert ident == "Control" + removePrefix(s, chars = {c}) + +proc removePrefix*(s: var string, prefix: string) {. + rtl, extern: "nsuRemovePrefixString".} = + ## Remove the first matching prefix (in-place) from a string. + ## + ## See also: + ## * `removeSuffix proc<#removeSuffix,string,string>`_ + ## * `startsWith proc<#startsWith,string,string>`_ + runnableExamples: + var answers = "yesyes" + answers.removePrefix("yes") + doAssert answers == "yes" + if s.startsWith(prefix): + s.delete(0, prefix.len - 1) + +proc removeSuffix*(s: var string, chars: set[char] = Newlines) {. + rtl, extern: "nsuRemoveSuffixCharSet".} = + ## Removes all characters from `chars` from the end of the string `s` + ## (in-place). + ## + ## See also: + ## * `removePrefix proc<#removePrefix,string,set[char]>`_ + runnableExamples: + var userInput = "Hello World!*~\r\n" + userInput.removeSuffix + doAssert userInput == "Hello World!*~" + userInput.removeSuffix({'~', '*'}) + doAssert userInput == "Hello World!" + + var otherInput = "Hello!?!" + otherInput.removeSuffix({'!', '?'}) + doAssert otherInput == "Hello" + + if s.len == 0: return + var last = s.high + while last > -1 and s[last] in chars: last -= 1 + s.setLen(last + 1) + +proc removeSuffix*(s: var string, c: char) {. + rtl, extern: "nsuRemoveSuffixChar".} = + ## Removes all occurrences of a single character (in-place) from the end + ## of a string. + ## + ## See also: + ## * `removePrefix proc<#removePrefix,string,char>`_ + ## * `endsWith proc<#endsWith,string,char>`_ + runnableExamples: + var table = "users" + table.removeSuffix('s') + doAssert table == "user" + + var dots = "Trailing dots......." + dots.removeSuffix('.') + doAssert dots == "Trailing dots" + + removeSuffix(s, chars = {c}) + +proc removeSuffix*(s: var string, suffix: string) {. + rtl, extern: "nsuRemoveSuffixString".} = + ## Remove the first matching suffix (in-place) from a string. + ## + ## See also: + ## * `removePrefix proc<#removePrefix,string,string>`_ + ## * `endsWith proc<#endsWith,string,string>`_ + runnableExamples: + var answers = "yeses" + answers.removeSuffix("es") + doAssert answers == "yes" + var newLen = s.len + if s.endsWith(suffix): + newLen -= len(suffix) + s.setLen(newLen) + + proc addSep*(dest: var string, sep = ", ", startLen: Natural = 0) {.noSideEffect, inline.} = ## Adds a separator to `dest` only if its length is bigger than `startLen`. @@ -1353,24 +1623,28 @@ proc addSep*(dest: var string, sep = ", ", startLen: Natural = 0) ## `startLen`. The following example creates a string describing ## an array of integers. runnableExamples: - var arr = "[" - for x in items([2, 3, 5, 7, 11]): - addSep(arr, startLen=len("[")) - add(arr, $x) - add(arr, "]") + var arr = "[" + for x in items([2, 3, 5, 7, 11]): + addSep(arr, startLen=len("[")) + add(arr, $x) + add(arr, "]") + doAssert arr == "[2, 3, 5, 7, 11]" + if dest.len > startLen: add(dest, sep) proc allCharsInSet*(s: string, theSet: set[char]): bool = - ## Returns true iff each character of `s` is in the set `theSet`. + ## Returns true if every character of `s` is in the set `theSet`. runnableExamples: doAssert allCharsInSet("aeea", {'a', 'e'}) == true doAssert allCharsInSet("", {'a', 'e'}) == true + for c in items(s): if c notin theSet: return false return true proc abbrev*(s: string, possibilities: openArray[string]): int = - ## Returns the index of the first item in ``possibilities`` which starts with ``s``, if not ambiguous. + ## Returns the index of the first item in ``possibilities`` which starts + ## with ``s``, if not ambiguous. ## ## Returns -1 if no item has been found and -2 if multiple items match. runnableExamples: @@ -1378,6 +1652,7 @@ proc abbrev*(s: string, possibilities: openArray[string]): int = doAssert abbrev("foo", ["college", "faculty", "industry"]) == -1 # Not found doAssert abbrev("fac", ["college", "faculty", "faculties"]) == -2 # Ambiguous doAssert abbrev("college", ["college", "colleges", "industry"]) == 0 + result = -1 # none found for i in 0..possibilities.len-1: if possibilities[i].startsWith(s): @@ -1391,9 +1666,10 @@ proc abbrev*(s: string, possibilities: openArray[string]): int = proc join*(a: openArray[string], sep: string = ""): string {. noSideEffect, rtl, extern: "nsuJoinSep".} = - ## Concatenates all strings in `a` separating them with `sep`. + ## Concatenates all strings in the container `a`, separating them with `sep`. runnableExamples: doAssert join(["A", "B", "Conclusion"], " -> ") == "A -> B -> Conclusion" + if len(a) > 0: var L = sep.len * (a.len-1) for i in 0..high(a): inc(L, a[i].len) @@ -1407,10 +1683,11 @@ proc join*(a: openArray[string], sep: string = ""): string {. proc join*[T: not string](a: openArray[T], sep: string = ""): string {. noSideEffect, rtl.} = - ## Converts all elements in `a` to strings using `$` and concatenates them - ## with `sep`. + ## Converts all elements in the container `a` to strings using `$`, + ## and concatenates them with `sep`. runnableExamples: doAssert join([1, 2, 3], " -> ") == "1 -> 2 -> 3" + result = "" for i, x in a: if i > 0: @@ -1441,14 +1718,13 @@ proc initSkipTable*(a: var SkipTable, sub: string) proc find*(a: SkipTable, s, sub: string, start: Natural = 0, last = 0): int {.noSideEffect, rtl, extern: "nsuFindStrA".} = - ## Searches for `sub` in `s` inside range `start`..`last` using preprocessed table `a`. - ## If `last` is unspecified, it defaults to `s.high`. + ## Searches for `sub` in `s` inside range `start`..`last` using preprocessed + ## table `a`. If `last` is unspecified, it defaults to `s.high` (the last + ## element). ## ## Searching is case-sensitive. If `sub` is not in `s`, -1 is returned. - let last = if last==0: s.high else: last - sLen = last - start + 1 subLast = sub.len - 1 if subLast == -1: @@ -1467,7 +1743,6 @@ proc find*(a: SkipTable, s, sub: string, start: Natural = 0, last = 0): int return skip dec i inc skip, a[s[skip + subLast]] - return -1 when not (defined(js) or defined(nimdoc) or defined(nimscript)): @@ -1479,10 +1754,14 @@ else: proc find*(s: string, sub: char, start: Natural = 0, last = 0): int {.noSideEffect, rtl, extern: "nsuFindChar".} = - ## Searches for `sub` in `s` inside range `start`..`last`. - ## If `last` is unspecified, it defaults to `s.high`. + ## Searches for `sub` in `s` inside range ``start..last`` (both ends included). + ## If `last` is unspecified, it defaults to `s.high` (the last element). ## ## Searching is case-sensitive. If `sub` is not in `s`, -1 is returned. + ## + ## See also: + ## * `rfind proc<#rfind,string,char,int>`_ + ## * `replace proc<#replace,string,char,char>`_ let last = if last==0: s.high else: last when nimvm: for i in int(start)..last: @@ -1499,34 +1778,72 @@ proc find*(s: string, sub: char, start: Natural = 0, last = 0): int {.noSideEffe if sub == s[i]: return i return -1 +proc find*(s: string, chars: set[char], start: Natural = 0, last = 0): int {.noSideEffect, + rtl, extern: "nsuFindCharSet".} = + ## Searches for `chars` in `s` inside range ``start..last`` (both ends included). + ## If `last` is unspecified, it defaults to `s.high` (the last element). + ## + ## If `s` contains none of the characters in `chars`, -1 is returned. + ## + ## See also: + ## * `rfind proc<#rfind,string,set[char],int>`_ + ## * `multiReplace proc<#multiReplace,string,varargs[]>`_ + let last = if last==0: s.high else: last + for i in int(start)..last: + if s[i] in chars: return i + return -1 + proc find*(s, sub: string, start: Natural = 0, last = 0): int {.noSideEffect, rtl, extern: "nsuFindStr".} = - ## Searches for `sub` in `s` inside range `start`..`last`. - ## If `last` is unspecified, it defaults to `s.high`. + ## Searches for `sub` in `s` inside range ``start..last`` (both ends included). + ## If `last` is unspecified, it defaults to `s.high` (the last element). ## ## Searching is case-sensitive. If `sub` is not in `s`, -1 is returned. + ## + ## See also: + ## * `rfind proc<#rfind,string,string,int>`_ + ## * `replace proc<#replace,string,string,string>`_ if sub.len > s.len: return -1 if sub.len == 1: return find(s, sub[0], start, last) var a {.noinit.}: SkipTable initSkipTable(a, sub) result = find(a, s, sub, start, last) -proc find*(s: string, chars: set[char], start: Natural = 0, last = 0): int {.noSideEffect, - rtl, extern: "nsuFindCharSet".} = - ## Searches for `chars` in `s` inside range `start`..`last`. - ## If `last` is unspecified, it defaults to `s.high`. +proc rfind*(s: string, sub: char, start: int = -1): int {.noSideEffect, + rtl.} = + ## Searches for characer `sub` in `s` in reverse, starting at position `start` + ## (default: the last character) and going backwards to the first character. ## - ## If `s` contains none of the characters in `chars`, -1 is returned. - let last = if last==0: s.high else: last - for i in int(start)..last: + ## Searching is case-sensitive. If `sub` is not in `s`, -1 is returned. + ## + ## See also: + ## * `find proc<#find,string,char,int,int>`_ + let realStart = if start == -1: s.len-1 else: start + for i in countdown(realStart, 0): + if sub == s[i]: return i + return -1 + +proc rfind*(s: string, chars: set[char], start: int = -1): int {.noSideEffect.} = + ## Searches for `chars` in `s` in reverse, starting at position `start` + ## (default: the last character) and going backwards to the first character. + ## + ## Searching is case-sensitive. If `sub` is not in `s`, -1 is returned. + ## + ## See also: + ## * `find proc<#find,string,set[char],Natural,int>`_ + let realStart = if start == -1: s.len-1 else: start + for i in countdown(realStart, 0): if s[i] in chars: return i return -1 proc rfind*(s, sub: string, start: int = -1): int {.noSideEffect.} = - ## Searches for `sub` in `s` in reverse, starting at `start` and going - ## backwards to 0. + ## Searches for string `sub` in `s` in reverse, starting at position `start` + ## (default: the last character) and going backwards to the first character. ## ## Searching is case-sensitive. If `sub` is not in `s`, -1 is returned. + ## + ## See also: + ## * `find proc<#find,string,string,Natural,int>`_ if sub.len == 0: return -1 let realStart = if start == -1: s.len else: start @@ -1539,54 +1856,34 @@ proc rfind*(s, sub: string, start: int = -1): int {.noSideEffect.} = if result != -1: return return -1 -proc rfind*(s: string, sub: char, start: int = -1): int {.noSideEffect, - rtl.} = - ## Searches for `sub` in `s` in reverse starting at position `start`. - ## - ## Searching is case-sensitive. If `sub` is not in `s`, -1 is returned. - let realStart = if start == -1: s.len-1 else: start - for i in countdown(realStart, 0): - if sub == s[i]: return i - return -1 -proc rfind*(s: string, chars: set[char], start: int = -1): int {.noSideEffect.} = - ## Searches for `chars` in `s` in reverse starting at position `start`. +proc count*(s: string, sub: char): int {.noSideEffect, + rtl, extern: "nsuCountChar".} = + ## Count the occurrences of the character `sub` in the string `s`. ## - ## Searching is case-sensitive. If `sub` is not in `s`, -1 is returned. - let realStart = if start == -1: s.len-1 else: start - for i in countdown(realStart, 0): - if s[i] in chars: return i - return -1 + ## See also: + ## * `countLines proc<#countLines,string>`_ + for c in s: + if c == sub: inc result -proc center*(s: string, width: int, fillChar: char = ' '): string {. - noSideEffect, rtl, extern: "nsuCenterString".} = - ## Return the contents of `s` centered in a string `width` long using - ## `fillChar` as padding. +proc count*(s: string, subs: set[char]): int {.noSideEffect, + rtl, extern: "nsuCountCharSet".} = + ## Count the occurrences of the group of character `subs` in the string `s`. ## - ## The original string is returned if `width` is less than or equal - ## to `s.len`. - if width <= s.len: return s - result = newString(width) - # Left padding will be one fillChar - # smaller if there are an odd number - # of characters - let - charsLeft = (width - s.len) - leftPadding = charsLeft div 2 - for i in 0 ..< width: - if i >= leftPadding and i < leftPadding + s.len: - # we are where the string should be located - result[i] = s[i-leftPadding] - else: - # we are either before or after where - # the string s should go - result[i] = fillChar + ## See also: + ## * `countLines proc<#countLines,string>`_ + doAssert card(subs) > 0 + for c in s: + if c in subs: inc result proc count*(s: string, sub: string, overlapping: bool = false): int {. noSideEffect, rtl, extern: "nsuCountString".} = ## Count the occurrences of a substring `sub` in the string `s`. ## Overlapping occurrences of `sub` only count when `overlapping` - ## is set to true. + ## is set to true (default: false). + ## + ## See also: + ## * `countLines proc<#countLines,string>`_ doAssert sub.len > 0 var i = 0 while true: @@ -1596,43 +1893,58 @@ proc count*(s: string, sub: string, overlapping: bool = false): int {. else: i += sub.len inc result -proc count*(s: string, sub: char): int {.noSideEffect, - rtl, extern: "nsuCountChar".} = - ## Count the occurrences of the character `sub` in the string `s`. - for c in s: - if c == sub: inc result - -proc count*(s: string, subs: set[char]): int {.noSideEffect, - rtl, extern: "nsuCountCharSet".} = - ## Count the occurrences of the group of character `subs` in the string `s`. - doAssert card(subs) > 0 - for c in s: - if c in subs: inc result - -proc quoteIfContainsWhite*(s: string): string {.deprecated.} = - ## Returns ``'"' & s & '"'`` if `s` contains a space and does not - ## start with a quote, else returns `s`. +proc countLines*(s: string): int {.noSideEffect, + rtl, extern: "nsuCountLines".} = + ## Returns the number of lines in the string `s`. + ## + ## This is the same as ``len(splitLines(s))``, but much more efficient + ## because it doesn't modify the string creating temporal objects. Every + ## `character literal <manual.html#lexical-analysis-character-literals>`_ + ## newline combination (CR, LF, CR-LF) is supported. ## - ## **DEPRECATED** as it was confused for shell quoting function. For this - ## application use `osproc.quoteShell <osproc.html#quoteShell>`_. - if find(s, {' ', '\t'}) >= 0 and s[0] != '"': result = '"' & s & '"' - else: result = s + ## In this context, a line is any string seperated by a newline combination. + ## A line can be an empty string. + ## + ## See also: + ## * `splitLines proc<#splitLines,string>`_ + runnableExamples: + doAssert countLines("First line\l and second line.") == 2 + result = 1 + var i = 0 + while i < s.len: + case s[i] + of '\c': + if i+1 < s.len and s[i+1] == '\l': inc i + inc result + of '\l': inc result + else: discard + inc i -proc contains*(s: string, c: char): bool {.noSideEffect.} = - ## Same as ``find(s, c) >= 0``. - return find(s, c) >= 0 proc contains*(s, sub: string): bool {.noSideEffect.} = ## Same as ``find(s, sub) >= 0``. + ## + ## See also: + ## * `find proc<#find,string,string,Natural,int>`_ return find(s, sub) >= 0 proc contains*(s: string, chars: set[char]): bool {.noSideEffect.} = ## Same as ``find(s, chars) >= 0``. + ## + ## See also: + ## * `find proc<#find,string,set[char],Natural,int>`_ return find(s, chars) >= 0 proc replace*(s, sub: string, by = ""): string {.noSideEffect, rtl, extern: "nsuReplaceStr".} = ## Replaces `sub` in `s` by the string `by`. + ## + ## See also: + ## * `find proc<#find,string,string,Natural,int>`_ + ## * `replace proc<#replace,string,char,char>`_ for replacing + ## single characters + ## * `replaceWord proc<#replaceWord,string,string,string>`_ + ## * `multiReplace proc<#multiReplace,string,varargs[]>`_ result = "" let subLen = sub.len if subLen == 0: @@ -1670,6 +1982,11 @@ proc replace*(s: string, sub, by: char): string {.noSideEffect, ## Replaces `sub` in `s` by the character `by`. ## ## Optimized version of `replace <#replace,string,string>`_ for characters. + ## + ## See also: + ## * `find proc<#find,string,char,Natural,int>`_ + ## * `replaceWord proc<#replaceWord,string,string,string>`_ + ## * `multiReplace proc<#multiReplace,string,varargs[]>`_ result = newString(s.len) var i = 0 while i < s.len: @@ -1682,7 +1999,7 @@ proc replaceWord*(s, sub: string, by = ""): string {.noSideEffect, ## Replaces `sub` in `s` by the string `by`. ## ## Each occurrence of `sub` has to be surrounded by word boundaries - ## (comparable to ``\\w`` in regular expressions), otherwise it is not + ## (comparable to ``\b`` in regular expressions), otherwise it is not ## replaced. if sub.len == 0: return s const wordChars = {'a'..'z', 'A'..'Z', '0'..'9', '_', '\128'..'\255'} @@ -1712,14 +2029,14 @@ proc multiReplace*(s: string, replacements: varargs[(string, string)]): string { ## Same as replace, but specialized for doing multiple replacements in a single ## pass through the input string. ## - ## multiReplace performs all replacements in a single pass, this means it can be used - ## to swap the occurences of "a" and "b", for instance. + ## `multiReplace` performs all replacements in a single pass, this means it + ## can be used to swap the occurences of "a" and "b", for instance. ## - ## If the resulting string is not longer than the original input string, only a single - ## memory allocation is required. + ## If the resulting string is not longer than the original input string, + ## only a single memory allocation is required. ## - ## The order of the replacements does matter. Earlier replacements are preferred over later - ## replacements in the argument list. + ## The order of the replacements does matter. Earlier replacements are + ## preferred over later replacements in the argument list. result = newStringOfCap(s.len) var i = 0 var fastChk: set[char] = {} @@ -1741,55 +2058,12 @@ proc multiReplace*(s: string, replacements: varargs[(string, string)]): string { add result, s[i] inc(i) -proc delete*(s: var string, first, last: int) {.noSideEffect, - rtl, extern: "nsuDelete".} = - ## Deletes in `s` the characters at position `first` .. `last`. - ## - ## This modifies `s` itself, it does not return a copy. - var i = first - var j = last+1 - var newLen = len(s)-j+i - while i < newLen: - s[i] = s[j] - inc(i) - inc(j) - setLen(s, newLen) -proc toOct*(x: BiggestInt, len: Positive): string {.noSideEffect, - rtl, extern: "nsuToOct".} = - ## Converts `x` into its octal representation. - ## - ## The resulting string is always `len` characters long. No leading ``0o`` - ## prefix is generated. - var - mask: BiggestInt = 7 - shift: BiggestInt = 0 - assert(len > 0) - result = newString(len) - for j in countdown(len-1, 0): - result[j] = chr(int((x and mask) shr shift) + ord('0')) - shift = shift + 3 - mask = mask shl 3 - -proc toBin*(x: BiggestInt, len: Positive): string {.noSideEffect, - rtl, extern: "nsuToBin".} = - ## Converts `x` into its binary representation. - ## - ## The resulting string is always `len` characters long. No leading ``0b`` - ## prefix is generated. - var - mask: BiggestInt = 1 - shift: BiggestInt = 0 - assert(len > 0) - result = newString(len) - for j in countdown(len-1, 0): - result[j] = chr(int((x and mask) shr shift) + ord('0')) - shift = shift + 1 - mask = mask shl 1 proc insertSep*(s: string, sep = '_', digits = 3): string {.noSideEffect, rtl, extern: "nsuInsertSep".} = - ## Inserts the separator `sep` after `digits` digits from right to left. + ## Inserts the separator `sep` after `digits` characters (default: 3) + ## from right to left. ## ## Even though the algorithm works with any string `s`, it is only useful ## if `s` contains a number. @@ -1811,11 +2085,15 @@ proc insertSep*(s: string, sep = '_', digits = 3): string {.noSideEffect, proc escape*(s: string, prefix = "\"", suffix = "\""): string {.noSideEffect, rtl, extern: "nsuEscape".} = - ## Escapes a string `s`. See `system.addEscapedChar <system.html#addEscapedChar>`_ - ## for the escaping scheme. + ## Escapes a string `s`. See `system.addEscapedChar + ## <system.html#addEscapedChar,string,char>`_ for the escaping scheme. ## ## The resulting string is prefixed with `prefix` and suffixed with `suffix`. ## Both may be empty strings. + ## + ## See also: + ## * `unescape proc<#unescape,string,string,string>`_ for the opposite + ## operation result = newStringOfCap(s.len + s.len shr 2) result.add(prefix) for c in items(s): @@ -1833,8 +2111,8 @@ proc unescape*(s: string, prefix = "\"", suffix = "\""): string {.noSideEffect, rtl, extern: "nsuUnescape".} = ## Unescapes a string `s`. ## - ## This complements `escape <#escape>`_ as it performs the opposite - ## operations. + ## This complements `escape proc<#escape,string,string,string>`_ + ## as it performs the opposite operations. ## ## If `s` does not begin with ``prefix`` and end with ``suffix`` a ## ValueError exception will be raised. @@ -1880,101 +2158,12 @@ proc validIdentifier*(s: string): bool {.noSideEffect, ## and is followed by any number of characters of the set `IdentChars`. runnableExamples: doAssert "abc_def08".validIdentifier + if s.len > 0 and s[0] in IdentStartChars: for i in 1..s.len-1: if s[i] notin IdentChars: return false return true -{.push warning[Deprecated]: off.} -proc editDistance*(a, b: string): int {.noSideEffect, - rtl, extern: "nsuEditDistance", - deprecated: "use editdistance.editDistanceAscii instead".} = - ## Returns the edit distance between `a` and `b`. - ## - ## This uses the `Levenshtein`:idx: distance algorithm with only a linear - ## memory overhead. - var len1 = a.len - var len2 = b.len - if len1 > len2: - # make `b` the longer string - return editDistance(b, a) - - # strip common prefix: - var s = 0 - while s < len1 and a[s] == b[s]: - inc(s) - dec(len1) - dec(len2) - # strip common suffix: - while len1 > 0 and len2 > 0 and a[s+len1-1] == b[s+len2-1]: - dec(len1) - dec(len2) - # trivial cases: - if len1 == 0: return len2 - if len2 == 0: return len1 - - # another special case: - if len1 == 1: - for j in s..s+len2-1: - if a[s] == b[j]: return len2 - 1 - return len2 - - inc(len1) - inc(len2) - var half = len1 shr 1 - # initalize first row: - #var row = cast[ptr array[0..high(int) div 8, int]](alloc(len2*sizeof(int))) - var row: seq[int] - newSeq(row, len2) - var e = s + len2 - 1 # end marker - for i in 1..len2 - half - 1: row[i] = i - row[0] = len1 - half - 1 - for i in 1 .. len1 - 1: - var char1 = a[i + s - 1] - var char2p: int - var D, x: int - var p: int - if i >= len1 - half: - # skip the upper triangle: - var offset = i - len1 + half - char2p = offset - p = offset - var c3 = row[p] + ord(char1 != b[s + char2p]) - inc(p) - inc(char2p) - x = row[p] + 1 - D = x - if x > c3: x = c3 - row[p] = x - inc(p) - else: - p = 1 - char2p = 0 - D = i - x = i - if i <= half + 1: - # skip the lower triangle: - e = len2 + i - half - 2 - # main: - while p <= e: - dec(D) - var c3 = D + ord(char1 != b[char2p + s]) - inc(char2p) - inc(x) - if x > c3: x = c3 - D = row[p] + 1 - if x > D: x = D - row[p] = x - inc(p) - # lower triangle sentinel: - if i <= half: - dec(D) - var c3 = D + ord(char1 != b[char2p + s]) - inc(x) - if x > c3: x = c3 - row[p] = x - result = row[e] -{.pop.} # floating point formating: when not defined(js): @@ -1982,10 +2171,11 @@ when not defined(js): importc: "sprintf", varargs, noSideEffect.} type - FloatFormatMode* = enum ## the different modes of floating point formating - ffDefault, ## use the shorter floating point notation - ffDecimal, ## use decimal floating point notation - ffScientific ## use scientific notation (using ``e`` character) + FloatFormatMode* = enum + ## the different modes of floating point formating + ffDefault, ## use the shorter floating point notation + ffDecimal, ## use decimal floating point notation + ffScientific ## use scientific notation (using ``e`` character) proc formatBiggestFloat*(f: BiggestFloat, format: FloatFormatMode = ffDefault, precision: range[-1..32] = 16; @@ -2001,6 +2191,11 @@ proc formatBiggestFloat*(f: BiggestFloat, format: FloatFormatMode = ffDefault, ## after the decimal point for Nim's ``biggestFloat`` type. ## ## If ``precision == -1``, it tries to format it nicely. + runnableExamples: + let x = 123.456 + doAssert x.formatBiggestFloat() == "123.4560000000000" + doAssert x.formatBiggestFloat(ffDecimal, 4) == "123.4560" + doAssert x.formatBiggestFloat(ffScientific, 2) == "1.23e+02" when defined(js): var precision = precision if precision == -1: @@ -2080,11 +2275,18 @@ proc formatFloat*(f: float, format: FloatFormatMode = ffDefault, doAssert x.formatFloat() == "123.4560000000000" doAssert x.formatFloat(ffDecimal, 4) == "123.4560" doAssert x.formatFloat(ffScientific, 2) == "1.23e+02" + result = formatBiggestFloat(f, format, precision, decimalSep) proc trimZeros*(x: var string) {.noSideEffect.} = ## Trim trailing zeros from a formatted floating point - ## value (`x`). Modifies the passed value. + ## value `x` (must be declared as ``var``). + ## + ## This modifies `x` itself, it does not return a copy. + runnableExamples: + var x = "123.456000000" + x.trimZeros() + doAssert x == "123.456" var spl: seq[string] if x.contains('.') or x.contains(','): if x.contains('e'): @@ -2114,6 +2316,9 @@ proc formatSize*(bytes: int64, ## ## `includeSpace` can be set to true to include the (SI preferred) space ## between the number and the unit (e.g. 1 KiB). + ## + ## See also: + ## * `strformat module<strformat.html>`_ for string interpolation and formatting runnableExamples: doAssert formatSize((1'i64 shl 31) + (300'i64 shl 20)) == "2.293GiB" doAssert formatSize((2.234*1024*1024).int) == "2.234MiB" @@ -2121,6 +2326,7 @@ proc formatSize*(bytes: int64, doAssert formatSize(4096, prefix=bpColloquial, includeSpace=true) == "4 kB" doAssert formatSize(4096) == "4KiB" doAssert formatSize(5_378_934, prefix=bpColloquial, decimalSep=',') == "5,13MB" + const iecPrefixes = ["", "Ki", "Mi", "Gi", "Ti", "Pi", "Ei", "Zi", "Yi"] const collPrefixes = ["", "k", "M", "G", "T", "P", "E", "Z", "Y"] var @@ -2216,6 +2422,9 @@ proc formatEng*(f: BiggestFloat, ## formatEng(4100, unit="", useUnitSpace=true) == "4.1e3 " # Space with useUnitSpace=true ## ## `decimalSep` is used as the decimal separator. + ## + ## See also: + ## * `strformat module<strformat.html>`_ for string interpolation and formatting var absolute: BiggestFloat significand: BiggestFloat @@ -2403,110 +2612,69 @@ proc `%` *(formatstr: string, a: openArray[string]): string {.noSideEffect, ## ## The variables are compared with `cmpIgnoreStyle`. `ValueError` is ## raised if an ill-formed format string has been passed to the `%` operator. + ## + ## See also: + ## * `strformat module<strformat.html>`_ for string interpolation and formatting result = newStringOfCap(formatstr.len + a.len shl 4) addf(result, formatstr, a) proc `%` *(formatstr, a: string): string {.noSideEffect, rtl, extern: "nsuFormatSingleElem".} = - ## This is the same as ``formatstr % [a]``. + ## This is the same as ``formatstr % [a]`` (see + ## `% proc<#%25,string,openArray[string]>`_). result = newStringOfCap(formatstr.len + a.len) addf(result, formatstr, [a]) proc format*(formatstr: string, a: varargs[string, `$`]): string {.noSideEffect, rtl, extern: "nsuFormatVarargs".} = - ## This is the same as ``formatstr % a`` except that it supports + ## This is the same as ``formatstr % a`` (see + ## `% proc<#%25,string,openArray[string]>`_) except that it supports ## auto stringification. + ## + ## See also: + ## * `strformat module<strformat.html>`_ for string interpolation and formatting result = newStringOfCap(formatstr.len + a.len) addf(result, formatstr, a) {.pop.} -proc removeSuffix*(s: var string, chars: set[char] = Newlines) {. - rtl, extern: "nsuRemoveSuffixCharSet".} = - ## Removes all characters from `chars` from the end of the string `s` - ## (in-place). - runnableExamples: - var userInput = "Hello World!*~\r\n" - userInput.removeSuffix - doAssert userInput == "Hello World!*~" - userInput.removeSuffix({'~', '*'}) - doAssert userInput == "Hello World!" - var otherInput = "Hello!?!" - otherInput.removeSuffix({'!', '?'}) - doAssert otherInput == "Hello" - if s.len == 0: return - var last = s.high - while last > -1 and s[last] in chars: last -= 1 - s.setLen(last + 1) -proc removeSuffix*(s: var string, c: char) {. - rtl, extern: "nsuRemoveSuffixChar".} = - ## Removes all occurrences of a single character (in-place) from the end - ## of a string. +proc strip*(s: string, leading = true, trailing = true, + chars: set[char] = Whitespace): string + {.noSideEffect, rtl, extern: "nsuStrip".} = + ## Strips leading or trailing `chars` (default: whitespace characters) + ## from `s` and returns the resulting string. ## - runnableExamples: - var table = "users" - table.removeSuffix('s') - doAssert table == "user" - - var dots = "Trailing dots......." - dots.removeSuffix('.') - doAssert dots == "Trailing dots" - removeSuffix(s, chars = {c}) - -proc removeSuffix*(s: var string, suffix: string) {. - rtl, extern: "nsuRemoveSuffixString".} = - ## Remove the first matching suffix (in-place) from a string. - runnableExamples: - var answers = "yeses" - answers.removeSuffix("es") - doAssert answers == "yes" - var newLen = s.len - if s.endsWith(suffix): - newLen -= len(suffix) - s.setLen(newLen) - -proc removePrefix*(s: var string, chars: set[char] = Newlines) {. - rtl, extern: "nsuRemovePrefixCharSet".} = - ## Removes all characters from `chars` from the start of the string `s` - ## (in-place). + ## If `leading` is true (default), leading `chars` are stripped. + ## If `trailing` is true (default), trailing `chars` are stripped. + ## If both are false, the string is returned unchanged. ## + ## See also: + ## * `stripLineEnd proc<#stripLineEnd,string>`_ runnableExamples: - var userInput = "\r\n*~Hello World!" - userInput.removePrefix - doAssert userInput == "*~Hello World!" - userInput.removePrefix({'~', '*'}) - doAssert userInput == "Hello World!" + let a = " vhellov " + let b = strip(a) + doAssert b == "vhellov" - var otherInput = "?!?Hello!?!" - otherInput.removePrefix({'!', '?'}) - doAssert otherInput == "Hello!?!" - var start = 0 - while start < s.len and s[start] in chars: start += 1 - if start > 0: s.delete(0, start - 1) + doAssert a.strip(leading = false) == " vhellov" + doAssert a.strip(trailing = false) == "vhellov " -proc removePrefix*(s: var string, c: char) {. - rtl, extern: "nsuRemovePrefixChar".} = - ## Removes all occurrences of a single character (in-place) from the start - ## of a string. - ## - runnableExamples: - var ident = "pControl" - ident.removePrefix('p') - doAssert ident == "Control" - removePrefix(s, chars = {c}) + doAssert b.strip(chars = {'v'}) == "hello" + doAssert b.strip(leading = false, chars = {'v'}) == "vhello" -proc removePrefix*(s: var string, prefix: string) {. - rtl, extern: "nsuRemovePrefixString".} = - ## Remove the first matching prefix (in-place) from a string. - ## - runnableExamples: - var answers = "yesyes" - answers.removePrefix("yes") - doAssert answers == "yes" - if s.startsWith(prefix): - s.delete(0, prefix.len - 1) + let c = "blaXbla" + doAssert c.strip(chars = {'b', 'a'}) == "laXbl" + doAssert c.strip(chars = {'b', 'a', 'l'}) == "X" + + var + first = 0 + last = len(s)-1 + if leading: + while first <= last and s[first] in chars: inc(first) + if trailing: + while last >= 0 and s[last] in chars: dec(last) + result = substr(s, first, last) proc stripLineEnd*(s: var string) = ## Returns ``s`` stripped from one of these suffixes: @@ -2520,6 +2688,7 @@ proc stripLineEnd*(s: var string) = s = "foo\r\n" s.stripLineEnd doAssert s == "foo" + if s.len > 0: case s[^1] of '\n': @@ -2532,6 +2701,331 @@ proc stripLineEnd*(s: var string) = else: discard + +iterator tokenize*(s: string, seps: set[char] = Whitespace): tuple[ + token: string, isSep: bool] = + ## Tokenizes the string `s` into substrings. + ## + ## Substrings are separated by a substring containing only `seps`. + ## Example: + ## + ## .. code-block:: nim + ## for word in tokenize(" this is an example "): + ## writeLine(stdout, word) + ## + ## Results in: + ## + ## .. code-block:: nim + ## (" ", true) + ## ("this", false) + ## (" ", true) + ## ("is", false) + ## (" ", true) + ## ("an", false) + ## (" ", true) + ## ("example", false) + ## (" ", true) + var i = 0 + while true: + var j = i + var isSep = j < s.len and s[j] in seps + while j < s.len and (s[j] in seps) == isSep: inc(j) + if j > i: + yield (substr(s, i, j-1), isSep) + else: + break + i = j + + + + + +# -------------------------------------------------------------------------- +# Deprecated procs + +{.push warning[Deprecated]: off.} +proc editDistance*(a, b: string): int {.noSideEffect, + rtl, extern: "nsuEditDistance", + deprecated: "use editdistance.editDistanceAscii instead".} = + ## **Deprecated**: Use `editdistance module<editdistance.html>`_ + ## + ## Returns the edit distance between `a` and `b`. + ## + ## This uses the `Levenshtein`:idx: distance algorithm with only a linear + ## memory overhead. + var len1 = a.len + var len2 = b.len + if len1 > len2: + # make `b` the longer string + return editDistance(b, a) + + # strip common prefix: + var s = 0 + while s < len1 and a[s] == b[s]: + inc(s) + dec(len1) + dec(len2) + # strip common suffix: + while len1 > 0 and len2 > 0 and a[s+len1-1] == b[s+len2-1]: + dec(len1) + dec(len2) + # trivial cases: + if len1 == 0: return len2 + if len2 == 0: return len1 + + # another special case: + if len1 == 1: + for j in s..s+len2-1: + if a[s] == b[j]: return len2 - 1 + return len2 + + inc(len1) + inc(len2) + var half = len1 shr 1 + # initalize first row: + #var row = cast[ptr array[0..high(int) div 8, int]](alloc(len2*sizeof(int))) + var row: seq[int] + newSeq(row, len2) + var e = s + len2 - 1 # end marker + for i in 1..len2 - half - 1: row[i] = i + row[0] = len1 - half - 1 + for i in 1 .. len1 - 1: + var char1 = a[i + s - 1] + var char2p: int + var D, x: int + var p: int + if i >= len1 - half: + # skip the upper triangle: + var offset = i - len1 + half + char2p = offset + p = offset + var c3 = row[p] + ord(char1 != b[s + char2p]) + inc(p) + inc(char2p) + x = row[p] + 1 + D = x + if x > c3: x = c3 + row[p] = x + inc(p) + else: + p = 1 + char2p = 0 + D = i + x = i + if i <= half + 1: + # skip the lower triangle: + e = len2 + i - half - 2 + # main: + while p <= e: + dec(D) + var c3 = D + ord(char1 != b[char2p + s]) + inc(char2p) + inc(x) + if x > c3: x = c3 + D = row[p] + 1 + if x > D: x = D + row[p] = x + inc(p) + # lower triangle sentinel: + if i <= half: + dec(D) + var c3 = D + ord(char1 != b[char2p + s]) + inc(x) + if x > c3: x = c3 + row[p] = x + result = row[e] +{.pop.} + +proc isNilOrEmpty*(s: string): bool {.noSideEffect, procvar, rtl, + extern: "nsuIsNilOrEmpty", + deprecated: "use 'x.len == 0' instead".} = + ## **Deprecated**: use 'x.len == 0' + ## + ## Checks if `s` is nil or empty. + result = len(s) == 0 + +proc isNilOrWhitespace*(s: string): bool {.noSideEffect, procvar, rtl, extern: "nsuIsNilOrWhitespace".} = + ## Checks if `s` is nil or consists entirely of whitespace characters. + result = true + for c in s: + if not c.isSpaceAscii(): + return false + +template isImpl(call) = + if s.len == 0: return false + result = true + for c in s: + if not call(c): return false + +proc isAlphaAscii*(s: string): bool {.noSideEffect, procvar, + rtl, extern: "nsuIsAlphaAsciiStr", + deprecated: "Deprecated since version 0.20 since its semantics are unclear".} = + ## **Deprecated**: Deprecated since version 0.20 since its semantics are unclear + ## + ## Checks whether or not `s` is alphabetical. + ## + ## This checks a-z, A-Z ASCII characters only. + ## Returns true if all characters in `s` are + ## alphabetic and there is at least one character + ## in `s`. + ## Use `Unicode module<unicode.html>`_ for UTF-8 support. + runnableExamples: + doAssert isAlphaAscii("fooBar") == true + doAssert isAlphaAscii("fooBar1") == false + doAssert isAlphaAscii("foo Bar") == false + isImpl isAlphaAscii + +proc isAlphaNumeric*(s: string): bool {.noSideEffect, procvar, + rtl, extern: "nsuIsAlphaNumericStr", + deprecated: "Deprecated since version 0.20 since its semantics are unclear".} = + ## **Deprecated**: Deprecated since version 0.20 since its semantics are unclear + ## + ## Checks whether or not `s` is alphanumeric. + ## + ## This checks a-z, A-Z, 0-9 ASCII characters only. + ## Returns true if all characters in `s` are + ## alpanumeric and there is at least one character + ## in `s`. + ## Use `Unicode module<unicode.html>`_ for UTF-8 support. + runnableExamples: + doAssert isAlphaNumeric("fooBar") == true + doAssert isAlphaNumeric("fooBar1") == true + doAssert isAlphaNumeric("foo Bar") == false + isImpl isAlphaNumeric + +proc isDigit*(s: string): bool {.noSideEffect, procvar, + rtl, extern: "nsuIsDigitStr", + deprecated: "Deprecated since version 0.20 since its semantics are unclear".} = + ## **Deprecated**: Deprecated since version 0.20 since its semantics are unclear + ## + ## Checks whether or not `s` is a numeric value. + ## + ## This checks 0-9 ASCII characters only. + ## Returns true if all characters in `s` are + ## numeric and there is at least one character + ## in `s`. + runnableExamples: + doAssert isDigit("1908") == true + doAssert isDigit("fooBar1") == false + isImpl isDigit + +proc isSpaceAscii*(s: string): bool {.noSideEffect, procvar, + rtl, extern: "nsuIsSpaceAsciiStr", + deprecated: "Deprecated since version 0.20 since its semantics are unclear".} = + ## **Deprecated**: Deprecated since version 0.20 since its semantics are unclear + ## + ## Checks whether or not `s` is completely whitespace. + ## + ## Returns true if all characters in `s` are whitespace + ## characters and there is at least one character in `s`. + runnableExamples: + doAssert isSpaceAscii(" ") == true + doAssert isSpaceAscii("") == false + isImpl isSpaceAscii + +template isCaseImpl(s, charProc, skipNonAlpha) = + var hasAtleastOneAlphaChar = false + if s.len == 0: return false + for c in s: + if skipNonAlpha: + var charIsAlpha = c.isAlphaAscii() + if not hasAtleastOneAlphaChar: + hasAtleastOneAlphaChar = charIsAlpha + if charIsAlpha and (not charProc(c)): + return false + else: + if not charProc(c): + return false + return if skipNonAlpha: hasAtleastOneAlphaChar else: true + +proc isLowerAscii*(s: string, skipNonAlpha: bool): bool {. + deprecated: "Deprecated since version 0.20 since its semantics are unclear".} = + ## **Deprecated**: Deprecated since version 0.20 since its semantics are unclear + ## + ## Checks whether ``s`` is lower case. + ## + ## This checks ASCII characters only. + ## + ## If ``skipNonAlpha`` is true, returns true if all alphabetical + ## characters in ``s`` are lower case. Returns false if none of the + ## characters in ``s`` are alphabetical. + ## + ## If ``skipNonAlpha`` is false, returns true only if all characters + ## in ``s`` are alphabetical and lower case. + ## + ## For either value of ``skipNonAlpha``, returns false if ``s`` is + ## an empty string. + ## Use `Unicode module<unicode.html>`_ for UTF-8 support. + runnableExamples: + doAssert isLowerAscii("1foobar", false) == false + doAssert isLowerAscii("1foobar", true) == true + doAssert isLowerAscii("1fooBar", true) == false + isCaseImpl(s, isLowerAscii, skipNonAlpha) + +proc isUpperAscii*(s: string, skipNonAlpha: bool): bool {. + deprecated: "Deprecated since version 0.20 since its semantics are unclear".} = + ## **Deprecated**: Deprecated since version 0.20 since its semantics are unclear + ## + ## Checks whether ``s`` is upper case. + ## + ## This checks ASCII characters only. + ## + ## If ``skipNonAlpha`` is true, returns true if all alphabetical + ## characters in ``s`` are upper case. Returns false if none of the + ## characters in ``s`` are alphabetical. + ## + ## If ``skipNonAlpha`` is false, returns true only if all characters + ## in ``s`` are alphabetical and upper case. + ## + ## For either value of ``skipNonAlpha``, returns false if ``s`` is + ## an empty string. + ## Use `Unicode module<unicode.html>`_ for UTF-8 support. + runnableExamples: + doAssert isUpperAscii("1FOO", false) == false + doAssert isUpperAscii("1FOO", true) == true + doAssert isUpperAscii("1Foo", true) == false + isCaseImpl(s, isUpperAscii, skipNonAlpha) + +proc wordWrap*(s: string, maxLineWidth = 80, + splitLongWords = true, + seps: set[char] = Whitespace, + newLine = "\n"): string {. + noSideEffect, rtl, extern: "nsuWordWrap", + deprecated: "use wrapWords in std/wordwrap instead".} = + ## **Deprecated**: use wrapWords in std/wordwrap instead + ## + ## Word wraps `s`. + result = newStringOfCap(s.len + s.len shr 6) + var spaceLeft = maxLineWidth + var lastSep = "" + for word, isSep in tokenize(s, seps): + if isSep: + lastSep = word + spaceLeft = spaceLeft - len(word) + continue + if len(word) > spaceLeft: + if splitLongWords and len(word) > maxLineWidth: + result.add(substr(word, 0, spaceLeft-1)) + var w = spaceLeft + var wordLeft = len(word) - spaceLeft + while wordLeft > 0: + result.add(newLine) + var L = min(maxLineWidth, wordLeft) + spaceLeft = maxLineWidth - L + result.add(substr(word, w, w+L-1)) + inc(w, L) + dec(wordLeft, L) + else: + spaceLeft = maxLineWidth - len(word) + result.add(newLine) + result.add(word) + else: + spaceLeft = spaceLeft - len(word) + result.add(lastSep & word) + lastSep.setLen(0) + + + when isMainModule: proc nonStaticTests = doAssert formatBiggestFloat(1234.567, ffDecimal, -1) == "1234.567000" diff --git a/lib/pure/subexes.nim b/lib/pure/subexes.nim deleted file mode 100644 index 638e71f04..000000000 --- a/lib/pure/subexes.nim +++ /dev/null @@ -1,406 +0,0 @@ -# -# -# Nim's Runtime Library -# (c) Copyright 2012 Andreas Rumpf -# -# See the file "copying.txt", included in this -# distribution, for details about the copyright. -# - -## Nim support for `substitution expressions`:idx: (`subex`:idx:). -## -## .. include:: ../../doc/subexes.txt -## - -{.push debugger:off .} # the user does not want to trace a part - # of the standard library! - -from strutils import parseInt, cmpIgnoreStyle, Digits -include "system/inclrtl" -import system/helpers2 - -proc findNormalized(x: string, inArray: openarray[string]): int = - var i = 0 - while i < high(inArray): - if cmpIgnoreStyle(x, inArray[i]) == 0: return i - inc(i, 2) # incrementing by 1 would probably lead to a - # security hole... - return -1 - -type - SubexError* = object of ValueError ## exception that is raised for - ## an invalid subex - -proc raiseInvalidFormat(msg: string) {.noinline.} = - raise newException(SubexError, "invalid format string: " & msg) - -type - FormatParser = object {.pure, final.} - when defined(js): - f: string # we rely on the '\0' terminator - # which JS's native string doesn't have - else: - f: cstring - num, i, lineLen: int - -template call(x: untyped): untyped = - p.i = i - x - i = p.i - -template callNoLineLenTracking(x: untyped): untyped = - let oldLineLen = p.lineLen - p.i = i - x - i = p.i - p.lineLen = oldLineLen - -proc getFormatArg(p: var FormatParser, a: openArray[string]): int = - const PatternChars = {'a'..'z', 'A'..'Z', '0'..'9', '\128'..'\255', '_'} - var i = p.i - var f = p.f - case f[i] - of '#': - result = p.num - inc i - inc p.num - of '1'..'9', '-': - var j = 0 - var negative = f[i] == '-' - if negative: inc i - while f[i] in Digits: - j = j * 10 + ord(f[i]) - ord('0') - inc i - result = if not negative: j-1 else: a.len-j - of 'a'..'z', 'A'..'Z', '\128'..'\255', '_': - var name = "" - while f[i] in PatternChars: - name.add(f[i]) - inc(i) - result = findNormalized(name, a)+1 - of '$': - inc(i) - call: - result = getFormatArg(p, a) - result = parseInt(a[result])-1 - else: - raiseInvalidFormat("'#', '$', number or identifier expected") - if result >=% a.len: raiseInvalidFormat(formatErrorIndexBound(result, a.len)) - p.i = i - -proc scanDollar(p: var FormatParser, a: openarray[string], s: var string) {. - noSideEffect.} - -proc emitChar(p: var FormatParser, x: var string, ch: char) {.inline.} = - x.add(ch) - if ch == '\L': p.lineLen = 0 - else: inc p.lineLen - -proc emitStrLinear(p: var FormatParser, x: var string, y: string) {.inline.} = - for ch in items(y): emitChar(p, x, ch) - -proc emitStr(p: var FormatParser, x: var string, y: string) {.inline.} = - x.add(y) - inc p.lineLen, y.len - -proc scanQuote(p: var FormatParser, x: var string, toAdd: bool) = - var i = p.i+1 - var f = p.f - while true: - if f[i] == '\'': - inc i - if f[i] != '\'': break - inc i - if toAdd: emitChar(p, x, '\'') - elif f[i] == '\0': raiseInvalidFormat("closing \"'\" expected") - else: - if toAdd: emitChar(p, x, f[i]) - inc i - p.i = i - -proc scanBranch(p: var FormatParser, a: openArray[string], - x: var string, choice: int) = - var i = p.i - var f = p.f - var c = 0 - var elsePart = i - var toAdd = choice == 0 - while true: - case f[i] - of ']': break - of '|': - inc i - elsePart = i - inc c - if toAdd: break - toAdd = choice == c - of '\'': - call: scanQuote(p, x, toAdd) - of '\0': raiseInvalidFormat("closing ']' expected") - else: - if toAdd: - if f[i] == '$': - inc i - call: scanDollar(p, a, x) - else: - emitChar(p, x, f[i]) - inc i - else: - inc i - if not toAdd and choice >= 0: - # evaluate 'else' part: - var last = i - i = elsePart - while true: - case f[i] - of '|', ']': break - of '\'': - call: scanQuote(p, x, true) - of '$': - inc i - call: scanDollar(p, a, x) - else: - emitChar(p, x, f[i]) - inc i - i = last - p.i = i+1 - -proc scanSlice(p: var FormatParser, a: openarray[string]): tuple[x, y: int] = - var slice = false - var i = p.i - var f = p.f - - if f[i] == '{': inc i - else: raiseInvalidFormat("'{' expected") - if f[i] == '.' and f[i+1] == '.': - inc i, 2 - slice = true - else: - call: result.x = getFormatArg(p, a) - if f[i] == '.' and f[i+1] == '.': - inc i, 2 - slice = true - if slice: - if f[i] != '}': - call: result.y = getFormatArg(p, a) - else: - result.y = high(a) - else: - result.y = result.x - if f[i] != '}': raiseInvalidFormat("'}' expected") - inc i - p.i = i - -proc scanDollar(p: var FormatParser, a: openarray[string], s: var string) = - var i = p.i - var f = p.f - case f[i] - of '$': - emitChar p, s, '$' - inc i - of '*': - for j in 0..a.high: emitStr p, s, a[j] - inc i - of '{': - call: - let (x, y) = scanSlice(p, a) - for j in x..y: emitStr p, s, a[j] - of '[': - inc i - var start = i - call: scanBranch(p, a, s, -1) - var x: int - if f[i] == '{': - inc i - call: x = getFormatArg(p, a) - if f[i] != '}': raiseInvalidFormat("'}' expected") - inc i - else: - call: x = getFormatArg(p, a) - var last = i - let choice = parseInt(a[x]) - i = start - call: scanBranch(p, a, s, choice) - i = last - of '\'': - var sep = "" - callNoLineLenTracking: scanQuote(p, sep, true) - if f[i] == '~': - # $' '~{1..3} - # insert space followed by 1..3 if not empty - inc i - call: - let (x, y) = scanSlice(p, a) - var L = 0 - for j in x..y: inc L, a[j].len - if L > 0: - emitStrLinear p, s, sep - for j in x..y: emitStr p, s, a[j] - else: - block StringJoin: - block OptionalLineLengthSpecifier: - var maxLen = 0 - case f[i] - of '0'..'9': - while f[i] in Digits: - maxLen = maxLen * 10 + ord(f[i]) - ord('0') - inc i - of '$': - # do not skip the '$' here for `getFormatArg`! - call: - maxLen = getFormatArg(p, a) - else: break OptionalLineLengthSpecifier - var indent = "" - case f[i] - of 'i': - inc i - callNoLineLenTracking: scanQuote(p, indent, true) - - call: - let (x, y) = scanSlice(p, a) - if maxLen < 1: emitStrLinear(p, s, indent) - var items = 1 - emitStr p, s, a[x] - for j in x+1..y: - emitStr p, s, sep - if items >= maxLen: - emitStrLinear p, s, indent - items = 0 - emitStr p, s, a[j] - inc items - of 'c': - inc i - callNoLineLenTracking: scanQuote(p, indent, true) - - call: - let (x, y) = scanSlice(p, a) - if p.lineLen + a[x].len > maxLen: emitStrLinear(p, s, indent) - emitStr p, s, a[x] - for j in x+1..y: - emitStr p, s, sep - if p.lineLen + a[j].len > maxLen: emitStrLinear(p, s, indent) - emitStr p, s, a[j] - - else: raiseInvalidFormat("unit 'c' (chars) or 'i' (items) expected") - break StringJoin - - call: - let (x, y) = scanSlice(p, a) - emitStr p, s, a[x] - for j in x+1..y: - emitStr p, s, sep - emitStr p, s, a[j] - else: - call: - var x = getFormatArg(p, a) - emitStr p, s, a[x] - p.i = i - - -type - Subex* = distinct string ## string that contains a substitution expression - -proc subex*(s: string): Subex = - ## constructs a *substitution expression* from `s`. Currently this performs - ## no syntax checking but this may change in later versions. - result = Subex(s) - -proc addf*(s: var string, formatstr: Subex, a: varargs[string, `$`]) {. - noSideEffect, rtl, extern: "nfrmtAddf".} = - ## The same as ``add(s, formatstr % a)``, but more efficient. - var p: FormatParser - p.f = formatstr.string - var i = 0 - while i < len(formatstr.string): - if p.f[i] == '$': - inc i - call: scanDollar(p, a, s) - else: - emitChar(p, s, p.f[i]) - inc(i) - -proc `%` *(formatstr: Subex, a: openarray[string]): string {.noSideEffect, - rtl, extern: "nfrmtFormatOpenArray".} = - ## The `substitution`:idx: operator performs string substitutions in - ## `formatstr` and returns a modified `formatstr`. This is often called - ## `string interpolation`:idx:. - ## - result = newStringOfCap(formatstr.string.len + a.len shl 4) - addf(result, formatstr, a) - -proc `%` *(formatstr: Subex, a: string): string {.noSideEffect, - rtl, extern: "nfrmtFormatSingleElem".} = - ## This is the same as ``formatstr % [a]``. - result = newStringOfCap(formatstr.string.len + a.len) - addf(result, formatstr, [a]) - -proc format*(formatstr: Subex, a: varargs[string, `$`]): string {.noSideEffect, - rtl, extern: "nfrmtFormatVarargs".} = - ## The `substitution`:idx: operator performs string substitutions in - ## `formatstr` and returns a modified `formatstr`. This is often called - ## `string interpolation`:idx:. - ## - result = newStringOfCap(formatstr.string.len + a.len shl 4) - addf(result, formatstr, a) - -{.pop.} - -when isMainModule: - from strutils import replace - - proc `%`(formatstr: string, a: openarray[string]): string = - result = newStringOfCap(formatstr.len + a.len shl 4) - addf(result, formatstr.Subex, a) - - proc `%`(formatstr: string, a: string): string = - result = newStringOfCap(formatstr.len + a.len) - addf(result, formatstr.Subex, [a]) - - - doAssert "$# $3 $# $#" % ["a", "b", "c"] == "a c b c" - doAssert "$animal eats $food." % ["animal", "The cat", "food", "fish"] == - "The cat eats fish." - - - doAssert "$[abc|def]# $3 $# $#" % ["17", "b", "c"] == "def c b c" - doAssert "$[abc|def]# $3 $# $#" % ["1", "b", "c"] == "def c b c" - doAssert "$[abc|def]# $3 $# $#" % ["0", "b", "c"] == "abc c b c" - doAssert "$[abc|def|]# $3 $# $#" % ["17", "b", "c"] == " c b c" - - doAssert "$[abc|def|]# $3 $# $#" % ["-9", "b", "c"] == " c b c" - doAssert "$1($', '{2..})" % ["f", "a", "b"] == "f(a, b)" - - doAssert "$[$1($', '{2..})|''''|fg'$3']1" % ["7", "a", "b"] == "fg$3" - - doAssert "$[$#($', '{#..})|''''|$3]1" % ["0", "a", "b"] == "0(a, b)" - doAssert "$' '~{..}" % "" == "" - doAssert "$' '~{..}" % "P0" == " P0" - doAssert "${$1}" % "1" == "1" - doAssert "${$$-1} $$1" % "1" == "1 $1" - - doAssert(("$#($', '10c'\n '{#..})" % ["doAssert", "longishA", "longish"]).replace(" \n", "\n") == - """doAssert( - longishA, - longish)""") - - doAssert(("type MyEnum* = enum\n $', '2i'\n '{..}" % ["fieldA", - "fieldB", "FiledClkad", "fieldD", "fieldE", "longishFieldName"]).replace(" \n", "\n") == - strutils.unindent(""" - type MyEnum* = enum - fieldA, fieldB, - FiledClkad, fieldD, - fieldE, longishFieldName""", 6)) - - doAssert subex"$1($', '{2..})" % ["f", "a", "b", "c"] == "f(a, b, c)" - - doAssert subex"$1 $[files|file|files]{1} copied" % ["1"] == "1 file copied" - - doAssert subex"$['''|'|''''|']']#" % "0" == "'|" - - doAssert((subex("type\n Enum = enum\n $', '40c'\n '{..}") % [ - "fieldNameA", "fieldNameB", "fieldNameC", "fieldNameD"]).replace(" \n", "\n") == - strutils.unindent(""" - type - Enum = enum - fieldNameA, fieldNameB, fieldNameC, - fieldNameD""", 6)) diff --git a/lib/pure/terminal.nim b/lib/pure/terminal.nim index 35dc2483c..2b3c08d0d 100644 --- a/lib/pure/terminal.nim +++ b/lib/pure/terminal.nim @@ -37,7 +37,7 @@ type var gTerm {.threadvar.}: PTerminal -proc newTerminal(): PTerminal +proc newTerminal(): PTerminal {.gcsafe.} proc getTerminal(): PTerminal {.inline.} = if isNil(gTerm): diff --git a/lib/pure/times.nim b/lib/pure/times.nim index 010551b5a..0104a97c1 100644 --- a/lib/pure/times.nim +++ b/lib/pure/times.nim @@ -1,35 +1,41 @@ # # # Nim's Runtime Library -# (c) Copyright 2017 Nim contributors +# (c) Copyright 2018 Nim contributors # # See the file "copying.txt", included in this # distribution, for details about the copyright. # ##[ - This module contains routines and types for dealing with time using a proleptic Gregorian calendar. - It's also available for the `JavaScript target <backends.html#the-javascript-target>`_. + The ``times`` module contains routines and types for dealing with time using + the `proleptic Gregorian calendar<https://en.wikipedia.org/wiki/Proleptic_Gregorian_calendar>`_. + It's also available for the + `JavaScript target <backends.html#backends-the-javascript-target>`_. - Although the types use nanosecond time resolution, the underlying resolution used by ``getTime()`` - depends on the platform and backend (JS is limited to millisecond precision). + Although the ``times`` module support nanosecond time resolution, the + resolution used by ``getTime()`` depends on the platform and backend + (JS is limited to millisecond precision). Examples: .. code-block:: nim - import times, os + # Simple benchmarking let time = cpuTime() - - sleep(100) # replace this with something to be timed + sleep(100) # Replace this with something to be timed echo "Time taken: ", cpuTime() - time - echo "My formatted time: ", format(now(), "d MMMM yyyy HH:mm") - echo "Using predefined formats: ", getClockStr(), " ", getDateStr() + # Current date & time + let now1 = now() # Current timestamp as a DateTime in local time + let now2 = now().utc # Current timestamp as a DateTime in UTC + let now3 = getTime() # Current timestamp as a Time - echo "cpuTime() float value: ", cpuTime() - echo "An hour from now : ", now() + 1.hours - echo "An hour from (UTC) now: ", getTime().utc + initDuration(hours = 1) + # Arithmetic using Duration + echo "One hour from now : ", now() + initDuration(hours = 1) + # Arithmetic using TimeInterval + echo "One year from now : ", now() + 1.years + echo "One month from now : ", now() + 1.months Parsing and Formatting Dates ---------------------------- @@ -97,14 +103,14 @@ | ``24 AD -> 24`` | ``24 BC -> -23`` | ``12345 AD -> 12345`` - ``z`` Displays the timezone offset from UTC. | ``GMT+7 -> +7`` - | ``GMT-5 -> -5`` - ``zz`` Same as above but with leading 0. | ``GMT+7 -> +07`` - | ``GMT-5 -> -05`` - ``zzz`` Same as above but with ``:mm`` where *mm* represents minutes. | ``GMT+7 -> +07:00`` - | ``GMT-5 -> -05:00`` - ``zzzz`` Same as above but with ``:ss`` where *ss* represents seconds. | ``GMT+7 -> +07:00:00`` - | ``GMT-5 -> -05:00:00`` + ``z`` Displays the timezone offset from UTC. | ``UTC+7 -> +7`` + | ``UTC-5 -> -5`` + ``zz`` Same as above but with leading 0. | ``UTC+7 -> +07`` + | ``UTC-5 -> -05`` + ``zzz`` Same as above but with ``:mm`` where *mm* represents minutes. | ``UTC+7 -> +07:00`` + | ``UTC-5 -> -05:00`` + ``zzzz`` Same as above but with ``:ss`` where *ss* represents seconds. | ``UTC+7 -> +07:00:00`` + | ``UTC-5 -> -05:00:00`` ``g`` Era: AD or BC | ``300 AD -> AD`` | ``300 BC -> BC`` ``fff`` Milliseconds display | ``1000000 nanoseconds -> 1`` @@ -117,72 +123,141 @@ inserted without quoting them: ``:`` ``-`` ``(`` ``)`` ``/`` ``[`` ``]`` ``,``. A literal ``'`` can be specified with ``''``. - However you don't need to necessarily separate format patterns, a + However you don't need to necessarily separate format patterns, an unambiguous format string like ``yyyyMMddhhmmss`` is valid too (although only for years in the range 1..9999). -]## - -{.push debugger:off.} # the user does not want to trace a part - # of the standard library! + Duration vs TimeInterval + ---------------------------- + The ``times`` module exports two similiar types that are both used to + represent some amount of time: ``Duration`` and ``TimeInterval``. + This section explains how they differ and when one should be prefered over the + other (short answer: use ``Duration`` unless support for months and years is + 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. + + Arithmetics with a ``Duration`` is very fast, especially when used with the + ``Time`` type, since it only involves basic arithmetic. Because ``Duration`` + 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), + the ``TimeInterval`` type uses seperate fields for every unit. The + ``TimeInterval``'s returned form the this module generally don't normalize + **anything**, so even units that could be normalized (like seconds, + milliseconds and so on) are left untouched. + + Arithmetics with a ``TimeInterval`` can be very slow, because it requires + timezone information. + + Since it's slower and more complex, the ``TimeInterval`` type should be + avoided unless the program explicitly needs the features it offers that + ``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. + + As an example, consider the amount of time between these two timestamps, both + in the same timezone: + + - 2018-03-25T12:00+02:00 + - 2018-03-26T12:00+01:00 + + If only the date & time is considered, it appears that exatly one day has + passed. However, the UTC offsets are different, which means that the + UTC offset was changed somewhere between. This happens twice each year for + timezones that use daylight savings time. Because of this change, the amount + of time that has passed is actually 25 hours. + + The ``TimeInterval`` type uses calendar units, and will say that exactly one + day has passed. The ``Duration`` type on the other hand normalizes everything + to seconds, and will therefore say that 90000 seconds has passed, which is + the same as 25 hours. +]## -import - strutils, parseutils, algorithm, math, options, strformat +import strutils, algorithm, math, options, strformat include "system/inclrtl" -# This is really bad, but overflow checks are broken badly for -# ints on the JS backend. See #6752. when defined(JS): + import jscore + + # This is really bad, but overflow checks are broken badly for + # ints on the JS backend. See #6752. {.push overflowChecks: off.} proc `*`(a, b: int64): int64 = - system.`* `(a, b) + system.`*`(a, b) proc `*`(a, b: int): int = - system.`* `(a, b) + system.`*`(a, b) proc `+`(a, b: int64): int64 = - system.`+ `(a, b) + system.`+`(a, b) proc `+`(a, b: int): int = - system.`+ `(a, b) + system.`+`(a, b) proc `-`(a, b: int64): int64 = - system.`- `(a, b) + system.`-`(a, b) proc `-`(a, b: int): int = - system.`- `(a, b) + system.`-`(a, b) proc inc(a: var int, b: int) = system.inc(a, b) proc inc(a: var int64, b: int) = system.inc(a, b) {.pop.} -when defined(posix): +elif defined(posix): import posix type CTime = posix.Time var realTimeClockId {.importc: "CLOCK_REALTIME", header: "<time.h>".}: Clockid - cpuClockId {.importc: "CLOCK_THREAD_CPUTIME_ID", header: "<time.h>".}: Clockid + cpuClockId + {.importc: "CLOCK_THREAD_CPUTIME_ID", header: "<time.h>".}: Clockid - proc gettimeofday(tp: var Timeval, unused: pointer = nil) {. - importc: "gettimeofday", header: "<sys/time.h>".} + proc gettimeofday(tp: var Timeval, unused: pointer = nil) + {.importc: "gettimeofday", header: "<sys/time.h>".} when not defined(freebsd) and not defined(netbsd) and not defined(openbsd): var timezone {.importc, header: "<time.h>".}: int - tzset() + when not defined(valgrind_workaround_10121): + tzset() elif defined(windows): - import winlean + import winlean, std/time_t + + type CTime = time_t.Time - when defined(i386) and defined(gcc): - type CTime {.importc: "time_t", header: "<time.h>".} = distinct int32 - else: - # newest version of Visual C++ defines time_t to be of 64 bits - type CTime {.importc: "time_t", header: "<time.h>".} = distinct int64 # visual c's c runtime exposes these under a different name var timezone {.importc: "_timezone", header: "<time.h>".}: int + type + Tm {.importc: "struct tm", header: "<time.h>", final, pure.} = object + tm_sec*: cint ## Seconds [0,60]. + tm_min*: cint ## Minutes [0,59]. + tm_hour*: cint ## Hour [0,23]. + tm_mday*: cint ## Day of month [1,31]. + tm_mon*: cint ## Month of year [0,11]. + tm_year*: cint ## Years since 1900. + tm_wday*: cint ## Day of week [0,6] (Sunday =0). + tm_yday*: cint ## Day of year [0,365]. + tm_isdst*: cint ## Daylight Savings flag. + + proc localtime(a1: var CTime): ptr Tm {.importc, header: "<time.h>".} + type - Month* = enum ## Represents a month. Note that the enum starts at ``1``, so ``ord(month)`` will give - ## the month number in the range ``[1..12]``. + Month* = enum ## Represents a month. Note that the enum starts at ``1``, + ## so ``ord(month)`` will give the month number in the + ## range ``1..12``. mJan = (1, "January") mFeb = "February" mMar = "March" @@ -216,13 +291,19 @@ type seconds: int64 nanosecond: NanosecondRange - DateTime* = object of RootObj ## Represents a time in different parts. - ## Although this type can represent leap - ## seconds, they are generally not supported - ## in this module. They are not ignored, - ## but the ``DateTime``'s returned by - ## procedures in this module will never have - ## a leap second. + DateTime* = object of RootObj ## \ + ## Represents a time in different parts. Although this type can represent + ## leap seconds, they are generally not supported in this module. They are + ## not ignored, but the ``DateTime``'s returned by procedures in this + ## module will never have a leap second. + ## + ## **Warning**: even though the fields of ``DateTime`` are exported, + ## they should never be mutated directly. Doing so is unsafe and will + ## result in the ``DateTime`` ending up in an invalid state. + ## + ## Instead of mutating the fields directly, use the ``Duration`` + ## and ``TimeInterval`` types for arithmetic and use the ``initDateTime`` + ## procedure for changing a specific field. nanosecond*: NanosecondRange ## The number of nanoseconds after the second, ## in the range 0 to 999_999_999. second*: SecondRange ## The number of seconds after the minute, @@ -233,27 +314,48 @@ type hour*: HourRange ## The number of hours past midnight, ## in the range 0 to 23. monthday*: MonthdayRange ## The day of the month, in the range 1 to 31. - month*: Month ## The current month. - year*: int ## The current year, using astronomical year numbering - ## (meaning that before year 1 is year 0, then year -1 and so on). - weekday*: WeekDay ## The current day of the week. + month*: Month ## The month. + year*: int ## The year, using astronomical year numbering + ## (meaning that before year 1 is year 0, + ## then year -1 and so on). + weekday*: WeekDay ## The day of the week. yearday*: YeardayRange ## The number of days since January 1, ## in the range 0 to 365. isDst*: bool ## Determines whether DST is in effect. ## Always false for the JavaScript backend. - timezone*: Timezone ## The timezone represented as an implementation of ``Timezone``. - utcOffset*: int ## The offset in seconds west of UTC, including any offset due to DST. - ## Note that the sign of this number is the opposite - ## of the one in a formatted offset string like ``+01:00`` - ## (which would be parsed into the UTC offset ``-3600``). - - TimeInterval* = object ## Represents a non-fixed duration of time. Can be used to add and subtract - ## non-fixed time units from a ``DateTime`` or ``Time``. - ## ``TimeInterval`` doesn't represent a fixed duration of time, - ## since the duration of some units depend on the context (e.g a year - ## can be either 365 or 366 days long). The non-fixed time units are years, - ## months and days. + timezone*: Timezone ## The timezone represented as an implementation + ## of ``Timezone``. + utcOffset*: int ## The offset in seconds west of UTC, including + ## any offset due to DST. Note that the sign of + ## this number is the opposite of the one in a + ## formatted offset string like ``+01:00`` (which + ## would be equivalent to the UTC offset + ## ``-3600``). + + Duration* = object ## Represents a fixed duration of time, meaning a duration + ## that has constant length independent of the context. + seconds: int64 + nanosecond: NanosecondRange + TimeUnit* = enum ## Different units of time. + Nanoseconds, Microseconds, Milliseconds, Seconds, Minutes, Hours, Days, + Weeks, Months, Years + + FixedTimeUnit* = range[Nanoseconds..Weeks] ## \ + ## Subrange of ``TimeUnit`` that only includes units of fixed duration. + ## These are the units that can be represented by a ``Duration``. + + TimeInterval* = object ## \ + ## Represents a non-fixed duration of time. Can be used to add and + ## subtract non-fixed time units from a ``DateTime`` or ``Time``. + ## Note that ``TimeInterval`` doesn't represent a fixed duration of time, + ## since the duration of some units depend on the context (e.g a year + ## can be either 365 or 366 days long). The non-fixed time units are + ## years, months, days and week. + ## + ## Note that ``TimeInterval``'s returned from the ``times`` module are + ## never normalized. If you want to normalize a time unit, ``Duration`` + ## should be used instead. nanoseconds*: int ## The number of nanoseconds microseconds*: int ## The number of microseconds milliseconds*: int ## The number of milliseconds @@ -265,19 +367,6 @@ type months*: int ## The number of months years*: int ## The number of years - Duration* = object ## Represents a fixed duration of time. - ## Uses the same time resolution as ``Time``. - ## This type should be prefered over ``TimeInterval`` unless - ## non-static time units is needed. - seconds: int64 - nanosecond: NanosecondRange - - TimeUnit* = enum ## Different units of time. - Nanoseconds, Microseconds, Milliseconds, Seconds, Minutes, Hours, Days, Weeks, Months, Years - - FixedTimeUnit* = range[Nanoseconds..Weeks] ## Subrange of ``TimeUnit`` that only includes units of fixed duration. - ## These are the units that can be represented by a ``Duration``. - Timezone* = ref object ## \ ## Timezone interface for supporting ``DateTime``'s of arbritary ## timezones. The ``times`` module only supplies implementations for the @@ -304,7 +393,6 @@ const secondsInMin = 60 secondsInHour = 60*60 secondsInDay = 60*60*24 - minutesInHour = 60 rateDiff = 10000000'i64 # 100 nsecs # The number of hectonanoseconds between 1601/01/01 (windows epoch) # and 1970/01/01 (unix epoch). @@ -321,8 +409,10 @@ const unitWeights: array[FixedTimeUnit, int64] = [ 7 * secondsInDay * 1e9.int64, ] -proc convert*[T: SomeInteger](unitFrom, unitTo: FixedTimeUnit, quantity: T): T {.inline.} = +proc convert*[T: SomeInteger](unitFrom, unitTo: FixedTimeUnit, quantity: T): T + {.inline.} = ## Convert a quantity of some duration unit to another duration unit. + ## This proc only deals with integers, so the result might be truncated. runnableExamples: doAssert convert(Days, Hours, 2) == 48 doAssert convert(Days, Weeks, 13) == 1 # Truncated @@ -344,21 +434,41 @@ proc normalize[T: Duration|Time](seconds, nanoseconds: int64): T = result.nanosecond = nanosecond.int # Forward declarations -proc utcTzInfo(time: Time): ZonedTime {.tags: [], raises: [], benign .} -proc localZonedTimeFromTime(time: Time): ZonedTime {.tags: [], raises: [], benign .} -proc localZonedTimeFromAdjTime(adjTime: Time): ZonedTime {.tags: [], raises: [], benign .} +proc utcTzInfo(time: Time): ZonedTime + {.tags: [], raises: [], benign.} +proc localZonedTimeFromTime(time: Time): ZonedTime + {.tags: [], raises: [], benign.} +proc localZonedTimeFromAdjTime(adjTime: Time): ZonedTime + {.tags: [], raises: [], benign.} proc initTime*(unix: int64, nanosecond: NanosecondRange): Time - {.tags: [], raises: [], benign noSideEffect.} - -proc initDuration*(nanoseconds, microseconds, milliseconds, - seconds, minutes, hours, days, weeks: int64 = 0): Duration - {.tags: [], raises: [], benign noSideEffect.} + {.tags: [], raises: [], benign, noSideEffect.} proc nanosecond*(time: Time): NanosecondRange = ## Get the fractional part of a ``Time`` as the number ## of nanoseconds of the second. time.nanosecond +proc initDuration*(nanoseconds, microseconds, milliseconds, + seconds, minutes, hours, days, weeks: int64 = 0): Duration = + ## Create a new duration. + runnableExamples: + let dur = initDuration(seconds = 1, milliseconds = 1) + doAssert dur.milliseconds == 1 + doAssert dur.seconds == 1 + + let seconds = convert(Weeks, Seconds, weeks) + + convert(Days, Seconds, days) + + convert(Minutes, Seconds, minutes) + + convert(Hours, Seconds, hours) + + convert(Seconds, Seconds, seconds) + + convert(Milliseconds, Seconds, milliseconds) + + convert(Microseconds, Seconds, microseconds) + + convert(Nanoseconds, Seconds, nanoseconds) + let nanoseconds = (convert(Milliseconds, Nanoseconds, milliseconds mod 1000) + + convert(Microseconds, Nanoseconds, microseconds mod 1_000_000) + + nanoseconds mod 1_000_000_000).int + # Nanoseconds might be negative so we must normalize. + result = normalize[Duration](seconds, nanoseconds) proc weeks*(dur: Duration): int64 {.inline.} = ## Number of whole weeks represented by the duration. @@ -411,9 +521,10 @@ proc fractional*(dur: Duration): Duration {.inline.} = doAssert dur.fractional == initDuration(nanoseconds = 5) initDuration(nanoseconds = dur.nanosecond) - -proc fromUnix*(unix: int64): Time {.benign, tags: [], raises: [], noSideEffect.} = - ## Convert a unix timestamp (seconds since ``1970-01-01T00:00:00Z``) to a ``Time``. +proc fromUnix*(unix: int64): Time + {.benign, tags: [], raises: [], noSideEffect.} = + ## Convert a unix timestamp (seconds since ``1970-01-01T00:00:00Z``) + ## to a ``Time``. runnableExamples: doAssert $fromUnix(0).utc == "1970-01-01T00:00:00Z" initTime(unix, 0) @@ -425,15 +536,16 @@ proc toUnix*(t: Time): int64 {.benign, tags: [], raises: [], noSideEffect.} = t.seconds proc fromWinTime*(win: int64): Time = - ## Convert a Windows file time (100-nanosecond intervals since ``1601-01-01T00:00:00Z``) - ## to a ``Time``. + ## Convert a Windows file time (100-nanosecond intervals since + ## ``1601-01-01T00:00:00Z``) to a ``Time``. const hnsecsPerSec = convert(Seconds, Nanoseconds, 1) div 100 let nanos = floorMod(win, hnsecsPerSec) * 100 let seconds = floorDiv(win - epochDiff, hnsecsPerSec) result = initTime(seconds, nanos) proc toWinTime*(t: Time): int64 = - ## Convert ``t`` to a Windows file time (100-nanosecond intervals since ``1601-01-01T00:00:00Z``). + ## Convert ``t`` to a Windows file time (100-nanosecond intervals + ## since ``1601-01-01T00:00:00Z``). result = t.seconds * rateDiff + epochDiff + t.nanosecond div 100 proc isLeapYear*(year: int): bool = @@ -441,7 +553,7 @@ proc isLeapYear*(year: int): bool = year mod 4 == 0 and (year mod 100 != 0 or year mod 400 == 0) proc getDaysInMonth*(month: Month, year: int): int = - ## Get the number of days in a ``month`` of a ``year``. + ## Get the number of days in ``month`` of ``year``. # http://www.dispersiondesign.com/articles/time/number_of_days_in_a_month case month of mFeb: result = if isLeapYear(year): 29 else: 28 @@ -452,15 +564,18 @@ proc getDaysInYear*(year: int): int = ## Get the number of days in a ``year`` result = 365 + (if isLeapYear(year): 1 else: 0) -proc assertValidDate(monthday: MonthdayRange, month: Month, year: int) {.inline.} = +proc assertValidDate(monthday: MonthdayRange, month: Month, year: int) + {.inline.} = assert monthday <= getDaysInMonth(month, year), - $year & "-" & intToStr(ord(month), 2) & "-" & $monthday & " is not a valid date" + $year & "-" & intToStr(ord(month), 2) & "-" & $monthday & + " is not a valid date" proc toEpochDay(monthday: MonthdayRange, month: Month, year: int): int64 = ## Get the epoch day from a year/month/day date. - ## The epoch day is the number of days since 1970/01/01 (it might be negative). - assertValidDate monthday, month, year + ## The epoch day is the number of days since 1970/01/01 + ## (it might be negative). # Based on http://howardhinnant.github.io/date_algorithms.html + assertValidDate monthday, month, year var (y, m, d) = (year, ord(month), monthday.int) if m <= 2: y.dec @@ -471,9 +586,11 @@ proc toEpochDay(monthday: MonthdayRange, month: Month, year: int): int64 = let doe = yoe * 365 + yoe div 4 - yoe div 100 + doy return era * 146097 + doe - 719468 -proc fromEpochDay(epochday: int64): tuple[monthday: MonthdayRange, month: Month, year: int] = +proc fromEpochDay(epochday: int64): + tuple[monthday: MonthdayRange, month: Month, year: int] = ## Get the year/month/day date from a epoch day. - ## The epoch day is the number of days since 1970/01/01 (it might be negative). + ## The epoch day is the number of days since 1970/01/01 + ## (it might be negative). # Based on http://howardhinnant.github.io/date_algorithms.html var z = epochday z.inc 719468 @@ -487,19 +604,23 @@ proc fromEpochDay(epochday: int64): tuple[monthday: MonthdayRange, month: Month, let m = mp + (if mp < 10: 3 else: -9) return (d.MonthdayRange, m.Month, (y + ord(m <= 2)).int) -proc getDayOfYear*(monthday: MonthdayRange, month: Month, year: int): YeardayRange {.tags: [], raises: [], benign .} = +proc getDayOfYear*(monthday: MonthdayRange, month: Month, year: int): + YeardayRange {.tags: [], raises: [], benign.} = ## Returns the day of the year. ## Equivalent with ``initDateTime(monthday, month, year, 0, 0, 0).yearday``. assertValidDate monthday, month, year - const daysUntilMonth: array[Month, int] = [0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334] - const daysUntilMonthLeap: array[Month, int] = [0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335] + const daysUntilMonth: array[Month, int] = + [0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334] + const daysUntilMonthLeap: array[Month, int] = + [0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335] if isLeapYear(year): result = daysUntilMonthLeap[month] + monthday - 1 else: result = daysUntilMonth[month] + monthday - 1 -proc getDayOfWeek*(monthday: MonthdayRange, month: Month, year: int): WeekDay {.tags: [], raises: [], benign .} = +proc getDayOfWeek*(monthday: MonthdayRange, month: Month, year: int): WeekDay + {.tags: [], raises: [], benign.} = ## Returns the day of the week enum from day, month and year. ## Equivalent with ``initDateTime(monthday, month, year, 0, 0, 0).weekday``. assertValidDate monthday, month, year @@ -511,8 +632,7 @@ proc getDayOfWeek*(monthday: MonthdayRange, month: Month, year: int): WeekDay {. # so we must correct for the WeekDay type. result = if wd == 0: dSun else: WeekDay(wd - 1) - -{. pragma: operator, rtl, noSideEffect, benign .} +{.pragma: operator, rtl, noSideEffect, benign.} template subImpl[T: Duration|Time](a: Duration|Time, b: Duration|Time): T = normalize[T](a.seconds - b.seconds, a.nanosecond - b.nanosecond) @@ -530,28 +650,6 @@ template lqImpl(a: Duration|Time, b: Duration|Time): bool = template eqImpl(a: Duration|Time, b: Duration|Time): bool = a.seconds == b.seconds and a.nanosecond == b.nanosecond - -proc initDuration*(nanoseconds, microseconds, milliseconds, - seconds, minutes, hours, days, weeks: int64 = 0): Duration = - runnableExamples: - let dur = initDuration(seconds = 1, milliseconds = 1) - doAssert dur.milliseconds == 1 - doAssert dur.seconds == 1 - - let seconds = convert(Weeks, Seconds, weeks) + - convert(Days, Seconds, days) + - convert(Minutes, Seconds, minutes) + - convert(Hours, Seconds, hours) + - convert(Seconds, Seconds, seconds) + - convert(Milliseconds, Seconds, milliseconds) + - convert(Microseconds, Seconds, microseconds) + - convert(Nanoseconds, Seconds, nanoseconds) - let nanoseconds = (convert(Milliseconds, Nanoseconds, milliseconds mod 1000) + - convert(Microseconds, Nanoseconds, microseconds mod 1_000_000) + - nanoseconds mod 1_000_000_000).int - # Nanoseconds might be negative so we must normalize. - result = normalize[Duration](seconds, nanoseconds) - const DurationZero* = initDuration() ## \ ## Zero value for durations. Useful for comparisons. ## @@ -568,7 +666,7 @@ proc toParts*(dur: Duration): DurationParts = ## ## This procedure is useful for converting ``Duration`` values to strings. runnableExamples: - var dp = toParts(initDuration(weeks=2, days=1)) + var dp = toParts(initDuration(weeks = 2, days = 1)) doAssert dp[Days] == 1 doAssert dp[Weeks] == 2 dp = toParts(initDuration(days = -1)) @@ -620,12 +718,14 @@ proc humanizeParts(parts: seq[string]): string = result.add "and " & parts[high(parts)] proc `$`*(dur: Duration): string = - ## Human friendly string representation of ``Duration``. + ## Human friendly string representation of a ``Duration``. runnableExamples: doAssert $initDuration(seconds = 2) == "2 seconds" doAssert $initDuration(weeks = 1, days = 2) == "1 week and 2 days" - doAssert $initDuration(hours = 1, minutes = 2, seconds = 3) == "1 hour, 2 minutes, and 3 seconds" - doAssert $initDuration(milliseconds = -1500) == "-1 second and -500 milliseconds" + doAssert $initDuration(hours = 1, minutes = 2, seconds = 3) == + "1 hour, 2 minutes, and 3 seconds" + doAssert $initDuration(milliseconds = -1500) == + "-1 second and -500 milliseconds" var parts = newSeq[string]() var numParts = toParts(dur) @@ -663,7 +763,7 @@ proc `<`*(a, b: Duration): bool {.operator.} = ## Use ``abs(a) < abs(b)`` to compare the absolute ## duration. runnableExamples: - doAssert initDuration(seconds = 1) < initDuration(seconds = 2) + doAssert initDuration(seconds = 1) < initDuration(seconds = 2) doAssert initDuration(seconds = -2) < initDuration(seconds = 1) ltImpl(a, b) @@ -673,23 +773,25 @@ proc `<=`*(a, b: Duration): bool {.operator.} = proc `==`*(a, b: Duration): bool {.operator.} = eqImpl(a, b) -proc `*`*(a: int64, b: Duration): Duration {.operator} = +proc `*`*(a: int64, b: Duration): Duration {.operator.} = ## Multiply a duration by some scalar. runnableExamples: doAssert 5 * initDuration(seconds = 1) == initDuration(seconds = 5) normalize[Duration](a * b.seconds, a * b.nanosecond) -proc `*`*(a: Duration, b: int64): Duration {.operator} = +proc `*`*(a: Duration, b: int64): Duration {.operator.} = ## Multiply a duration by some scalar. runnableExamples: doAssert initDuration(seconds = 1) * 5 == initDuration(seconds = 5) b * a -proc `div`*(a: Duration, b: int64): Duration {.operator} = +proc `div`*(a: Duration, b: int64): Duration {.operator.} = ## Integer division for durations. runnableExamples: - doAssert initDuration(seconds = 3) div 2 == initDuration(milliseconds = 1500) - doAssert initDuration(nanoseconds = 3) div 2 == initDuration(nanoseconds = 1) + doAssert initDuration(seconds = 3) div 2 == + initDuration(milliseconds = 1500) + doAssert initDuration(nanoseconds = 3) div 2 == + initDuration(nanoseconds = 1) let carryOver = convert(Seconds, Nanoseconds, a.seconds mod b) normalize[Duration](a.seconds div b, (a.nanosecond + carryOver) div b) @@ -718,7 +820,7 @@ proc `<`*(a, b: Time): bool {.operator, extern: "ntLtTime".} = ## Returns true iff ``a < b``, that is iff a happened before b. ltImpl(a, b) -proc `<=` * (a, b: Time): bool {.operator, extern: "ntLeTime".} = +proc `<=`*(a, b: Time): bool {.operator, extern: "ntLeTime".} = ## Returns true iff ``a <= b``. lqImpl(a, b) @@ -787,8 +889,10 @@ proc initDateTime(zt: ZonedTime, zone: Timezone): DateTime = proc newTimezone*( name: string, - zonedTimeFromTimeImpl: proc (time: Time): ZonedTime {.tags: [], raises: [], benign.}, - zonedTimeFromAdjTimeImpl: proc (adjTime: Time): ZonedTime {.tags: [], raises: [], benign.} + zonedTimeFromTimeImpl: proc (time: Time): ZonedTime + {.tags: [], raises: [], benign.}, + zonedTimeFromAdjTimeImpl: proc (adjTime: Time): ZonedTime + {.tags: [], raises: [], benign.} ): Timezone = ## Create a new ``Timezone``. ## @@ -851,11 +955,13 @@ proc `==`*(zone1, zone2: Timezone): bool = doAssert local() != utc() zone1.name == zone2.name -proc inZone*(time: Time, zone: Timezone): DateTime {.tags: [], raises: [], benign.} = +proc inZone*(time: Time, zone: Timezone): DateTime + {.tags: [], raises: [], benign.} = ## Convert ``time`` into a ``DateTime`` using ``zone`` as the timezone. result = initDateTime(zone.zonedTimeFromTime(time), zone) -proc inZone*(dt: DateTime, zone: Timezone): DateTime {.tags: [], raises: [], benign.} = +proc inZone*(dt: DateTime, zone: Timezone): DateTime + {.tags: [], raises: [], benign.} = ## Returns a ``DateTime`` representing the same point in time as ``dt`` but ## using ``zone`` as the timezone. dt.toTime.inZone(zone) @@ -869,94 +975,38 @@ proc toAdjTime(dt: DateTime): Time = result = initTime(seconds, dt.nanosecond) when defined(JS): - type JsDate = object - proc newDate(year, month, date, hours, minutes, seconds, milliseconds: int): JsDate {.tags: [], raises: [], importc: "new Date".} - proc newDate(): JsDate {.importc: "new Date".} - proc newDate(value: float): JsDate {.importc: "new Date".} - proc getTimezoneOffset(js: JsDate): int {.tags: [], raises: [], benign, importcpp.} - proc getDay(js: JsDate): int {.tags: [], raises: [], benign, importcpp.} - proc getFullYear(js: JsDate): int {.tags: [], raises: [], benign, importcpp.} - proc getHours(js: JsDate): int {.tags: [], raises: [], benign, importcpp.} - proc getMilliseconds(js: JsDate): int {.tags: [], raises: [], benign, importcpp.} - proc getMinutes(js: JsDate): int {.tags: [], raises: [], benign, importcpp.} - proc getMonth(js: JsDate): int {.tags: [], raises: [], benign, importcpp.} - proc getSeconds(js: JsDate): int {.tags: [], raises: [], benign, importcpp.} - proc getTime(js: JsDate): int {.tags: [], raises: [], noSideEffect, benign, importcpp.} - proc getDate(js: JsDate): int {.tags: [], raises: [], benign, importcpp.} - proc getUTCDate(js: JsDate): int {.tags: [], raises: [], benign, importcpp.} - proc getUTCFullYear(js: JsDate): int {.tags: [], raises: [], benign, importcpp.} - proc getUTCHours(js: JsDate): int {.tags: [], raises: [], benign, importcpp.} - proc getUTCMilliseconds(js: JsDate): int {.tags: [], raises: [], benign, importcpp.} - proc getUTCMinutes(js: JsDate): int {.tags: [], raises: [], benign, importcpp.} - proc getUTCMonth(js: JsDate): int {.tags: [], raises: [], benign, importcpp.} - proc getUTCSeconds(js: JsDate): int {.tags: [], raises: [], benign, importcpp.} - proc getUTCDay(js: JsDate): int {.tags: [], raises: [], benign, importcpp.} - proc getYear(js: JsDate): int {.tags: [], raises: [], benign, importcpp.} - proc setFullYear(js: JsDate, year: int): void {.tags: [], raises: [], benign, importcpp.} - - proc localZonedTimeFromTime(time: Time): ZonedTime = - let jsDate = newDate(time.seconds.float * 1000) - let offset = jsDate.getTimezoneOffset() * secondsInMin - result.time = time - result.utcOffset = offset - result.isDst = false - - proc localZonedTimeFromAdjTime(adjTime: Time): ZonedTime = - let utcDate = newDate(adjTime.seconds.float * 1000) - let localDate = newDate(utcDate.getUTCFullYear(), utcDate.getUTCMonth(), utcDate.getUTCDate(), - utcDate.getUTCHours(), utcDate.getUTCMinutes(), utcDate.getUTCSeconds(), 0) - - # This is as dumb as it looks - JS doesn't support years in the range 0-99 in the constructor - # because they are assumed to be 19xx... - # Because JS doesn't support timezone history, it doesn't really matter in practice. - if utcDate.getUTCFullYear() in 0 .. 99: - localDate.setFullYear(utcDate.getUTCFullYear()) - - result.utcOffset = localDate.getTimezoneOffset() * secondsInMin - result.time = adjTime + initDuration(seconds = result.utcOffset) - result.isDst = false + proc localZonedTimeFromTime(time: Time): ZonedTime = + let jsDate = newDate(time.seconds * 1000) + let offset = jsDate.getTimezoneOffset() * secondsInMin + result.time = time + result.utcOffset = offset + result.isDst = false -else: - when defined(freebsd) or defined(netbsd) or defined(openbsd) or - defined(macosx): - type - StructTm {.importc: "struct tm".} = object - second {.importc: "tm_sec".}, - minute {.importc: "tm_min".}, - hour {.importc: "tm_hour".}, - monthday {.importc: "tm_mday".}, - month {.importc: "tm_mon".}, - year {.importc: "tm_year".}, - weekday {.importc: "tm_wday".}, - yearday {.importc: "tm_yday".}, - isdst {.importc: "tm_isdst".}: cint - gmtoff {.importc: "tm_gmtoff".}: clong - else: - type - StructTm {.importc: "struct tm".} = object - second {.importc: "tm_sec".}, - minute {.importc: "tm_min".}, - hour {.importc: "tm_hour".}, - monthday {.importc: "tm_mday".}, - month {.importc: "tm_mon".}, - year {.importc: "tm_year".}, - weekday {.importc: "tm_wday".}, - yearday {.importc: "tm_yday".}, - isdst {.importc: "tm_isdst".}: cint - when defined(linux) and defined(amd64) or defined(haiku): - gmtoff {.importc: "tm_gmtoff".}: clong - zone {.importc: "tm_zone".}: cstring - type - StructTmPtr = ptr StructTm + proc localZonedTimeFromAdjTime(adjTime: Time): ZonedTime = + let utcDate = newDate(adjTime.seconds * 1000) + let localDate = newDate(utcDate.getUTCFullYear(), utcDate.getUTCMonth(), + utcDate.getUTCDate(), utcDate.getUTCHours(), utcDate.getUTCMinutes(), + utcDate.getUTCSeconds(), 0) + + # This is as dumb as it looks - JS doesn't support years in the range + # 0-99 in the constructor because they are assumed to be 19xx... + # Because JS doesn't support timezone history, + # it doesn't really matter in practice. + if utcDate.getUTCFullYear() in 0 .. 99: + localDate.setFullYear(utcDate.getUTCFullYear()) - proc localtime(timer: ptr CTime): StructTmPtr {. importc: "localtime", header: "<time.h>", tags: [].} + result.utcOffset = localDate.getTimezoneOffset() * secondsInMin + result.time = adjTime + initDuration(seconds = result.utcOffset) + result.isDst = false - proc toAdjUnix(tm: StructTm): int64 = - let epochDay = toEpochday(tm.monthday, (tm.month + 1).Month, tm.year.int + 1900) +else: + proc toAdjUnix(tm: Tm): int64 = + let epochDay = toEpochday(tm.tm_mday, (tm.tm_mon + 1).Month, + tm.tm_year.int + 1900) result = epochDay * secondsInDay - result.inc tm.hour * secondsInHour - result.inc tm.minute * 60 - result.inc tm.second + result.inc tm.tm_hour * secondsInHour + result.inc tm.tm_min * 60 + result.inc tm.tm_sec proc getLocalOffsetAndDst(unix: int64): tuple[offset: int, dst: bool] = # Windows can't handle unix < 0, so we fall back to unix = 0. @@ -964,7 +1014,7 @@ else: when defined(windows): if unix < 0: var a = 0.CTime - let tmPtr = localtime(addr(a)) + let tmPtr = localtime(a) if not tmPtr.isNil: let tm = tmPtr[] return ((0 - tm.toAdjUnix).int, false) @@ -973,10 +1023,10 @@ else: # In case of a 32-bit time_t, we fallback to the closest available # timezone information. var a = clamp(unix, low(CTime), high(CTime)).CTime - let tmPtr = localtime(addr(a)) + let tmPtr = localtime(a) if not tmPtr.isNil: let tm = tmPtr[] - return ((a.int64 - tm.toAdjUnix).int, tm.isdst > 0) + return ((a.int64 - tm.toAdjUnix).int, tm.tm_isdst > 0) return (0, false) proc localZonedTimeFromTime(time: Time): ZonedTime = @@ -985,7 +1035,7 @@ else: result.utcOffset = offset result.isDst = dst - proc localZonedTimeFromAdjTime(adjTime: Time): ZonedTime = + proc localZonedTimeFromAdjTime(adjTime: Time): ZonedTime = var adjUnix = adjTime.seconds let past = adjUnix - secondsInDay let (pastOffset, _) = getLocalOffsetAndDst(past) @@ -995,7 +1045,7 @@ else: var utcOffset: int if pastOffset == futureOffset: - utcOffset = pastOffset.int + utcOffset = pastOffset.int else: if pastOffset > futureOffset: adjUnix -= secondsInHour @@ -1029,8 +1079,8 @@ proc utc*(): TimeZone = proc local*(): TimeZone = ## Get the ``Timezone`` implementation for the local timezone. runnableExamples: - doAssert now().timezone == local() - doAssert local().name == "LOCAL" + doAssert now().timezone == local() + doAssert local().name == "LOCAL" if localInstance.isNil: localInstance = newTimezone("LOCAL", localZonedTimeFromTime, localZonedTimeFromAdjTime) @@ -1064,7 +1114,8 @@ proc getTime*(): Time {.tags: [TimeEffect], benign.} = elif defined(macosx) or defined(freebsd): var a: Timeval gettimeofday(a) - result = initTime(a.tv_sec.int64, convert(Microseconds, Nanoseconds, a.tv_usec.int)) + result = initTime(a.tv_sec.int64, + convert(Microseconds, Nanoseconds, a.tv_usec.int)) elif defined(posix): var ts: Timespec discard clock_gettime(realTimeClockId, ts) @@ -1085,13 +1136,17 @@ proc initTimeInterval*(nanoseconds, microseconds, milliseconds, days, weeks, months, years: int = 0): TimeInterval = ## Creates a new ``TimeInterval``. ## + ## This proc doesn't perform any normalization! For example, + ## ``initTimeInterval(hours = 24)`` and ``initTimeInterval(days = 1)`` are + ## not equal. + ## ## You can also use the convenience procedures called ``milliseconds``, ## ``seconds``, ``minutes``, ``hours``, ``days``, ``months``, and ``years``. - ## runnableExamples: - let day = initTimeInterval(hours=24) + let day = initTimeInterval(hours = 24) let dt = initDateTime(01, mJan, 2000, 12, 00, 00, utc()) doAssert $(dt + day) == "2000-01-02T12:00:00Z" + doAssert initTimeInterval(hours = 24) != initTimeInterval(days = 1) result.nanoseconds = nanoseconds result.microseconds = microseconds result.milliseconds = milliseconds @@ -1119,7 +1174,7 @@ proc `+`*(ti1, ti2: TimeInterval): TimeInterval = proc `-`*(ti: TimeInterval): TimeInterval = ## Reverses a time interval runnableExamples: - let day = -initTimeInterval(hours=24) + let day = -initTimeInterval(hours = 24) doAssert day.hours == -24 result = TimeInterval( @@ -1140,9 +1195,9 @@ proc `-`*(ti1, ti2: TimeInterval): TimeInterval = ## ## Time components are subtracted one-by-one, see output: runnableExamples: - let ti1 = initTimeInterval(hours=24) - let ti2 = initTimeInterval(hours=4) - doAssert (ti1 - ti2) == initTimeInterval(hours=20) + let ti1 = initTimeInterval(hours = 24) + let ti2 = initTimeInterval(hours = 4) + doAssert (ti1 - ti2) == initTimeInterval(hours = 20) result = ti1 + (-ti2) @@ -1159,13 +1214,13 @@ proc getClockStr*(): string {.rtl, extern: "nt$1", tags: [TimeEffect].} = ':' & intToStr(dt.second, 2) proc toParts* (ti: TimeInterval): TimeIntervalParts = - ## Converts a `TimeInterval` into an array consisting of its time units, - ## starting with nanoseconds and ending with years + ## Converts a ``TimeInterval`` into an array consisting of its time units, + ## starting with nanoseconds and ending with years. ## ## This procedure is useful for converting ``TimeInterval`` values to strings. ## E.g. then you need to implement custom interval printing runnableExamples: - var tp = toParts(initTimeInterval(years=1, nanoseconds=123)) + var tp = toParts(initTimeInterval(years = 1, nanoseconds = 123)) doAssert tp[Years] == 1 doAssert tp[Nanoseconds] == 123 @@ -1175,9 +1230,10 @@ proc toParts* (ti: TimeInterval): TimeIntervalParts = index += 1 proc `$`*(ti: TimeInterval): string = - ## Get string representation of `TimeInterval` + ## Get string representation of ``TimeInterval``. runnableExamples: - doAssert $initTimeInterval(years=1, nanoseconds=123) == "1 year and 123 nanoseconds" + doAssert $initTimeInterval(years = 1, nanoseconds = 123) == + "1 year and 123 nanoseconds" doAssert $initTimeInterval() == "0 nanoseconds" var parts: seq[string] = @[] @@ -1203,7 +1259,7 @@ proc milliseconds*(ms: int): TimeInterval {.inline.} = proc seconds*(s: int): TimeInterval {.inline.} = ## TimeInterval of ``s`` seconds. ## - ## ``echo getTime() + 5.second`` + ## ``echo getTime() + 5.seconds`` initTimeInterval(seconds = s) proc minutes*(m: int): TimeInterval {.inline.} = @@ -1242,7 +1298,8 @@ proc years*(y: int): TimeInterval {.inline.} = ## ``echo getTime() + 2.years`` initTimeInterval(years = y) -proc evaluateInterval(dt: DateTime, interval: TimeInterval): tuple[adjDur, absDur: Duration] = +proc evaluateInterval(dt: DateTime, interval: TimeInterval): + tuple[adjDur, absDur: Duration] = ## Evaluates how many nanoseconds the interval is worth ## in the context of ``dt``. ## The result in split into an adjusted diff and an absolute diff. @@ -1281,10 +1338,10 @@ proc evaluateInterval(dt: DateTime, interval: TimeInterval): tuple[adjDur, absDu minutes = interval.minutes, hours = interval.hours) - proc initDateTime*(monthday: MonthdayRange, month: Month, year: int, hour: HourRange, minute: MinuteRange, second: SecondRange, - nanosecond: NanosecondRange, zone: Timezone = local()): DateTime = + nanosecond: NanosecondRange, + zone: Timezone = local()): DateTime = ## Create a new ``DateTime`` in the specified timezone. runnableExamples: let dt1 = initDateTime(30, mMar, 2017, 00, 00, 00, 00, utc()) @@ -1292,12 +1349,12 @@ proc initDateTime*(monthday: MonthdayRange, month: Month, year: int, assertValidDate monthday, month, year let dt = DateTime( - monthday: monthday, - year: year, - month: month, - hour: hour, - minute: minute, - second: second, + monthday: monthday, + year: year, + month: month, + hour: hour, + minute: minute, + second: second, nanosecond: nanosecond ) result = initDateTime(zone.zonedTimeFromAdjTime(dt.toAdjTime), zone) @@ -1314,14 +1371,15 @@ proc initDateTime*(monthday: MonthdayRange, month: Month, year: int, proc `+`*(dt: DateTime, interval: TimeInterval): DateTime = ## Adds ``interval`` to ``dt``. Components from ``interval`` are added - ## in the order of their size, i.e first the ``years`` component, then the ``months`` - ## component and so on. The returned ``DateTime`` will have the same timezone as the input. - ## - ## Note that when adding months, monthday overflow is allowed. This means that if the resulting - ## month doesn't have enough days it, the month will be incremented and the monthday will be - ## set to the number of days overflowed. So adding one month to `31 October` will result in `31 November`, - ## which will overflow and result in `1 December`. + ## in the order of their size, i.e first the ``years`` component, then the + ## ``months`` component and so on. The returned ``DateTime`` will have the + ## same timezone as the input. ## + ## Note that when adding months, monthday overflow is allowed. This means that + ## if the resulting month doesn't have enough days it, the month will be + ## incremented and the monthday will be set to the number of days overflowed. + ## So adding one month to `31 October` will result in `31 November`, which + ## will overflow and result in `1 December`. runnableExamples: let dt = initDateTime(30, mMar, 2017, 00, 00, 00, utc()) doAssert $(dt + 1.months) == "2017-04-30T00:00:00Z" @@ -1341,9 +1399,10 @@ proc `+`*(dt: DateTime, interval: TimeInterval): DateTime = result = initDateTime(zt, dt.timezone) proc `-`*(dt: DateTime, interval: TimeInterval): DateTime = - ## Subtract ``interval`` from ``dt``. Components from ``interval`` are subtracted - ## in the order of their size, i.e first the ``years`` component, then the ``months`` - ## component and so on. The returned ``DateTime`` will have the same timezone as the input. + ## Subtract ``interval`` from ``dt``. Components from ``interval`` are + ## subtracted in the order of their size, i.e first the ``years`` component, + ## then the ``months`` component and so on. The returned ``DateTime`` will + ## have the same timezone as the input. runnableExamples: let dt = initDateTime(30, mMar, 2017, 00, 00, 00, utc()) doAssert $(dt - 5.days) == "2017-03-25T00:00:00Z" @@ -1377,15 +1436,15 @@ proc `-`*(dt1, dt2: DateTime): Duration = dt1.toTime - dt2.toTime proc `<`*(a, b: DateTime): bool = - ## Returns true iff ``a < b``, that is iff a happened before b. + ## Returns true iff ``a`` happened before ``b``. return a.toTime < b.toTime -proc `<=` * (a, b: DateTime): bool = - ## Returns true iff ``a <= b``. +proc `<=`*(a, b: DateTime): bool = + ## Returns true iff ``a`` happened before or at the same time as ``b``. return a.toTime <= b.toTime proc `==`*(a, b: DateTime): bool = - ## Returns true if ``a == b``, that is if both dates represent the same point in time. + ## Returns true iff ``a`` and ``b`` represent the same point in time. return a.toTime == b.toTime proc isStaticInterval(interval: TimeInterval): bool = @@ -1486,8 +1545,8 @@ proc between*(startDt, endDt: DateTime): TimeInterval = #Months if endDt.month < startDt.month: - result.months = endDt.month.int() + 12 - startDt.month.int() - endDt.year -= 1 + result.months = endDt.month.int() + 12 - startDt.month.int() + endDt.year -= 1 else: result.months = endDt.month.int() - startDt.month.int() @@ -1602,7 +1661,13 @@ type ## be encoded as ``@[Lit.byte, 3.byte, 'f'.byte, 'o'.byte, 'o'.byte]``. formatStr: string -const FormatLiterals = { ' ', '-', '/', ':', '(', ')', '[', ']', ',' } + TimeParseError* = object of ValueError ## \ + ## Raised when parsing input using a ``TimeFormat`` fails. + + TimeFormatParseError* = object of ValueError ## \ + ## Raised when parsing a ``TimeFormat`` string fails. + +const FormatLiterals = {' ', '-', '/', ':', '(', ')', '[', ']', ','} proc `$`*(f: TimeFormat): string = ## Returns the format string that was used to construct ``f``. @@ -1612,9 +1677,34 @@ proc `$`*(f: TimeFormat): string = f.formatStr proc raiseParseException(f: TimeFormat, input: string, msg: string) = - raise newException(ValueError, + raise newException(TimeParseError, &"Failed to parse '{input}' with format '{f}'. {msg}") +proc parseInt(s: string, b: var int, start = 0, maxLen = int.high, + allowSign = false): int = + var sign = -1 + var i = start + let stop = start + min(s.high - start + 1, maxLen) - 1 + if allowSign and i <= stop: + if s[i] == '+': + inc(i) + elif s[i] == '-': + inc(i) + sign = 1 + if i <= stop and s[i] in {'0'..'9'}: + b = 0 + while i <= stop and s[i] in {'0'..'9'}: + let c = ord(s[i]) - ord('0') + if b >= (low(int) + c) div 10: + b = b * 10 - c + else: + return 0 + inc(i) + if sign == -1 and b == low(int): + return 0 + b = b * sign + result = i - start + iterator tokens(f: string): tuple[kind: FormatTokenKind, token: string] = var i = 0 var currToken = "" @@ -1639,15 +1729,15 @@ iterator tokens(f: string): tuple[kind: FormatTokenKind, token: string] = i.inc if i > f.high: - raise newException(ValueError, + raise newException(TimeFormatParseError, &"Unclosed ' in time format string. " & "For a literal ', use ''.") i.inc yield (tkLiteral, token) of FormatLiterals: - yieldCurrToken() - yield (tkLiteral, $f[i]) - i.inc + yieldCurrToken() + yield (tkLiteral, $f[i]) + i.inc else: # Check if the letter being added matches previous accumulated buffer. if currToken.len == 0 or currToken[0] == f[i]: @@ -1696,7 +1786,8 @@ proc stringToPattern(str: string): FormatPattern = of "zzz": result = zzz of "zzzz": result = zzzz of "g": result = g - else: raise newException(ValueError, &"'{str}' is not a valid pattern") + else: raise newException(TimeFormatParseError, + &"'{str}' is not a valid pattern") proc initTimeFormat*(format: string): TimeFormat = ## Construct a new time format for parsing & formatting time types. @@ -1715,7 +1806,7 @@ proc initTimeFormat*(format: string): TimeFormat = else: result.patterns.add(FormatPattern.Lit.byte) if token.len > 255: - raise newException(ValueError, + raise newException(TimeFormatParseError, "Format literal is to long:" & token) result.patterns.add(token.len.byte) for c in token: @@ -1804,12 +1895,12 @@ proc formatPattern(dt: DateTime, pattern: FormatPattern, result: var string) = else: result.add '+' & $year of UUUU: - result.add $dt.year + result.add $dt.year of z, zz, zzz, zzzz: if dt.timezone != nil and dt.timezone.name == "Etc/UTC": result.add 'Z' else: - result.add if -dt.utcOffset >= 0: '+' else: '-' + result.add if -dt.utcOffset >= 0: '+' else: '-' let absOffset = abs(dt.utcOffset) case pattern: of z: @@ -1828,20 +1919,15 @@ proc formatPattern(dt: DateTime, pattern: FormatPattern, result: var string) = result.add h & ":" & m & ":" & s else: assert false of g: - result.add if dt.year < 1: "BC" else: "AD" + result.add if dt.year < 1: "BC" else: "AD" of Lit: assert false # Can't happen proc parsePattern(input: string, pattern: FormatPattern, i: var int, parsed: var ParsedTime): bool = - template takeInt(allowedWidth: Slice[int]): int = + template takeInt(allowedWidth: Slice[int], allowSign = false): int = var sv: int - let max = i + allowedWidth.b - 1 - var pd = - if max > input.high: - parseInt(input, sv, i) - else: - parseInt(input[i..max], sv) - if pd notin allowedWidth: + var pd = parseInt(input, sv, i, allowedWidth.b, allowSign) + if pd < allowedWidth.a: return false i.inc pd sv @@ -1853,11 +1939,13 @@ proc parsePattern(input: string, pattern: FormatPattern, i: var int, case pattern of d: - parsed.monthday = some(takeInt(1..2)) - result = parsed.monthday.get() in MonthdayRange + let monthday = takeInt(1..2) + parsed.monthday = some(monthday) + result = monthday in MonthdayRange of dd: - parsed.monthday = some(takeInt(2..2)) - result = parsed.monthday.get() in MonthdayRange + let monthday = takeInt(2..2) + parsed.monthday = some(monthday) + result = monthday in MonthdayRange of ddd: result = input.substr(i, i+2).toLowerAscii() in [ "sun", "mon", "tue", "wed", "thu", "fri", "sat"] @@ -1992,8 +2080,8 @@ proc parsePattern(input: string, pattern: FormatPattern, i: var int, result = year > 0 of yyyy: let year = - if input[i] in { '+', '-' }: - takeInt(4..high(int)) + if input[i] in {'+', '-'}: + takeInt(4..high(int), allowSign = true) else: takeInt(4..4) result = year > 0 @@ -2004,13 +2092,13 @@ proc parsePattern(input: string, pattern: FormatPattern, i: var int, result = year > 0 of uuuu: let year = - if input[i] in { '+', '-' }: - takeInt(4..high(int)) + if input[i] in {'+', '-'}: + takeInt(4..high(int), allowSign = true) else: takeInt(4..4) parsed.year = some(year) of UUUU: - parsed.year = some(takeInt(1..high(int))) + parsed.year = some(takeInt(1..high(int), allowSign = true)) of z, zz, zzz, zzzz: case input[i] of '+', '-': @@ -2055,9 +2143,8 @@ proc parsePattern(input: string, pattern: FormatPattern, i: var int, else: result = false of y, yyy, yyyyy: - raise newException(ValueError, - &"The pattern '{pattern}' is only valid for formatting") - of Lit: assert false # Can't happen + raiseAssert "Pattern is invalid for parsing: " & $pattern + of Lit: doAssert false, "Can't happen" proc toDateTime(p: ParsedTime, zone: Timezone, f: TimeFormat, input: string): DateTime = @@ -2147,7 +2234,8 @@ proc format*(dt: DateTime, f: TimeFormat): string {.raises: [].} = formatPattern(dt, f.patterns[idx].FormatPattern, result = result) idx.inc -proc format*(dt: DateTime, f: string): string = +proc format*(dt: DateTime, f: string): string + {.raises: [TimeFormatParseError].} = ## Shorthand for constructing a ``TimeFormat`` and using it to format ``dt``. ## ## See `Parsing and formatting dates`_ for documentation of the @@ -2163,7 +2251,8 @@ proc format*(dt: DateTime, f: static[string]): string {.raises: [].} = const f2 = initTimeFormat(f) result = dt.format(f2) -proc format*(time: Time, f: string, zone: Timezone = local()): string {.tags: [].} = +proc format*(time: Time, f: string, zone: Timezone = local()): string + {.raises: [TimeFormatParseError].} = ## Shorthand for constructing a ``TimeFormat`` and using it to format ## ``time``. Will use the timezone specified by ``zone``. ## @@ -2175,13 +2264,14 @@ proc format*(time: Time, f: string, zone: Timezone = local()): string {.tags: [] doAssert format(tm, "yyyy-MM-dd'T'HH:mm:ss", utc()) == "1970-01-01T00:00:00" time.inZone(zone).format(f) -proc format*(time: Time, f: static[string], - zone: Timezone = local()): string {.tags: [].} = +proc format*(time: Time, f: static[string], zone: Timezone = local()): string + {.raises: [].} = ## Overload that validates ``f`` at compile time. const f2 = initTimeFormat(f) result = time.inZone(zone).format(f2) -proc parse*(input: string, f: TimeFormat, zone: Timezone = local()): DateTime = +proc parse*(input: string, f: TimeFormat, zone: Timezone = local()): DateTime + {.raises: [TimeParseError, Defect].} = ## Parses ``input`` as a ``DateTime`` using the format specified by ``f``. ## If no UTC offset was parsed, then ``input`` is assumed to be specified in ## the ``zone`` timezone. If a UTC offset was parsed, the result will be @@ -2217,11 +2307,12 @@ proc parse*(input: string, f: TimeFormat, zone: Timezone = local()): DateTime = if patIdx <= f.patterns.high: raiseParseException(f, input, - "Parsing ended but there was still patterns remaining") + "Parsing ended but there was still patterns remaining") result = toDateTime(parsed, zone, f, input) -proc parse*(input, f: string, tz: Timezone = local()): DateTime = +proc parse*(input, f: string, tz: Timezone = local()): DateTime + {.raises: [TimeParseError, TimeFormatParseError, Defect].} = ## Shorthand for constructing a ``TimeFormat`` and using it to parse ## ``input`` as a ``DateTime``. ## @@ -2233,12 +2324,14 @@ proc parse*(input, f: string, tz: Timezone = local()): DateTime = let dtFormat = initTimeFormat(f) result = input.parse(dtFormat, tz) -proc parse*(input: string, f: static[string], zone: Timezone = local()): DateTime = +proc parse*(input: string, f: static[string], zone: Timezone = local()): + DateTime {.raises: [TimeParseError, Defect].} = ## Overload that validates ``f`` at compile time. const f2 = initTimeFormat(f) result = input.parse(f2, zone) -proc parseTime*(input, f: string, zone: Timezone): Time = +proc parseTime*(input, f: string, zone: Timezone): Time + {.raises: [TimeParseError, TimeFormatParseError, Defect].} = ## Shorthand for constructing a ``TimeFormat`` and using it to parse ## ``input`` as a ``DateTime``, then converting it a ``Time``. ## @@ -2249,7 +2342,8 @@ proc parseTime*(input, f: string, zone: Timezone): Time = doAssert parseTime(tStr, "yyyy-MM-dd'T'HH:mm:sszzz", utc()) == fromUnix(0) parse(input, f, zone).toTime() -proc parseTime*(input: string, f: static[string], zone: Timezone): Time = +proc parseTime*(input: string, f: static[string], zone: Timezone): Time + {.raises: [TimeParseError, Defect].} = ## Overload that validates ``format`` at compile time. const f2 = initTimeFormat(f) result = input.parse(f2, zone).toTime() @@ -2267,7 +2361,7 @@ proc `$`*(dt: DateTime): string {.tags: [], raises: [], benign.} = result = format(dt, "yyyy-MM-dd'T'HH:mm:sszzz") proc `$`*(time: Time): string {.tags: [], raises: [], benign.} = - ## converts a `Time` value to a string representation. It will use the local + ## Converts a `Time` value to a string representation. It will use the local ## time zone and use the format ``yyyy-MM-dd'T'HH-mm-sszzz``. runnableExamples: let dt = initDateTime(01, mJan, 1970, 00, 00, 00, local()) @@ -2275,8 +2369,6 @@ proc `$`*(time: Time): string {.tags: [], raises: [], benign.} = doAssert $tm == "1970-01-01T00:00:00" & format(dt, "zzz") $time.local -{.pop.} - proc countLeapYears*(yearSpan: int): int = ## Returns the number of leap years spanned by a given number of years. ## @@ -2318,7 +2410,8 @@ when not defined(JS): type Clock {.importc: "clock_t".} = distinct int - proc getClock(): Clock {.importc: "clock", header: "<time.h>", tags: [TimeEffect].} + proc getClock(): Clock + {.importc: "clock", header: "<time.h>", tags: [TimeEffect].} var clocksPerSec {.importc: "CLOCKS_PER_SEC", nodecl.}: int @@ -2376,59 +2469,68 @@ when defined(JS): # Deprecated procs when not defined(JS): - proc unixTimeToWinTime*(time: CTime): int64 {.deprecated: "Use toWinTime instead".} = + proc unixTimeToWinTime*(time: CTime): int64 + {.deprecated: "Use toWinTime instead".} = ## Converts a UNIX `Time` (``time_t``) to a Windows file time ## ## **Deprecated:** use ``toWinTime`` instead. result = int64(time) * rateDiff + epochDiff - proc winTimeToUnixTime*(time: int64): CTime {.deprecated: "Use fromWinTime instead".} = + proc winTimeToUnixTime*(time: int64): CTime + {.deprecated: "Use fromWinTime instead".} = ## Converts a Windows time to a UNIX `Time` (``time_t``) ## ## **Deprecated:** use ``fromWinTime`` instead. result = CTime((time - epochDiff) div rateDiff) -proc initInterval*(seconds, minutes, hours, days, months, - years: int = 0): TimeInterval {.deprecated.} = +proc initInterval*(seconds, minutes, hours, days, months, years: int = 0): + TimeInterval {.deprecated.} = ## **Deprecated since v0.18.0:** use ``initTimeInterval`` instead. initTimeInterval(0, 0, 0, seconds, minutes, hours, days, 0, months, years) -proc fromSeconds*(since1970: float): Time {.tags: [], raises: [], benign, deprecated.} = +proc fromSeconds*(since1970: float): Time + {.tags: [], raises: [], benign, deprecated.} = ## Takes a float which contains the number of seconds since the unix epoch and ## returns a time object. ## ## **Deprecated since v0.18.0:** use ``fromUnix`` instead - let nanos = ((since1970 - since1970.int64.float) * convert(Seconds, Nanoseconds, 1).float).int + let nanos = ((since1970 - since1970.int64.float) * + convert(Seconds, Nanoseconds, 1).float).int initTime(since1970.int64, nanos) -proc fromSeconds*(since1970: int64): Time {.tags: [], raises: [], benign, deprecated.} = +proc fromSeconds*(since1970: int64): Time + {.tags: [], raises: [], benign, deprecated.} = ## Takes an int which contains the number of seconds since the unix epoch and ## returns a time object. ## ## **Deprecated since v0.18.0:** use ``fromUnix`` instead fromUnix(since1970) -proc toSeconds*(time: Time): float {.tags: [], raises: [], benign, deprecated.} = +proc toSeconds*(time: Time): float + {.tags: [], raises: [], benign, deprecated.} = ## Returns the time in seconds since the unix epoch. ## ## **Deprecated since v0.18.0:** use ``toUnix`` instead time.seconds.float + time.nanosecond / convert(Seconds, Nanoseconds, 1) -proc getLocalTime*(time: Time): DateTime {.tags: [], raises: [], benign, deprecated.} = +proc getLocalTime*(time: Time): DateTime + {.tags: [], raises: [], benign, deprecated.} = ## Converts the calendar time `time` to broken-time representation, ## expressed relative to the user's specified time zone. ## ## **Deprecated since v0.18.0:** use ``local`` instead time.local -proc getGMTime*(time: Time): DateTime {.tags: [], raises: [], benign, deprecated.} = +proc getGMTime*(time: Time): DateTime + {.tags: [], raises: [], benign, deprecated.} = ## Converts the calendar time `time` to broken-down time representation, ## expressed in Coordinated Universal Time (UTC). ## ## **Deprecated since v0.18.0:** use ``utc`` instead time.utc -proc getTimezone*(): int {.tags: [TimeEffect], raises: [], benign, deprecated.} = +proc getTimezone*(): int + {.tags: [TimeEffect], raises: [], benign, deprecated.} = ## Returns the offset of the local (non-DST) timezone in seconds west of UTC. ## ## **Deprecated since v0.18.0:** use ``now().utcOffset`` to get the current @@ -2436,45 +2538,14 @@ proc getTimezone*(): int {.tags: [TimeEffect], raises: [], benign, deprecated.} when defined(JS): return newDate().getTimezoneOffset() * 60 elif defined(freebsd) or defined(netbsd) or defined(openbsd): - var a: CTime - discard time(a) - let lt = localtime(addr(a)) - # BSD stores in `gmtoff` offset east of UTC in seconds, - # but posix systems using west of UTC in seconds - return -(lt.gmtoff) + # This is wrong since it will include DST offsets, but the behavior has + # always been wrong for bsd and the proc is deprecated so lets ignore it. + return now().utcOffset else: return timezone -proc timeInfoToTime*(dt: DateTime): Time {.tags: [], benign, deprecated.} = - ## Converts a broken-down time structure to calendar time representation. - ## - ## **Deprecated since v0.14.0:** use ``toTime`` instead. - dt.toTime - -when defined(JS): - var start = getTime() - proc getStartMilsecs*(): int {.deprecated, tags: [TimeEffect], benign.} = - let dur = getTime() - start - result = (convert(Seconds, Milliseconds, dur.seconds) + - convert(Nanoseconds, Milliseconds, dur.nanosecond)).int -else: - proc getStartMilsecs*(): int {.deprecated, tags: [TimeEffect], benign.} = - ## get the milliseconds from the start of the program. - ## - ## **Deprecated since v0.8.10:** use ``epochTime`` or ``cpuTime`` instead. - when defined(macosx): - result = toInt(toFloat(int(getClock())) / (toFloat(clocksPerSec) / 1000.0)) - else: - result = int(getClock()) div (clocksPerSec div 1000) - -proc timeToTimeInterval*(t: Time): TimeInterval {.deprecated.} = - ## Converts a Time to a TimeInterval. - ## - ## **Deprecated since v0.14.0:** use ``toTimeInterval`` instead. - # Milliseconds not available from Time - t.toTimeInterval() - -proc getDayOfWeek*(day, month, year: int): WeekDay {.tags: [], raises: [], benign, deprecated.} = +proc getDayOfWeek*(day, month, year: int): WeekDay + {.tags: [], raises: [], benign, deprecated.} = ## **Deprecated since v0.18.0:** use ## ``getDayOfWeek(monthday: MonthdayRange; month: Month; year: int)`` instead. getDayOfWeek(day, month.Month, year) diff --git a/lib/pure/typetraits.nim b/lib/pure/typetraits.nim index c53b68864..a373a9370 100644 --- a/lib/pure/typetraits.nim +++ b/lib/pure/typetraits.nim @@ -10,30 +10,13 @@ ## This module defines compile-time reflection procs for ## working with types -proc name*(t: typedesc): string {.magic: "TypeTrait".} - ## Returns the name of the given type. - ## - ## Example: - ## - ## .. code-block:: - ## - ## import typetraits - ## - ## proc `$`*(T: typedesc): string = name(T) - ## - ## template test(x): typed = - ## echo "type: ", type(x), ", value: ", x - ## - ## test 42 - ## # --> type: int, value: 42 - ## test "Foo" - ## # --> type: string, value: Foo - ## test(@['A','B']) - ## # --> type: seq[char], value: @[A, B] +include "system/helpers" # for `isNamedTuple` + +export system.`$` +export isNamedTuple -proc `$`*(t: typedesc): string = - ## An alias for `name`. - name(t) +proc name*(t: typedesc): string {.magic: "TypeTrait".} + ## Alias for system.`$`(t) since Nim v0.20.0. proc arity*(t: typedesc): int {.magic: "TypeTrait".} = ## Returns the arity of the given type. This is the number of "type" components or @@ -64,4 +47,22 @@ proc supportsCopyMem*(t: typedesc): bool {.magic: "TypeTrait".} when isMainModule: - doAssert $type(42) == "int" + static: + doAssert $type(42) == "int" + doAssert int.name == "int" + + const a1 = name(int) + const a2 = $(int) + const a3 = $int + doAssert a1 == "int" + doAssert a2 == "int" + doAssert a3 == "int" + + proc fun[T: typedesc](t: T) = + const a1 = name(t) + const a2 = $(t) + const a3 = $t + doAssert a1 == "int" + doAssert a2 == "int" + doAssert a3 == "int" + fun(int) diff --git a/lib/pure/unicode.nim b/lib/pure/unicode.nim index 712cc46c8..27eabecc6 100644 --- a/lib/pure/unicode.nim +++ b/lib/pure/unicode.nim @@ -8,6 +8,11 @@ # ## This module provides support to handle the Unicode UTF-8 encoding. +## +## There are no specialized ``insert``, ``delete``, ``add`` and ``contains`` +## procedures for ``seq[Rune]`` in this module because the generic variants +## of these procedures in the system module already work with it. + {.deadCodeElim: on.} # dce option deprecated @@ -15,7 +20,7 @@ include "system/inclrtl" type RuneImpl = int32 # underlying type of Rune - Rune* = distinct RuneImpl ## type that can hold any Unicode character + Rune* = distinct RuneImpl ## Unicode code point. Can hold any Unicode character. Rune16* = distinct int16 ## 16 bit Unicode character proc `<=%`*(a, b: Rune): bool = return int(a) <=% int(b) @@ -25,7 +30,12 @@ proc `==`*(a, b: Rune): bool = return int(a) == int(b) template ones(n: untyped): untyped = ((1 shl n)-1) proc runeLen*(s: string): int {.rtl, extern: "nuc$1".} = - ## Returns the number of Unicode characters of the string ``s`` + ## Returns the number of runes of the string ``s``. + runnableExamples: + let a = "añyóng" + doAssert a.runeLen == 6 + ## note: a.len == 8 + var i = 0 while i < len(s): if ord(s[i]) <=% 127: inc(i) @@ -38,7 +48,12 @@ proc runeLen*(s: string): int {.rtl, extern: "nuc$1".} = inc(result) proc runeLenAt*(s: string, i: Natural): int = - ## Returns the number of bytes the rune starting at ``s[i]`` takes + ## Returns the number of bytes the rune starting at ``s[i]`` takes. + runnableExamples: + let a = "añyóng" + doAssert a.runeLenAt(0) == 1 + doAssert a.runeLenAt(1) == 2 + if ord(s[i]) <=% 127: result = 1 elif ord(s[i]) shr 5 == 0b110: result = 2 elif ord(s[i]) shr 4 == 0b1110: result = 3 @@ -50,7 +65,7 @@ proc runeLenAt*(s: string, i: Natural): int = const replRune = Rune(0xFFFD) template fastRuneAt*(s: string, i: int, result: untyped, doInc = true) = - ## Returns the Unicode character ``s[i]`` in ``result``. If ``doInc == true`` + ## Returns the rune ``s[i]`` in ``result``. If ``doInc == true`` ## ``i`` is incremented by the number of bytes that have been processed. bind ones if ord(s[i]) <=% 127: @@ -152,17 +167,21 @@ proc validateUtf8*(s: string): int = return -1 proc runeAt*(s: string, i: Natural): Rune = - ## Returns the unicode character in ``s`` at byte index ``i`` + ## Returns the rune in ``s`` at **byte index** ``i``. + runnableExamples: + let a = "añyóng" + doAssert a.runeAt(1) == "ñ".runeAt(0) + doAssert a.runeAt(2) == "ñ".runeAt(1) + doAssert a.runeAt(3) == "y".runeAt(0) fastRuneAt(s, i, result, false) template fastToUTF8Copy*(c: Rune, s: var string, pos: int, doInc = true) = - ## Copies UTF-8 representation of `c` into the preallocated string `s` - ## starting at position `pos`. If `doInc == true`, `pos` is incremented + ## Copies UTF-8 representation of ``c`` into the preallocated string ``s`` + ## starting at position ``pos``. If ``doInc == true``, ``pos`` is incremented ## by the number of bytes that have been processed. ## - ## To be the most efficient, make sure `s` is preallocated - ## with an additional amount equal to the byte length of - ## `c`. + ## To be the most efficient, make sure ``s`` is preallocated + ## with an additional amount equal to the byte length of ``c``. var i = RuneImpl(c) if i <=% 127: s.setLen(pos+1) @@ -207,28 +226,39 @@ template fastToUTF8Copy*(c: Rune, s: var string, pos: int, doInc = true) = discard # error, exception? proc toUTF8*(c: Rune): string {.rtl, extern: "nuc$1".} = - ## Converts a rune into its UTF-8 representation + ## Converts a rune into its UTF-8 representation. + runnableExamples: + let a = "añyóng" + doAssert a.runeAt(1).toUTF8 == "ñ" + result = "" fastToUTF8Copy(c, result, 0, false) proc add*(s: var string; c: Rune) = + ## Adds a rune ``c`` to a string ``s``. + runnableExamples: + var s = "abc" + let c = "ä".runeAt(0) + s.add(c) + doAssert s == "abcä" + let pos = s.len fastToUTF8Copy(c, s, pos, false) proc `$`*(rune: Rune): string = - ## Converts a Rune to a string + ## An alias for `toUTF8 <#toUTF8%2CRune>`_. rune.toUTF8 proc `$`*(runes: seq[Rune]): string = - ## Converts a sequence of Runes to a string + ## Converts a sequence of Runes to a string. result = "" for rune in runes: result.add rune proc runeOffset*(s: string, pos:Natural, start: Natural = 0): int = - ## Returns the byte position of unicode character - ## at position pos in s with an optional start byte position. - ## returns the special value -1 if it runs out of the string + ## Returns the byte position of rune + ## at position ``pos`` in ``s`` with an optional start byte position. + ## Returns the special value -1 if it runs out of the string. ## ## Beware: This can lead to unoptimized code and slow execution! ## Most problems can be solved more efficiently by using an iterator @@ -244,7 +274,7 @@ proc runeOffset*(s: string, pos:Natural, start: Natural = 0): int = return o proc runeAtPos*(s: string, pos: int): Rune = - ## Returns the unicode character at position pos + ## Returns the rune at position ``pos``. ## ## Beware: This can lead to unoptimized code and slow execution! ## Most problems can be solved more efficiently by using an iterator @@ -252,7 +282,7 @@ proc runeAtPos*(s: string, pos: int): Rune = fastRuneAt(s, runeOffset(s, pos), result, false) proc runeStrAtPos*(s: string, pos: Natural): string = - ## Returns the unicode character at position pos as UTF8 String + ## Returns the rune at position ``pos`` as UTF8 String. ## ## Beware: This can lead to unoptimized code and slow execution! ## Most problems can be solved more efficiently by using an iterator @@ -262,7 +292,7 @@ proc runeStrAtPos*(s: string, pos: Natural): string = proc runeReverseOffset*(s: string, rev:Positive): (int, int) = ## Returns a tuple with the the byte offset of the - ## unicode character at position ``rev`` in s counting + ## rune at position ``rev`` in ``s``, counting ## from the end (starting with 1) and the total ## number of runes in the string. Returns a negative value ## for offset if there are to few runes in the string to @@ -286,13 +316,21 @@ proc runeReverseOffset*(s: string, rev:Positive): (int, int) = return (-a, rev.int-a) return (x, -a+rev.int) -proc runeSubStr*(s: string, pos:int, len:int = int.high): string = - ## Returns the UTF-8 substring starting at codepoint pos - ## with len codepoints. If pos or len is negative they count from - ## the end of the string. If len is not given it means the longest +proc runeSubStr*(s: string, pos: int, len: int = int.high): string = + ## Returns the UTF-8 substring starting at codepoint ``pos`` + ## with ``len`` codepoints. If ``pos`` or ``len`` is negative they count from + ## the end of the string. If ``len`` is not given it means the longest ## possible string. ## - ## (Needs some examples) + runnableExamples: + let s = "Hänsel ««: 10,00€" + doAssert(runeSubStr(s, 0, 2) == "Hä") + doAssert(runeSubStr(s, 10, 1) == ":") + doAssert(runeSubStr(s, -6) == "10,00€") + doAssert(runeSubStr(s, 10) == ": 10,00€") + doAssert(runeSubStr(s, 12, 5) == "10,00") + doAssert(runeSubStr(s, -6, 3) == "10,") + if pos < 0: let (o, rl) = runeReverseOffset(s, -pos) if len >= rl: @@ -1321,7 +1359,7 @@ proc binarySearch(c: RuneImpl, tab: openArray[int], len, stride: int): int = return -1 proc toLower*(c: Rune): Rune {.rtl, extern: "nuc$1", procvar.} = - ## Converts ``c`` into lower case. This works for any Unicode character. + ## Converts ``c`` into lower case. This works for any rune. ## If possible, prefer ``toLower`` over ``toUpper``. var c = RuneImpl(c) var p = binarySearch(c, tolowerRanges, len(tolowerRanges) div 3, 3) @@ -1333,7 +1371,7 @@ proc toLower*(c: Rune): Rune {.rtl, extern: "nuc$1", procvar.} = return Rune(c) proc toUpper*(c: Rune): Rune {.rtl, extern: "nuc$1", procvar.} = - ## Converts ``c`` into upper case. This works for any Unicode character. + ## Converts ``c`` into upper case. This works for any rune. ## If possible, prefer ``toLower`` over ``toUpper``. var c = RuneImpl(c) var p = binarySearch(c, toupperRanges, len(toupperRanges) div 3, 3) @@ -1345,7 +1383,7 @@ proc toUpper*(c: Rune): Rune {.rtl, extern: "nuc$1", procvar.} = return Rune(c) proc toTitle*(c: Rune): Rune {.rtl, extern: "nuc$1", procvar.} = - ## Converts ``c`` to title case + ## Converts ``c`` to title case. var c = RuneImpl(c) var p = binarySearch(c, toTitleSinglets, len(toTitleSinglets) div 2, 2) if p >= 0 and c == toTitleSinglets[p]: @@ -1353,7 +1391,7 @@ proc toTitle*(c: Rune): Rune {.rtl, extern: "nuc$1", procvar.} = return Rune(c) proc isLower*(c: Rune): bool {.rtl, extern: "nuc$1", procvar.} = - ## Returns true iff ``c`` is a lower case Unicode character. + ## Returns true iff ``c`` is a lower case rune. ## If possible, prefer ``isLower`` over ``isUpper``. var c = RuneImpl(c) # Note: toUpperRanges is correct here! @@ -1365,7 +1403,7 @@ proc isLower*(c: Rune): bool {.rtl, extern: "nuc$1", procvar.} = return true proc isUpper*(c: Rune): bool {.rtl, extern: "nuc$1", procvar.} = - ## Returns true iff ``c`` is a upper case Unicode character. + ## Returns true iff ``c`` is a upper case rune. ## If possible, prefer ``isLower`` over ``isUpper``. var c = RuneImpl(c) # Note: toLowerRanges is correct here! @@ -1377,7 +1415,7 @@ proc isUpper*(c: Rune): bool {.rtl, extern: "nuc$1", procvar.} = return true proc isAlpha*(c: Rune): bool {.rtl, extern: "nuc$1", procvar.} = - ## Returns true iff ``c`` is an *alpha* Unicode character (i.e., a letter) + ## Returns true iff ``c`` is an *alpha* rune (i.e., a letter) if isUpper(c) or isLower(c): return true var c = RuneImpl(c) @@ -1389,18 +1427,18 @@ proc isAlpha*(c: Rune): bool {.rtl, extern: "nuc$1", procvar.} = return true proc isTitle*(c: Rune): bool {.rtl, extern: "nuc$1", procvar.} = - ## Returns true iff ``c`` is a Unicode titlecase character + ## Returns true iff ``c`` is a Unicode titlecase character. return isUpper(c) and isLower(c) proc isWhiteSpace*(c: Rune): bool {.rtl, extern: "nuc$1", procvar.} = - ## Returns true iff ``c`` is a Unicode whitespace character + ## Returns true iff ``c`` is a Unicode whitespace character. var c = RuneImpl(c) var p = binarySearch(c, spaceRanges, len(spaceRanges) div 2, 2) if p >= 0 and c >= spaceRanges[p] and c <= spaceRanges[p+1]: return true proc isCombining*(c: Rune): bool {.rtl, extern: "nuc$1", procvar.} = - ## Returns true iff ``c`` is a Unicode combining character + ## Returns true iff ``c`` is a Unicode combining character. var c = RuneImpl(c) # Optimized to return false immediately for ASCII @@ -1424,12 +1462,12 @@ template runeCheck(s, runeProc) = proc isAlpha*(s: string): bool {.noSideEffect, procvar, rtl, extern: "nuc$1Str".} = - ## Returns true iff `s` contains all alphabetic unicode characters. + ## Returns true iff ``s`` contains all alphabetic runes. runeCheck(s, isAlpha) proc isSpace*(s: string): bool {.noSideEffect, procvar, rtl, extern: "nuc$1Str".} = - ## Returns true iff `s` contains all whitespace unicode characters. + ## Returns true iff ``s`` contains all whitespace runes. runeCheck(s, isWhiteSpace) template runeCaseCheck(s, runeProc, skipNonAlpha) = @@ -1459,7 +1497,7 @@ proc isLower*(s: string, skipNonAlpha: bool): bool {. ## Checks whether ``s`` is lower case. ## ## If ``skipNonAlpha`` is true, returns true if all alphabetical - ## runes in ``s`` are lower case. Returns false if none of the + ## runes in ``s`` are lower case. Returns false if none of the ## runes in ``s`` are alphabetical. ## ## If ``skipNonAlpha`` is false, returns true only if all runes in @@ -1474,7 +1512,7 @@ proc isUpper*(s: string, skipNonAlpha: bool): bool {. ## Checks whether ``s`` is upper case. ## ## If ``skipNonAlpha`` is true, returns true if all alphabetical - ## runes in ``s`` are upper case. Returns false if none of the + ## runes in ``s`` are upper case. Returns false if none of the ## runes in ``s`` are alphabetical. ## ## If ``skipNonAlpha`` is false, returns true only if all runes in @@ -1485,7 +1523,7 @@ proc isUpper*(s: string, skipNonAlpha: bool): bool {. runeCaseCheck(s, isUpper, skipNonAlpha) template convertRune(s, runeProc) = - ## Convert runes in `s` using `runeProc` as the converter. + ## Convert runes in ``s`` using ``runeProc`` as the converter. result = newString(len(s)) var @@ -1502,20 +1540,20 @@ template convertRune(s, runeProc) = proc toUpper*(s: string): string {.noSideEffect, procvar, rtl, extern: "nuc$1Str".} = - ## Converts `s` into upper-case unicode characters. + ## Converts ``s`` into upper-case runes. convertRune(s, toUpper) proc toLower*(s: string): string {.noSideEffect, procvar, rtl, extern: "nuc$1Str".} = - ## Converts `s` into lower-case unicode characters. + ## Converts ``s`` into lower-case runes. convertRune(s, toLower) proc swapCase*(s: string): string {.noSideEffect, procvar, rtl, extern: "nuc$1".} = - ## Swaps the case of unicode characters in `s` + ## Swaps the case of runes in ``s``. ## - ## Returns a new string such that the cases of all unicode characters - ## are swapped if possible + ## Returns a new string such that the cases of all runes + ## are swapped if possible. var i = 0 @@ -1538,7 +1576,7 @@ proc swapCase*(s: string): string {.noSideEffect, procvar, proc capitalize*(s: string): string {.noSideEffect, procvar, rtl, extern: "nuc$1".} = - ## Converts the first character of `s` into an upper-case unicode character. + ## Converts the first character of ``s`` into an upper-case rune. if len(s) == 0: return s @@ -1552,10 +1590,10 @@ proc capitalize*(s: string): string {.noSideEffect, procvar, proc translate*(s: string, replacements: proc(key: string): string): string {. rtl, extern: "nuc$1".} = - ## Translates words in a string using the `replacements` proc to substitute - ## words inside `s` with their replacements + ## Translates words in a string using the ``replacements`` proc to substitute + ## words inside ``s`` with their replacements. ## - ## `replacements` is any proc that takes a word and returns + ## ``replacements`` is any proc that takes a word and returns ## a new word to fill it's place. # Allocate memory for the new string based on the old one. @@ -1601,10 +1639,10 @@ proc translate*(s: string, replacements: proc(key: string): string): string {. proc title*(s: string): string {.noSideEffect, procvar, rtl, extern: "nuc$1".} = - ## Converts `s` to a unicode title. + ## Converts ``s`` to a unicode title. ## ## Returns a new string such that the first character - ## in each word inside `s` is capitalized + ## in each word inside ``s`` is capitalized. var i = 0 @@ -1631,10 +1669,10 @@ proc title*(s: string): string {.noSideEffect, procvar, proc isTitle*(s: string): bool {.noSideEffect, procvar, rtl, extern: "nuc$1Str", deprecated: "Deprecated since version 0.20 since its semantics are unclear".}= - ## Checks whether or not `s` is a unicode title. + ## Checks whether or not ``s`` is a unicode title. ## - ## Returns true if the first character in each word inside `s` - ## are upper case and there is at least one character in `s`. + ## Returns true if the first character in each word inside ``s`` + ## are upper case and there is at least one character in ``s``. if s.len == 0: return false @@ -1656,7 +1694,7 @@ proc isTitle*(s: string): bool {.noSideEffect, procvar, firstRune = true iterator runes*(s: string): Rune = - ## Iterates over any unicode character of the string ``s`` returning runes + ## Iterates over any rune of the string ``s`` returning runes. var i = 0 result: Rune @@ -1665,7 +1703,7 @@ iterator runes*(s: string): Rune = yield result iterator utf8*(s: string): string = - ## Iterates over any unicode character of the string ``s`` returning utf8 values + ## Iterates over any rune of the string ``s`` returning utf8 values. var o = 0 while o < s.len: let n = runeLenAt(s, o) @@ -1673,7 +1711,7 @@ iterator utf8*(s: string): string = o += n proc toRunes*(s: string): seq[Rune] = - ## Obtains a sequence containing the Runes in ``s`` + ## Obtains a sequence containing the Runes in ``s``. result = newSeq[Rune]() for r in s.runes: result.add(r) @@ -1696,15 +1734,14 @@ proc cmpRunesIgnoreCase*(a, b: string): int {.rtl, extern: "nuc$1", procvar.} = result = a.len - b.len proc reversed*(s: string): string = - ## Returns the reverse of ``s``, interpreting it as Unicode characters. - ## Unicode combining characters are correctly interpreted as well: - ## - ## .. code-block:: nim - ## - ## assert reversed("Reverse this!") == "!siht esreveR" - ## assert reversed("先秦兩漢") == "漢兩秦先" - ## assert reversed("as⃝df̅") == "f̅ds⃝a" - ## assert reversed("a⃞b⃞c⃞") == "c⃞b⃞a⃞" + ## Returns the reverse of ``s``, interpreting it as runes. + ## Unicode combining characters are correctly interpreted as well. + runnableExamples: + assert reversed("Reverse this!") == "!siht esreveR" + assert reversed("先秦兩漢") == "漢兩秦先" + assert reversed("as⃝df̅") == "f̅ds⃝a" + assert reversed("a⃞b⃞c⃞") == "c⃞b⃞a⃞" + var i = 0 lastI = 0 @@ -1731,7 +1768,7 @@ proc reversed*(s: string): string = reverseUntil(len(s)) proc graphemeLen*(s: string; i: Natural): Natural = - ## The number of bytes belonging to 's[i]' including following combining + ## The number of bytes belonging to ``s[i]`` including following combining ## characters. var j = i.int var r, r2: Rune @@ -1744,7 +1781,7 @@ proc graphemeLen*(s: string; i: Natural): Natural = result = j-i proc lastRune*(s: string; last: int): (Rune, int) = - ## length of the last rune in 's[0..last]'. Returns the rune and its length + ## Length of the last rune in ``s[0..last]``. Returns the rune and its length ## in bytes. if s[last] <= chr(127): result = (Rune(s[last]), 1) @@ -1778,7 +1815,7 @@ proc stringHasSep(s: string, index: int, sep: Rune): bool = return sep == rune template splitCommon(s, sep, maxsplit: untyped, sepLen: int = -1) = - ## Common code for split procedures + ## Common code for split procedures. var last = 0 splits = maxsplit @@ -1801,9 +1838,9 @@ template splitCommon(s, sep, maxsplit: untyped, sepLen: int = -1) = iterator split*(s: string, seps: openarray[Rune] = unicodeSpaces, maxsplit: int = -1): string = - ## Splits the unicode string `s` into substrings using a group of separators. + ## Splits the unicode string ``s`` into substrings using a group of separators. ## - ## Substrings are separated by a substring containing only `seps`. + ## Substrings are separated by a substring containing only ``seps``. ## ## .. code-block:: nim ## for word in split("this\lis an\texample"): @@ -1844,7 +1881,7 @@ iterator split*(s: string, seps: openarray[Rune] = unicodeSpaces, splitCommon(s, seps, maxsplit) iterator splitWhitespace*(s: string): string = - ## Splits a unicode string at whitespace runes + ## Splits a unicode string at whitespace runes. splitCommon(s, unicodeSpaces, -1) template accResult(iter: untyped) = @@ -1858,9 +1895,9 @@ proc splitWhitespace*(s: string): seq[string] {.noSideEffect, accResult(splitWhitespace(s)) iterator split*(s: string, sep: Rune, maxsplit: int = -1): string = - ## Splits the unicode string `s` into substrings using a single separator. + ## Splits the unicode string ``s`` into substrings using a single separator. ## - ## Substrings are separated by the rune `sep`. + ## Substrings are separated by the rune ``sep``. ## The code: ## ## .. code-block:: nim @@ -1898,11 +1935,11 @@ proc split*(s: string, sep: Rune, maxsplit: int = -1): seq[string] {.noSideEffec proc strip*(s: string, leading = true, trailing = true, runes: openarray[Rune] = unicodeSpaces): string {.noSideEffect, rtl, extern: "nucStrip".} = - ## Strips leading or trailing `runes` from `s` and returns + ## Strips leading or trailing ``runes`` from ``s`` and returns ## the resulting string. ## - ## If `leading` is true, leading `runes` are stripped. - ## If `trailing` is true, trailing `runes` are stripped. + ## If ``leading`` is true, leading ``runes`` are stripped. + ## If ``trailing`` is true, trailing ``runes`` are stripped. ## If both are false, the string is returned unchanged. var s_i = 0 ## starting index into string ``s`` @@ -1948,9 +1985,9 @@ proc strip*(s: string, leading = true, trailing = true, proc repeat*(c: Rune, count: Natural): string {.noSideEffect, rtl, extern: "nucRepeatRune".} = - ## Returns a string of `count` Runes `c`. + ## Returns a string of ``count`` Runes ``c``. ## - ## The returned string will have a rune-length of `count`. + ## The returned string will have a rune-length of ``count``. let s = $c result = newStringOfCap(count * s.len) for i in 0 ..< count: @@ -1958,11 +1995,11 @@ proc repeat*(c: Rune, count: Natural): string {.noSideEffect, proc align*(s: string, count: Natural, padding = ' '.Rune): string {. noSideEffect, rtl, extern: "nucAlignString".} = - ## Aligns a unicode string `s` with `padding`, so that it has a rune-length - ## of `count`. + ## Aligns a unicode string ``s`` with ``padding``, so that it has a rune-length + ## of ``count``. ## - ## `padding` characters (by default spaces) are added before `s` resulting in - ## right alignment. If ``s.runelen >= count``, no spaces are added and `s` is + ## ``padding`` characters (by default spaces) are added before ``s`` resulting in + ## right alignment. If ``s.runelen >= count``, no spaces are added and ``s`` is ## returned unchanged. If you need to left align a string use the `alignLeft ## proc <#alignLeft>`_. runnableExamples: @@ -1985,11 +2022,11 @@ proc align*(s: string, count: Natural, padding = ' '.Rune): string {. proc alignLeft*(s: string, count: Natural, padding = ' '.Rune): string {. noSideEffect.} = - ## Left-Aligns a unicode string `s` with `padding`, so that it has a - ## rune-length of `count`. + ## Left-Aligns a unicode string ``s`` with ``padding``, so that it has a + ## rune-length of ``count``. ## - ## `padding` characters (by default spaces) are added after `s` resulting in - ## left alignment. If ``s.runelen >= count``, no spaces are added and `s` is + ## ``padding`` characters (by default spaces) are added after ``s`` resulting in + ## left alignment. If ``s.runelen >= count``, no spaces are added and ``s`` is ## returned unchanged. If you need to right align a string use the `align ## proc <#align>`_. runnableExamples: diff --git a/lib/pure/unittest.nim b/lib/pure/unittest.nim index 135a24e9a..ce147cccc 100644 --- a/lib/pure/unittest.nim +++ b/lib/pure/unittest.nim @@ -499,7 +499,7 @@ template test*(name, body) {.dirty.} = finally: if testStatusIMPL == FAILED: - programResult += 1 + programResult = 1 let testResult = TestResult( suiteName: when declared(testSuiteName): testSuiteName else: "", testName: name, @@ -540,7 +540,7 @@ template fail* = when declared(testStatusIMPL): testStatusIMPL = FAILED else: - programResult += 1 + programResult = 1 ensureInitialized() diff --git a/lib/pure/uri.nim b/lib/pure/uri.nim index d296017dd..bfb411fc8 100644 --- a/lib/pure/uri.nim +++ b/lib/pure/uri.nim @@ -8,6 +8,35 @@ # ## This module implements URI parsing as specified by RFC 3986. +## +## A Uniform Resource Identifier (URI) provides a simple and extensible +## means for identifying a resource. A URI can be further classified +## as a locator, a name, or both. The term “Uniform Resource Locator” +## (URL) refers to the subset of URIs. +## +## Basic usage +## =========== +## +## Combine URIs +## ------------- +## .. code-block:: +## import uri +## let host = parseUri("https://nim-lang.org") +## let blog = "/blog.html" +## let bloguri = host / blog +## assert $host == "https://nim-lang.org" +## assert $bloguri == "https://nim-lang.org/blog.html" +## +## Access URI item +## --------------- +## .. code-block:: +## import uri +## let res = parseUri("sftp://127.0.0.1:4343") +## if isAbsolute(res): +## echo "Connect to port: " & res.port +## # --> Connect to port: 4343 +## else: +## echo "Wrong format" import strutils, parseutils type @@ -18,44 +47,24 @@ type hostname*, port*, path*, query*, anchor*: string opaque*: bool -{.push warning[deprecated]: off.} -proc `$`*(url: Url): string {.deprecated: "use Uri instead".} = - ## **Deprecated since 0.9.6**: Use ``Uri`` instead. - return string(url) - -proc `/`*(a, b: Url): Url {.deprecated: "use Uri instead".} = - ## Joins two URLs together, separating them with / if needed. - ## - ## **Deprecated since 0.9.6**: Use ``Uri`` instead. - var urlS = $a - var bS = $b - if urlS == "": return b - if urlS[urlS.len-1] != '/': - urlS.add('/') - if bS[0] == '/': - urlS.add(bS.substr(1)) - else: - urlS.add(bs) - result = Url(urlS) - -proc add*(url: var Url, a: Url) {.deprecated: "use Uri instead".} = - ## Appends url to url. - ## - ## **Deprecated since 0.9.6**: Use ``Uri`` instead. - url = url / a -{.pop.} - proc encodeUrl*(s: string, usePlus=true): string = ## Encodes a URL according to RFC3986. ## ## This means that characters in the set ## ``{'a'..'z', 'A'..'Z', '0'..'9', '-', '.', '_', '~'}`` are ## carried over to the result. - ## All other characters are encoded as ``''%xx'`` where ``xx`` + ## All other characters are encoded as ``%xx`` where ``xx`` ## denotes its hexadecimal value. ## ## As a special rule, when the value of ``usePlus`` is true, - ## spaces are encoded as ``'+'`` instead of ``'%20'``. + ## spaces are encoded as ``+`` instead of ``%20``. + ## + ## **See also:** + ## * `decodeUrl proc<#decodeUrl,string>`_ + runnableExamples: + assert encodeUrl("https://nim-lang.org") == "https%3A%2F%2Fnim-lang.org" + assert encodeUrl("https://nim-lang.org/this is a test") == "https%3A%2F%2Fnim-lang.org%2Fthis+is+a+test" + assert encodeUrl("https://nim-lang.org/this is a test", false) == "https%3A%2F%2Fnim-lang.org%2Fthis%20is%20a%20test" result = newStringOfCap(s.len + s.len shr 2) # assume 12% non-alnum-chars let fromSpace = if usePlus: "+" else: "%20" for c in s: @@ -70,12 +79,19 @@ proc encodeUrl*(s: string, usePlus=true): string = proc decodeUrl*(s: string, decodePlus=true): string = ## Decodes a URL according to RFC3986. ## - ## This means that any ``'%xx'`` (where ``xx`` denotes a hexadecimal + ## This means that any ``%xx`` (where ``xx`` denotes a hexadecimal ## value) are converted to the character with ordinal number ``xx``, ## and every other character is carried over. ## - ## As a special rule, when the value of ``decodePlus`` is true, ``'+'`` + ## As a special rule, when the value of ``decodePlus`` is true, ``+`` ## characters are converted to a space. + ## + ## **See also:** + ## * `encodeUrl proc<#encodeUrl,string>`_ + runnableExamples: + assert decodeUrl("https%3A%2F%2Fnim-lang.org") == "https://nim-lang.org" + assert decodeUrl("https%3A%2F%2Fnim-lang.org%2Fthis+is+a+test") == "https://nim-lang.org/this is a test" + assert decodeUrl("https%3A%2F%2Fnim-lang.org%2Fthis%20is%20a%20test", false) == "https://nim-lang.org/this is a test" proc handleHexChar(c: char, x: var int) {.inline.} = case c of '0'..'9': x = (x shl 4) or (ord(c) - ord('0')) @@ -150,7 +166,14 @@ proc parsePath(uri: string, i: var int, result: var Uri) = i.inc parseUntil(uri, result.anchor, {}, i) proc initUri*(): Uri = - ## Initializes a URI. + ## Initializes a URI with ``scheme``, ``username``, ``password``, + ## ``hostname``, ``port``, ``path``, ``query`` and ``anchor``. + ## + ## **See also:** + ## * `Uri type <#Uri>`_ for available fields in the URI type + runnableExamples: + var uri: Uri + assert initUri() == uri result = Uri(scheme: "", username: "", password: "", hostname: "", port: "", path: "", query: "", anchor: "") @@ -163,6 +186,16 @@ proc resetUri(uri: var Uri) = proc parseUri*(uri: string, result: var Uri) = ## Parses a URI. The `result` variable will be cleared before. + ## + ## **See also:** + ## * `Uri type <#Uri>`_ for available fields in the URI type + ## * `initUri proc <#initUri,>`_ for initializing a URI + runnableExamples: + var res = initUri() + parseUri("https://nim-lang.org/docs/manual.html", res) + assert res.scheme == "https" + assert res.hostname == "nim-lang.org" + assert res.path == "/docs/manual.html" resetUri(result) var i = 0 @@ -201,6 +234,14 @@ proc parseUri*(uri: string, result: var Uri) = proc parseUri*(uri: string): Uri = ## Parses a URI and returns it. + ## + ## **See also:** + ## * `Uri type <#Uri>`_ for available fields in the URI type + runnableExamples: + let res = parseUri("ftp://Username:Password@Hostname") + assert res.username == "Username" + assert res.password == "Password" + assert res.scheme == "ftp" result = initUri() parseUri(uri, result) @@ -251,22 +292,18 @@ proc combine*(base: Uri, reference: Uri): Uri = ## This uses the algorithm specified in ## `section 5.2.2 of RFC 3986 <http://tools.ietf.org/html/rfc3986#section-5.2.2>`_. ## - ## This means that the slashes inside the base URI's path as well as reference - ## URI's path affect the resulting URI. - ## - ## For building URIs you may wish to use \`/\` instead. - ## - ## Examples: - ## - ## .. code-block:: - ## let foo = combine(parseUri("http://example.com/foo/bar"), parseUri("/baz")) - ## assert foo.path == "/baz" - ## - ## let bar = combine(parseUri("http://example.com/foo/bar"), parseUri("baz")) - ## assert bar.path == "/foo/baz" + ## This means that the slashes inside the base URIs path as well as reference + ## URIs path affect the resulting URI. ## - ## let bar = combine(parseUri("http://example.com/foo/bar/"), parseUri("baz")) - ## assert bar.path == "/foo/bar/baz" + ## **See also:** + ## * `/ proc <#/,Uri,string>`_ for building URIs + runnableExamples: + let foo = combine(parseUri("https://nim-lang.org/foo/bar"), parseUri("/baz")) + assert foo.path == "/baz" + let bar = combine(parseUri("https://nim-lang.org/foo/bar"), parseUri("baz")) + assert bar.path == "/foo/baz" + let qux = combine(parseUri("https://nim-lang.org/foo/bar/"), parseUri("baz")) + assert qux.path == "/foo/bar/baz" template setAuthority(dest, src): untyped = dest.hostname = src.hostname @@ -302,32 +339,42 @@ proc combine*(base: Uri, reference: Uri): Uri = proc combine*(uris: varargs[Uri]): Uri = ## Combines multiple URIs together. + ## + ## **See also:** + ## * `/ proc <#/,Uri,string>`_ for building URIs + runnableExamples: + let foo = combine(parseUri("https://nim-lang.org/blog.html"), parseUri("/install.html")) + assert foo.hostname == "nim-lang.org" + assert foo.path == "/install.html" result = uris[0] for i in 1 ..< uris.len: result = combine(result, uris[i]) proc isAbsolute*(uri: Uri): bool = - ## returns true if URI is absolute, false otherwise + ## Returns true if URI is absolute, false otherwise. + runnableExamples: + let foo = parseUri("https://nim-lang.org") + assert isAbsolute(foo) == true + let bar = parseUri("nim-lang") + assert isAbsolute(bar) == false return uri.scheme != "" and (uri.hostname != "" or uri.path != "") proc `/`*(x: Uri, path: string): Uri = - ## Concatenates the path specified to the specified URI's path. + ## Concatenates the path specified to the specified URIs path. ## - ## Contrary to the ``combine`` procedure you do not have to worry about - ## the slashes at the beginning and end of the path and URI's path + ## Contrary to the `combine proc <#combine,Uri,Uri>`_ you do not have to worry about + ## the slashes at the beginning and end of the path and URIs path ## respectively. ## - ## Examples: - ## - ## .. code-block:: - ## let foo = parseUri("http://example.com/foo/bar") / "/baz" - ## assert foo.path == "/foo/bar/baz" - ## - ## let bar = parseUri("http://example.com/foo/bar") / "baz" - ## assert bar.path == "/foo/bar/baz" - ## - ## let bar = parseUri("http://example.com/foo/bar/") / "baz" - ## assert bar.path == "/foo/bar/baz" + ## **See also:** + ## * `combine proc <#combine,Uri,Uri>`_ + runnableExamples: + let foo = parseUri("https://nim-lang.org/foo/bar") / "/baz" + assert foo.path == "/foo/bar/baz" + let bar = parseUri("https://nim-lang.org/foo/bar") / "baz" + assert bar.path == "/foo/bar/baz" + let qux = parseUri("https://nim-lang.org/foo/bar/") / "baz" + assert qux.path == "/foo/bar/baz" result = x if result.path.len == 0: @@ -348,6 +395,9 @@ proc `/`*(x: Uri, path: string): Uri = proc `$`*(u: Uri): string = ## Returns the string representation of the specified URI object. + runnableExamples: + let foo = parseUri("https://nim-lang.org") + assert $foo == "https://nim-lang.org" result = "" if u.scheme.len > 0: result.add(u.scheme) diff --git a/lib/std/sha1.nim b/lib/std/sha1.nim index e7e6697cf..b5660f244 100644 --- a/lib/std/sha1.nim +++ b/lib/std/sha1.nim @@ -7,7 +7,32 @@ # distribution, for details about the copyright. # -## Note: Import ``std/sha1`` to use this module +## **Note:** Import ``std/sha1`` to use this module +## +## SHA-1 (Secure Hash Algorithm 1) is a cryptographic hash function which +## takes an input and produces a 160-bit (20-byte) hash value known as a +## message digest. +## +## .. code-block:: +## import std/sha1 +## +## let accessName = secureHash("John Doe") +## assert $accessName == "AE6E4D1209F17B460503904FAD297B31E9CF6362" +## +## .. code-block:: +## import std/sha1 +## +## let +## a = secureHashFile("myFile.nim") +## b = parseSecureHash("10DFAEBF6BFDBC7939957068E2EFACEC4972933C") +## +## if a == b: +## echo "Files match" +## +## **See also:** +## * `base64 module<base64.html>`_ implements a base64 encoder and decoder +## * `hashes module<hashes.html>`_ for efficient computations of hash values for diverse Nim types +## * `md5 module<md5.html>`_ implements the MD5 checksum algorithm import strutils from endians import bigEndian32, bigEndian64 @@ -170,23 +195,61 @@ proc finalize(ctx: var Sha1State): Sha1Digest = # Public API proc secureHash*(str: string): SecureHash = + ## Generates a ``SecureHash`` from a ``str``. + ## + ## **See also:** + ## * `secureHashFile proc <#secureHashFile,string>`_ for generating a ``SecureHash`` from a file + ## * `parseSecureHash proc <#parseSecureHash,string>`_ for converting a string ``hash`` to ``SecureHash`` + runnableExamples: + let hash = secureHash("Hello World") + assert hash == parseSecureHash("0A4D55A8D778E5022FAB701977C5D840BBC486D0") var state = newSha1State() state.update(str) SecureHash(state.finalize()) proc secureHashFile*(filename: string): SecureHash = + ## Generates a ``SecureHash`` from a file. + ## + ## **See also:** + ## * `secureHash proc <#secureHash,string>`_ for generating a ``SecureHash`` from a string + ## * `parseSecureHash proc <#parseSecureHash,string>`_ for converting a string ``hash`` to ``SecureHash`` secureHash(readFile(filename)) proc `$`*(self: SecureHash): string = + ## Returns the string representation of a ``SecureHash``. + ## + ## **See also:** + ## * `secureHash proc <#secureHash,string>`_ for generating a ``SecureHash`` from a string + runnableExamples: + let hash = secureHash("Hello World") + assert $hash == "0A4D55A8D778E5022FAB701977C5D840BBC486D0" result = "" for v in Sha1Digest(self): result.add(toHex(int(v), 2)) proc parseSecureHash*(hash: string): SecureHash = + ## Converts a string ``hash`` to ``SecureHash``. + ## + ## **See also:** + ## * `secureHash proc <#secureHash,string>`_ for generating a ``SecureHash`` from a string + ## * `secureHashFile proc <#secureHashFile,string>`_ for generating a ``SecureHash`` from a file + runnableExamples: + let + hashStr = "0A4D55A8D778E5022FAB701977C5D840BBC486D0" + secureHash = secureHash("Hello World") + assert secureHash == parseSecureHash(hashStr) for i in 0 ..< Sha1DigestSize: Sha1Digest(result)[i] = uint8(parseHexInt(hash[i*2] & hash[i*2 + 1])) proc `==`*(a, b: SecureHash): bool = + ## Checks if two ``SecureHash`` values are identical. + runnableExamples: + let + a = secureHash("Hello World") + b = secureHash("Goodbye World") + c = parseSecureHash("0A4D55A8D778E5022FAB701977C5D840BBC486D0") + assert a != b + assert a == c # Not a constant-time comparison, but that's acceptable in this context Sha1Digest(a) == Sha1Digest(b) diff --git a/lib/std/time_t.nim b/lib/std/time_t.nim new file mode 100644 index 000000000..37918ee6c --- /dev/null +++ b/lib/std/time_t.nim @@ -0,0 +1,23 @@ +# +# +# Nim's Runtime Library +# (c) Copyright 2019 Nim contributors +# +# See the file "copying.txt", included in this +# distribution, for details about the copyright. +# + +when defined(nimdoc): + type + impl = distinct int64 + Time* = impl ## \ + ## Wrapper for ``time_t``. On posix, this is an alias to ``posix.Time``. +elif defined(windows): + when defined(i386) and defined(gcc): + type Time* {.importc: "time_t", header: "<time.h>".} = distinct int32 + else: + # newest version of Visual C++ defines time_t to be of 64 bits + type Time* {.importc: "time_t", header: "<time.h>".} = distinct int64 +elif defined(posix): + import posix + export posix.Time \ No newline at end of file diff --git a/lib/std/wordwrap.nim b/lib/std/wordwrap.nim index c7898b339..4b0dc4417 100644 --- a/lib/std/wordwrap.nim +++ b/lib/std/wordwrap.nim @@ -24,6 +24,11 @@ proc wrapWords*(s: string, maxLineWidth = 80, seps: set[char] = Whitespace, newLine = "\n"): string {.noSideEffect.} = ## Word wraps `s`. + runnableExamples: + doAssert "12345678901234567890".wrapWords() == "12345678901234567890" + doAssert "123456789012345678901234567890".wrapWords(20) == "12345678901234567890\n1234567890" + doAssert "Hello Bob. Hello John.".wrapWords(13, false) == "Hello Bob.\nHello John." + doAssert "Hello Bob. Hello John.".wrapWords(13, true, {';'}) == "Hello Bob. He\nllo John." result = newStringOfCap(s.len + s.len shr 6) var spaceLeft = maxLineWidth var lastSep = "" diff --git a/lib/system.nim b/lib/system.nim index ea767e27a..d4a86e53b 100644 --- a/lib/system.nim +++ b/lib/system.nim @@ -118,6 +118,22 @@ proc defined*(x: untyped): bool {.magic: "Defined", noSideEffect, compileTime.} ## # Do here programmer friendly expensive sanity checks. ## # Put here the normal code +when defined(nimHasRunnableExamples): + proc runnableExamples*(body: untyped) {.magic: "RunnableExamples".} + ## A section you should use to mark `runnable example`:idx: code with. + ## + ## - In normal debug and release builds code within + ## a ``runnableExamples`` section is ignored. + ## - The documentation generator is aware of these examples and considers them + ## part of the ``##`` doc comment. As the last step of documentation + ## generation the examples are put into an ``$file_example.nim`` file, + ## compiled and tested. The collected examples are + ## put into their own module to ensure the examples do not refer to + ## non-exported symbols. +else: + template runnableExamples*(body: untyped) = + discard + proc declared*(x: untyped): bool {.magic: "Defined", noSideEffect, compileTime.} ## Special compile-time procedure that checks whether `x` is ## declared. `x` has to be an identifier or a qualified identifier. @@ -553,7 +569,12 @@ type trace: string else: trace: seq[StackTraceEntry] - raiseId: uint # set when exception is raised + when defined(nimBoostrapCsources0_19_0): + # see #10315, bootstrap with `nim cpp` from csources gave error: + # error: no member named 'raise_id' in 'Exception' + raise_id: uint # set when exception is raised + else: + raiseId: uint # set when exception is raised up: ref Exception # used for stacking exceptions. Not exported! Defect* = object of Exception ## \ @@ -590,7 +611,7 @@ type ## Raised when assertion is proved wrong. ## ## Usually the result of using the `assert() template <#assert>`_. - ValueError* = object of Defect ## \ + ValueError* = object of CatchableError ## \ ## Raised for string and object conversion errors. KeyError* = object of ValueError ## \ ## Raised if a key cannot be found in a table. @@ -1479,12 +1500,9 @@ const # for string literals, it allows for some optimizations. {.push profiler: off.} -when defined(nimKnowsNimvm): - let nimvm* {.magic: "Nimvm".}: bool = false - ## may be used only in "when" expression. - ## It is true in Nim VM context and false otherwise -else: - const nimvm*: bool = false +let nimvm* {.magic: "Nimvm", compileTime.}: bool = false + ## may be used only in "when" expression. + ## It is true in Nim VM context and false otherwise {.pop.} proc compileOption*(option: string): bool {. @@ -1539,7 +1557,7 @@ else: ## ``string`` if the taint mode is not ## turned on. -when defined(profiler): +when defined(profiler) and not defined(nimscript): proc nimProfile() {.compilerProc, noinline.} when hasThreadSupport: {.pragma: rtlThreadVar, threadvar.} @@ -1555,7 +1573,7 @@ const ## is the value that should be passed to `quit <#quit>`_ to indicate ## failure. -when defined(nodejs): +when defined(nodejs) and not defined(nimscript): var programResult* {.importc: "process.exitCode".}: int programResult = 0 else: @@ -1601,7 +1619,7 @@ elif defined(genode): -elif defined(nodejs): +elif defined(nodejs) and not defined(nimscript): proc quit*(errorcode: int = QuitSuccess) {.magic: "Exit", importc: "process.exit", noreturn.} @@ -1692,7 +1710,7 @@ proc insert*[T](x: var seq[T], item: T, i = 0.Natural) {.noSideEffect.} = else: when defined(js): var it : T - {.emit: "`x`.splice(`i`, 0, `it`);".} + {.emit: "`x` = `x` || []; `x`.splice(`i`, 0, `it`);".} else: defaultImpl() x[i] = item @@ -1795,21 +1813,26 @@ proc toFloat*(i: int): float {. proc toBiggestFloat*(i: BiggestInt): BiggestFloat {. magic: "ToBiggestFloat", noSideEffect, importc: "toBiggestFloat".} - ## converts an biggestint `i` into a ``biggestfloat``. If the conversion + ## converts a biggestint `i` into a ``biggestfloat``. If the conversion ## fails, `ValueError` is raised. However, on most platforms the ## conversion cannot fail. proc toInt*(f: float): int {. - magic: "ToInt", noSideEffect, importc: "toInt".} + magic: "ToInt", noSideEffect, importc: "toInt".} = ## converts a floating point number `f` into an ``int``. Conversion - ## rounds `f` if it does not contain an integer value. If the conversion - ## fails (because `f` is infinite for example), `ValueError` is raised. + ## rounds `f` half away from 0, see https://en.wikipedia.org/wiki/Rounding#Round_half_away_from_zero + ## Note that some floating point numbers (e.g. infinity or even 1e19) + ## cannot be accurately converted. + runnableExamples: + doAssert toInt(0.49) == 0 + doAssert toInt(0.5) == 1 + doAssert toInt(-0.5) == -1 ## rounding is symmetrical proc toBiggestInt*(f: BiggestFloat): BiggestInt {. - magic: "ToBiggestInt", noSideEffect, importc: "toBiggestInt".} - ## converts a biggestfloat `f` into a ``biggestint``. Conversion - ## rounds `f` if it does not contain an integer value. If the conversion - ## fails (because `f` is infinite for example), `ValueError` is raised. + magic: "ToBiggestInt", noSideEffect, importc: "toBiggestInt".} = + ## Same as `toInt` but for BiggestFloat to ``BiggestInt``. + runnableExamples: + doAssert toBiggestInt(0.49) == 0 proc addQuitProc*(quitProc: proc() {.noconv.}) {. importc: "atexit", header: "<stdlib.h>".} @@ -2624,8 +2647,8 @@ proc `==`*[T: tuple|object](x, y: T): bool = return true proc `<=`*[T: tuple](x, y: T): bool = - ## generic ``<=`` operator for tuples that is lifted from the components - ## of `x` and `y`. This implementation uses `cmp`. + ## generic lexicographic ``<=`` operator for tuples that is lifted from the + ## components of `x` and `y`. This implementation uses `cmp`. for a, b in fields(x, y): var c = cmp(a, b) if c < 0: return true @@ -2633,8 +2656,8 @@ proc `<=`*[T: tuple](x, y: T): bool = return true proc `<`*[T: tuple](x, y: T): bool = - ## generic ``<`` operator for tuples that is lifted from the components - ## of `x` and `y`. This implementation uses `cmp`. + ## generic lexicographic ``<`` operator for tuples that is lifted from the + ## components of `x` and `y`. This implementation uses `cmp`. for a, b in fields(x, y): var c = cmp(a, b) if c < 0: return true @@ -2651,19 +2674,28 @@ proc compiles*(x: untyped): bool {.magic: "Compiles", noSideEffect, compileTime. ## echo "'+' for integers is available" discard +include "system/helpers" # for `lineInfoToString`, `isNamedTuple` + proc `$`*[T: tuple|object](x: T): string = ## generic ``$`` operator for tuples that is lifted from the components ## of `x`. Example: ## ## .. code-block:: nim ## $(23, 45) == "(23, 45)" + ## $(a: 23, b: 45) == "(a: 23, b: 45)" ## $() == "()" result = "(" var firstElement = true + const isNamed = T is object or isNamedTuple(T) + when not isNamed: + var count = 0 for name, value in fieldPairs(x): if not firstElement: result.add(", ") - result.add(name) - result.add(": ") + when isNamed: + result.add(name) + result.add(": ") + else: + count.inc when compiles($value): when value isnot string and value isnot seq and compiles(value.isNil): if value.isNil: result.add "nil" @@ -2673,6 +2705,11 @@ proc `$`*[T: tuple|object](x: T): string = firstElement = false else: result.add("...") + firstElement = false + when not isNamed: + if count == 1: + result.add(",") # $(1,) should print as the semantically legal (1,) + result.add(")") proc collectionToString[T](x: T, prefix, separator, suffix: string): string = @@ -2711,6 +2748,16 @@ proc `$`*[T](x: seq[T]): string = ## $(@[23, 45]) == "@[23, 45]" collectionToString(x, "@[", ", ", "]") +proc `$`*[T, U](x: HSlice[T, U]): string = + ## generic ``$`` operator for slices that is lifted from the components + ## of `x`. Example: + ## + ## .. code-block:: nim + ## $(1 .. 5) == "1 .. 5" + result = $x.a + result.add(" .. ") + result.add($x.b) + # ----------------- GC interface --------------------------------------------- when not defined(nimscript) and hasAlloc: @@ -2760,8 +2807,8 @@ when not defined(nimscript) and hasAlloc: when not defined(JS) and not defined(nimscript) and hasAlloc: proc nimGC_setStackBottom*(theStackBottom: pointer) {.compilerRtl, noinline, benign.} - ## Expands operating GC stack range to `theStackBottom`. Does nothing - ## if current stack bottom is already lower than `theStackBottom`. + ## Expands operating GC stack range to `theStackBottom`. Does nothing + ## if current stack bottom is already lower than `theStackBottom`. else: template GC_disable* = @@ -3075,7 +3122,7 @@ when not defined(JS): #and not defined(nimscript): proc initStackBottom() {.inline, compilerproc.} = # WARNING: This is very fragile! An array size of 8 does not work on my # Linux 64bit system. -- That's because the stack direction is the other - # way round. + # way around. when declared(nimGC_setStackBottom): var locals {.volatile.}: pointer locals = addr(locals) @@ -3147,7 +3194,7 @@ when not defined(JS): #and not defined(nimscript): result = x.len - y.len when defined(nimscript): - proc readFile*(filename: string): string {.tags: [ReadIOEffect], benign.} + proc readFile*(filename: string): TaintedString {.tags: [ReadIOEffect], benign.} ## Opens a file named `filename` for reading, calls `readAll ## <#readAll>`_ and closes the file afterwards. Returns the string. ## Raises an IO exception in case of an error. If # you need to call @@ -3196,14 +3243,15 @@ when not defined(JS): #and not defined(nimscript): proc open*(f: var File, filename: string, mode: FileMode = fmRead, bufSize: int = -1): bool {.tags: [], - benign.} + raises: [], benign.} ## Opens a file named `filename` with given `mode`. ## ## Default mode is readonly. Returns true iff the file could be opened. ## This throws no exception if the file could not be opened. proc open*(f: var File, filehandle: FileHandle, - mode: FileMode = fmRead): bool {.tags: [], benign.} + mode: FileMode = fmRead): bool {.tags: [], raises: [], + benign.} ## Creates a ``File`` from a `filehandle` with given `mode`. ## ## Default mode is readonly. Returns true iff the file could be opened. @@ -3212,7 +3260,7 @@ when not defined(JS): #and not defined(nimscript): mode: FileMode = fmRead, bufSize: int = -1): File = ## Opens a file named `filename` with given `mode`. ## - ## Default mode is readonly. Raises an ``IO`` exception if the file + ## Default mode is readonly. Raises an ``IOError`` if the file ## could not be opened. if not open(result, filename, mode, bufSize): sysFatal(IOError, "cannot open: ", filename) @@ -3415,6 +3463,10 @@ when not defined(JS): #and not defined(nimscript): ## allows you to override the behaviour of your application when CTRL+C ## is pressed. Only one such hook is supported. + when not defined(useNimRtl): + proc unsetControlCHook*() + ## reverts a call to setControlCHook + proc writeStackTrace*() {.tags: [], gcsafe.} ## writes the current stack trace to ``stderr``. This is only works ## for debug builds. Since it's usually used for debugging, this @@ -3431,6 +3483,10 @@ when not defined(JS): #and not defined(nimscript): when defined(memtracker): include "system/memtracker" + when defined(gcDestructors): + include "core/strs" + include "core/seqs" + when hostOS == "standalone": include "system/embedded" else: @@ -3479,10 +3535,7 @@ when not defined(JS): #and not defined(nimscript): {.pop.} {.push stack_trace: off, profiler:off.} when hasAlloc: - when defined(gcDestructors): - include "core/strs" - include "core/seqs" - else: + when not defined(gcDestructors): include "system/sysstr" {.pop.} when hasAlloc: include "system/strmantle" @@ -3568,7 +3621,7 @@ when not defined(JS): #and not defined(nimscript): when defined(endb) and not defined(nimscript): include "system/debugger" - when defined(profiler) or defined(memProfiler): + when (defined(profiler) or defined(memProfiler)) and not defined(nimscript): include "system/profiler" {.pop.} # stacktrace @@ -3609,7 +3662,7 @@ elif defined(JS): proc deallocShared(p: pointer) = discard proc reallocShared(p: pointer, newsize: Natural): pointer = discard - when defined(JS): + when defined(JS) and not defined(nimscript): include "system/jssys" include "system/reprjs" elif defined(nimscript): @@ -3833,15 +3886,21 @@ proc gorgeEx*(command: string, input = "", cache = ""): tuple[output: string, ## Same as `gorge` but also returns the precious exit code. discard -proc `+=`*[T: SomeOrdinal|uint|uint64](x: var T, y: T) {. +proc `+=`*[T: SomeInteger](x: var T, y: T) {. magic: "Inc", noSideEffect.} - ## Increments an ordinal + ## Increments an integer + +proc `+=`*[T: enum|bool](x: var T, y: T) {. + magic: "Inc", noSideEffect, deprecated: "use `inc` instead".} -proc `-=`*[T: SomeOrdinal|uint|uint64](x: var T, y: T) {. +proc `-=`*[T: SomeInteger](x: var T, y: T) {. magic: "Dec", noSideEffect.} ## Decrements an ordinal -proc `*=`*[T: SomeOrdinal|uint|uint64](x: var T, y: T) {. +proc `-=`*[T: enum|bool](x: var T, y: T) {. + magic: "Dec", noSideEffect, deprecated: "0.20.0, use `dec` instead".} + +proc `*=`*[T: SomeInteger](x: var T, y: T) {. inline, noSideEffect.} = ## Binary `*=` operator for ordinals x = x * y @@ -3919,7 +3978,7 @@ proc instantiationInfo*(index = -1, fullPaths = false): tuple[ template currentSourcePath*: string = instantiationInfo(-1, true).filename ## returns the full file-system path of the current source -proc raiseAssert*(msg: string) {.noinline.} = +proc raiseAssert*(msg: string) {.noinline, noReturn.} = sysFatal(AssertionError, msg) proc failedAssertImpl*(msg: string) {.raises: [], tags: [].} = @@ -3929,8 +3988,6 @@ proc failedAssertImpl*(msg: string) {.raises: [], tags: [].} = tags: [].} Hide(raiseAssert)(msg) -include "system/helpers" # for `lineInfoToString` - template assertImpl(cond: bool, msg: string, expr: string, enabled: static[bool]) = const loc = $instantiationInfo(-1, true) bind instantiationInfo @@ -4326,24 +4383,7 @@ when defined(windows) and appType == "console" and defined(nimSetUtf8CodePage): importc: "SetConsoleOutputCP".} discard setConsoleOutputCP(65001) # 65001 - utf-8 codepage - -when defined(nimHasRunnableExamples): - proc runnableExamples*(body: untyped) {.magic: "RunnableExamples".} - ## A section you should use to mark `runnable example`:idx: code with. - ## - ## - In normal debug and release builds code within - ## a ``runnableExamples`` section is ignored. - ## - The documentation generator is aware of these examples and considers them - ## part of the ``##`` doc comment. As the last step of documentation - ## generation the examples are put into an ``$file_example.nim`` file, - ## compiled and tested. The collected examples are - ## put into their own module to ensure the examples do not refer to - ## non-exported symbols. -else: - template runnableExamples*(body: untyped) = - discard - -template doAssertRaises*(exception, code: untyped): typed = +template doAssertRaises*(exception: typedesc, code: untyped): typed = ## Raises ``AssertionError`` if specified ``code`` does not raise the ## specified exception. Example: ## @@ -4351,16 +4391,24 @@ template doAssertRaises*(exception, code: untyped): typed = ## doAssertRaises(ValueError): ## raise newException(ValueError, "Hello World") var wrong = false - try: - if true: - code - wrong = true - except exception: - discard - except Exception as exc: - raiseAssert(astToStr(exception) & - " wasn't raised, another error was raised instead by:\n"& - astToStr(code)) + when Exception is exception: + try: + if true: + code + wrong = true + except Exception: + discard + else: + try: + if true: + code + wrong = true + except exception: + discard + except Exception as exc: + raiseAssert(astToStr(exception) & + " wasn't raised, another error was raised instead by:\n"& + astToStr(code)) if wrong: raiseAssert(astToStr(exception) & " wasn't raised by:\n" & astToStr(code)) @@ -4420,3 +4468,12 @@ when defined(genode): componentConstructHook(env) # Perform application initialization # and return to thread entrypoint. + +proc `$`*(t: typedesc): string {.magic: "TypeTrait".} = + ## Returns the name of the given type. + ## + ## For more procedures dealing with ``typedesc``, see ``typetraits.nim``. + runnableExamples: + doAssert $(type(42)) == "int" + doAssert $(type("Foo")) == "string" + static: doAssert $(type(@['A', 'B'])) == "seq[char]" diff --git a/lib/system/alloc.nim b/lib/system/alloc.nim index b090117a9..e938dc475 100644 --- a/lib/system/alloc.nim +++ b/lib/system/alloc.nim @@ -20,7 +20,7 @@ template track(op, address, size) = # Each chunk starts at an address that is divisible by the page size. const - InitialMemoryRequest = 128 * PageSize # 0.5 MB + nimMinHeapPages {.intdefine.} = 128 # 0.5 MB SmallChunkSize = PageSize MaxFli = 30 MaxLog2Sli = 5 # 32, this cannot be increased without changing 'uint32' @@ -588,8 +588,8 @@ proc getBigChunk(a: var MemRegion, size: int): PBigChunk = sysAssert((size and PageMask) == 0, "getBigChunk: unaligned chunk") result = findSuitableBlock(a, fl, sl) if result == nil: - if size < InitialMemoryRequest: - result = requestOsChunks(a, InitialMemoryRequest) + if size < nimMinHeapPages * PageSize: + result = requestOsChunks(a, nimMinHeapPages * PageSize) splitChunk(a, result, size) else: result = requestOsChunks(a, size) diff --git a/lib/system/ansi_c.nim b/lib/system/ansi_c.nim index 38910dbd6..af34060d8 100644 --- a/lib/system/ansi_c.nim +++ b/lib/system/ansi_c.nim @@ -51,7 +51,7 @@ when defined(windows): elif defined(macosx) or defined(linux) or defined(freebsd) or defined(openbsd) or defined(netbsd) or defined(solaris) or defined(dragonfly) or defined(nintendoswitch) or defined(genode) or - defined(aix): + defined(aix) or hostOS == "standalone": const SIGABRT = cint(6) SIGFPE = cint(8) diff --git a/lib/system/embedded.nim b/lib/system/embedded.nim index 4d453fcca..fb89e7f0f 100644 --- a/lib/system/embedded.nim +++ b/lib/system/embedded.nim @@ -40,6 +40,7 @@ proc reraiseException() {.compilerRtl.} = proc writeStackTrace() = discard +proc unsetControlCHook() = discard proc setControlCHook(hook: proc () {.noconv.}) = discard proc closureIterSetupExc(e: ref Exception) {.compilerproc, inline.} = diff --git a/lib/system/excpt.nim b/lib/system/excpt.nim index 84a1da343..3efefead4 100644 --- a/lib/system/excpt.nim +++ b/lib/system/excpt.nim @@ -536,3 +536,8 @@ proc setControlCHook(hook: proc () {.noconv.}) = # ugly cast, but should work on all architectures: type SignalHandler = proc (sign: cint) {.noconv, benign.} c_signal(SIGINT, cast[SignalHandler](hook)) + +when not defined(useNimRtl): + proc unsetControlCHook() = + # proc to unset a hook set by setControlCHook + c_signal(SIGINT, signalHandler) diff --git a/lib/system/gc.nim b/lib/system/gc.nim index fb20edbbb..9ae388aa9 100644 --- a/lib/system/gc.nim +++ b/lib/system/gc.nim @@ -17,9 +17,9 @@ const CycleIncrease = 2 # is a multiplicative increase InitialCycleThreshold = 4*1024*1024 # X MB because cycle checking is slow - ZctThreshold = 500 # we collect garbage if the ZCT's size - # reaches this threshold - # this seems to be a good value + InitialZctThreshold = 500 # we collect garbage if the ZCT's size + # reaches this threshold + # this seems to be a good value withRealTime = defined(useRealtimeGC) when withRealTime and not declared(getTicks): @@ -78,6 +78,7 @@ type when nimCoroutines: activeStack: ptr GcStack # current executing coroutine stack. cycleThreshold: int + zctThreshold: int when useCellIds: idGenerator: int zct: CellSeq # the zero count table @@ -253,6 +254,7 @@ proc initGC() = when traceGC: for i in low(CellState)..high(CellState): init(states[i]) gch.cycleThreshold = InitialCycleThreshold + gch.zctThreshold = InitialZctThreshold gch.stat.stackScans = 0 gch.stat.cycleCollections = 0 gch.stat.maxThreshold = 0 @@ -771,11 +773,7 @@ proc collectCTBody(gch: var GcHeap) = c_fprintf(stdout, "[GC] missed deadline: %ld\n", duration) proc collectCT(gch: var GcHeap) = - # stackMarkCosts prevents some pathological behaviour: Stack marking - # becomes more expensive with large stacks and large stacks mean that - # cells with RC=0 are more likely to be kept alive by the stack. - let stackMarkCosts = max(stackSize() div (16*sizeof(int)), ZctThreshold) - if (gch.zct.len >= stackMarkCosts or (cycleGC and + if (gch.zct.len >= gch.zctThreshold or (cycleGC and getOccupiedMem(gch.region)>=gch.cycleThreshold) or alwaysGC) and gch.recGcLock == 0: when false: @@ -783,6 +781,7 @@ proc collectCT(gch: var GcHeap) = cellsetReset(gch.marked) markForDebug(gch) collectCTBody(gch) + gch.zctThreshold = max(InitialZctThreshold, gch.zct.len * CycleIncrease) when withRealTime: proc toNano(x: int): Nanos {.inline.} = @@ -793,10 +792,11 @@ when withRealTime: proc GC_step(gch: var GcHeap, us: int, strongAdvice: bool) = gch.maxPause = us.toNano - if (gch.zct.len >= ZctThreshold or (cycleGC and + if (gch.zct.len >= gch.zctThreshold or (cycleGC and getOccupiedMem(gch.region)>=gch.cycleThreshold) or alwaysGC) or strongAdvice: collectCTBody(gch) + gch.zctThreshold = max(InitialZctThreshold, gch.zct.len * CycleIncrease) proc GC_step*(us: int, strongAdvice = false, stackSize = -1) {.noinline.} = if stackSize >= 0: diff --git a/lib/system/gc_common.nim b/lib/system/gc_common.nim index 565453298..ebd3dada2 100644 --- a/lib/system/gc_common.nim +++ b/lib/system/gc_common.nim @@ -418,7 +418,7 @@ proc prepareDealloc(cell: PCell) = decTypeSize(cell, t) proc deallocHeap*(runFinalizers = true; allowGcAfterwards = true) = - ## Frees the thread local heap. Runs every finalizer if ``runFinalizers``` + ## Frees the thread local heap. Runs every finalizer if ``runFinalizers`` ## is true. If ``allowGcAfterwards`` is true, a minimal amount of allocation ## happens to ensure the GC can continue to work after the call ## to ``deallocHeap``. diff --git a/lib/system/gc_ms.nim b/lib/system/gc_ms.nim index d8cb3e2d0..aa5fb6aea 100644 --- a/lib/system/gc_ms.nim +++ b/lib/system/gc_ms.nim @@ -485,8 +485,9 @@ proc collectCTBody(gch: var GcHeap) = sysAssert(allocInv(gch.region), "collectCT: end") proc collectCT(gch: var GcHeap; size: int) = + let fmem = getFreeMem(gch.region) if (getOccupiedMem(gch.region) >= gch.cycleThreshold or - size > getFreeMem(gch.region)) and gch.recGcLock == 0: + size > fmem and fmem > InitialThreshold) and gch.recGcLock == 0: collectCTBody(gch) when not defined(useNimRtl): diff --git a/lib/system/gc_regions.nim b/lib/system/gc_regions.nim index 8a1446944..3b908fb08 100644 --- a/lib/system/gc_regions.nim +++ b/lib/system/gc_regions.nim @@ -23,6 +23,21 @@ when defined(nimphpext): proc osDeallocPages(p: pointer, size: int) {.inline.} = efree(p) +elif defined(useMalloc): + proc roundup(x, v: int): int {.inline.} = + result = (x + (v-1)) and not (v-1) + proc emalloc(size: int): pointer {.importc: "malloc", header: "<stdlib.h>".} + proc efree(mem: pointer) {.importc: "free", header: "<stdlib.h>".} + + proc osAllocPages(size: int): pointer {.inline.} = + emalloc(size) + + proc osTryAllocPages(size: int): pointer {.inline.} = + emalloc(size) + + proc osDeallocPages(p: pointer, size: int) {.inline.} = + efree(p) + else: include osalloc @@ -108,6 +123,8 @@ template `+!`(p: pointer, s: int): pointer = template `-!`(p: pointer, s: int): pointer = cast[pointer](cast[int](p) -% s) +const nimMinHeapPages {.intdefine.} = 4 + proc allocSlowPath(r: var MemRegion; size: int) = # we need to ensure that the underlying linked list # stays small. Say we want to grab 16GB of RAM with some @@ -116,9 +133,8 @@ proc allocSlowPath(r: var MemRegion; size: int) = # 8MB, 16MB, 32MB, 64MB, 128MB, 512MB, 1GB, 2GB, 4GB, 8GB, # 16GB --> list contains only 20 elements! That's reasonable. if (r.totalSize and 1) == 0: - r.nextChunkSize = - if r.totalSize < 64 * 1024: PageSize*4 - else: r.nextChunkSize*2 + r.nextChunkSize = if r.totalSize < 64 * 1024: PageSize*nimMinHeapPages + else: r.nextChunkSize*2 var s = roundup(size+sizeof(BaseChunk), PageSize) var fresh: Chunk if s > r.nextChunkSize: @@ -242,6 +258,13 @@ proc deallocAll*() = tlRegion.deallocAll() proc deallocOsPages(r: var MemRegion) = r.deallocAll() template withScratchRegion*(body: untyped) = + let obs = obstackPtr() + try: + body + finally: + setObstackPtr(obs) + +when false: var scratch: MemRegion let oldRegion = tlRegion tlRegion = scratch diff --git a/lib/system/helpers.nim b/lib/system/helpers.nim index fb1218684..a7e47915e 100644 --- a/lib/system/helpers.nim +++ b/lib/system/helpers.nim @@ -1,11 +1,28 @@ -## helpers used system.nim and other modules, avoids code duplication while -## also minimizing symbols exposed in system.nim +# helpers used system.nim and other modules, avoids code duplication while +# also minimizing symbols exposed in system.nim # # TODO: move other things here that should not be exposed in system.nim proc lineInfoToString(file: string, line, column: int): string = file & "(" & $line & ", " & $column & ")" -proc `$`(info: type(instantiationInfo(0))): string = +type InstantiationInfo = tuple[filename: string, line: int, column: int] + +proc `$`(info: InstantiationInfo): string = # The +1 is needed here + # instead of overriding `$` (and changing its meaning), consider explicit name. lineInfoToString(info.fileName, info.line, info.column+1) + +proc isNamedTuple(T: type): bool = + ## return true for named tuples, false for any other type. + when T isnot tuple: result = false + else: + var t: T + for name, _ in t.fieldPairs: + when name == "Field0": + return compiles(t.Field0) + else: + return true + # empty tuple should be un-named, + # see https://github.com/nim-lang/Nim/issues/8861#issue-356631191 + return false diff --git a/lib/system/helpers2.nim b/lib/system/helpers2.nim index 1c9e7c068..c67a2c278 100644 --- a/lib/system/helpers2.nim +++ b/lib/system/helpers2.nim @@ -1,3 +1,5 @@ +# imported by other modules, unlike helpers.nim which is included + template formatErrorIndexBound*[T](i, a, b: T): string = "index out of bounds: (a:" & $a & ") <= (i:" & $i & ") <= (b:" & $b & ") " diff --git a/lib/system/jssys.nim b/lib/system/jssys.nim index 8be19e5b8..d7718e4f4 100644 --- a/lib/system/jssys.nim +++ b/lib/system/jssys.nim @@ -46,7 +46,7 @@ proc nimCharToStr(x: char): string {.compilerproc.} = result[0] = x proc isNimException(): bool {.asmNoStackFrame.} = - asm "return `lastJSError`.m_type;" + asm "return `lastJSError` && `lastJSError`.m_type;" proc getCurrentException*(): ref Exception {.compilerRtl, benign.} = if isNimException(): result = cast[ref Exception](lastJSError) diff --git a/lib/system/mmdisp.nim b/lib/system/mmdisp.nim index 89bc11d2c..9cc7ab323 100644 --- a/lib/system/mmdisp.nim +++ b/lib/system/mmdisp.nim @@ -72,6 +72,8 @@ when defined(boehmgc): proc boehmGCincremental {. importc: "GC_enable_incremental", boehmGC.} proc boehmGCfullCollect {.importc: "GC_gcollect", boehmGC.} + proc boehmGC_set_all_interior_pointers(flag: cint) {. + importc: "GC_set_all_interior_pointers", boehmGC.} proc boehmAlloc(size: int): pointer {.importc: "GC_malloc", boehmGC.} proc boehmAllocAtomic(size: int): pointer {. importc: "GC_malloc_atomic", boehmGC.} @@ -148,6 +150,7 @@ when defined(boehmgc): proc nimGC_setStackBottom(theStackBottom: pointer) = discard proc initGC() = + boehmGC_set_all_interior_pointers(0) boehmGCinit() when hasThreadSupport: boehmGC_allow_register_threads() diff --git a/lib/system/nimscript.nim b/lib/system/nimscript.nim index fc4b574e4..e879fda83 100644 --- a/lib/system/nimscript.nim +++ b/lib/system/nimscript.nim @@ -46,7 +46,7 @@ proc copyDir(src, dest: string) {. tags: [ReadIOEffect, WriteIOEffect], raises: [OSError].} = builtin proc createDir(dir: string) {.tags: [WriteIOEffect], raises: [OSError].} = builtin -proc getOsError: string = builtin +proc getError: string = builtin proc setCurrentDir(dir: string) = builtin proc getCurrentDir*(): string = ## Retrieves the current working directory. @@ -143,6 +143,7 @@ proc existsDir*(dir: string): bool = proc selfExe*(): string = ## Returns the currently running nim or nimble executable. + # TODO: consider making this as deprecated alias of `getCurrentCompilerExe` builtin proc toExe*(filename: string): string = @@ -177,9 +178,12 @@ var mode*: ScriptMode ## Set this to influence how mkDir, rmDir, rmFile etc. ## behave +template checkError(exc: untyped): untyped = + let err = getError() + if err.len > 0: raise newException(exc, err) + template checkOsError = - let err = getOsError() - if err.len > 0: raise newException(OSError, err) + checkError(OSError) template log(msg: string, body: untyped) = if mode in {ScriptMode.Verbose, ScriptMode.Whatif}: @@ -331,6 +335,26 @@ proc cppDefine*(define: string) = ## needs to be mangled. builtin +proc stdinReadLine(): TaintedString {. + tags: [ReadIOEffect], raises: [IOError].} = + builtin + +proc stdinReadAll(): TaintedString {. + tags: [ReadIOEffect], raises: [IOError].} = + builtin + +proc readLineFromStdin*(): TaintedString {.raises: [IOError].} = + ## Reads a line of data from stdin - blocks until \n or EOF which happens when stdin is closed + log "readLineFromStdin": + result = stdinReadLine() + checkError(EOFError) + +proc readAllFromStdin*(): TaintedString {.raises: [IOError].} = + ## Reads all data from stdin - blocks until EOF which happens when stdin is closed + log "readAllFromStdin": + result = stdinReadAll() + checkError(EOFError) + when not defined(nimble): template `==?`(a, b: string): bool = cmpIgnoreStyle(a, b) == 0 template task*(name: untyped; description: string; body: untyped): untyped = @@ -340,14 +364,15 @@ when not defined(nimble): ## .. code-block:: nim ## task build, "default build is via the C backend": ## setCommand "c" - proc `name Task`*() = body + proc `name Task`*() = + setCommand "nop" + body let cmd = getCommand() if cmd.len == 0 or cmd ==? "help": setCommand "help" writeTask(astToStr(name), description) elif cmd ==? astToStr(name): - setCommand "nop" `name Task`() # nimble has its own implementation for these things. diff --git a/lib/system/profiler.nim b/lib/system/profiler.nim index ffd6fd0c5..0649f1176 100644 --- a/lib/system/profiler.nim +++ b/lib/system/profiler.nim @@ -13,6 +13,9 @@ # (except perhaps loops that have no side-effects). At every Nth call a # stack trace is taken. A stack tace is a list of cstrings. +when defined(profiler) and defined(memProfiler): + {.error: "profiler and memProfiler cannot be defined at the same time (See Embedded Stack Trace Profiler (ESTP) User Guide) for more details".} + {.push profiler: off.} const @@ -57,13 +60,13 @@ proc captureStackTrace(f: PFrame, st: var StackTrace) = b = b.prev var - profilingRequestedHook*: proc (): bool {.nimcall, benign.} + profilingRequestedHook*: proc (): bool {.nimcall, locks: 0, gcsafe.} ## set this variable to provide a procedure that implements a profiler in ## user space. See the `nimprof` module for a reference implementation. when defined(memProfiler): type - MemProfilerHook* = proc (st: StackTrace, requestedSize: int) {.nimcall, benign.} + MemProfilerHook* = proc (st: StackTrace, requestedSize: int) {.nimcall, locks: 0, gcsafe.} var profilerHook*: MemProfilerHook @@ -87,9 +90,10 @@ else: proc callProfilerHook(hook: ProfilerHook) {.noinline.} = # 'noinline' so that 'nimProfile' does not perform the stack allocation # in the common case. - var st: StackTrace - captureStackTrace(framePtr, st) - hook(st) + when not defined(nimdoc): + var st: StackTrace + captureStackTrace(framePtr, st) + hook(st) proc nimProfile() = ## This is invoked by the compiler in every loop and on every proc entry! diff --git a/lib/system/strmantle.nim b/lib/system/strmantle.nim index ceaecb4f9..3c681fc53 100644 --- a/lib/system/strmantle.nim +++ b/lib/system/strmantle.nim @@ -7,7 +7,7 @@ # distribution, for details about the copyright. # -## Compilerprocs for strings that do not depend on the string implementation. +# Compilerprocs for strings that do not depend on the string implementation. proc cmpStrings(a, b: string): int {.inline, compilerProc.} = let alen = a.len diff --git a/lib/system/sysio.nim b/lib/system/sysio.nim index 20964b166..5b0278d74 100644 --- a/lib/system/sysio.nim +++ b/lib/system/sysio.nim @@ -74,10 +74,21 @@ proc raiseEIO(msg: string) {.noinline, noreturn.} = proc raiseEOF() {.noinline, noreturn.} = sysFatal(EOFError, "EOF reached") +proc strerror(errnum: cint): cstring {.importc, header: "<string.h>".} + +when not defined(NimScript): + var + errno {.importc, header: "<errno.h>".}: cint ## error variable + proc checkErr(f: File) = - if c_ferror(f) != 0: - c_clearerr(f) - raiseEIO("Unknown IO Error") + when not defined(NimScript): + if c_ferror(f) != 0: + let msg = "errno: " & $errno & " `" & $strerror(errno) & "`" + c_clearerr(f) + raiseEIO(msg) + else: + # shouldn't happen + quit(1) {.push stackTrace:off, profiler:off.} proc readBuffer(f: File, buffer: pointer, len: Natural): int = diff --git a/lib/system/timers.nim b/lib/system/timers.nim index dd8350e2d..4cd2fe840 100644 --- a/lib/system/timers.nim +++ b/lib/system/timers.nim @@ -49,7 +49,7 @@ elif defined(macosx): mach_timebase_info(timeBaseInfo) proc `-`(a, b: Ticks): Nanos = - result = (a.int64 - b.int64) * timeBaseInfo.numer div timeBaseInfo.denom + result = (a.int64 - b.int64) * timeBaseInfo.numer div timeBaseInfo.denom elif defined(posixRealtime): type diff --git a/lib/windows/winlean.nim b/lib/windows/winlean.nim index 3e37b824c..6c480d03a 100644 --- a/lib/windows/winlean.nim +++ b/lib/windows/winlean.nim @@ -516,9 +516,6 @@ type fd_count*: cint # unsigned fd_array*: array[0..FD_SETSIZE-1, SocketHandle] - Timeval* = object - tv_sec*, tv_usec*: int32 - AddrInfo* = object ai_flags*: cint ## Input flags. ai_family*: cint ## Address family of socket. @@ -531,6 +528,14 @@ type SockLen* = cuint +when defined(cpp): + type + Timeval* {.importc: "timeval", header: "<time.h>".} = object + tv_sec*, tv_usec*: int32 +else: + type + Timeval* = object + tv_sec*, tv_usec*: int32 var SOMAXCONN* {.importc, header: "winsock2.h".}: cint diff --git a/lib/wrappers/openssl.nim b/lib/wrappers/openssl.nim index 2f072d5c7..9ee73a44d 100644 --- a/lib/wrappers/openssl.nim +++ b/lib/wrappers/openssl.nim @@ -38,7 +38,11 @@ when useWinVersion: from winlean import SocketHandle else: - const versions = "(.1.1|.38|.39|.41|.43|.44|.45|.46|.10|.1.0.2|.1.0.1|.1.0.0|.0.9.9|.0.9.8|)" + when defined(osx): + # todo: find a better workaround for #10281 (caused by #10230) + const versions = "(.1.1|.38|.39|.41|.43|.44|.45|.46|.10|.1.0.2|.1.0.1|.1.0.0|.0.9.9|.0.9.8|)" + else: + const versions = "(.1.1|.1.0.2|.1.0.1|.1.0.0|.0.9.9|.0.9.8|.46|.45|.44|.43|.41|.39|.38|.10|)" when defined(macosx): const |