Merge branch 'intops' of github.com:status-im/nim-stew into intops

This commit is contained in:
Jacek Sieka 2023-05-24 17:43:17 +02:00
commit 422b1540de
No known key found for this signature in database
GPG Key ID: A1B09461ABB656B8
21 changed files with 332 additions and 126 deletions

View File

@ -26,7 +26,7 @@ jobs:
cpu: amd64
#- os: windows
#cpu: i386
branch: [version-1-2, version-1-4, version-1-6, devel]
branch: [version-1-2, version-1-4, version-1-6, version-2-0, devel]
include:
- target:
os: linux

View File

@ -1,11 +1,9 @@
# stew - status e-something w-something
[![Build Status (Travis)](https://img.shields.io/travis/status-im/nim-stew/master.svg?label=Linux%20/%20macOS "Linux/macOS build status (Travis)")](https://travis-ci.org/status-im/nim-stew)
[![Windows build status (Appveyor)](https://img.shields.io/appveyor/ci/nimbus/nim-stew/master.svg?label=Windows "Windows build status (Appveyor)")](https://ci.appveyor.com/project/nimbus/nim-stew)
[![License: Apache](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0)
[![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT)
![Stability: experimental](https://img.shields.io/badge/stability-experimental-orange.svg)
![Github action](https://github.com/status-im/nim-stew/workflows/nim-stew%20CI/badge.svg)
![Github action](https://github.com/status-im/nim-stew/workflows/CI/badge.svg)
`stew` is collection of utilities, std library extensions and budding libraries
that are frequently used at Status, but are too small to deserve their own

View File

@ -30,7 +30,9 @@ proc run(args, path: string) =
task test, "Run all tests":
build "", "tests/test_helper"
for args in [
"--threads:off",
"--threads:on -d:nimTypeNames",
"--threads:on -d:noIntrinsicsBitOpts -d:noIntrinsicsEndians"
]: run args, "tests/all_tests"
"--threads:off",
"--threads:on -d:nimTypeNames",
"--threads:on -d:noIntrinsicsBitOpts -d:noIntrinsicsEndians"]:
run args, "tests/all_tests"
if (NimMajor, NimMinor) > (1, 6):
run args & " --mm:refc", "tests/all_tests"

View File

@ -15,7 +15,7 @@ func assignImpl[T](tgt: var openArray[T], src: openArray[T]) =
mixin assign
when supportsCopyMem(T):
if tgt.len > 0:
copyMem(addr tgt[0], unsafeAddr src[0], sizeof(tgt[0]) * tgt.len)
moveMem(addr tgt[0], unsafeAddr src[0], sizeof(tgt[0]) * tgt.len)
else:
for i in 0..<tgt.len:
assign(tgt[i], src[i])
@ -52,7 +52,7 @@ func assign*[T](tgt: var T, src: T) =
when sizeof(src) <= sizeof(int):
tgt = src
else:
copyMem(addr tgt, unsafeAddr src, sizeof(tgt))
moveMem(addr tgt, unsafeAddr src, sizeof(tgt))
elif T is object|tuple:
for t, s in fields(tgt, src):
when supportsCopyMem(type s) and sizeof(s) <= sizeof(int) * 2:

View File

@ -170,13 +170,13 @@ proc decode*[T: byte|char](btype: typedesc[Base58C], instr: openArray[T],
result = Base58Status.Incorrect
return
let ch = alphabet.decode[int8(instr[i])]
if ch == -1:
if ch < 0:
outlen = 0
result = Base58Status.Incorrect
return
var c = cast[uint32](ch)
var c = uint32(ch)
for j in countdown(size - 1, 0):
let t = cast[uint64](buffer[j]) * 58 + c
let t = uint64(buffer[j]) * 58 + c
c = cast[uint32]((t and 0x3F_0000_0000'u64) shr 32)
buffer[j] = cast[uint32](t and 0xFFFF_FFFF'u32)
if c != 0:

View File

@ -82,7 +82,7 @@ func log2truncNim(x: uint8|uint16|uint32): int =
v = v or v shr 4
v = v or v shr 8
v = v or v shr 16
cast[int](lookup[uint32(v * 0x07C4ACDD'u32) shr 27])
int(lookup[uint32(v * 0x07C4ACDD'u32) shr 27])
func log2truncNim(x: uint64): int =
## Quickly find the log base 2 of a 64-bit integer.
@ -169,9 +169,9 @@ when (defined(gcc) or defined(llvm_gcc) or defined(clang)) and useBuiltins:
cast[int](builtin_ffs(cast[cint](x.cuint)))
func log2truncBuiltin(v: uint8|uint16|uint32): int =
cast[int](31 - cast[cuint](builtin_clz(v.uint32)))
int(31 - cast[cuint](builtin_clz(v.uint32)))
func log2truncBuiltin(v: uint64): int =
cast[int](63 - cast[cuint](builtin_clzll(v)))
int(63 - cast[cuint](builtin_clzll(v)))
elif defined(vcc) and useBuiltins:
const arch64 = sizeof(int) == 8

View File

@ -18,14 +18,16 @@ export arrayops.`&`, arrayops.initArrayWith, arrayops.`[]=`
when (NimMajor, NimMinor) < (1, 4):
{.push raises: [Defect].}
{.pragma: hexRaises, raises: [Defect, ValueError].}
else:
{.push raises: [].}
{.pragma: hexRaises, raises: [ValueError].}
########################################################################################################
##################################### Hex utilities ################################################
proc readHexChar*(c: char): byte
{.raises: [ValueError, Defect], noSideEffect, inline.} =
{.hexRaises, noSideEffect, inline.} =
## Converts an hex char to a byte
case c
of '0'..'9': result = byte(ord(c) - ord('0'))
@ -42,7 +44,7 @@ template skip0xPrefix(hexStr: openArray[char]): int =
func hexToByteArrayImpl(
hexStr: openArray[char], output: var openArray[byte], fromIdx, toIdx: int):
int {.raises: [ValueError, Defect].} =
int {.hexRaises.} =
var sIdx = skip0xPrefix(hexStr)
# Fun with closed intervals
doAssert fromIdx >= 0 and
@ -65,7 +67,7 @@ func hexToByteArrayImpl(
func hexToByteArray*(
hexStr: openArray[char], output: var openArray[byte], fromIdx, toIdx: int)
{.raises: [ValueError, Defect].} =
{.hexRaises.} =
## Read hex-encoded data from `hexStr[mapHex(fromIdx..toIdx)]` and store
## corresponding bytes in `output[fromIdx..toIdx]` where `mapHex` takes into
## account stripped characters.
@ -80,7 +82,7 @@ func hexToByteArray*(
discard hexToByteArrayImpl(hexStr, output, fromIdx, toIdx)
func hexToByteArray*(hexStr: openArray[char], output: var openArray[byte])
{.raises: [ValueError, Defect].} =
{.hexRaises.} =
## Read hex-encoded data from `hexStr` and store corresponding bytes in
## `output`.
##
@ -92,7 +94,7 @@ func hexToByteArray*(hexStr: openArray[char], output: var openArray[byte])
hexToByteArray(hexStr, output, 0, output.high)
func hexToByteArray*[N: static[int]](hexStr: openArray[char]): array[N, byte]
{.raises: [ValueError, Defect], noinit.}=
{.hexRaises, noinit.}=
## Read hex-encoded data from `hexStr` returning an array of N bytes.
##
## * `0x`/`0X` is stripped if present
@ -103,7 +105,7 @@ func hexToByteArray*[N: static[int]](hexStr: openArray[char]): array[N, byte]
hexToByteArray(hexStr, result)
func hexToByteArray*(hexStr: openArray[char], N: static int): array[N, byte]
{.raises: [ValueError, Defect], noinit.}=
{.hexRaises, noinit.}=
## Read hex-encoded data from `hexStr` returning an array of N bytes.
##
## * `0x`/`0X` is stripped if present
@ -114,7 +116,7 @@ func hexToByteArray*(hexStr: openArray[char], N: static int): array[N, byte]
hexToByteArray(hexStr, result)
func hexToByteArrayStrict*(hexStr: openArray[char], output: var openArray[byte])
{.raises: [ValueError, Defect].} =
{.hexRaises.} =
## Read hex-encoded data from `hexStr` and store corresponding bytes in
## `output`.
##
@ -126,7 +128,7 @@ func hexToByteArrayStrict*(hexStr: openArray[char], output: var openArray[byte])
raise (ref ValueError)(msg: "hex string too long")
func hexToByteArrayStrict*[N: static[int]](hexStr: openArray[char]): array[N, byte]
{.raises: [ValueError, Defect], noinit, inline.}=
{.hexRaises, noinit, inline.}=
## Read hex-encoded data from `hexStr` and store corresponding bytes in
## `output`.
##
@ -137,7 +139,7 @@ func hexToByteArrayStrict*[N: static[int]](hexStr: openArray[char]): array[N, by
hexToByteArrayStrict(hexStr, result)
func hexToByteArrayStrict*(hexStr: openArray[char], N: static int): array[N, byte]
{.raises: [ValueError, Defect], noinit, inline.}=
{.hexRaises, noinit, inline.}=
## Read hex-encoded data from `hexStr` and store corresponding bytes in
## `output`.
##
@ -148,7 +150,7 @@ func hexToByteArrayStrict*(hexStr: openArray[char], N: static int): array[N, byt
hexToByteArrayStrict(hexStr, result)
func fromHex*[N](A: type array[N, byte], hexStr: string): A
{.raises: [ValueError, Defect], noinit, inline.}=
{.hexRaises, noinit, inline.}=
## Read hex-encoded data from `hexStr` returning an array of N bytes.
##
## * `0x`/`0X` is stripped if present
@ -159,7 +161,7 @@ func fromHex*[N](A: type array[N, byte], hexStr: string): A
hexToByteArray(hexStr, result)
func hexToPaddedByteArray*[N: static[int]](hexStr: string): array[N, byte]
{.raises: [ValueError, Defect].} =
{.hexRaises.} =
## Read a hex string and store it in a byte array `output`.
## The string may be shorter than the byte array.
## No "endianness" reordering is done.
@ -188,7 +190,7 @@ func hexToPaddedByteArray*[N: static[int]](hexStr: string): array[N, byte]
bIdx += shift shr 2
func hexToSeqByte*(hexStr: string): seq[byte]
{.raises: [ValueError, Defect].} =
{.hexRaises.} =
## Read an hex string and store it in a sequence of bytes. No "endianness" reordering is done.
if (hexStr.len and 1) == 1:
raise (ref ValueError)(msg: "hex string must have even length")

View File

@ -19,9 +19,13 @@ elif sizeof(int) == 4:
type
AnyItem* = byte|char|int8|uint16|int16|uint32|int32|uint|int
when (NimMajor, NimMinor) < (1, 4):
{.push raises: [Defect].}
else:
{.push raises: [].}
proc isEqual*[A: AnyItem, B: AnyItem](c: typedesc[CT], a: openArray[A],
b: openArray[B]): bool {.
raises: [Defect] .} =
b: openArray[B]): bool =
## Perform constant time comparison of two arrays ``a`` and ``b``.
##
## Please note that it only makes sense to compare arrays of the same length.

View File

@ -112,7 +112,7 @@ import
"."/[results, sorted_set]
when (NimMajor, NimMinor) < (1, 4):
{.push raises: [Defect].}
{.push raises: [Defect, CatchableError].}
else:
{.push raises: [].}

View File

@ -325,13 +325,17 @@ proc shift*[K,V](rq: var KeyedQueue[K,V]): Result[KeyedQueuePair[K,V],void] =
##
## Using the notation introduced with `rq.append` and `rq.prepend`, the
## item returned and deleted is the *left-most* item.
type T = KeyedQueuePair[K,V]
if 0 < rq.tab.len:
noKeyError("shift"):
let kvp = KeyedQueuePair[K,V](
key: rq.kFirst,
data: rq.tab[rq.kFirst].data)
rq.shiftImpl
return ok(KeyedQueuePair[K,V](kvp))
when kvp is T:
return ok(kvp)
else:
return ok(T(kvp))
err()
proc shiftKey*[K,V](rq: var KeyedQueue[K,V]): Result[K,void] =
@ -355,13 +359,17 @@ proc pop*[K,V](rq: var KeyedQueue[K,V]): Result[KeyedQueuePair[K,V],void] =
##
## Using the notation introduced with `rq.append` and `rq.prepend`, the
## item returned and deleted is the *right-most* item.
type T = KeyedQueuePair[K,V]
if 0 < rq.tab.len:
noKeyError("pop"):
let kvp = KeyedQueuePair[K,V](
key: rq.kLast,
data: rq.tab[rq.kLast].data)
rq.popImpl
return ok(KeyedQueuePair[K,V](kvp))
when kvp is T:
return ok(kvp)
else:
return ok(T(kvp))
err()
proc popKey*[K,V](rq: var KeyedQueue[K,V]): Result[K,void] =
@ -450,7 +458,7 @@ proc eq*[K,V](rq: var KeyedQueue[K,V]; key: K): Result[V,void] =
return ok(rq.tab[key].data)
proc `[]`*[K,V](rq: var KeyedQueue[K,V]; key: K): V
{.gcsafe,raises: [Defect,KeyError].} =
{.gcsafe,raises: [KeyError].} =
## This function provides a simplified version of the `eq()` function with
## table semantics. Note that this finction throws a `KeyError` exception
## unless the argument `key` exists in the queue.

View File

@ -31,6 +31,11 @@ type
kQVfyPrvNxtExpected
kQVfyFirstExpected
when (NimMajor, NimMinor) < (1, 4):
{.push raises: [Defect].}
else:
{.push raises: [].}
# ------------------------------------------------------------------------------
# Public functions, debugging
# ------------------------------------------------------------------------------
@ -50,7 +55,7 @@ proc `$`*[K,V](item: KeyedQueueItem[K,V]): string =
"(" & $item.value & ", link[" & $item.prv & "," & $item.kNxt & "])"
proc verify*[K,V](rq: var KeyedQueue[K,V]): Result[void,(K,V,KeyedQueueInfo)]
{.gcsafe,raises: [Defect,KeyError].} =
{.gcsafe,raises: [KeyError].} =
## Check for consistency. Returns an error unless the argument
## queue `rq` is consistent.
let tabLen = rq.tab.len

View File

@ -104,7 +104,7 @@ func checkedEnumAssign*[E: enum, I: SomeInteger](res: var E, value: I): bool =
if value notin E:
return false
res = E value
res = cast[E](value)
return true
func isZeroMemory*[T](x: T): bool =

View File

@ -445,7 +445,7 @@ template isOk*(self: Result): bool = self.oResultPrivate
template isErr*(self: Result): bool = not self.oResultPrivate
when not defined(nimHasEffectsOfs):
template effectsOf(f: untyped) {.pragma.}
template effectsOf(f: untyped) {.pragma, used.}
func map*[T0, E, T1](
self: Result[T0, E], f: proc(x: T0): T1):
@ -845,15 +845,85 @@ template unsafeError*[T](self: Result[T, void]) =
## See also: `unsafeGet`
assert not self.oResultPrivate # Emulate field access defect in debug builds
func optValue*[T, E](self: Result[T, E]): Opt[T] =
## Return the value of a Result as an Opt, or none if Result is an error
if self.oResultPrivate:
Opt.some(self.vResultPrivate)
else:
Opt.none(T)
func optError*[T, E](self: Result[T, E]): Opt[E] =
## Return the error of a Result as an Opt, or none if Result is a value
if self.oResultPrivate:
Opt.none(E)
else:
Opt.some(self.eResultPrivate)
# Alternative spellings for get
template value*[T, E](self: Result[T, E]): T = self.get()
template value*[T: not void, E](self: var Result[T, E]): var T = self.get()
template isOkOr*[T, E](self: Result[T, E], body: untyped) =
## Evaluate `body` iff result has been assigned an error
## `body` is evaluated lazily.
##
## Example:
## ```
## let
## v = Result[int, string].err("hello")
## x = v.isOkOr: echo "not ok"
## # experimental: direct error access using an unqualified `error` symbol
## z = v.isOkOr: echo error
## ```
##
## `error` access:
##
## TODO experimental, might change in the future
##
## The template contains a shortcut for accessing the error of the result,
## it can only be used outside of generic code,
## see https://github.com/status-im/nim-stew/issues/161#issuecomment-1397121386
let s = (self) # TODO avoid copy
if not s.oResultPrivate:
when E isnot void:
template error: E {.used, inject.} = s.eResultPrivate
body
template isErrOr*[T, E](self: Result[T, E], body: untyped) =
## Evaluate `body` iff result has been assigned a value
## `body` is evaluated lazily.
##
## Example:
## ```
## let
## v = Result[int, string].err("hello")
## x = v.isOkOr: echo "not ok"
## # experimental: direct error access using an unqualified `error` symbol
## z = v.isOkOr: echo error
## ```
##
## `value` access:
##
## TODO experimental, might change in the future
##
## The template contains a shortcut for accessing the value of the result,
## it can only be used outside of generic code,
## see https://github.com/status-im/nim-stew/issues/161#issuecomment-1397121386
let s = (self) # TODO avoid copy
if s.oResultPrivate:
when T isnot void:
template value: T {.used, inject.} = s.vResultPrivate
body
template valueOr*[T: not void, E](self: Result[T, E], def: untyped): T =
## Fetch value of result if set, or evaluate `def`
## `def` is evaluated lazily, and must be an expression of `T` or exit
## the scope (for example using `return` / `raise`)
##
## See `isOkOr` for a version that works with `Result[void, E]`.
##
## Example:
## ```
## let
@ -869,11 +939,12 @@ template valueOr*[T: not void, E](self: Result[T, E], def: untyped): T =
## TODO experimental, might change in the future
##
## The template contains a shortcut for accessing the error of the result,
## without specifying the error - it can only be used outside of generic code,
## it can only be used outside of generic code,
## see https://github.com/status-im/nim-stew/issues/161#issuecomment-1397121386
##
let s = (self) # TODO avoid copy
if s.oResultPrivate: s.vResultPrivate
if s.oResultPrivate:
s.vResultPrivate
else:
when E isnot void:
template error: E {.used, inject.} = s.eResultPrivate
@ -883,8 +954,11 @@ template errorOr*[T, E: not void](self: Result[T, E], def: untyped): E =
## Fetch error of result if not set, or evaluate `def`
## `def` is evaluated lazily, and must be an expression of `T` or exit
## the scope (for example using `return` / `raise`)
##
## See `isErrOr` for a version that works with `Result[T, void]`.
let s = (self) # TODO avoid copy
if not s.oResultPrivate: s.eResultPrivate
if not s.oResultPrivate:
s.eResultPrivate
else:
when T isnot void:
template value: T {.used, inject.} = s.vResultPrivate

View File

@ -1,55 +0,0 @@
# From: https://github.com/nim-lang/Nim/pull/11067/
proc parseHex*[T: SomeInteger](s: string, number: var T, start = 0, maxLen = 0): int
{.inline, noSideEffect.} =
## Parses a hexadecimal number and stores its value in ``number``.
##
## Returns the number of the parsed characters or 0 in case of an error.
## If error, the value of ``number`` is not changed.
##
## If ``maxLen == 0``, the parsing continues until the first non-hex character
## or to the end of the string. Otherwise, no more than ``maxLen`` characters
## are parsed starting from the ``start`` position.
##
## It does not check for overflow. If the value represented by the string is
## too big to fit into ``number``, only the value of last fitting characters
## will be stored in ``number`` without producing an error.
runnableExamples:
var num: int
doAssert parseHex("4E_69_ED", num) == 8
doAssert num == 5138925
doAssert parseHex("X", num) == 0
doAssert parseHex("#ABC", num) == 4
var num8: int8
doAssert parseHex("0x_4E_69_ED", num8) == 11
doAssert num8 == 0xED'i8
doAssert parseHex("0x_4E_69_ED", num8, 3, 2) == 2
doAssert num8 == 0x4E'i8
var num8u: uint8
doAssert parseHex("0x_4E_69_ED", num8u) == 11
doAssert num8u == 237
var num64: int64
doAssert parseHex("4E69ED4E69ED", num64) == 12
doAssert num64 == 86216859871725
var i = start
var output = T(0)
var foundDigit = false
let last = min(s.len, if maxLen == 0: s.len else: i + maxLen)
if i + 1 < last and s[i] == '0' and (s[i+1] in {'x', 'X'}): inc(i, 2)
elif i < last and s[i] == '#': inc(i)
while i < last:
case s[i]
of '_': discard
of '0'..'9':
output = output shl 4 or T(ord(s[i]) - ord('0'))
foundDigit = true
of 'a'..'f':
output = output shl 4 or T(ord(s[i]) - ord('a') + 10)
foundDigit = true
of 'A'..'F':
output = output shl 4 or T(ord(s[i]) - ord('A') + 10)
foundDigit = true
else: break
inc(i)
if foundDigit:
number = output
result = i - start

127
stew/shims/parseutils.nim Normal file
View File

@ -0,0 +1,127 @@
# From: https://github.com/nim-lang/Nim/pull/11067/
proc parseHex*[T: SomeInteger](s: string, number: var T, start = 0, maxLen = 0): int
{.inline, noSideEffect.} =
## Parses a hexadecimal number and stores its value in ``number``.
##
## Returns the number of the parsed characters or 0 in case of an error.
## If error, the value of ``number`` is not changed.
##
## If ``maxLen == 0``, the parsing continues until the first non-hex character
## or to the end of the string. Otherwise, no more than ``maxLen`` characters
## are parsed starting from the ``start`` position.
##
## It does not check for overflow. If the value represented by the string is
## too big to fit into ``number``, only the value of last fitting characters
## will be stored in ``number`` without producing an error.
runnableExamples:
var num: int
doAssert parseHex("4E_69_ED", num) == 8
doAssert num == 5138925
doAssert parseHex("X", num) == 0
doAssert parseHex("#ABC", num) == 4
var num8: int8
doAssert parseHex("0x_4E_69_ED", num8) == 11
doAssert num8 == 0xED'i8
doAssert parseHex("0x_4E_69_ED", num8, 3, 2) == 2
doAssert num8 == 0x4E'i8
var num8u: uint8
doAssert parseHex("0x_4E_69_ED", num8u) == 11
doAssert num8u == 237
var num64: int64
doAssert parseHex("4E69ED4E69ED", num64) == 12
doAssert num64 == 86216859871725
var i = start
var output = T(0)
var foundDigit = false
let last = min(s.len, if maxLen == 0: s.len else: i + maxLen)
if i + 1 < last and s[i] == '0' and (s[i+1] in {'x', 'X'}): inc(i, 2)
elif i < last and s[i] == '#': inc(i)
while i < last:
case s[i]
of '_': discard
of '0'..'9':
output = output shl 4 or T(ord(s[i]) - ord('0'))
foundDigit = true
of 'a'..'f':
output = output shl 4 or T(ord(s[i]) - ord('a') + 10)
foundDigit = true
of 'A'..'F':
output = output shl 4 or T(ord(s[i]) - ord('A') + 10)
foundDigit = true
else: break
inc(i)
if foundDigit:
number = output
result = i - start
import std/parseutils; export parseutils
#From https://github.com/nim-lang/Nim/pull/21349 in -devel and in version-1-6:
# https://github.com/nim-lang/Nim/commit/c546ba5d23bb2e7bc562a071c88efd94cca7b89e
# https://github.com/nim-lang/Nim/commit/fca6a0bd6a6d3b9a25d1272e29bc39e88853188e
when not declared(parseSize): # Odd code formatting to minimize diff v. mainLine
const Whitespace = {' ', '\t', '\v', '\r', '\l', '\f'}
func toLowerAscii(c: char): char =
if c in {'A'..'Z'}: char(uint8(c) xor 0b0010_0000'u8) else: c
func parseSize*(s: string, size: var int64, alwaysBin=false): int =
## Parse a size qualified by binary or metric units into `size`. This format
## is often called "human readable". Result is the number of processed chars
## or 0 on parse errors and size is rounded to the nearest integer. Trailing
## garbage like "/s" in "1k/s" is allowed and detected by `result < s.len`.
##
## To simplify use, following non-rare wild conventions, and since fractional
## data like milli-bytes is so rare, unit matching is case-insensitive but for
## the 'i' distinguishing binary-metric from metric (which cannot be 'I').
##
## An optional trailing 'B|b' is ignored but processed. I.e., you must still
## know if units are bytes | bits or infer this fact via the case of s[^1] (if
## users can even be relied upon to use 'B' for byte and 'b' for bit or have
## that be s[^1]).
##
## If `alwaysBin==true` then scales are always binary-metric, but e.g. "KiB"
## is still accepted for clarity. If the value would exceed the range of
## `int64`, `size` saturates to `int64.high`. Supported metric prefix chars
## include k, m, g, t, p, e, z, y (but z & y saturate unless the number is a
## small fraction).
##
## **See also:**
## * https://en.wikipedia.org/wiki/Binary_prefix
## * `formatSize module<strutils.html>`_ for formatting
runnableExamples:
var res: int64 # caller must still know if 'b' refers to bytes|bits
doAssert parseSize("10.5 MB", res) == 7
doAssert res == 10_500_000 # decimal metric Mega prefix
doAssert parseSize("64 mib", res) == 6
doAssert res == 67108864 # 64 shl 20
doAssert parseSize("1G/h", res, true) == 2 # '/' stops parse
doAssert res == 1073741824 # 1 shl 30, forced binary metric
const prefix = "b" & "kmgtpezy" # byte|bit & lowCase metric-ish prefixes
const scaleM = [1.0, 1e3, 1e6, 1e9, 1e12, 1e15, 1e18, 1e21, 1e24] # 10^(3*idx)
const scaleB = [1.0, 1024, 1048576, 1073741824, 1099511627776.0, # 2^(10*idx)
1125899906842624.0, 1152921504606846976.0, # ldexp?
1.180591620717411303424e21, 1.208925819614629174706176e24]
var number: float
var scale = 1.0
result = parseFloat(s, number)
if number < 0: # While parseFloat accepts negatives ..
result = 0 #.. we do not since sizes cannot be < 0
if result > 0:
let start = result # Save spot to maybe unwind white to EOS
while result < s.len and s[result] in Whitespace:
inc result
if result < s.len: # Illegal starting char => unity
if (let si = prefix.find(s[result].toLowerAscii); si >= 0):
inc result # Now parse the scale
scale = if alwaysBin: scaleB[si] else: scaleM[si]
if result < s.len and s[result] == 'i':
scale = scaleB[si] # Switch from default to binary-metric
inc result
if result < s.len and s[result].toLowerAscii == 'b':
inc result # Skip optional '[bB]'
else: # Unwind result advancement when there..
result = start #..is no unit to the end of `s`.
var sizeF = number * scale + 0.5 # Saturate to int64.high when too big
size = if sizeF > 9223372036854774784.0: int64.high else: sizeF.int64
# Above constant=2^63-1024 avoids C UB; github.com/nim-lang/Nim/issues/20102 or
# stackoverflow.com/questions/20923556/math-pow2-63-1-math-pow2-63-512-is-true

View File

@ -21,3 +21,5 @@ when (NimMajor, NimMinor) < (1, 4):
RangeDefect* = RangeError
ReraiseDefect* = ReraiseError
StackOverflowDefect* = StackOverflowError
else:
{.used.}

View File

@ -88,7 +88,7 @@ type
RbResult[SortedSetItemRef[K,V]]
when (NimMajor, NimMinor) < (1, 4):
{.push raises: [Defect].}
{.push raises: [Defect,CatchableError].}
else:
{.push raises: [].}
@ -301,7 +301,7 @@ proc `$`*[K,V](rc: SortedSetResult[K,V]): string =
proc verify*[K,V](sl: var SortedSet[K,V]):
Result[void,(SortedSetItemRef[K,V],RbInfo)]
{.gcsafe, raises: [Defect,CatchableError].} =
{.gcsafe.} =
## Checks for consistency, may print an error message. Returns `rbOk` if
## the argument list `sl` is consistent. This function traverses all the
## internal data nodes which might be time consuming. So it would not be

View File

@ -18,11 +18,20 @@ type
## for the equivalent of `a < b`
proc(a, b: C): bool {.gcsafe.}
RbPrnFn* = ##\
## Handle error message
proc(code: RbInfo; ctxInfo: string)
{.gcsafe, raises: [Defect,CatchableError].}
when (NimMajor, NimMinor) < (1, 4):
type
RbPrnFn* = ##\
## Handle error message
proc(code: RbInfo; ctxInfo: string)
{.gcsafe, raises: [Defect,CatchableError].}
else:
type
RbPrnFn* = ##\
## Handle error message
proc(code: RbInfo; ctxInfo: string)
{.gcsafe, raises: [].}
type
RbDdebug[C,K] = object
tree: RbTreeRef[C,K] ## tree, not-Nil
node: RbNodeRef[C] ## current node
@ -34,7 +43,7 @@ type
msg: string ## collect data
when (NimMajor, NimMinor) < (1, 4):
{.push raises: [Defect].}
{.push raises: [Defect, CatchableError].}
else:
{.push raises: [].}
@ -52,8 +61,7 @@ proc pp[C](n: RbNodeRef[C]): string =
result &= "~black"
proc doError[C,K](d: var RbDdebug[C,K]; t: RbInfo; s: string):
Result[void,(C,RbInfo)]
{.gcsafe, raises: [Defect,CatchableError].} =
Result[void,(C,RbInfo)] {.gcsafe.} =
if not d.pr.isNil:
var msg = s &
": <" & d.node.pp &
@ -63,44 +71,44 @@ proc doError[C,K](d: var RbDdebug[C,K]; t: RbInfo; s: string):
err((d.node.casket,t))
proc rootIsRed[C,K](d: var RbDdebug[C,K]): Result[void,(C,RbInfo)]
{.gcsafe, raises: [Defect,CatchableError].} =
{.gcsafe.} =
d.doError(rbVfyRootIsRed, "Root node is red")
proc redNodeRedLinkLeft[C,K](d: var RbDdebug[C,K]): Result[void,(C,RbInfo)]
{.gcsafe, raises: [Defect,CatchableError].} =
{.gcsafe.} =
d.doError(rbVfyRedParentRedLeftLink, "Parent node and left link red")
proc redNodeRedLinkRight[C,K](d: var RbDdebug[C,K]): Result[void,(C,RbInfo)]
{.gcsafe, raises: [Defect,CatchableError].} =
{.gcsafe.} =
d.doError(rbVfyRedParentRedRightLink, "Parent node and right link red")
proc redNodeRedLinkBoth[C,K](d: var RbDdebug[C,K]): Result[void,(C,RbInfo)]
{.gcsafe, raises: [Defect,CatchableError].} =
{.gcsafe.} =
d.doError(rbVfyRedParentRedBothLinks, "Parent node and both links red")
proc linkLeftCompError[C,K](d: var RbDdebug[C,K]): Result[void,(C,RbInfo)]
{.gcsafe, raises: [Defect,CatchableError].} =
{.gcsafe.} =
d.doError(rbVfyLeftLinkGtParent, "Left node greater than parent")
proc linkRightCompError[C,K](d: var RbDdebug[C,K]): Result[void,(C,RbInfo)]
{.gcsafe, raises: [Defect,CatchableError].} =
{.gcsafe.} =
d.doError(rbVfyRightLinkLtParent, "Right node greater than parent")
proc linkBothCompError[C,K](d: var RbDdebug[C,K]): Result[void,(C,RbInfo)]
{.gcsafe, raises: [Defect,CatchableError].} =
{.gcsafe.} =
d.doError(rbVfyBothLinkCmpParentReversed,
"Left node greater than parent greater than right node")
proc blackChainLevelError[C,K](d: var RbDdebug[C,K]): Result[void,(C,RbInfo)]
{.gcsafe, raises: [Defect,CatchableError].} =
{.gcsafe.} =
d.doError(rbVfyBlackChainLevelMismatch,
"Inconsistent length of black node chains")
proc subTreeVerify[C,K](d: var RbDdebug[C,K]): Result[void,(C,RbInfo)]
{.gcsafe, raises: [Defect,CatchableError].} =
{.gcsafe.} =
let node = d.node
doAssert not node.isNil
@ -168,7 +176,7 @@ proc subTreeVerify[C,K](d: var RbDdebug[C,K]): Result[void,(C,RbInfo)]
proc rbTreeVerify*[C,K](rbt: RbTreeRef[C,K];
lt: RbLtFn[C] = nil; pr: RbPrnFn = nil):
Result[void,(C,RbInfo)]
{.gcsafe, raises: [Defect,CatchableError].} =
{.gcsafe.} =
## Verifies the argument tree `rbt` for
## * No consecutively linked red nodes down the tree
## * Link consisteny: value(leftLink) < value(node) < value(rightLink). This

View File

@ -9,6 +9,11 @@
import unittest2
import ../stew/ctops
when (NimMajor, NimMinor) < (1, 6):
type DefectEx = AssertionError
else:
type DefectEx = AssertionDefect
suite "Constant-time operations test suite":
test "isEqual() test":
let
@ -97,17 +102,17 @@ suite "Constant-time operations test suite":
CT.isEqual(int64Arr, uint64Arr) == true
# Empty arrays
expect(AssertionError):
expect(DefectEx):
discard CT.isEqual(emptyArray, emptyArray)
expect(AssertionError):
expect(DefectEx):
discard CT.isEqual(emptyArray, emptyString)
expect(AssertionError):
expect(DefectEx):
discard CT.isEqual(emptyArray, emptySeq)
# Arrays, where T is different type size
expect(AssertionError):
expect(DefectEx):
discard CT.isEqual(int8Arr, int16Arr)
expect(AssertionError):
expect(DefectEx):
discard CT.isEqual(int16Arr, int32Arr)
expect(AssertionError):
expect(DefectEx):
discard CT.isEqual(int8Arr, intArr)

View File

@ -17,8 +17,6 @@ import
../stew/keyed_queue/kq_debug
const
usedStrutils = newSeq[string]().join(" ")
lruCacheLimit = 10
lruCacheModulo = 13
@ -50,7 +48,7 @@ let
proc `$`(rc: KeyedQueuePair[uint,uint]): string =
"(" & $rc.key & "," & $rc.data & ")"
proc `$`(rc: Result[KeyedQueuePair[uint,uint],void]): string =
proc `$`(rc: Result[KeyedQueuePair[uint,uint],void]): string {.used.} =
result = "<"
if rc.isOk:
result &= $rc.value.key & "," & $rc.value.data
@ -136,7 +134,7 @@ proc compileGenericFunctions(rq: var KUQueue) =
rq[0] = 0 # so `rq[0]` works
discard rq[0]
let ignoreValues = (
let ignoreValues {.used.} = (
(rq.append(0,0), rq.push(0,0),
rq.replace(0,0),
rq.prepend(0,0), rq.unshift(0,0),
@ -533,10 +531,13 @@ suite "KeyedQueue: Data queue as LRU cache":
c1 = keyList.toLruCache
sq = toSeq(c1.q.nextPairs).mapIt(it.key.fromKey)
s0 = sq
inx = 5
key = sq[5].toKey
sq.delete(5,5) # delete index 5 in sequence
when (NimMajor, NimMinor) >= (1, 6):
sq.delete(5..5) # delete index 5 in sequence
else:
sq.delete(5, 5) # delete index 5 in sequence
noisy.say &"sq: {s0} <off sq[5]({key})> {sq}"
check c1.q.delete(key).value.key == key

View File

@ -70,6 +70,15 @@ block:
doAssert rOk.get() == rOk.unsafeGet()
rOk.isOkOr: raiseAssert "should not end up in here"
rErr.isErrOr: raiseAssert "should not end up in here"
rErr.isOkOr:
doAssert error == rErr.error()
rOk.isErrOr:
doAssert value == rOk.value()
doAssert rOk.valueOr(failFast()) == rOk.value()
let rErrV = rErr.valueOr:
error.len
@ -88,12 +97,18 @@ block:
doAssert c.isErr
# De-reference
when (NimMajor, NimMinor) >= (1, 6):
{.warning[BareExcept]:off.}
try:
echo rErr[]
doAssert false
except:
discard
when (NimMajor, NimMinor) >= (1, 6):
{.warning[BareExcept]:on.}
# Comparisons
doAssert (rOk == rOk)
doAssert (rErr == rErr)
@ -144,6 +159,12 @@ block:
# Expectations
doAssert rOk.expect("testOk never fails") == 42
# Conversions to Opt
doAssert rOk.optValue() == Opt.some(rOk.get())
doAssert rOk.optError().isNone()
doAssert rErr.optValue().isNone()
doAssert rErr.optError() == Opt.some(rErr.error())
# Question mark operator
func testQn(): Result[int, string] =
let x = ?works() - ?works()
@ -356,6 +377,10 @@ block: # Result[T, void] aka `Opt`
doAssert Opt.some(42).get() == 42
doAssert Opt.none(int).isNone()
# Construct Result from Opt
doAssert oOk.orErr("error").value() == oOk.get()
doAssert oErr.orErr("error").error() == "error"
block: # `cstring` dangling reference protection
type CSRes = Result[void, cstring]