From 6349423329c5ef7854a4ad00c44465c0cc81b3ed Mon Sep 17 00:00:00 2001 From: Josh Holtrop Date: Fri, 6 Dec 2019 22:17:37 -0500 Subject: [PATCH] add gapbuffer --- .gitignore | 1 + Makefile | 5 + Rsconscript | 6 +- src/jes/core/gapbuffer.d | 229 +++++++++++++++++++++++++++++++++++++++ 4 files changed, 240 insertions(+), 1 deletion(-) create mode 100644 src/jes/core/gapbuffer.d diff --git a/.gitignore b/.gitignore index 0e6670b..f428bfd 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,4 @@ /test/tmp/ /coverage/ /jes +/jestest diff --git a/Makefile b/Makefile index 35a399a..2277f0d 100644 --- a/Makefile +++ b/Makefile @@ -5,3 +5,8 @@ all: .PHONY: clean clean: ./rscons clean + +.PHONY: test +test: + ./rscons build + ./jestest diff --git a/Rsconscript b/Rsconscript index 527bdce..0999d1c 100644 --- a/Rsconscript +++ b/Rsconscript @@ -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 diff --git a/src/jes/core/gapbuffer.d b/src/jes/core/gapbuffer.d new file mode 100644 index 0000000..2a35675 --- /dev/null +++ b/src/jes/core/gapbuffer.d @@ -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); + } +}