## SliceMem represents a slice of memory, but it includes a reference to the ## original container (of any type) or even a custom destructor, so memory is ## properly freed when it's no longer in use. ## ## It's called "SliceMem" because "MemSlice" is taken by std/memfiles. import std/strformat import std/hashes import std/macros type SliceMem*[T] = object data*: ptr UncheckedArray[T] byte_len*: int destroy_ref: ref CustomDestructor CustomDestructor = object destroy: proc() {.closure, raises: [].} proc `=destroy`(s: var CustomDestructor) = if s.destroy != nil: s.destroy() s.destroy = nil template toInt(p: pointer): int = cast[int](p) # template toPointer(i: int): pointer = cast[pointer](p) template toPointer*(s: SliceMem): pointer = s.data.pointer proc newSliceMem*[T, U](container: sink U; p: pointer, byte_len: int): SliceMem[T] = ## Create a SliceMem from a container, a pointer, and a length in bytes. result = SliceMem[T]( data: cast[ptr UncheckedArray[T]](p), byte_len: byte_len, destroy_ref: (ref CustomDestructor)(destroy: proc()= # we don't actually need to destroy the container here, destroying # the closure will do it for us. Calling it allows us to use custom # destructors though. discard container ), ) proc newSliceMem*[T](p: ptr T, byte_len: int, destructor: proc() {.closure, raises: [].}): SliceMem[T] = ## Create a SliceMem from a pointer to a type, a length in bytes, and a ## destructor closure. result = SliceMem[T]( data: cast[ptr UncheckedArray[T]](p), byte_len: byte_len, destroy_ref: (ref CustomDestructor)(destroy: destructor), ) proc newSliceMem*(p: pointer, byte_len: int, destructor: proc() {.closure, raises: [].}): SliceMem[byte] {.inline.} = newSliceMem(cast[ptr byte](p), byte_len, destructor) template newSliceMem*(container: not pointer, p, byte_len: untyped): untyped = ## Template to automatically determine the type for `newSliceMem[T,U]` newSliceMem[typeof(container.items)](container, p, byte_len) proc newSliceMem*[T, U](container: sink U; first, last: pointer): SliceMem[T] = ## Create a SliceMem from a container and the two pointers of the first and ## last elements in the container. Usually you will want to use `toSliceMem` ## instead. result = SliceMem[T]( data: cast[ptr UncheckedArray[T]](first), byte_len: (last.toInt -% first.toInt) + sizeof(T), destroy_ref: (ref CustomDestructor)(destroy: proc()= discard container ), ) proc newSliceMem*[T](size: int): SliceMem[T] = ## Create a SliceMem from new memory, similar to ArrRef[T]. var r: ref byte unsafeNew(r, size * sizeof(T)) result = SliceMem[T]( data: cast[ptr UncheckedArray[T]](r), byte_len: size * sizeof(T), destroy_ref: (ref CustomDestructor)(destroy: proc()= discard r ), ) macro noMove(e: untyped): untyped = if e.kind == nnkCommand and e[0].repr == "move": e[1] else: e template toSliceMem*(container, slice: untyped): untyped = ## Create a SliceMem from a container and a slice that indicates the range. ## The container needs to have `[]` and `items()` (like `seq`). If it doesn't ## you need to pass pointers directly to `newSliceMem` instead. if slice.len != 0: # TODO: check that with boundChecks:off it doesn't try to read the value let first = noMove(container)[slice.a].addr let last = noMove(container)[slice.b].addr newSliceMem[typeof(noMove(container).items)](container, first, last) else: SliceMem[typeof(container.items)]() template toSliceMem*(container): untyped = ## Create a SliceMem from a container, with all its contents. The container ## needs to have `[]` and `items()` (like `seq`). If it doesn't you need to ## pass pointers directly to `newSliceMem` instead. let s = noMove(container).low .. noMove(container).high toSliceMem(container, s) template len*[T](s: SliceMem[T]): Natural = s.byte_len div sizeof(T) template low*[T](s: SliceMem[T]): Natural = 0 template high*[T](s: SliceMem[T]): int = s.len - 1 proc `[]`*[T](s: SliceMem[T], i: Natural): var T {.inline.} = when compileOption("rangechecks"): if i >= s.len: raise newException(RangeDefect, &"index out of range: {i} >= {s.len}") s.data[i] proc `[]=`*[T](s: SliceMem[T], i: Natural, v: T) {.inline.} = when compileOption("rangechecks"): if i >= s.len: raise newException(RangeDefect, &"index out of range: {i} >= {s.len}") s.data[i] = v template `[]`*[T](s: SliceMem[T], i: BackwardsIndex): T = s[s.len - i.int] template `[]=`*[T](s: SliceMem[T], i: BackwardsIndex, v: T) = s[s.len - i.int] = v template `[]`*[T](s: SliceMem[T], i: BackwardsIndex): T = s[s.len - i.int] template `[]=`*[T](s: SliceMem[T], i: BackwardsIndex, v: T) = s[s.len - i.int] = v template `[]`*[T](a: SliceMem[T], s: Slice[int]): var SliceMem[T] = toSliceMem(a, s) iterator items*[T](s: SliceMem[T]): T = for i in 0 ..< s.len: yield s.data[i] iterator pairs*[T](s: SliceMem[T]): tuple[key: int, val: T] = for i in 0 ..< s.len: yield (i, s.data[i]) iterator mitems*[T](s: SliceMem[T]): var T = for i in 0 ..< s.len: yield s.data[i] iterator mpairs*[T](s: SliceMem[T]): tuple[key: int, val: var T] = for i in 0 ..< s.len: yield (i, s.data[i]) proc `$`*[T](s: SliceMem[T]): string = result = "SliceMem([" if s.byte_len != 0: let hi = s.high for i in 0 ..< hi: result &= $s.data[i] & ", " result &= $s.data[hi] result &= "])" proc hash*[T](s: SliceMem[T]): Hash = # Make use of stdlib's murmur3 hash(cast[ptr UncheckedArray[byte]](s.data).toOpenArray(0, s.byte_len - 1)) template toOpenArray*(s: SliceMem): untyped = s.data.toOpenArray(0, s.byte_len - 1) template toOpenArrayByte*(s: SliceMem): untyped = cast[ptr UncheckedArray[byte]](s.data).toOpenArray(0, s.byte_len - 1) template to*[T](s: SliceMem, typ: typedesc[T]): SliceMem[T] = cast[SliceMem[T]](s) template to*[T](s: seq[SliceMem], typ: typedesc[T]): seq[SliceMem[T]] = var s2 = newSeqOfCap[SliceMem[T]](s.len) for slice in s: s2.add cast[SliceMem[T]](slice) s2 template concatImpl(slices: untyped) = var total = 0 for s in slices: total += s.len result = newSliceMem[T](total) var offset = 0 for s in slices: copyMem(result[offset].addr, s.data, s.len * sizeof(T)) offset += s.len proc concat*[T](slices: varargs[SliceMem[T]]): SliceMem[T] = concatImpl(slices) proc concat*[T](slices: seq[SliceMem[T]]): SliceMem[T] = concatImpl(slices) template `&`*[T,U](a: SliceMem[T], b: SliceMem[U]): SliceMem[T] = concat(a, b.to(T))