nim-eth/eth/trie/nibbles.nim

166 lines
4.3 KiB
Nim

import
trie_defs
type
NibblesRange* = object
bytes: ByteRange
ibegin, iend: int
proc initNibbleRange*(bytes: ByteRange): NibblesRange =
result.bytes = bytes
result.ibegin = 0
result.iend = bytes.len * 2
# can't be a const: https://github.com/status-im/nim-eth/issues/6
# we can't initialise it here, but since it's already zeroed memory, we don't need to
var zeroNibblesRange* {.threadvar.}: NibblesRange
proc `{}`(r: NibblesRange, pos: int): byte {.inline.} =
## This is a helper for a more raw access to the nibbles.
## It works with absolute positions.
if pos > r.iend: raise newException(RangeError, "index out of range")
return if (pos and 1) != 0: (r.bytes[pos div 2] and 0xf)
else: (r.bytes[pos div 2] shr 4)
template `[]`*(r: NibblesRange, i: int): byte = r{r.ibegin + i}
proc len*(r: NibblesRange): int =
r.iend - r.ibegin
proc `==`*(lhs, rhs: NibblesRange): bool =
if lhs.len == rhs.len:
for i in 0 ..< lhs.len:
if lhs[i] != rhs[i]:
return false
return true
else:
return false
proc `$`*(r: NibblesRange): string =
result = newStringOfCap(100)
for i in r.ibegin ..< r.iend:
let n = int r{i}
let c = if n > 9: char(ord('a') + n - 10)
else: char(ord('0') + n)
result.add c
proc slice*(r: NibblesRange, ibegin: int, iend = -1): NibblesRange =
result.bytes = r.bytes
result.ibegin = r.ibegin + ibegin
let e = if iend < 0: r.iend + iend + 1
else: r.ibegin + iend
doAssert ibegin >= 0 and e <= result.bytes.len * 2
result.iend = e
template writeFirstByte(nibbleCountExpr) {.dirty.} =
let nibbleCount = nibbleCountExpr
var oddnessFlag = (nibbleCount and 1) != 0
newSeq(result, (nibbleCount div 2) + 1)
result[0] = byte((int(isLeaf) * 2 + int(oddnessFlag)) shl 4)
var writeHead = 0
template writeNibbles(r) {.dirty.} =
for i in r.ibegin ..< r.iend:
let nextNibble = r{i}
if oddnessFlag:
result[writeHead] = result[writeHead] or nextNibble
else:
inc writeHead
result[writeHead] = nextNibble shl 4
oddnessFlag = not oddnessFlag
proc hexPrefixEncode*(r: NibblesRange, isLeaf = false): Bytes =
writeFirstByte(r.len)
writeNibbles(r)
proc hexPrefixEncode*(r1, r2: NibblesRange, isLeaf = false): Bytes =
writeFirstByte(r1.len + r2.len)
writeNibbles(r1)
writeNibbles(r2)
proc hexPrefixEncodeByte*(val: byte, isLeaf = false): byte =
doAssert val < 16
result = (((byte(isLeaf) * 2) + 1) shl 4) or val
proc sharedPrefixLen*(lhs, rhs: NibblesRange): int =
result = 0
while result < lhs.len and result < rhs.len:
if lhs[result] != rhs[result]: break
inc result
proc startsWith*(lhs, rhs: NibblesRange): bool =
sharedPrefixLen(lhs, rhs) == rhs.len
proc hexPrefixDecode*(r: ByteRange): tuple[isLeaf: bool, nibbles: NibblesRange] =
result.nibbles = initNibbleRange(r)
if r.len > 0:
result.isLeaf = (r[0] and 0x20) != 0
let hasOddLen = (r[0] and 0x10) != 0
result.nibbles.ibegin = 2 - int(hasOddLen)
else:
result.isLeaf = false
template putNibble(bytes, x: untyped) =
if odd:
bytes[pos] = (bytes[pos] and 0xF0) or x
inc pos
else:
bytes[pos] = x shl 4
template putNibbles(bytes, src: untyped) =
for i in 0 ..< src.len:
bytes.putNibble(src[i])
odd = not odd
template calcNeededBytes(len: int): int =
(len shr 1) + (len and 1)
proc `&`*(a, b: NibblesRange): NibblesRange =
let
len = a.len + b.len
bytesNeeded = calcNeededBytes(len)
var
bytes = newSeq[byte](bytesNeeded)
odd = false
pos = 0
bytes.putNibbles(a)
bytes.putNibbles(b)
result = initNibbleRange(bytes.toRange)
result.iend = len
proc cloneAndReserveNibble*(a: NibblesRange): NibblesRange =
let
len = a.len + 1
bytesNeeded = calcNeededBytes(len)
var
bytes = newSeq[byte](bytesNeeded)
odd = false
pos = 0
bytes.putNibbles(a)
result = initNibbleRange(bytes.toRange)
result.iend = len
proc replaceLastNibble*(a: var NibblesRange, b: byte) =
var
odd = (a.len and 1) == 0
pos = (a.len shr 1) - odd.int
putNibble(MutRange[byte](a.bytes), b)
proc getBytes*(a: NibblesRange): ByteRange =
a.bytes
when false:
proc keyOf(r: ByteRange): NibblesRange =
let firstIdx = if r.len == 0: 0
elif (r[0] and 0x10) != 0: 1
else: 2
return initNibbleRange(s).slice(firstIdx)