I just wrote my first Nim module and was hoping someone with some experience with Nim or similar languages could take a look. In particular I was hoping someone could speak if the use of template is idiomatic (or if they'd be better as a proc) as I have no experience using them in any other programming language.

I was also curious if it'd be a good idea to macro the two add implementations to reduce the amount of redundant code

import macros

  RingBuffer*[T] = object
    ## A ring buffer is a sequence-like type which can store
    ## up to `length` elements. After `length` elements are
    ## added to the buffer, new items will begin to replace
    ## the oldest ones (i.e. the elements at the start of the
    ## buffer).
    data: seq[T]
    # indicates where elements should be positioned in data
    head, tail: int
    size, length*: int

template adjustHead(b: expr): stmt =  
  b.head = (b.length + b.tail - b.size + 1) mod b.length;

template adjustTail(b, change: expr): stmt =  
  b.tail = (b.tail + change) mod b.length

proc newRingBuffer*[T](length: int): RingBuffer[T] =
  ## Construct a new Ringbuffer which can hold up to `length` elements
  let s = newSeq[T](length)
  RingBuffer[T](data: s, head: 0, tail: -1, size: 0, length: length)

proc `[]`*[T](b: RingBuffer[T], idx: int): T {.inline} =
  ## Get an item at index (adjusted)
  b.data[(idx + b.head) mod b.length]

proc `[]=`*[T](b: var RingBuffer[T], idx: int, item: T) {.raises: [IndexError].} =
  ## Set an item at index (adjusted)
  if idx == b.size: inc(b.size)
  elif idx > b.size: raise newException(IndexError, "Index " & $idx & " out of bound")

  b.data[(idx + b.head) mod b.length] = item

proc len*(b: RingBuffer): int = b.size

proc add*[T](b: var RingBuffer[T], item: T) =
  ## Add an element to the buffer 
  adjustTail(b, 1)
  b.data[b.tail] = item
  b.size = min(b.size + 1, b.length)

proc add*[T](b: var RingBuffer[T], data: openArray[T]) =
  ## Add elements to the buffer 
  for item in data:
    adjustTail(b, 1)
    b.data[b.tail] = item
  b.size = min(b.size + len(data), b.length)

proc pop*[T](b: var RingBuffer[T]): T =
  ## Remove an element from the buffer and return it
  result = b.data[b.tail] # Note: will throw error if oob
  adjustTail(b, -1)
  b.size -= 1

iterator `items`*[T](b: RingBuffer[T]): T =
  for i in 0..b.size - 1:
    yield b[i]

iterator `pairs`*[T](b: RingBuffer[T]): tuple[a: int, b: T] =
  for i in 0..b.size - 1:
    yield (i, b[i])

proc `@`*[T](b: RingBuffer[T]): seq[T] =
  ## Convert the buffer to a sequence
  var s = newSeq[T](b.size)
  for i, item in b:
    s[i] = item
  result = s

converter toSeq*[T](b: RingBuffer[T]): seq[T] = @b


Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Browse other questions tagged or ask your own question.