Skip to content

Commit

Permalink
Add generic vector class
Browse files Browse the repository at this point in the history
  • Loading branch information
edubart committed Feb 15, 2020
1 parent 1f1c6bd commit 3e80e60
Show file tree
Hide file tree
Showing 6 changed files with 221 additions and 15 deletions.
30 changes: 17 additions & 13 deletions lib/sequence.nelua
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,9 @@
-- By default its use the garbage collector unless explicitly told not to do so,
-- thus by default there is no need to manually reset the sequence.

require 'memory'

## local make_sequence = generalize(function(T, allocator)
## staticassert(traits.is_type(T), "invalid type '%s'", T)
## local codenameprefix = 'sequence_'..T.name
## local codenameprefix = 'nelua_sequence_'..T.name
## if allocator then
local allocator: type = #[allocator]#
## codenameprefix = codenameprefix..'_'..allocator.nick
Expand Down Expand Up @@ -42,8 +40,8 @@ require 'memory'
self.impl.size = 0
end

-- Reset sequence to zeroed state, freeing all used resources.
-- This is more useful free resources when not using the garbage collector.
-- Resets the sequence to zeroed state, freeing all used resources.
-- This is more useful to free resources when not using the garbage collector.
function SequenceT:reset()
if not self.impl then return end
self:clear()
Expand All @@ -65,21 +63,21 @@ require 'memory'
self.impl = (@SequenceImplT*)(allocator.alloc0(#SequenceImplT))
end

-- Reserve at least `n` elements on the sequence capacity.
-- Reserve at least `n` elements on the sequence storage.
function SequenceT:reserve(n: usize <autocast>)
self:init()
local cap: usize = n + 1
if self.impl.data.size >= cap then return end
self.impl.data = allocator.spanrealloc0(self.impl.data, cap)
end

-- Resizes the sequence so that it contains `n` elements as copies of `v`.
-- Resizes the sequence so that it contains `n` elements assigned to `v`.
function SequenceT:resize(n: usize <autocast>, v: T)
self:init()
if n <= self.impl.size then return end
self:reserve(n)
for i=self.impl.size+1,<n do
self.impl.data[i+1] = v
for i=self.impl.size+1,n do
self.impl.data[i] = v
end
self.impl.size = n
end
Expand All @@ -105,13 +103,19 @@ require 'memory'
return ret
end

-- Return reference to element at index `i`.
-- Returns the number of elements the sequence can store before triggering a reallocation.
function SequenceT:capacity(): isize <inline>
if unlikely(not self.impl or self.impl.data.size == 0) then return 0 end
return (@isize)(self.impl.data.size) - 1
end

-- Returns reference to element at index `i`.
-- If `i` is the sequence size plus 1, then a zeroed element is added and return its reference.
-- If `i` is larger then the sequence size plus 1, then throws a runtime error.
function SequenceT:__atindex(i: usize <autocast>): T* <inline>
self:init()
if unlikely(i > self.impl.size) then
check(i == self.impl.size + 1, 'sequence.at: index out of range')
check(i == self.impl.size + 1, 'sequence.__atindex: index out of range')
self.impl.size = self.impl.size + 1
end
if unlikely(self.impl.size + 1 > self.impl.data.size) then
Expand All @@ -120,13 +124,13 @@ require 'memory'
return &self.impl.data[i]
end

-- Return the number of elements in the sequence. It never counts the element at 0.
-- Returns the number of elements in the sequence. It never counts the element at 0.
function SequenceT:__len(): isize <inline>
if unlikely(not self.impl) then return 0 end
return (@isize)(self.impl.size)
end

-- Initialize a sequence elements from a fixed array.
-- Initializes sequence elements from a fixed array.
-- This allows to use sequence initialization with braces.
function SequenceT.__convert(values: #[concept(function(x)
if x.type:is_array_of(T) then return true end
Expand Down
125 changes: 125 additions & 0 deletions lib/vector.nelua
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
-- Vector type
--
-- This type is typically used as an efficient vector.
-- Its elements starts at index 0 and go up to length-1 (like C arrays),
-- at the moment this should not be passed as value, only as reference.
--
-- By default its use the garbage collector unless explicitly told not to do so,
-- thus by default there is no need to manually reset the vector.

## local make_vector = generalize(function(T, allocator)
## staticassert(traits.is_type(T), "invalid type '%s'", T)
## local codename = 'nelua_vector_'..T.name
## if allocator then
local allocator: type = #[allocator]#
## codename = codename..'_'..allocator.nick
## else
require 'allocators.gc'
local allocator: type = @gc_allocator
## end

local T = @#[T]#
local VectorT <codename #[codename]#> = @record {
size: usize,
data: span(T)
}

function VectorT:_grow()
local cap: usize = 1
if likely(self.data.size ~= 0) then cap = self.data.size * 2 end
self.data = allocator.spanrealloc0(self.data, cap)
end

-- Resets and removes all elements from the vector.
function VectorT:clear()
local zero: T
for i:usize=0,<self.size do
self.data[i] = zero
end
self.size = 0
end

-- Resets the vector to zeroed state, freeing all used resources.
-- This is more useful to free resources when not using the garbage collector.
function VectorT:reset()
self:clear()
allocator.spandealloc(self.data)
self.data = (@span(T)){}
self.size = 0
end

-- Reserve at least `n` elements on the vector storage.
function VectorT:reserve(cap: usize <autocast>)
if likely(self.data.size >= cap) then return end
self.data = allocator.spanrealloc0(self.data, cap)
end

-- Resizes the vector so that it contains `n` elements assigned to `v`.
function VectorT:resize(n: usize <autocast>, v: T)
if n <= self.size then return end
self:reserve(n)
for i=self.size,<n do
self.data[i] = v
end
self.size = n
end

-- Adds a new element at the end of the vector.
function VectorT:push(x: T)
local newsize: usize = self.size + 1
if unlikely(newsize >= self.data.size) then
self:_grow()
end
self.data[self.size] = x
self.size = newsize
end

-- Removes the last element in the vector and returns its value.
-- If the vector is empty, then throws a runtime error.
function VectorT:pop()
check(self.size > 0, 'vector.pop: length is 0')
local zero: T
self.size = self.size - 1
local ret: T = self.data[self.size]
self.data[self.size] = zero
return ret
end

-- Returns reference to element at index `i`.
-- If `i` is greater of equal to vector size, then throws a runtime error.
function VectorT:__atindex(i: usize <autocast>): T* <inline>
check(i < self.size, 'vector.at: index out of range')
return &self.data[i]
end

-- Returns the number of elements the vector can store before triggering a reallocation.
function VectorT:capacity(): isize
return (@isize)(self.data.size)
end

-- Returns the number of elements in the vector.
function VectorT:__len(): isize
return (@isize)(self.size)
end

-- Initializes vector elements from a fixed array.
-- This allows to use vector initialization with braces.
function VectorT.__convert(values: #[concept(function(x)
if x.type:is_array_of(T) then return true end
end)]#): VectorT <inline>
local self: VectorT
self:reserve(#values)
self.size = #values
for i:usize=0,<#values do
self.data[i] = values[i]
end
return self
end
##[[VectorT.value.choose_braces_type = function(node)
return types.ArrayType(nil, T, #node[1])
end]]

## return VectorT
## end)

global vector: type = #[make_vector]#
1 change: 1 addition & 0 deletions rockspecs/nelua-dev-1.rockspec
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@ build = {
['lib/math.nelua'] = 'lib/math.nelua',
['lib/memory.nelua'] = 'lib/memory.nelua',
['lib/sequence.nelua'] = 'lib/sequence.nelua',
['lib/vector.nelua'] = 'lib/vector.nelua',
['lib/mystring.nelua'] = 'lib/mystring.nelua',
['lib/mytable.nelua'] = 'lib/mytable.nelua',
['lib/os.nelua'] = 'lib/os.nelua',
Expand Down
6 changes: 6 additions & 0 deletions spec/07-stdlib_spec.lua
Original file line number Diff line number Diff line change
Expand Up @@ -16,5 +16,11 @@ end)
it("memory", function()
assert.run_c_from_file('tests/memory_test.nelua')
end)
it("sequence", function()
assert.run_c_from_file('tests/sequence_test.nelua')
end)
it("vector", function()
assert.run_c_from_file('tests/vector_test.nelua')
end)

end)
18 changes: 16 additions & 2 deletions tests/sequence_test.nelua
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,10 @@ do -- braces initializer
seq = {4}
assert(#seq == 1 and seq[0] == 0 and seq[1] == 4)
seq = {}
assert(#seq == 0)
end

do -- indexing new elements and clearing
do -- indexing new elements and clear
local seq: sequence(integer) = {}
seq[0] = 1
assert(seq[0] == 1)
Expand All @@ -32,11 +33,24 @@ do -- indexing new elements and clearing
assert(#seq == 0)
end

do -- reserve and resize
local seq: sequence(integer) = {}
assert(seq:capacity() == 0)
seq:push(1)
assert(seq:capacity() == 1)
seq:reserve(4)
assert(seq:capacity() == 4)
seq:resize(2, 1)
assert(#seq == 2 and seq[0] == 0 and seq[1] == 1 and seq[2] == 1)
seq:resize(4, 2)
assert(#seq == 4 and seq[0] == 0 and seq[1] == 1 and seq[2] == 1 and seq[3] == 2 and seq[4] == 2)
end

do -- push and pop
local seq: sequence(integer) = {}
seq:push(1)
assert(#seq == 1 and seq[0] == 0 and seq[1] == 1)
seq:pop()
assert(seq:pop() == 1)
assert(#seq == 0 and seq[0] == 0)
seq:push(1) seq:push(2) seq:push(3)
assert(#seq == 3 and seq[0] == 0 and seq[1] == 1 and seq[2] == 2 and seq[3] == 3)
Expand Down
56 changes: 56 additions & 0 deletions tests/vector_test.nelua
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
require 'vector'

do -- braces initializer
local vec: vector(integer) = {}
assert(#vec == 0 and vec:capacity() == 0)
vec = {1,2,3}
assert(#vec == 3 and vec[0] == 1 and vec[1] == 2 and vec[2] == 3)
vec = {4}
assert(#vec == 1 and vec[0] == 4)
vec = {}
assert(#vec == 0)
end

do -- reset and clear
local vec: vector(integer)
assert(#vec == 0 and vec:capacity() == 0)
vec = {1,2,3}
assert(#vec == 3 and vec:capacity() == 3)
vec:clear()
assert(#vec == 0 and vec:capacity() == 3)
vec:reset()
assert(#vec == 0 and vec:capacity() == 0)
end

do -- reserve and resize
local vec: vector(integer) = {}
assert(vec:capacity() == 0)
vec:push(1)
assert(vec:capacity() == 1)
vec:reserve(4)
assert(vec:capacity() == 4)
vec:resize(2, 1)
assert(#vec == 2 and vec[0] == 1 and vec[1] == 1)
vec:resize(4, 2)
assert(#vec == 4 and vec[0] == 1 and vec[1] == 1 and vec[2] == 2 and vec[3] == 2)
end

do -- push and pop
local vec: vector(integer)
assert(#vec == 0)
vec:push(1)
assert(#vec == 1 and vec[0] == 1 and vec:capacity() == 1)
vec:push(2)
assert(#vec == 2 and vec[0] == 1 and vec[1] == 2 and vec:capacity() == 2)
assert(vec:pop() == 2)
assert(#vec == 1 and vec[0] == 1)
assert(vec:pop() == 1)
assert(#vec == 0)
end

require 'allocators.generic'
do -- custom allocator
local vec: vector(integer, generic_allocator) = {1,2,3}
assert(#vec == 3 and vec[0] == 1 and vec[1] == 2 and vec[2] == 3)
vec:reset()
end

0 comments on commit 3e80e60

Please sign in to comment.