.cr
Crystal
(text/x-crystal)
require "./markable.cr"

# A simple dynamic array, see [Crystal's documentation](https://crystal-lang.org/api/0.32.1/Array.html) for more detail.
class Array(T) 
  @size = 0
  @capacity = 0
  # getter size, capacity
  # protected setter size

  def size
    @size
  end

  def size=(new_size)
    abort "size must be smaller than capacity" if new_size > @capacity
    @size = new_size
  end

  def capacity
    @capacity
  end

  @buffer : T* = Pointer(T).null

  def to_unsafe
    @buffer
  end

  private def recalculate_capacity
    @capacity = Allocator.block_size_for_ptr(@buffer) // sizeof(T)
  end

  private def expand(new_capacity)
    if @size > new_capacity
      abort "size must be smaller than capacity"
    end
    if @buffer.null?
      @buffer = Pointer(T).malloc_atomic(new_capacity)
    else
      @buffer = @buffer.realloc(new_capacity.to_u64)
    end
    recalculate_capacity
  end

  def initialize(initial_capacity : Int = 0)
    if initial_capacity > 0
      @buffer = Pointer(T).new(initial_capacity.to_u64)
      recalculate_capacity
    end
  end

  def self.build(capacity : Int) : self
    ary = Array(T).new(capacity)
    ary.write_barrier do
      ary.size = (yield ary.to_unsafe).to_i
    end
    ary
  end

  def self.new(size : Int, &block : Int32 -> T)
    Array(T).build(size) do |buffer|
      size.to_i.times do |i|
        buffer[i] = yield i
      end
      size
    end
  end

  def each(&block)
    @size.times do |i|
      yield @buffer[i]
    end
  end

  def each_with_index(&block)
    @size.times do |i|
      yield @buffer[i], i
    end
  end

  def reverse_each
    i = size - 1
    while i >= 0
      yield @buffer[i]
      i -= 1
    end
  end

  def clone
    Array(T).build(size) do |buffer|
      size.times do |i|
        buffer[i] = to_unsafe[i]
      end
      size
    end
  end

  def [](idx : Int)
    abort "accessing out of bounds!" unless 0 <= idx < @size
    @buffer[idx]
  end

  def []?(idx : Int) : T?
    return nil unless 0 <= idx < @size
    @buffer[idx]
  end

  def []=(idx : Int, value : T)
    abort "accessing out of bounds!" unless 0 <= idx < @size
    @buffer[idx] = value
  end

  def push(value : T)
    write_barrier do
      if @size < @capacity
        @buffer[@size] = value
      else
        expand(@size + 1)
        @buffer[@size] = value
      end
      @size += 1
    end
  end

  def clear
    write_barrier do
      @size = 0
    end
  end
  
  def join(sep : String? = "")
    str = ""
    @size.times do |i|
      str += @buffer[i]
      str += sep
    end
    str
  end

  @[NoInline]
  def mark(&block : Void* ->)
    return if @buffer.null?
    yield @buffer.as(Void*)
    {% unless T < Int || T < Struct %}
      each do |obj|
        yield obj.as(Void*)
      end
    {% end %}
  end
end