add ranges

This commit is contained in:
Jacek Sieka 2019-07-06 20:45:27 +02:00
parent c95cee45bc
commit 904f84c8e0
No known key found for this signature in database
GPG Key ID: A1B09461ABB656B8
12 changed files with 1088 additions and 2 deletions

View File

@ -22,7 +22,7 @@ broken out into separate repositories.
follow the following layout - if you've used C++'s `boost`, you'll feel right at
home:
```
```bash
# Single-module libraries
stew/small.nim # small libraries that fits in one module
@ -34,7 +34,7 @@ stew/libname/stuff.nim # Detail import file
# support for multiple nim versions - code in here typically has been taken
# from nim `devel` branch and `name` will reexport the corresponding std lib
# module
# stew/shims/macros.nim - module that reexports `macros.nim` adding code from newer nim versions
stew/shims/macros.nim # module that reexports `macros.nim` adding code from newer nim versions
# Tests are in the tests folder (duh!)
# To execute, run either `all_tests.nim` or specific `test_xxx.nim` files:
@ -48,6 +48,17 @@ for different Nim versions, such that code using `stew` works well with multiple
versions of Nim. If `stew` is not working with the Nim version you're using, we
welcome patches.
You can create multiple versions of your code using the following pattern:
```nim
when (NimMajor,NimMinor,NimPatch) >= (0,19,9):
discard
elif (NimMajor,NimMinor,NimPatch) >= (0,19,0):
discard
else
{.fatal: "unsupported nim version"}
```
## Notable libraries
Libraries are documented either in-module or on a separate README in their
@ -55,6 +66,7 @@ respective folders
- `bitops2` - an updated version of `bitops.nim`, filling in gaps in original code
- `byteutils` - utilities that make working with the Nim `byte` type convenient
- `ranges` - utility functions for working with parts and blobs of memory
- `shims` - backports of nim `devel` code to the stable version that Status is using
## Using stew in your project
@ -64,6 +76,13 @@ are no versioned releases and we will not maintain API/ABI stability. Instead,
make sure you pin your dependency to a specific git hash (for example using a
submodule) or copy the file to your project instead.
Typically, you will import either a top-level library or drill down into its
submodules:
```nim
import stew/bitops2
import stew/ranges/ptr_arith
```
:warning: No API/ABI stability - pick a commit and stick with it :warning:
## Contributing to stew

6
stew/ranges.nim Normal file
View File

@ -0,0 +1,6 @@
import
ranges/memranges,
ranges/typedranges
export
memranges, typedranges

230
stew/ranges/bitranges.nim Normal file
View File

@ -0,0 +1,230 @@
import
typedranges, ptr_arith
type
BitRange* = object
data: MutByteRange
start: int
mLen: int
BitIndexable = SomeUnsignedInt
template `@`(s, idx: untyped): untyped =
(when idx is BackwardsIndex: s.len - int(idx) else: int(idx))
proc bits*(a: MutByteRange, start, len: int): BitRange =
doAssert start <= len
doAssert len <= 8 * a.len
result.data = a
result.start = start
result.mLen = len
template bits*(a: var seq[byte], start, len: int): BitRange =
bits(a.toRange, start, len)
template bits*(a: MutByteRange): BitRange =
bits(a, 0, a.len * 8)
template bits*(a: var seq[byte]): BitRange =
bits(a.toRange, 0, a.len * 8)
template bits*(a: MutByteRange, len: int): BitRange =
bits(a, 0, len)
template bits*(a: var seq[byte], len: int): BitRange =
bits(a.toRange, 0, len)
template bits*(bytes: MutByteRange, slice: HSlice): BitRange =
bits(bytes, bytes @ slice.a, bytes @ slice.b)
template bits*(x: BitRange): BitRange = x
template mostSignificantBit(T: typedesc): auto =
const res = 1 shl (sizeof(T) * 8 - 1)
T(res)
template getBit*(x: BitIndexable, bit: Natural): bool =
## reads a bit from `x`, assuming 0 to be the position of the
## most significant bit
(x and mostSignificantBit(x.type) shr bit) != 0
template getBitLE*(x: BitIndexable, bit: Natural): bool =
## reads a bit from `x`, assuming 0 to be the position of the
## least significant bit
type T = type(x)
(x and T(0b1 shl bit)) != 0
proc setBit*(x: var BitIndexable, bit: Natural, val: bool) =
## writes a bit in `x`, assuming 0 to be the position of the
## most significant bit
let mask = mostSignificantBit(x.type) shr bit
if val:
x = x or mask
else:
x = x and not mask
proc setBitLE*(x: var BitIndexable, bit: Natural, val: bool) =
## writes a bit in `x`, assuming 0 to be the position of the
## least significant bit
type T = type(x)
let mask = 0b1 shl bit
if val:
x = x or mask
else:
x = x and not mask
proc raiseBit*(x: var BitIndexable, bit: Natural) =
## raises a bit in `x`, assuming 0 to be the position of the
## most significant bit
type T = type(x)
let mask = mostSignificantBit(x.type) shr bit
x = x or mask
proc lowerBit*(x: var BitIndexable, bit: Natural) =
## raises a bit in `x`, assuming 0 to be the position of the
## most significant bit
type T = type(x)
let mask = mostSignificantBit(x.type) shr bit
x = x and not mask
proc raiseBitLE*(x: var BitIndexable, bit: Natural) =
## raises bit in `x`, assuming 0 to be the position of the
## least significant bit
type T = type(x)
let mask = 0b1 shl bit
x = x or mask
proc lowerBitLE*(x: var BitIndexable, bit: Natural) =
## raises bit in a byte, assuming 0 to be the position of the
## least significant bit
type T = type(x)
let mask = 0b1 shl bit
x = x and not mask
proc len*(r: BitRange): int {.inline.} = r.mLen
template getAbsoluteBit(bytes, absIdx: untyped): bool =
## Returns a bit with a position relative to the start of
## the underlying range. Not to be confused with a position
## relative to the start of the BitRange (i.e. the two would
## match only when range.start == 0).
let
byteToCheck = absIdx shr 3 # the same as absIdx / 8
bitToCheck = (absIdx and 0b111)
getBit(bytes[byteToCheck], bitToCheck)
template setAbsoluteBit(bytes, absIdx, value) =
let
byteToWrite = absIdx shr 3 # the same as absIdx / 8
bitToWrite = (absIdx and 0b111)
setBit(bytes[byteToWrite], bitToWrite, value)
iterator enumerateBits(x: BitRange): (int, bool) =
var p = x.start
var i = 0
let e = x.len
while i != e:
yield (i, getAbsoluteBit(x.data, p))
inc p
inc i
proc getBit*(bytes: openarray[byte], pos: Natural): bool =
getAbsoluteBit(bytes, pos)
proc setBit*(bytes: var openarray[byte], pos: Natural, value: bool) =
setAbsoluteBit(bytes, pos, value)
iterator items*(x: BitRange): bool =
for _, v in enumerateBits(x): yield v
iterator pairs*(x: BitRange): (int, bool) =
for i, v in enumerateBits(x): yield (i, v)
proc `[]`*(x: BitRange, idx: int): bool {.inline.} =
doAssert idx < x.len
let p = x.start + idx
result = getAbsoluteBit(x.data, p)
proc sliceNormalized(x: BitRange, ibegin, iend: int): BitRange =
doAssert ibegin >= 0 and
ibegin < x.len and
iend < x.len and
iend + 1 >= ibegin # the +1 here allows the result to be
# an empty range
result.data = x.data
result.start = x.start + ibegin
result.mLen = iend - ibegin + 1
proc `[]`*(r: BitRange, s: HSlice): BitRange {.inline.} =
sliceNormalized(r, r @ s.a, r @ s.b)
proc `==`*(a, b: BitRange): bool =
if a.len != b.len: return false
for i in 0 ..< a.len:
if a[i] != b[i]: return false
true
proc `[]=`*(r: var BitRange, idx: Natural, val: bool) {.inline.} =
doAssert idx < r.len
let absIdx = r.start + idx
setAbsoluteBit(r.data, absIdx, val)
proc setAbsoluteBit(x: BitRange, absIdx: int, val: bool) {.inline.} =
## Assumes the destination bit is already zeroed.
## Works with absolute positions similar to `getAbsoluteBit`
doAssert absIdx < x.len
let
byteToWrite = absIdx shr 3 # the same as absIdx / 8
bitToWrite = (absIdx and 0b111)
if val:
raiseBit x.data[byteToWrite], bitToWrite
proc pushFront*(x: var BitRange, val: bool) =
doAssert x.start > 0
dec x.start
x[0] = val
inc x.mLen
template neededBytes(nBits: int): int =
(nBits shr 3) + ord((nBits and 0b111) != 0)
static:
doAssert neededBytes(2) == 1
doAssert neededBytes(8) == 1
doAssert neededBytes(9) == 2
proc `&`*(a, b: BitRange): BitRange =
let totalLen = a.len + b.len
var bytes = newSeq[byte](totalLen.neededBytes)
result = bits(bytes, 0, totalLen)
for i in 0 ..< a.len: result.setAbsoluteBit(i, a[i])
for i in 0 ..< b.len: result.setAbsoluteBit(i + a.len, b[i])
proc `$`*(r: BitRange): string =
result = newStringOfCap(r.len)
for b in r:
result.add(if b: '1' else: '0')
proc fromBits*(T: typedesc, r: BitRange, offset, num: Natural): T =
doAssert(num <= sizeof(T) * 8)
# XXX: Nim has a bug that a typedesc parameter cannot be used
# in a type coercion, so we must define an alias here:
type TT = T
for i in 0 ..< num:
result = (result shl 1) or TT(r[offset + i])
proc parse*(T: typedesc[BitRange], s: string): BitRange =
var bytes = newSeq[byte](s.len.neededBytes)
for i, c in s:
case c
of '0': discard
of '1': raiseBit(bytes[i shr 3], i and 0b111)
else: doAssert false
result = bits(bytes, 0, s.len)

32
stew/ranges/memranges.nim Normal file
View File

@ -0,0 +1,32 @@
import
ptr_arith
type
MemRange* = object
start: pointer
size: csize
template len*(mr: MemRange): int = mr.size
template `[]`*(mr: MemRange, idx: int): byte = (cast[ptr byte](shift(mr.start, idx)))[]
proc baseAddr*(mr: MemRange): pointer = mr.start
proc makeMemRange*(start: pointer, size: csize): MemRange =
result.start = start
result.size = size
proc toMemRange*(x: string): MemRange =
result.start = x.cstring.pointer
result.size = x.len
proc toMemRange*[T](x: openarray[T], fromIdx, toIdx: int): MemRange =
doAssert(fromIdx >= 0 and toIdx >= fromIdx and fromIdx < x.len and toIdx < x.len)
result.start = unsafeAddr x[fromIdx]
result.size = (toIdx - fromIdx + 1) * T.sizeof
proc toMemRange*[T](x: openarray[T], fromIdx: int): MemRange {.inline.} =
toMemRange(x, fromIdx, x.high)
proc toMemRange*[T](x: openarray[T]): MemRange {.inline.} =
toMemRange(x, 0, x.high)
template toMemRange*(mr: MemRange): MemRange = mr

27
stew/ranges/ptr_arith.nim Normal file
View File

@ -0,0 +1,27 @@
proc baseAddr*[T](x: openarray[T]): pointer = cast[pointer](x)
proc shift*(p: pointer, delta: int): pointer {.inline.} =
cast[pointer](cast[int](p) + delta)
proc distance*(a, b: pointer): int {.inline.} =
cast[int](b) - cast[int](a)
proc shift*[T](p: ptr T, delta: int): ptr T {.inline.} =
cast[ptr T](shift(cast[pointer](p), delta * sizeof(T)))
when (NimMajor,NimMinor,NimPatch) >= (0,19,9):
template makeOpenArray*[T](p: ptr T, len: int): auto =
toOpenArray(cast[ptr UncheckedArray[T]](p), 0, len - 1)
template makeOpenArray*(p: pointer, T: type, len: int): auto =
toOpenArray(cast[ptr UncheckedArray[T]](p), 0, len - 1)
elif (NimMajor,NimMinor,NimPatch) >= (0,19,0):
# TODO: These are fallbacks until we upgrade to 0.19.9
template makeOpenArray*(p: pointer, T: type, len: int): auto =
toOpenArray(cast[ptr array[0, T]](p)[], 0, len - 1)
template makeOpenArray*[T](p: ptr T, len: int): auto =
toOpenArray(cast[ptr array[0, T]](p)[], 0, len - 1)
else:
{.fatal: "Unsupported nim version".}

133
stew/ranges/stackarrays.nim Normal file
View File

@ -0,0 +1,133 @@
## Stack-allocated arrays should be used with great care.
##
## They pose several major risks:
##
## 1. They should not be used inside resumable procs
## (i.e. closure iterators and async procs)
##
## Future versions of the library may automatically
## detect such usages and flag them as errors
##
## 2. The user code should be certain that enough stack space
## is available for the allocation and there will be enough
## room for additional calls after the allocation.
##
## Future versions of this library may provide checks
##
## Please note that the stack size on certain platforms
## may be very small (e.g. 8 to 32 kb on some Android versions)
##
## Before using alloca-backed arrays, consider using:
##
## 1. A regular stack array with a reasonable size
##
## 2. A global {.threadvar.} sequence that can be resized when
## needed (only in non-reentrant procs)
##
## Other possible future directions:
##
## Instead of `alloca`, we may start using a shadow stack that will be much
## harder to overflow. This will work by allocating a very large chunk of the
## address space at program init (e.g. 1TB on a 64-bit system) and then by
## gradually committing the individual pages to memory as they are requested.
##
## Such a scheme will even allow us to resize the stack array on demand
## in situations where the final size is not known upfront. With a resizing
## factor of 2, we'll never waste more than 50% of the memory which should
## be reasonable for short-lived allocations.
##
type
StackArray*[T] = object
bufferLen: int32
buffer: ptr UncheckedArray[T]
when defined(windows):
proc alloca(n: int): pointer {.importc, header: "<malloc.h>".}
else:
proc alloca(n: int): pointer {.importc, header: "<alloca.h>".}
proc raiseRangeError(s: string) =
raise newException(RangeError, s)
proc raiseOutOfRange =
raiseRangeError "index out of range"
template len*(a: StackArray): int =
int(a.bufferLen)
template high*(a: StackArray): int =
int(a.bufferLen) - 1
template low*(a: StackArray): int =
0
template `[]`*(a: StackArray, i: int): auto =
if i < 0 or i >= a.len: raiseOutOfRange()
a.buffer[i]
proc `[]=`*(a: StackArray, i: int, val: a.T) {.inline.} =
if i < 0 or i >= a.len: raiseOutOfRange()
a.buffer[i] = val
template `[]`*(a: StackArray, i: BackwardsIndex): auto =
if int(i) < 1 or int(i) > a.len: raiseOutOfRange()
a.buffer[a.len - int(i)]
proc `[]=`*(a: StackArray, i: BackwardsIndex, val: a.T) =
if int(i) < 1 or int(i) > a.len: raiseOutOfRange()
a.buffer[a.len - int(i)] = val
iterator items*(a: StackArray): a.T =
for i in 0 .. a.high:
yield a.buffer[i]
iterator mitems*(a: var StackArray): var a.T =
for i in 0 .. a.high:
yield a.buffer[i]
iterator pairs*(a: StackArray): a.T =
for i in 0 .. a.high:
yield (i, a.buffer[i])
iterator mpairs*(a: var StackArray): (int, var a.T) =
for i in 0 .. a.high:
yield (i, a.buffer[i])
template allocaAux(sz: int, init: static[bool]): pointer =
let s = sz
let b = alloca(s)
when init: zeroMem(b, s)
b
template allocStackArrayAux(T: typedesc, size: int, init: static[bool]): StackArray[T] =
let sz = int(size) # Evaluate size only once
if sz < 0: raiseRangeError "allocation with a negative size"
# XXX: is it possible to perform a stack size check before calling `alloca`?
# On thread init, Nim may record the base address and the capacity of the stack,
# so in theory we can verify that we still have enough room for the allocation.
# Research this.
StackArray[T](bufferLen: int32(sz), buffer: cast[ptr UncheckedArray[T]](allocaAux(sz * sizeof(T), init)))
template allocStackArray*(T: typedesc, size: int): StackArray[T] =
allocStackArrayAux(T, size, true)
template allocStackArrayNoInit*(T: typedesc, size: int): StackArray[T] =
allocStackArrayAux(T, size, false)
template getBuffer*(a: StackArray): untyped =
when (NimMajor,NimMinor,NimPatch)>=(0,19,9):
a.buffer
else:
a.buffer[]
template toOpenArray*(a: StackArray): auto =
toOpenArray(a.getBuffer, 0, a.high)
template toOpenArray*(a: StackArray, first: int): auto =
if first < 0 or first >= a.len: raiseOutOfRange()
toOpenArray(a.getBuffer, first, a.high)
template toOpenArray*(a: StackArray, first, last: int): auto =
if first < 0 or first >= last or last <= a.len: raiseOutOfRange()
toOpenArray(a.getBuffer, first, last)

251
stew/ranges/typedranges.nim Normal file
View File

@ -0,0 +1,251 @@
import ./ptr_arith, typetraits, hashes
const rangesGCHoldEnabled = not defined(rangesDisableGCHold)
const unsafeAPIEnabled* = defined(rangesEnableUnsafeAPI)
type
# A view into immutable array
Range* {.shallow.} [T] = object
when rangesGCHoldEnabled:
gcHold: seq[T]
start: ptr T
mLen: int
# A view into mutable array
MutRange* {.shallow.} [T] = distinct Range[T]
ByteRange* = Range[byte]
MutByteRange* = MutRange[byte]
proc isLiteral[T](s: seq[T]): bool {.inline.} =
type
SeqHeader = object
length, reserved: int
(cast[ptr SeqHeader](s).reserved and (1 shl (sizeof(int) * 8 - 2))) != 0
proc toImmutableRange[T](a: seq[T]): Range[T] =
if a.len != 0:
when rangesGCHoldEnabled:
if not isLiteral(a):
shallowCopy(result.gcHold, a)
else:
result.gcHold = a
result.start = addr result.gcHold[0]
result.mLen = a.len
when unsafeAPIEnabled:
proc toImmutableRangeNoGCHold[T](a: openarray[T]): Range[T] =
if a.len != 0:
result.start = unsafeAddr a[0]
result.mLen = a.len
proc toImmutableRange[T](a: openarray[T]): Range[T] {.inline.} =
toImmutableRangeNoGCHold(a)
proc toRange*[T](a: var seq[T]): MutRange[T] {.inline.} =
MutRange[T](toImmutableRange(a))
when unsafeAPIEnabled:
proc toRange*[T](a: var openarray[T]): MutRange[T] {.inline.} =
MutRange[T](toImmutableRange(a))
template initStackRange*[T](sz: static[int]): MutRange[T] =
var data: array[sz, T]
data.toRange()
proc toRange*[T](a: openarray[T]): Range[T] {.inline.} = toImmutableRange(a)
proc unsafeRangeConstruction*[T](a: var openarray[T]): MutRange[T] {.inline.} =
MutRange[T](toImmutableRange(a))
proc unsafeRangeConstruction*[T](a: openarray[T]): Range[T] {.inline.} =
toImmutableRange(a)
proc newRange*[T](sz: int): MutRange[T] {.inline.} =
MutRange[T](toImmutableRange(newSeq[T](sz)))
proc toRange*[T](a: seq[T]): Range[T] {.inline.} = toImmutableRange(a)
converter toImmutableRange*[T](a: MutRange[T]): Range[T] {.inline.} = Range[T](a)
proc len*(r: Range): int {.inline.} = int(r.mLen)
proc high*(r: Range): int {.inline.} = r.len - 1
proc low*(r: Range): int {.inline.} = 0
proc elemAt[T](r: MutRange[T], idx: int): var T {.inline.} =
doAssert(idx < r.len)
Range[T](r).start.shift(idx)[]
proc `[]=`*[T](r: MutRange[T], idx: int, v: T) {.inline.} = r.elemAt(idx) = v
proc `[]`*[T](r: MutRange[T], i: int): var T = r.elemAt(i)
proc `[]`*[T](r: Range[T], idx: int): T {.inline.} =
doAssert(idx < r.len)
r.start.shift(idx)[]
proc `==`*[T](a, b: Range[T]): bool =
if a.len != b.len: return false
equalMem(a.start, b.start, sizeof(T) * a.len)
iterator ptrs[T](r: Range[T]): (int, ptr T) =
var p = r.start
var i = 0
let e = r.len
while i != e:
yield (i, p)
p = p.shift(1)
inc i
iterator items*[T](r: Range[T]): T =
for _, v in ptrs(r): yield v[]
iterator pairs*[T](r: Range[T]): (int, T) =
for i, v in ptrs(r): yield (i, v[])
iterator mitems*[T](r: MutRange[T]): var T =
for _, v in ptrs(r): yield v[]
iterator mpairs*[T](r: MutRange[T]): (int, var T) =
for i, v in ptrs(r): yield (i, v[])
proc toSeq*[T](r: Range[T]): seq[T] =
result = newSeqOfCap[T](r.len)
for i in r: result.add(i)
proc `$`*(r: Range): string =
result = "R["
for i, v in r:
if i != 0:
result &= ", "
result &= $v
result &= "]"
proc sliceNormalized[T](r: Range[T], ibegin, iend: int): Range[T] =
doAssert ibegin >= 0 and
ibegin < r.len and
iend < r.len and
iend + 1 >= ibegin # the +1 here allows the result to be
# an empty range
when rangesGCHoldEnabled:
shallowCopy(result.gcHold, r.gcHold)
result.start = r.start.shift(ibegin)
result.mLen = iend - ibegin + 1
proc slice*[T](r: Range[T], ibegin = 0, iend = -1): Range[T] =
let e = if iend < 0: r.len + iend
else: iend
sliceNormalized(r, ibegin, e)
proc slice*[T](r: MutRange[T], ibegin = 0, iend = -1): MutRange[T] {.inline.} =
MutRange[T](Range[T](r).slice(ibegin, iend))
template `^^`(s, i: untyped): untyped =
(when i is BackwardsIndex: s.len - int(i) else: int(i))
proc `[]`*[T, U, V](r: Range[T], s: HSlice[U, V]): Range[T] {.inline.} =
sliceNormalized(r, r ^^ s.a, r ^^ s.b)
proc `[]`*[T, U, V](r: MutRange[T], s: HSlice[U, V]): MutRange[T] {.inline.} =
MutRange[T](sliceNormalized(r, r ^^ s.a, r ^^ s.b))
proc `[]=`*[T, U, V](r: MutRange[T], s: HSlice[U, V], v: openarray[T]) =
let a = r ^^ s.a
let b = r ^^ s.b
let L = b - a + 1
if L == v.len:
for i in 0..<L: r[i + a] = v[i]
else:
raise newException(RangeError, "different lengths for slice assignment")
template toOpenArray*[T](r: Range[T]): auto =
when false:
# when (NimMajor,NimMinor,NimPatch)>=(0,19,9):
# error message in Nim HEAD 2019-01-02:
# "for a 'var' type a variable needs to be passed, but 'toOpenArray(cast[ptr UncheckedArray[T]](curHash.start), 0, high(curHash))' is immutable"
toOpenArray(cast[ptr UncheckedArray[T]](r.start), 0, r.high)
else:
# NOTE: `0` in `array[0, T]` is irrelevant
toOpenArray(cast[ptr array[0, T]](r.start)[], 0, r.high)
proc `[]=`*[T, U, V](r: MutRange[T], s: HSlice[U, V], v: Range[T]) {.inline.} =
r[s] = toOpenArray(v)
proc baseAddr*[T](r: Range[T]): ptr T {.inline.} = r.start
proc gcHolder*[T](r: Range[T]): ptr T {.inline.} =
## This procedure is used only for shallow test, do not use it
## in production.
when rangesGCHoldEnabled:
if r.len > 0:
result = unsafeAddr r.gcHold[0]
template toRange*[T](a: Range[T]): Range[T] = a
# this preferred syntax doesn't work
# see https://github.com/nim-lang/Nim/issues/7995
#template copyRange[T](dest: seq[T], destOffset: int, src: Range[T]) =
# when supportsCopyMem(T):
template copyRange[T](E: typedesc, dest: seq[T], destOffset: int, src: Range[T]) =
when supportsCopyMem(E):
if dest.len != 0 and src.len != 0:
copyMem(dest[destOffset].unsafeAddr, src.start, sizeof(T) * src.len)
else:
for i in 0..<src.len:
dest[i + destOffset] = src[i]
proc concat*[T](v: varargs[Range[T], toRange]): seq[T] =
var len = 0
for c in v: inc(len, c.len)
result = newSeq[T](len)
len = 0
for c in v:
copyRange(T, result, len, c)
inc(len, c.len)
proc `&`*[T](a, b: Range[T]): seq[T] =
result = newSeq[T](a.len + b.len)
copyRange(T, result, 0, a)
copyRange(T, result, a.len, b)
proc hash*(x: Range): Hash =
result = hash(toOpenArray(x))
template advanceImpl(a, b: untyped): bool =
var res = false
if b == 0:
res = true
elif b > 0:
if isNil(a.start) or a.mLen <= 0:
res = false
else:
if a.mLen - b < 0:
res = false
else:
a.start = a.start.shift(b)
a.mLen -= b
res = true
res
proc tryAdvance*[T](x: var Range[T], idx: int): bool =
## Move internal start offset of range ``x`` by ``idx`` elements forward.
##
## Returns ``true`` if operation got completed successfully, or
## ``false`` if you are trying to overrun range ``x``.
result = x.advanceImpl(idx)
proc tryAdvance*[T](x: var MutRange[T], idx: int): bool {.inline.} =
## Move internal start offset of range ``x`` by ``idx`` elements forward.
##
## Returns ``true`` if operation got completed successfully, or
## ``false`` if you are trying to overrun range ``x``.
result = tryAdvance(Range[T](x), idx)
proc advance*[T](x: var Range[T], idx: int) =
## Move internal start offset of range ``x`` by ``idx`` elements forward.
let res = x.advanceImpl(idx)
if not res: raise newException(IndexError, "Advance Error")
proc advance*[T](x: var MutRange[T], idx: int) {.inline.} =
## Move internal start offset of range ``x`` by ``idx`` elements forward.
advance(Range[T](x), idx)

View File

@ -8,4 +8,6 @@
# at your option. This file may not be copied, modified, or distributed except according to those terms.
import
ranges/all,
test_byteutils

2
tests/ranges/all.nim Normal file
View File

@ -0,0 +1,2 @@
import
ttypedranges, tstackarrays, tbitranges

View File

@ -0,0 +1,83 @@
import
random, unittest,
../../stew/ranges/bitranges
proc randomBytes(n: int): seq[byte] =
result = newSeq[byte](n)
for i in 0 ..< result.len:
result[i] = byte(rand(256))
suite "bit ranges":
test "basic":
var a = @[byte 0b10101010, 0b11110000, 0b00001111, 0b01010101]
var bSeq = @[byte 0b10101010, 0b00000000, 0b00000000, 0b11111111]
var b = bits(bSeq, 8)
var cSeq = @[byte 0b11110000, 0b00001111, 0b00000000, 0b00000000]
var c = bits(cSeq, 16)
var dSeq = @[byte 0b00001111, 0b00000000, 0b00000000, 0b00000000]
var d = bits(dSeq, 8)
var eSeq = @[byte 0b01010101, 0b00000000, 0b00000000, 0b00000000]
var e = bits(eSeq, 8)
var m = a.bits
var n = m[0..7]
check n == b
check n.len == 8
check b.len == 8
check c == m[8..23]
check $(d) == "00001111"
check $(e) == "01010101"
var f = int.fromBits(e, 0, 4)
check f == 0b0101
let k = n & d
check(k.len == n.len + d.len)
check($k == $n & $d)
var asciiSeq = @[byte('A'),byte('S'),byte('C'),byte('I'),byte('I')]
let asciiBits = bits(asciiSeq)
check $asciiBits == "0100000101010011010000110100100101001001"
test "concat operator":
randomize(5000)
for i in 0..<256:
var xSeq = randomBytes(rand(i))
var ySeq = randomBytes(rand(i))
let x = xSeq.bits
let y = ySeq.bits
var z = x & y
check z.len == x.len + y.len
check($z == $x & $y)
test "get set bits":
randomize(1000)
for i in 0..<256:
# produce random vector
var xSeq = randomBytes(i)
var ySeq = randomBytes(i)
var x = xSeq.bits
var y = ySeq.bits
for idx, bit in x:
y[idx] = bit
check x == y
test "constructor with start":
var a = @[byte 0b10101010, 0b11110000, 0b00001111, 0b01010101]
var b = a.bits(1, 8)
check b.len == 8
check b[0] == false
check $b == "01010101"
b[0] = true
check $b == "11010101"
check b[0] == true
b.pushFront(false)
check b[0] == false
check $b == "011010101"

View File

@ -0,0 +1,47 @@
import
unittest, math,
../../stew/ranges/[stackarrays, ptr_arith]
suite "Stack arrays":
test "Basic operations work as expected":
var arr = allocStackArray(int, 10)
check:
type(arr[0]) is int
arr.len == 10
# all items should be initially zero
for i in arr: check i == 0
for i in 0 .. arr.high: check arr[i] == 0
arr[0] = 3
arr[5] = 10
arr[9] = 6
check:
sum(arr.toOpenArray) == 19
arr[5] == 10
arr[^1] == 6
cast[ptr int](shift(addr arr[0], 5))[] == 10
test "Allocating with a negative size throws a RangeError":
expect RangeError:
var arr = allocStackArray(string, -1)
test "The array access is bounds-checked":
var arr = allocStackArray(string, 3)
arr[2] = "test"
check arr[2] == "test"
expect RangeError:
arr[3] = "another test"
test "proof of stack allocation":
proc fun() =
# NOTE: has to be inside a proc otherwise x1 not allocated on stack.
var x1 = 0
var arr = allocStackArray(int, 3)
check:
# stack can go either up or down, hence `abs`.
# 1024 should be large enough (was 312 on OSX).
abs(cast[int](x1.addr) - cast[int](addr(arr[0]))) < 1024
fun()

View File

@ -0,0 +1,254 @@
import
unittest, sets,
../../stew/ranges/[typedranges, ptr_arith]
suite "Typed ranges":
test "basic stuff":
var r = newRange[int](5)
r[0] = 1
r[1 .. ^1] = [2, 3, 4, 5]
check $r == "R[1, 2, 3, 4, 5]"
var s = newSeq[int]()
for a in r: s.add(a)
check s == @[1, 2, 3, 4, 5]
test "subrange":
var a = newRange[int](5)
let b = toRange(@[1, 2, 3])
a[1 .. 3] = b
check a.toSeq == @[0, 1, 2, 3, 0]
check:
a[2 .. 2].len == 1
a[1 ..< 1].len == 0
test "equality operator":
var x = toRange(@[0, 1, 2, 3, 4, 5])
var y = x[1 .. ^2]
var z = toRange(@[1, 2, 3, 4])
check y == z
check x != z
test "concat operation":
var a = toRange(@[1,2,3])
var b = toRange(@[4,5,6])
var c = toRange(@[7,8,9])
var d = @[1,2,3,4,5,6,7,8,9]
var e = @[1,2,3,4,5,6]
var f = @[4,5,6,7,8,9]
var x = concat(a, b, c)
var y = a & b
check x == d
check y == e
var z = concat(b, @[7,8,9])
check z == f
let u = toRange(newSeq[int](0))
let v = toRange(@[3])
check concat(u, v) == @[3]
check (v & u) == @[3]
test "complex types concat operation":
type
Jaeger = object
name: string
weight: int
var A = Jaeger(name: "Gipsy Avenger", weight: 2004)
var B = Jaeger(name: "Striker Eureka", weight: 1850)
var C = Jaeger(name: "Saber Athena", weight: 1628)
var D = Jaeger(name: "Cherno Alpha", weight: 2412)
var k = toRange(@[A, B])
var m = toRange(@[C, D])
var n = concat(k, m)
check n == @[A, B, C ,D]
check n != @[A, B, C ,C]
test "shallowness":
var s = @[1, 2, 3]
var r = s.toRange()
var r2 = r
s[0] = 5
check(r[0] == 5)
s[1] = 10
check(r2[1] == 10)
var r3 = r[2..2]
s[2] = 15
check(r3[0] == 15)
test "hash function":
var a = toRange(@[1,2,3])
var b = toRange(@[4,5,6])
var c = toRange(@[7,8,9])
var d = toRange(@[1,2,3,4,5,6,7,8,9])
var e = toRange(@[1,2,3,4,5,6,7,8,9])
var x = toSet([a, b, c, a, b])
check x.len == 3
check a in x
var z = toRange(@[7,8,9])
var y = toSet([z, b, c])
check z in y
check z in x
var u = d[0..2]
var v = d[3..5]
var uu = e[0..2]
var vv = e[3..5]
check hash(u) != hash(v)
check hash(uu) == hash(u)
check hash(v) == hash(vv)
check hash(uu) != hash(vv)
test "toOpenArray":
var a = toRange(@[1,2,3])
check $a.toOpenArray == "[1, 2, 3]"
test "MutRange[T] shallow test":
var b = @[1, 2, 3, 4, 5, 6]
var r1 = b.toRange()
var r2 = r1
b[0] = 5
b[1] = 10
b[2] = 15
var r3 = r1[1..1]
var a0 = cast[uint](addr b[0])
var a1 = cast[uint](r1.gcHolder)
var a2 = cast[uint](r2.gcHolder)
var a3 = cast[uint](r3.gcHolder)
check:
a1 == a0
a2 == a0
a3 == a0
test "Range[T] shallow test":
var r1 = toRange(@[1, 2, 3, 4, 5, 6])
var r2 = r1
var r3 = r1[1..1]
var a1 = cast[uint](r1.gcHolder)
var a2 = cast[uint](r2.gcHolder)
var a3 = cast[uint](r3.gcHolder)
check:
a2 == a1
a3 == a1
test "tryAdvance(Range)":
var a: Range[int]
check:
a.tryAdvance(1) == false
a.tryAdvance(-1) == false
a.tryAdvance(0) == true
var b = toRange(@[1, 2, 3])
check:
b.tryAdvance(-1) == false
$b.toOpenArray == "[1, 2, 3]"
b.tryAdvance(0) == true
$b.toOpenArray == "[1, 2, 3]"
b.tryAdvance(1) == true
$b.toOpenArray == "[2, 3]"
b.tryAdvance(1) == true
$b.toOpenArray == "[3]"
b.tryAdvance(1) == true
$b.toOpenArray == "[]"
b.tryAdvance(1) == false
$b.toOpenArray == "[]"
test "advance(Range)":
template aecheck(a, b): int =
var res = 0
try:
a.advance(b)
res = 1
except IndexError:
res = 2
res
var a: Range[int]
check:
a.aecheck(1) == 2
a.aecheck(-1) == 2
a.aecheck(0) == 1
var b = toRange(@[1, 2, 3])
check:
b.aecheck(-1) == 2
$b.toOpenArray == "[1, 2, 3]"
b.aecheck(0) == 1
$b.toOpenArray == "[1, 2, 3]"
b.aecheck(1) == 1
$b.toOpenArray == "[2, 3]"
b.aecheck(1) == 1
$b.toOpenArray == "[3]"
b.aecheck(1) == 1
$b.toOpenArray == "[]"
b.aecheck(1) == 2
$b.toOpenArray == "[]"
test "tryAdvance(MutRange)":
var a: MutRange[int]
check:
a.tryAdvance(1) == false
a.tryAdvance(-1) == false
a.tryAdvance(0) == true
var buf = @[1, 2, 3]
var b = toRange(buf)
check:
b.tryAdvance(-1) == false
$b.toOpenArray == "[1, 2, 3]"
b.tryAdvance(0) == true
$b.toOpenArray == "[1, 2, 3]"
b.tryAdvance(1) == true
$b.toOpenArray == "[2, 3]"
b.tryAdvance(1) == true
$b.toOpenArray == "[3]"
b.tryAdvance(1) == true
$b.toOpenArray == "[]"
b.tryAdvance(1) == false
$b.toOpenArray == "[]"
test "advance(MutRange)":
template aecheck(a, b): int =
var res = 0
try:
a.advance(b)
res = 1
except IndexError:
res = 2
res
var a: MutRange[int]
check:
a.aecheck(1) == 2
a.aecheck(-1) == 2
a.aecheck(0) == 1
var buf = @[1, 2, 3]
var b = toRange(buf)
check:
b.aecheck(-1) == 2
$b.toOpenArray == "[1, 2, 3]"
b.aecheck(0) == 1
$b.toOpenArray == "[1, 2, 3]"
b.aecheck(1) == 1
$b.toOpenArray == "[2, 3]"
b.aecheck(1) == 1
$b.toOpenArray == "[3]"
b.aecheck(1) == 1
$b.toOpenArray == "[]"
b.aecheck(1) == 2
$b.toOpenArray == "[]"
test "make openarrays from pointers":
var str = "test 1,2,3"
var charPtr: ptr char = addr str[7]
var regularPtr: pointer = addr str[5]
check:
# (regularPtr.makeOpenArray(char, 4).len == 4)
(regularPtr.makeOpenArray(char, 5) == "1,2,3")
(regularPtr.makeOpenArray(char, 5) == str[5..9])
# (charPtr.makeOpenArray(3).len == 3)
(charPtr.makeOpenArray(3) == "2,3")
(charPtr.makeOpenArray(1) == str[7..7])