add gapbuffer

This commit is contained in:
Josh Holtrop 2019-12-06 22:17:37 -05:00
parent f650256986
commit 6349423329
4 changed files with 240 additions and 1 deletions

1
.gitignore vendored
View File

@ -3,3 +3,4 @@
/test/tmp/
/coverage/
/jes
/jestest

View File

@ -5,3 +5,8 @@ all:
.PHONY: clean
clean:
./rscons clean
.PHONY: test
test:
./rscons build
./jestest

View File

@ -3,9 +3,13 @@ configure do
end
build do
Rscons::Environment.new do |env|
main_env = Rscons::Environment.new do |env|
env["sources"] = glob("src/**/*.{c,d}")
env["D_IMPORT_PATH"] += glob("src/**")
env.Program("jes", "${sources}")
end
main_env.clone do |env|
env["DFLAGS"] += %w[-g -funittest]
env.Program("jestest", "${sources}")
end
end

229
src/jes/core/gapbuffer.d Normal file
View File

@ -0,0 +1,229 @@
module jes.core.gapbuffer;
import core.stdc.string;
/**
* A GapBuffer object maintains a buffer in memory with a gap in the middle
* where quick insertions can be done.
*
* The GapBuffer grows automatically as data is added.
*/
class GapBuffer
{
/** Number of bytes to initially allocate for an empty gap buffer. */
enum size_t INIT_ALLOC = 8u;
/** Number of extra bytes to allocate when growing. */
enum size_t EXTRA_ALLOC = 2000u;
/** Size of data. */
private size_t m_size;
/** Data. */
private ubyte m_data[];
/** Gap position. */
private size_t m_gap_position;
/**
* Construct a GapBuffer object that is initially empty.
*
* @param alloc Number of bytes to initially allocate.
*/
this(size_t alloc = INIT_ALLOC)
{
m_data = new ubyte[alloc];
}
/**
* Construct a GapBuffer object using the dynamic array passed in.
*
* @param data Initial data to use to populate the gap buffer.
*/
this(ubyte data[])
{
m_data = data;
m_size = data.length;
m_gap_position = m_size;
}
/**
* Get the address of a byte in the gap buffer.
*
* @param offset Offset of data in the gap buffer.
*/
ubyte * address(size_t offset)
{
if (offset < m_gap_position)
{
return &m_data[offset];
}
else
{
return &m_data[offset + gap_size()];
}
}
/**
* Move the gap to the end of the buffer so that all data is congruent.
*/
void compact()
{
move_gap(m_size);
}
/**
* Ensure that there are at least length bytes free.
*
* @param length The number of bytes that must be free.
*/
private void ensure_free(size_t length)
{
if (gap_size() < length)
{
m_data.length = m_size + length + EXTRA_ALLOC;
}
}
/**
* Erase data from the gap buffer.
*
* @param position The index of the data to remove.
* @param length The length of the data to remove.
*/
void erase(size_t position, size_t length)
{
if ((position < m_size) && ((position + length) <= m_size))
{
move_gap(position);
m_size -= length;
}
}
/**
* Calculate the size of the gap in the gap buffer.
*/
private size_t gap_size() const
{
return m_data.length - m_size;
}
/**
* Insert data into the gap buffer.
*
* @param position The index at which to insert the data.
* @param data The data to insert.
* @param length The length of the data to insert.
*/
void insert(size_t position, const ubyte * data, size_t length)
{
if (position <= m_size)
{
ensure_free(length);
move_gap(position);
memcpy(&m_data[position], data, length);
m_gap_position += length;
m_size += length;
}
}
/**
* Move the gap in the gap buffer to the requested position.
*
* @param position The buffer index to move the gap to.
*/
private void move_gap(size_t position)
{
if (position < m_gap_position)
{
memmove(&m_data[position + gap_size()], &m_data[position], m_gap_position - position);
m_gap_position = position;
}
else if (position > m_gap_position)
{
memmove(&m_data[m_gap_position], &m_data[m_gap_position + gap_size()], position - m_gap_position);
m_gap_position = position;
}
}
/**
* Get the size of the data in the gap buffer.
*/
size_t size() const
{
return m_size;
}
version(unittest) invariant
{
assert(m_data !is null);
assert(m_size <= m_data.length);
assert(m_gap_position <= m_size);
}
unittest
{
GapBuffer gb = new GapBuffer();
assert(gb.size() == 0u);
ubyte[20] dat = 5u;
gb.insert(0u, dat.ptr, dat.length);
assert(gb.size() == 20u);
for (size_t i = 0u; i < 20u; i++)
{
assert(*gb.address(i) == 5u);
}
dat[] = 10u;
gb.insert(5u, dat.ptr, dat.length);
assert(gb.size() == 40u);
for (size_t i = 0u; i < 40u; i++)
{
if (i < 5u)
{
assert(*gb.address(i) == 5u);
}
else if (i < 25u)
{
assert(*gb.address(i) == 10u);
}
else
{
assert(*gb.address(i) == 5u);
}
}
gb.erase(15u, 15u);
assert(gb.size() == 25u);
for (size_t i = 0u; i < 25u; i++)
{
if (i < 5u)
{
assert(*gb.address(i) == 5u);
}
else if (i < 15u)
{
assert(*gb.address(i) == 10u);
}
else
{
assert(*gb.address(i) == 5u);
}
}
assert((gb.address(24u) - gb.address(0u)) > 24u);
gb.compact();
assert((gb.address(24u) - gb.address(0u)) == 24u);
gb.insert(30u, dat.ptr, dat.length);
assert(gb.size() == 25u);
}
unittest
{
ubyte[10] dat = 22u;
GapBuffer gb = new GapBuffer(dat);
assert(gb.size() == 10u);
assert(*gb.address(5u) == 22u);
ubyte[5] newdat = 11u;
gb.insert(0u, newdat.ptr, newdat.length);
assert(gb.size() == 15u);
assert(*gb.address(3u) == 11u);
assert(*gb.address(10u) == 22u);
}
}