Replace GapBuffer with TextBuffer.

This commit is contained in:
Josh Holtrop 2020-11-10 19:56:58 -05:00
parent 581b83fe54
commit 2db942f47d
2 changed files with 168 additions and 229 deletions

View File

@ -1,229 +0,0 @@
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);
}
}

168
src/jes/core/textbuffer.d Normal file
View File

@ -0,0 +1,168 @@
module jes.core.textbuffer;
import core.stdc.string;
import core.stdc.stdlib;
class TextBuffer
{
/** Number of bytes to initially allocate for an empty text buffer. */
enum size_t INITIAL_CAPACITY = 8u;
/** Number of extra bytes to allocate when growing. */
enum size_t GROWTH_CAPACITY = 2000u;
/** Size of data. */
private size_t m_size;
/** Underlying capacity in memory. */
private size_t m_capacity;
/** Data. */
private ubyte * m_data;
/**
* Construct a TextBuffer object that is initially empty.
*
* @param capacity The number of bytes to initially allocate.
*/
this(size_t capacity = INITIAL_CAPACITY)
{
m_data = cast(ubyte *)malloc(capacity);
}
/**
* Destructor for a TextBuffer object.
*/
~this()
{
free(m_data);
}
/**
* Get the address of a byte in the text buffer.
*
* @param offset Byte offset into the text buffer.
*/
ubyte * address(size_t offset)
{
return &m_data[offset];
}
/**
* Erase data from the text buffer.
*
* @param position The index of the data to remove.
* @param n The number of bytes to remove.
*/
void erase(size_t position, size_t n)
{
if ((position < m_size) && (position + n <= m_size))
{
memmove(&m_data[position], &m_data[position + n], m_size - (position + n));
m_size -= n;
}
}
/**
* Insert data into the text buffer.
*
* @param position The index at which to insert the data.
* @param data The data to insert.
* @param n The number of bytes to insert.
*/
void insert(size_t position, const ubyte * data, size_t n)
{
if (position <= m_size)
{
ensure_free(n);
memmove(&m_data[position + n], &m_data[position], m_size - position);
memcpy(&m_data[position], data, n);
m_size += n;
}
}
/**
* Get the size of the data in the text buffer.
*/
@property size_t size() const
{
return m_size;
}
/**
* Ensure that there are at least n free.
*
* @param n The number of bytes that must be free.
*/
private void ensure_free(size_t n)
{
if ((m_capacity - m_size) < n)
{
/* Grow the text buffer. */
m_capacity = m_size + n + GROWTH_CAPACITY;
ubyte * new_data = cast(ubyte *)malloc(m_capacity);
memcpy(new_data, m_data, m_size);
free(m_data);
m_data = new_data;
}
}
version(unittest) invariant
{
assert(m_data !is null);
assert(m_size <= m_capacity);
}
unittest
{
TextBuffer tb = new TextBuffer();
assert(tb.size == 0u);
ubyte[20] dat = 5u;
tb.insert(0u, dat.ptr, dat.length);
ubyte * first_address = tb.address(5u);
assert(tb.size() == 20u);
for (size_t i = 0u; i < 20u; i++)
{
assert(*tb.address(i) == 5u);
}
dat[] = 10u;
tb.insert(5u, dat.ptr, dat.length);
assert(tb.size() == 40u);
for (size_t i = 0u; i < 40u; i++)
{
if (i < 5u)
{
assert(*tb.address(i) == 5u);
}
else if (i < 25u)
{
assert(*tb.address(i) == 10u);
}
else
{
assert(*tb.address(i) == 5u);
}
}
tb.erase(15u, 15u);
assert(tb.size() == 25u);
for (size_t i = 0u; i < 25u; i++)
{
if (i < 5u)
{
assert(*tb.address(i) == 5u);
}
else if (i < 15u)
{
assert(*tb.address(i) == 10u);
}
else
{
assert(*tb.address(i) == 5u);
}
}
assert((tb.address(24u) - tb.address(0u)) == 24u);
tb.insert(30u, dat.ptr, dat.length);
assert(tb.size == 25u);
assert(first_address == tb.address(5u));
}
}