## SliceMem is a general purpose array that can be casted to any type, and does ## not create copies when assigned multiple times, like a reference type, unless ## you use `copy`. It can be created by itself, from another container such as ## a seq (without copying when possible), or from a pointer and a custom ## destructor. ## ## It's called "SliceMem" because "MemSlice" is taken by std/memfiles. import std/strformat import std/hashes import std/macros # for noMove() 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. runnableExamples: let x = @[1,2,3,4,5] # You can omit the [int] here because the container is iterable let s = newSliceMem[int](x, x[0].addr, x.len * sizeof(x[0])) 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, len: int, destructor: proc() {.closure, raises: [].}): SliceMem[T] = # Create a SliceMem from a pointer to a type, a length, and a destructor # closure. runnableExamples: let x = createShared(int, 5) proc destroy() = deallocShared(x) let s = newSliceMem(x, 5, destroy) result = SliceMem[T]( data: cast[ptr UncheckedArray[T]](p), byte_len: len * sizeof(T), destroy_ref: (ref CustomDestructor)(destroy: destructor), ) proc newSliceMem*(p: pointer, byte_len: int, destructor: proc() {.closure, raises: [].}): SliceMem[byte] {.inline.} = ## Create a SliceMem from a pointer without type, a length in bytes, and a ## destructor closure. Same as newSliceMem[T](...) but assumes type is byte. 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]. runnableExamples: let s = newSliceMem[int](10) for i,n in s.mpairs: n = (i+1)*11 doAssert $s == "SliceMem([11, 22, 33, 44, 55, 66, 77, 88, 99, 110])" 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 = # remove the "move" from an expression 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. runnableExamples: var x = @[1,2,3,4,5] let a = x.toSliceMem(1..3) let b = toSliceMem(move x, 1..3) # This also works, and ensures that # the contents of x are not copied {.line.}: 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. runnableExamples: var x = @[1,2,3,4,5] let a = x.toSliceMem let b = toSliceMem(move x) # This also works, and ensures that # the contents of x are not copied {.line.}: let s = noMove(container).low .. noMove(container).high when not compiles(container[s.a].addr): # expression container[x] has no address because it's a literal, # let's make it real let c = container toSliceMem(c, s) else: toSliceMem(container, s) proc copy*[T](s: SliceMem[T]): SliceMem[T] = ## Creates a copy of a SliceMem that doesn't reference the original one. result = newSliceMem[byte](s.byte_len).to(T) copyMem(result.toPointer, s.toPointer, s.byte_len) 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 the SliceMem from one type to another cast[SliceMem[T]](s) template to*[T](s: openArray[SliceMem], typ: typedesc[T]): seq[SliceMem[T]] = ## Cast a seq of SliceMems from one type to another var s2 = newSeqOfCap[SliceMem[T]](s.len) for slice in s: s2.add cast[SliceMem[T]](slice) s2 proc concat*[T](slices: varargs[SliceMem[T]]): SliceMem[T] = ## Concatenates a list of SliceMems into a single one. Contents are copied. runnableExamples: let a = @[1,2,3,4,5].toSliceMem let b = @[6,7,8,9,0].toSliceMem var x = concat(a,b) x = concat(a,b,a,a,b) # You can pass any amount of slices as arguments x = concat(@[b,b,a,a]) # or you can pass a seq 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 template `&`*[T,U](a: SliceMem[T], b: SliceMem[U]): SliceMem[T] = ## Concatenates two SliceMems (of any type) into a new one with the type of ## the first one. concat(a, b.to(T)) proc serializeToSeq*[T](slices: openArray[SliceMem[T]], align = sizeof(int)): seq[SliceMem[byte]] = ## Converts a list of SliceMems into a format that can be saved to file and ## deserialized later. See `serialize` and `deserialize`. This proc returns a ## seq that is ready to concatenate or to write to a file. let mask = align - 1 assert((align and mask) == 0, "Align must be a power of two") var offset = 0 let align_count = newSliceMem[int32](2) align_count[0] = align.int32 align_count[1] = slices.len.int32 result.add align_count.to(byte) offset += align_count.byte_len let lengths = newSliceMem[int64](slices.len) result.add lengths.to(byte) offset += lengths.byte_len let pad_bytes = newSliceMem[byte](align) for i,s in slices: let pad = ((align-(offset and mask)) and mask) if pad != 0: result.add pad_bytes[0 ..< pad] offset += pad lengths[i] = s.byte_len result.add s.to(byte) offset += s.byte_len proc serialize*[T](slices: openArray[SliceMem[T]], align = sizeof(int)): SliceMem[byte] = ## Converts a list of SliceMems into a single segment of memory that can be ## later deserialized into the separate SliceMems again. Use `deserialize` ## for the inverse process. Use `serializeToSeq` to obtain the separate ## formatted slices before they're concatenated and avoid copies. runnableExamples: let a = @[1,2,3,4,5].toSliceMem let b = @[6,7,8,9,0].toSliceMem let serialized = serialize(@[a,b]) let slices = serialized.to(int).deserialize doAssert $slices == "@[SliceMem([1, 2, 3, 4, 5]), SliceMem([6, 7, 8, 9, 0])]" concat(serializeToSeq(slices, align)) proc deserialize*[T](data: SliceMem[T]): seq[SliceMem[T]] = ## Reverts the process done by `serialize` except the conversion to byte. ## You can convert to the appropriate type before or after deserialization ## with `to` (see example in `serialize`) let bytes = data.to(byte) let i32 = data.to(int32) let align = i32[0] let count = i32[1] let mask = align - 1 assert((align and mask) == 0, "Wrong format, align is invalid") let lengths = bytes[8 ..< 8 + count*sizeof(int64)].to(int64) var offset = 8 + lengths.byte_len for i in 0 ..< count: offset += ((align-(offset and mask)) and mask) let len = lengths[i].int result.add bytes[offset ..< offset+len].to(T) offset += len template toString*(s: SliceMem): string = ## Copies the contents of the SliceMem to a new string of the same length. var str = newString(s.byte_len) if s.byte_len != 0: copyMem(str[0].addr, s.toPointer, s.byte_len) str