Replace GapBuffer with TextBuffer.
This commit is contained in:
parent
581b83fe54
commit
2db942f47d
@ -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
168
src/jes/core/textbuffer.d
Normal 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));
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user