arraybuf: seq-like fixed-capacity container stored on the stack (#227)
Also includes `evalOnceAs`, a handy utility for avoiding creating a temporary in templates when seeking to avoid double-evaluation of parameters.
This commit is contained in:
parent
af07b0a70d
commit
fc09b2e023
|
@ -0,0 +1,104 @@
|
|||
# stew
|
||||
# Copyright 2024 Status Research & Development GmbH
|
||||
# Licensed under either of
|
||||
#
|
||||
# * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0)
|
||||
# * MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT)
|
||||
#
|
||||
# at your option. This file may not be copied, modified, or distributed except according to those terms.
|
||||
|
||||
import ./[evalonce, arrayops]
|
||||
|
||||
type ArrayBuf*[N: static int, T] = object
|
||||
## An fixed-capacity, allocation-free buffer with a seq-like API - suitable
|
||||
## for keeping small amounts of data since the full capacity is reserved on
|
||||
## instantiation (using an `array`).
|
||||
#
|
||||
# `N` must be "simple enough" or one of these will trigger
|
||||
# TODO https://github.com/nim-lang/Nim/issues/24043
|
||||
# TODO https://github.com/nim-lang/Nim/issues/24044
|
||||
# TODO https://github.com/nim-lang/Nim/issues/24045
|
||||
buf*: array[N, T]
|
||||
|
||||
when sizeof(int) > sizeof(uint8):
|
||||
when N <= int(uint8.high):
|
||||
n*: uint8
|
||||
else:
|
||||
when sizeof(int) > sizeof(uint16):
|
||||
when N <= int(uint16.high):
|
||||
n*: uint16
|
||||
else:
|
||||
when sizeof(int) > sizeof(uint32):
|
||||
# TODO https://github.com/nim-lang/Nim/issues/24041
|
||||
when N <= cast[int](uint32.high):
|
||||
n*: uint32
|
||||
else:
|
||||
n*: int
|
||||
else:
|
||||
n*: int
|
||||
else:
|
||||
n*: int
|
||||
else:
|
||||
n*: int
|
||||
# Number of entries actually in use - uses the smallest unsigned integer
|
||||
# that can hold values up to the capacity to avoid wasting memory on
|
||||
# alignment and counting, specially when `T = byte` and odd sizes are used
|
||||
|
||||
template len*(b: ArrayBuf): int =
|
||||
int(b.n)
|
||||
|
||||
template setLen*(b: var ArrayBuf, newLenParam: int) =
|
||||
newLenParam.evalOnceAs(newLen)
|
||||
let nl = typeof(b.n)(newLen)
|
||||
for i in newLen ..< b.len():
|
||||
reset(b.buf[b.len() - i - 1]) # reset cleared items when shrinking
|
||||
b.n = nl
|
||||
|
||||
template data*(bParam: ArrayBuf): openArray =
|
||||
bParam.evalOnceAs(b)
|
||||
b.buf.toOpenArray(0, b.len() - 1)
|
||||
|
||||
template data*(bParam: var ArrayBuf): var openArray =
|
||||
bParam.evalOnceAs(b)
|
||||
b.buf.toOpenArray(0, b.len() - 1)
|
||||
|
||||
iterator items*[N, T](b: ArrayBuf[N, T]): lent T =
|
||||
for i in 0 ..< b.len:
|
||||
yield b.d[i]
|
||||
|
||||
iterator mitems*[N, T](b: var ArrayBuf[N, T]): var T =
|
||||
for i in 0 ..< b.len:
|
||||
yield b.d[i]
|
||||
|
||||
iterator pairs*[N, T](b: ArrayBuf[N, T]): (int, lent T) =
|
||||
for i in 0 ..< b.len:
|
||||
yield (i, b.buf[i])
|
||||
|
||||
template `[]`*[N, T](b: ArrayBuf[N, T], i: int): lent T =
|
||||
b.buf[i]
|
||||
|
||||
template `[]`*[N, T](b: var ArrayBuf[N, T], i: int): var T =
|
||||
b.buf[i]
|
||||
|
||||
template `[]=`*[N, T](b: var ArrayBuf[N, T], i: int, v: T) =
|
||||
b.buf[i] = v
|
||||
|
||||
template `==`*(a, b: ArrayBuf): bool =
|
||||
a.data() == b.data()
|
||||
|
||||
template `<`*(a, b: ArrayBuf): bool =
|
||||
a.data() < b.data()
|
||||
|
||||
template add*[N, T](b: var ArrayBuf[N, T], v: T) =
|
||||
## Adds items up to capacity then drops the rest
|
||||
# TODO `b` is evaluated multiple times but since it's a `var` this should
|
||||
# _hopefully_ be fine..
|
||||
if b.len < N:
|
||||
b.buf[b.len] = v
|
||||
b.n += 1
|
||||
|
||||
template add*[N, T](b: var ArrayBuf[N, T], v: openArray[T]) =
|
||||
## Adds items up to capacity then drops the rest
|
||||
# TODO `b` is evaluated multiple times but since it's a `var` this should
|
||||
# _hopefully_ be fine..
|
||||
b.n += typeof(b.n)(b.buf.toOpenArray(b.len, N - 1).copyFrom(v))
|
|
@ -0,0 +1,42 @@
|
|||
# stew
|
||||
# Copyright 2024 Status Research & Development GmbH
|
||||
# Licensed under either of
|
||||
#
|
||||
# * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0)
|
||||
# * MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT)
|
||||
#
|
||||
# at your option. This file may not be copied, modified, or distributed except according to those terms.
|
||||
|
||||
import std/macros
|
||||
|
||||
macro evalOnceAs*(exp, alias: untyped): untyped =
|
||||
## Ensure that `exp` is evaluated only once unless it is a symbol in which
|
||||
## case it's used directly.
|
||||
##
|
||||
## A common case where this is useful is template parameters which, when
|
||||
## an expression is passed in, get evaluated multiple times.
|
||||
##
|
||||
## Based on a similar macro in std/sequtils
|
||||
expectKind(alias, nnkIdent)
|
||||
|
||||
let
|
||||
body = nnkStmtList.newTree()
|
||||
val =
|
||||
if exp.kind == nnkSym:
|
||||
# The symbol can be used directly
|
||||
# TODO dot expressions? etc..
|
||||
exp
|
||||
else:
|
||||
let val = genSym(ident = "evalOnce_" & $alias)
|
||||
body.add newLetStmt(val, exp)
|
||||
val
|
||||
body.add(
|
||||
newProc(
|
||||
name = genSym(nskTemplate, $alias),
|
||||
params = [getType(untyped)],
|
||||
body = val,
|
||||
procType = nnkTemplateDef,
|
||||
)
|
||||
)
|
||||
|
||||
body
|
|
@ -11,6 +11,7 @@ import ranges/all
|
|||
|
||||
import
|
||||
test_assign2,
|
||||
test_arraybuf,
|
||||
test_arrayops,
|
||||
test_base10,
|
||||
test_base32,
|
||||
|
|
|
@ -0,0 +1,36 @@
|
|||
# stew
|
||||
# Copyright 2024 Status Research & Development GmbH
|
||||
# Licensed under either of
|
||||
#
|
||||
# * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0)
|
||||
# * MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT)
|
||||
#
|
||||
# at your option. This file may not be copied, modified, or distributed except according to those terms.
|
||||
|
||||
{.used.}
|
||||
|
||||
import
|
||||
../stew/arraybuf,
|
||||
unittest2
|
||||
|
||||
suite "ArrayBuf":
|
||||
test "single evaluation":
|
||||
var v: byte = 0
|
||||
proc f(): ArrayBuf[33, byte] =
|
||||
v += 1
|
||||
result.add v
|
||||
|
||||
# check doesn't support `openArray` (!)
|
||||
doAssert f().data() == [byte 1]
|
||||
|
||||
test "overflow add":
|
||||
var v: ArrayBuf[2, byte]
|
||||
v.add(byte 0)
|
||||
doAssert v.data() == [byte 0]
|
||||
|
||||
v.add([byte 1, 2, 3])
|
||||
|
||||
doAssert v.data() == [byte 0, 1]
|
||||
|
||||
v.add(byte 4)
|
||||
doAssert v.data() == [byte 0, 1]
|
Loading…
Reference in New Issue