mirror of
https://github.com/status-im/nim-ranges.git
synced 2025-01-12 22:04:17 +00:00
Merge pull request #14 from status-im/feature-bitranges
helpers for manipulating binary formats using bit sequences
This commit is contained in:
commit
1391b9595a
215
ranges/bitranges.nim
Normal file
215
ranges/bitranges.nim
Normal file
@ -0,0 +1,215 @@
|
|||||||
|
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 =
|
||||||
|
assert start <= len
|
||||||
|
assert 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 getBitBE*(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 setBitBE*(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 raiseBitBE*(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 lowerBitBE*(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)
|
||||||
|
|
||||||
|
getBitBE(bytes[byteToCheck], bitToCheck)
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
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.} =
|
||||||
|
assert idx < x.len
|
||||||
|
let p = x.start + idx
|
||||||
|
result = getAbsoluteBit(x.data, p)
|
||||||
|
|
||||||
|
proc sliceNormalized(x: BitRange, ibegin, iend: int): BitRange =
|
||||||
|
assert ibegin >= 0 and
|
||||||
|
ibegin < x.len and
|
||||||
|
iend >= ibegin and
|
||||||
|
iend < x.len
|
||||||
|
|
||||||
|
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.} =
|
||||||
|
assert idx < r.len
|
||||||
|
|
||||||
|
let
|
||||||
|
absIdx = r.start + idx
|
||||||
|
byteToWrite = absIdx shr 3 # the same as absIdx / 8
|
||||||
|
bitToWrite = (absIdx and 0b111)
|
||||||
|
|
||||||
|
setBitBE r.data[byteToWrite], bitToWrite, val
|
||||||
|
|
||||||
|
proc setAbsoluteBit(x: BitRange, absIdx: int, val: bool) {.inline.} =
|
||||||
|
## Assumes the destination bit is already zeroed.
|
||||||
|
## Works with absolute positions similar to `getAbsoluteBit`
|
||||||
|
assert absIdx < x.len
|
||||||
|
let
|
||||||
|
byteToWrite = absIdx shr 3 # the same as absIdx / 8
|
||||||
|
bitToWrite = (absIdx and 0b111)
|
||||||
|
|
||||||
|
if val:
|
||||||
|
raiseBitBE x.data[byteToWrite], bitToWrite
|
||||||
|
|
||||||
|
template neededBytes(nBits: int): int =
|
||||||
|
(nBits shr 3) + ord((nBits and 0b111) != 0)
|
||||||
|
|
||||||
|
static:
|
||||||
|
assert neededBytes(2) == 1
|
||||||
|
assert neededBytes(8) == 1
|
||||||
|
assert 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 =
|
||||||
|
assert(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': raiseBitBE(bytes[i shr 3], i and 0b111)
|
||||||
|
else: assert false
|
||||||
|
result = bits(bytes, 0, s.len)
|
||||||
|
|
@ -14,6 +14,9 @@ type
|
|||||||
# A view into mutable array
|
# A view into mutable array
|
||||||
MutRange*[T] = distinct Range[T]
|
MutRange*[T] = distinct Range[T]
|
||||||
|
|
||||||
|
ByteRange* = Range[byte]
|
||||||
|
MutByteRange* = MutRange[byte]
|
||||||
|
|
||||||
proc toImmutableRange[T](a: seq[T]): Range[T] =
|
proc toImmutableRange[T](a: seq[T]): Range[T] =
|
||||||
if a.len != 0:
|
if a.len != 0:
|
||||||
when rangesGCHoldEnabled:
|
when rangesGCHoldEnabled:
|
||||||
|
@ -1,2 +1,2 @@
|
|||||||
import
|
import
|
||||||
ttypedranges, tstackarrays
|
ttypedranges, tstackarrays, tbitranges
|
||||||
|
70
tests/tbitranges.nim
Normal file
70
tests/tbitranges.nim
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
import
|
||||||
|
random, unittest,
|
||||||
|
../ranges/bitranges
|
||||||
|
|
||||||
|
proc randomBytes(n: int): seq[byte] =
|
||||||
|
result = newSeq[byte](n)
|
||||||
|
for i in 0 ..< result.len:
|
||||||
|
result[i] = byte(rand(256))
|
||||||
|
|
||||||
|
suite "bir 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(0)
|
||||||
|
|
||||||
|
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
|
Loading…
x
Reference in New Issue
Block a user