From 580043724cc929a3d6903312680a4cb4c107e268 Mon Sep 17 00:00:00 2001 From: Zahary Karadjov Date: Wed, 20 Jun 2018 19:45:10 +0300 Subject: [PATCH] helpers for manipulating binary formats using bit sequences --- ranges/bitranges.nim | 215 +++++++++++++++++++++++++++++++++++++++++ ranges/typedranges.nim | 3 + tests/all.nim | 2 +- tests/tbitranges.nim | 70 ++++++++++++++ 4 files changed, 289 insertions(+), 1 deletion(-) create mode 100644 ranges/bitranges.nim create mode 100644 tests/tbitranges.nim diff --git a/ranges/bitranges.nim b/ranges/bitranges.nim new file mode 100644 index 0000000..6941bd8 --- /dev/null +++ b/ranges/bitranges.nim @@ -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) + diff --git a/ranges/typedranges.nim b/ranges/typedranges.nim index 4dd3e2e..84f2d44 100644 --- a/ranges/typedranges.nim +++ b/ranges/typedranges.nim @@ -14,6 +14,9 @@ type # A view into mutable array MutRange*[T] = distinct Range[T] + ByteRange* = Range[byte] + MutByteRange* = MutRange[byte] + proc toImmutableRange[T](a: seq[T]): Range[T] = if a.len != 0: when rangesGCHoldEnabled: diff --git a/tests/all.nim b/tests/all.nim index fcd4fc0..fc19e34 100644 --- a/tests/all.nim +++ b/tests/all.nim @@ -1,2 +1,2 @@ import - ttypedranges, tstackarrays + ttypedranges, tstackarrays, tbitranges diff --git a/tests/tbitranges.nim b/tests/tbitranges.nim new file mode 100644 index 0000000..62978c0 --- /dev/null +++ b/tests/tbitranges.nim @@ -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