Documentation, usability updates, and removing debug code.

+ First pass at documenting the Buffer class.
+ Starting Arena documentation.
+ Various usability updates, such as overloading operators.
+ Remove debug traces.
This commit is contained in:
Kyle Isom 2023-10-09 22:41:07 -07:00
parent 0f1eff514d
commit 386869df44
8 changed files with 344 additions and 90 deletions

View File

@ -268,7 +268,8 @@ operator<<(std::ostream &os, Arena &arena)
{ {
auto cursor = arena.Store(); auto cursor = arena.Store();
char cursorString[33] = {0}; char cursorString[33] = {0};
snprintf(cursorString, 32, "%#016llx", cursor); snprintf(cursorString, 32, "%#016llx",
(long long unsigned int)cursor);
os << "Arena<"; os << "Arena<";
switch (arena.Type()) { switch (arena.Type()) {
@ -328,4 +329,4 @@ Arena::Write(const char *path)
} }
} // namespace klib } // namespace klib

35
Arena.h
View File

@ -1,9 +1,29 @@
/// @file Arena.h ///
/// @author K. Isom /// \file Arena.h
/// @brief Memory management using an arena. /// \author K. Isom
/// @section DESCRIPTION /// \date 2023-10-06
/// \brief Memory management using an arena.
///
/// \section COPYRIGHT
/// Copyright 2023 K. Isom <kyle@imap.cc>
///
/// Permission to use, copy, modify, and/or distribute this software for
/// any purpose with or without fee is hereby granted, provided that the
/// above copyright notice and this permission notice appear in all copies.
///
/// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
/// WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
/// WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR
/// BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES
/// OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
/// WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
/// ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
/// SOFTWARE.
///
/// \section DESCRIPTION
/// Arena defines a memory management backend for pre-allocating memory. /// Arena defines a memory management backend for pre-allocating memory.
#ifndef KIMODEM_ARENA_H #ifndef KIMODEM_ARENA_H
#define KIMODEM_ARENA_H #define KIMODEM_ARENA_H
@ -17,13 +37,20 @@
namespace klib { namespace klib {
/// \enum ArenaType describes the type of #Arena.
enum ArenaType : uint8_t { enum ArenaType : uint8_t {
/// ARENA_UNINIT is an unintialized arena.
ARENA_UNINIT, ARENA_UNINIT,
/// ARENA_STATIC is an arena backed by a static block of memory.
ARENA_STATIC, ARENA_STATIC,
/// ARENA_ALLOC is an arena backed by allocated memory.
ARENA_ALLOC, ARENA_ALLOC,
/// ARENA_MMAP is an arena backed by a memory-mapped file.
ARENA_MMAP, ARENA_MMAP,
}; };
/// Arena is the class that implements a memory arena.
class Arena { class Arena {
public: public:
Arena(); Arena();

122
Buffer.cc
View File

@ -1,6 +1,24 @@
// ///
// Created by kyle on 2023-10-09. /// \file Buffer.cc
// /// \author K. Isom <kyle@imap.cc>
/// \date 2023-10-09
///
/// \section COPYRIGHT
/// Copyright 2023 K. Isom <kyle@imap.cc>
///
/// Permission to use, copy, modify, and/or distribute this software for
/// any purpose with or without fee is hereby granted, provided that the
/// above copyright notice and this permission notice appear in all copies.
///
/// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
/// WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
/// WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR
/// BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES
/// OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
/// WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
/// ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
/// SOFTWARE.
///
#include <cassert> #include <cassert>
#include <cstring> #include <cstring>
@ -24,8 +42,6 @@ nearestPower(size_t x)
return 0; return 0;
} }
std::cout << "x -> ";
x--; x--;
x |= x >> 1; x |= x >> 1;
@ -35,28 +51,26 @@ nearestPower(size_t x)
x |= x >> 16; x |= x >> 16;
x |= x >> 32; x |= x >> 32;
std::cout << x + 1 << std::endl;
return x + 1; return x + 1;
} }
Buffer::Buffer() Buffer::Buffer()
: contents(nullptr), length(0), capacity(0) : contents(nullptr), length(0), capacity(0), autoTrim(true)
{ {
this->Resize(defaultCapacity); this->Resize(defaultCapacity);
} }
Buffer::Buffer(size_t initialCapacity) Buffer::Buffer(size_t initialCapacity)
: contents(nullptr), length(0), capacity(0) : contents(nullptr), length(0), capacity(0), autoTrim(true)
{ {
this->Resize(initialCapacity); this->Resize(initialCapacity);
} }
Buffer::Buffer(const char *data) Buffer::Buffer(const char *data)
: contents(nullptr), length(0), capacity(0) : contents(nullptr), length(0), capacity(0), autoTrim(true)
{ {
size_t datalen = strnlen(data, maxReasonableLine); size_t datalen = strnlen(data, maxReasonableLine);
@ -64,6 +78,13 @@ Buffer::Buffer(const char *data)
} }
Buffer::Buffer(const std::string s)
: contents(nullptr), length(0), capacity(0), autoTrim(true)
{
this->Append(s);
}
bool bool
Buffer::Append(const char *s) Buffer::Append(const char *s)
{ {
@ -72,8 +93,16 @@ Buffer::Append(const char *s)
return this->Append((uint8_t *) s, slen); return this->Append((uint8_t *) s, slen);
} }
bool bool
Buffer::Append(uint8_t *data, size_t datalen) Buffer::Append(const std::string s)
{
return this->Append((const uint8_t *) s.c_str(), s.size());
}
bool
Buffer::Append(const uint8_t *data, const size_t datalen)
{ {
auto resized = false; auto resized = false;
auto newCap = this->mustGrow(datalen); auto newCap = this->mustGrow(datalen);
@ -89,15 +118,16 @@ Buffer::Append(uint8_t *data, size_t datalen)
return resized; return resized;
} }
bool bool
Buffer::Append(uint8_t c) Buffer::Append(const uint8_t c)
{ {
return this->Append(&c, 1); return this->Append(&c, 1);
} }
bool bool
Buffer::Insert(size_t index, const char *s) Buffer::Insert(const size_t index, const char *s)
{ {
size_t slen = strnlen(s, maxReasonableLine); size_t slen = strnlen(s, maxReasonableLine);
@ -106,7 +136,14 @@ Buffer::Insert(size_t index, const char *s)
bool bool
Buffer::Insert(size_t index, uint8_t *data, size_t datalen) Buffer::Insert(const size_t index, const std::string s)
{
return this->Insert(index, (const uint8_t *) s.c_str(), s.size());
}
bool
Buffer::Insert(const size_t index, const uint8_t *data, const size_t datalen)
{ {
auto resized = this->shiftRight(index, datalen); auto resized = this->shiftRight(index, datalen);
@ -115,15 +152,16 @@ Buffer::Insert(size_t index, uint8_t *data, size_t datalen)
return resized; return resized;
} }
bool bool
Buffer::Insert(size_t index, uint8_t c) Buffer::Insert(const size_t index, const uint8_t c)
{ {
return this->Insert(index, &c, 1); return this->Insert(index, &c, 1);
} }
bool bool
Buffer::Remove(size_t index, size_t count) Buffer::Remove(const size_t index, const size_t count)
{ {
auto resized = this->shiftLeft(index, count); auto resized = this->shiftLeft(index, count);
@ -180,6 +218,7 @@ Buffer::Clear()
if (this->length == 0) { if (this->length == 0) {
return; return;
} }
memset(this->contents, 0, this->length); memset(this->contents, 0, this->length);
this->length = 0; this->length = 0;
} }
@ -187,23 +226,16 @@ Buffer::Clear()
void void
Buffer::Reclaim() Buffer::Reclaim()
{ {
std::cout << "clear" << std::endl;
this->Clear(); this->Clear();
std::cout << "nullptr check" << std::endl;
if (this->contents == nullptr) { if (this->contents == nullptr) {
std::cout << "assert checks" << std::endl;
assert(this->length == 0); assert(this->length == 0);
assert(this->capacity == 0); assert(this->capacity == 0);
return; return;
} }
std::cout << "delete " << this->Capacity() << "B" << std::endl;
this->HexDump(std::cout);
delete this->contents; delete this->contents;
std::cout << "reset contents" << std::endl;
this->contents = nullptr; this->contents = nullptr;
std::cout << "reset capacity" << std::endl;
this->capacity = 0; this->capacity = 0;
} }
@ -223,13 +255,13 @@ void
Buffer::HexDump(std::ostream &os) Buffer::HexDump(std::ostream &os)
{ {
#ifndef NDEBUG #ifndef NDEBUG
size_t index = 0; size_t index = 0;
os << std::hex; os << std::hex;
os << std::setfill('0'); os << std::setfill('0');
for (index = 0; index < this->length; index++) { for (index = 0; index < this->length; index++) {
bool eol = (index % 16) == 0; bool eol = (index % 16) == 0;
if (eol && (index > 0)) { if (eol && (index > 0)) {
os << std::endl; os << std::endl;
} }
@ -240,7 +272,7 @@ Buffer::HexDump(std::ostream &os)
os << std::setw(2); os << std::setw(2);
} }
os << (unsigned short)this->contents[index]; os << (unsigned short) this->contents[index];
if ((index % 15) != 0 || (index == 0)) { if ((index % 15) != 0 || (index == 0)) {
os << " "; os << " ";
@ -268,7 +300,8 @@ Buffer::shiftRight(size_t offset, size_t delta)
if (this->length == 0) return 0; if (this->length == 0) return 0;
memmove(this->contents + (offset + delta), this->contents + offset, this->length); memmove(this->contents + (offset + delta), this->contents + offset,
this->length);
return resized; return resized;
} }
@ -276,21 +309,44 @@ Buffer::shiftRight(size_t offset, size_t delta)
bool bool
Buffer::shiftLeft(size_t offset, size_t delta) Buffer::shiftLeft(size_t offset, size_t delta)
{ {
// for (size_t i = offset; i < this->length; i++) { memmove(this->contents + offset, this->contents + (offset + delta),
// this->contents[i] = this->contents[i+delta]; this->length);
// }
memmove(this->contents + offset, this->contents + (offset + delta), this->length); if (this->AutoTrimIsEnabled()) {
return this->Trim() != 0;
return this->Trim() != 0; }
return false;
} }
uint8_t & uint8_t &
Buffer::operator[](size_t index) Buffer::operator[](size_t index)
{ {
if (index > this->length) {
throw std::range_error("array index out of bounds");
}
return this->contents[index]; return this->contents[index];
} }
bool
operator==(const Buffer &lhs, const Buffer &rhs)
{
if (lhs.length != rhs.length) {
return false;
}
return memcmp(lhs.contents, rhs.contents, rhs.length) == 0;
}
std::ostream &
operator<<(std::ostream &os, const Buffer &buf)
{
// std::string s((const char *)buf.Contents(), buf.Length());
os << const_cast<uint8_t *>(buf.Contents());
return os;
}
} // namespace klib } // namespace klib

233
Buffer.h
View File

@ -1,6 +1,30 @@
// ///
// Created by kyle on 2023-10-09. /// \file Buffer.hcc
// /// \author K. Isom <kyle@imap.cc>
/// \date 2023-10-09
/// \brief Buffer implements basic line buffers.
///
/// \section COPYRIGHT
/// Copyright 2023 K. Isom <kyle@imap.cc>
///
/// Permission to use, copy, modify, and/or distribute this software for
/// any purpose with or without fee is hereby granted, provided that the
/// above copyright notice and this permission notice appear in all copies.
///
/// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
/// WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
/// WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR
/// BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES
/// OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
/// WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
/// ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
/// SOFTWARE.
///
/// \section DESCRIPTION
/// Buffer implements a basic uint8_t line buffer that is intended for use in text
/// editing. It allocates memory in powers of two, and will grow or shrink
/// as needed.
///
#ifndef KGE_BUFFER_H #ifndef KGE_BUFFER_H
#define KGE_BUFFER_H #define KGE_BUFFER_H
@ -11,50 +35,197 @@
namespace klib { namespace klib {
/// Buffer is a basic line buffer.
///
/// The buffer manages its own internal memory, growing and shrinking
/// as needed. Its capacity is separate from its length; the optimal
/// capacity is determined as the nearest power of two that is greater
/// than the length of the buffer. For example, if the buffer has a
/// length of 5 bytes, 8 bytes will be allocated. If the buffer is 9
/// bytes, 16 bytes will be allocated.
///
/// The #Append and #Insert methods will call #Resize as necessary to grow
/// the buffer. Similarly the #Remove methods will call #Trim to reclaim some
/// memory if possible, but only if #AutoTrimIsEnabled (it is by default).
class Buffer { class Buffer {
public: public:
/// A Buffer can be constructed empty, with no memory allocated (yet).
Buffer(); Buffer();
explicit Buffer(size_t);
explicit Buffer(const char *);
~Buffer() { this->Reclaim(); }
uint8_t *Contents() { return this->contents; } /// A Buffer can be constructed with an explicit capacity.
size_t Length() const { return this->length; }; ///
size_t Capacity() const { return this->capacity; } /// \param initialCapacity The initial allocation size for the buffer.
explicit Buffer(size_t initialCapacity);
bool Append(const char *s); /// A Buffer can be initialized with a starting C-style string.
bool Append(uint8_t *data, size_t datalen); explicit Buffer(const char *s);
bool Append(uint8_t c);
bool Insert(size_t index, const char *s); /// A Buffer can be initialized with a starting string.
bool Insert(size_t index, uint8_t *data, size_t datalen); explicit Buffer(const std::string s);
bool Insert(size_t index, uint8_t c);
bool Remove(size_t index, size_t count); ~Buffer()
bool Remove(size_t index); // remove single char { this->Reclaim(); }
/// Contents returns the Buffer's contents.
uint8_t *Contents() const
{ return this->contents; }
/// Length returns the length of the data currently stored in the
/// buffer.
size_t Length() const
{ return this->length; };
/// Capacity returns the amount of memory allocated to the Buffer.
size_t Capacity() const
{ return this->capacity; }
/// Append copies in a C-style string to the end of the buffer.
///
/// \param s The string to append.
/// \return True if the Buffer was resized.
bool Append(const char *s);
/// Append copies in a string to the end of the buffer.
///
/// \param s The string to append.
/// \return True if the Buffer was resized.
bool Append(const std::string s);
/// Append copies in a byte buffer to the end of the buffer.
///
/// \param data The byte buffer to insert.
/// \param datalen The length of the byte buffer.
/// \return True if the Buffer was resized.
bool Append(const uint8_t *data, const size_t datalen);
/// Append copies a single character to the end of the buffer.
///
/// \param c The character to append.
/// \return True if the Buffer was resized.
bool Append(const uint8_t c);
/// Insert copies a C-style string into the buffer at index.
///
/// \param index The index to insert the string at.
/// \param s The string to insert.
/// \return True if the Buffer was resized.
bool Insert(const size_t index, const char *s);
/// Insert copies a string into the buffer at index.
///
/// \param index The index the string should be inserted at.
/// \param s The string to insert.
/// \return True if the Buffer was resized.
bool Insert(const size_t index, const std::string s);
/// Insert copies a uint8_t buffer into the buffer at index.
///
/// \param index The index to insert the buffer at.
/// \param data The buffer to insert.
/// \param datalen The size of the data buffer.
/// \return True if the Buffer was resized.
bool
Insert(const size_t index, const uint8_t *data, const size_t datalen);
/// Insert copies a character into the buffer at index.
///
/// \param index The index to insert the character at.
/// \param c The character to insert.
/// \return True if the Buffer was resized.
bool Insert(const size_t index, const uint8_t c);
/// Remove removes `count` bytes from the buffer at `index`.
///
/// \param index The starting index to remove bytes from.
/// \param count The number of bytes to remove.
/// \return True if the Buffer was resized.
bool Remove(const size_t index, const size_t count);
/// Remove removes a single byte from the buffer.
///
/// \param index The index pointing to the byte to be removed.
/// \return True if the Buffer was resized.
bool Remove(size_t index); // remove single char
/* memory management */ /* memory management */
void Resize(size_t newCapacity);
size_t Trim();
void Clear();
void Reclaim();
void HexDump(std::ostream &os); /// Resize changes the capacity of the buffer to `newCapacity`.
///
/// If newCapacity is less than the length of the Buffer, it
/// will remove enough bytes from the end to make this happen.
///
/// \param newCapacity The new capacity for the Buffer.
void Resize(size_t newCapacity);
/// Trim will resize the Buffer to an appropriate size based on
/// its length.
///
/// \return The new capacity of the Buffer.
size_t Trim();
/// DisableAutoTrim prevents the #Buffer from automatically
/// trimming memory after a call to #Remove.
void DisableAutoTrim()
{ this->autoTrim = false; }
/// EnableAutoTrim enables automatically trimming memory after
/// calls to #Remove.
void EnableAutoTrim()
{ this->autoTrim = true; }
/// AutoTrimIsEnabled returns true if autotrim is enabled.
///
/// \return #Remove will call Trim.
bool AutoTrimIsEnabled()
{ return this->autoTrim; }
/// Clear removes the data stored in the buffer. It will not
/// call #Trim; the capacity of the buffer will not be altered.
void Clear();
/// Reclaim the memory in the buffer: the buffer will call #Clear,
/// followed by deleting any allocated memory.
void Reclaim();
/// HexDump dumps the data in the buffer to the output stream;
/// it is intended as a debugging tool.
///
/// \param os The output stream to write to.
void HexDump(std::ostream &os);
/// This operator allows the data in the buffer to be accessed
/// as if it were an array. If the index is out of bounds, it
/// will throw a range_error.
///
/// \throws std::range_error.
///
/// \param index The index to retrieve.
/// \return
uint8_t &operator[](size_t index);
/// Two buffers are equal if their lengths are the same and
/// their contents are the same. Equality is irrespective of
/// their capacities.
friend bool operator==(const Buffer &lhs, const Buffer &rhs);
uint8_t &operator[](size_t index);
private: private:
size_t mustGrow(size_t delta) const; size_t mustGrow(size_t delta) const;
bool shiftRight(size_t offset, size_t delta);
bool shiftLeft(size_t offset, size_t delta);
uint8_t *contents; bool shiftRight(size_t offset, size_t delta);
size_t length;
size_t capacity; bool shiftLeft(size_t offset, size_t delta);
uint8_t *contents;
size_t length;
size_t capacity;
bool autoTrim;
}; };
std::ostream &operator<<(std::ostream &os, const Buffer &buf);
inline bool operator!=(const Buffer &lhs, const Buffer &rhs) { return !(lhs == rhs); };
} // namespace klib } // namespace klib
#endif //KGE_BUFFER_H
#endif // KGE_BUFFER_H

View File

@ -4,7 +4,7 @@ find_package(Doxygen)
if (${DOXYGEN_FOUND}) if (${DOXYGEN_FOUND})
doxygen_add_docs(klib_docs doxygen_add_docs(klib_docs
${HEADER_FILES} ${SOURCE_FILES} ${HEADER_FILES} ${SOURCE_FILES})
USE_STAMP_FILE) # USE_STAMP_FILE)
endif () endif ()

View File

@ -33,10 +33,10 @@ AssertionFailed::AssertionFailed(std::string message) : msg(message)
} }
char * char *
AssertionFailed::what() const noexcept AssertionFailed::what()
{ {
return const_cast<char *>(this->msg.c_str()); return const_cast<char *>(this->msg.c_str());
} }
} // namespace klib } // namespace klib

2
Test.h
View File

@ -19,7 +19,7 @@ class AssertionFailed : public std::exception {
public: public:
explicit AssertionFailed(std::string message); explicit AssertionFailed(std::string message);
char *what() const noexcept override; char *what();
public: public:
std::string msg; std::string msg;

View File

@ -1,6 +1,8 @@
#include <cassert>
#include <iostream> #include <iostream>
#include "Buffer.h" #include "Buffer.h"
using namespace klib;
int int
@ -9,35 +11,26 @@ main(int argc, char *argv[])
(void) argc; (void) argc;
(void) argv; (void) argv;
klib::Buffer buffer("hlo, world"); Buffer buffer("hlo, world");
std::cout << buffer.Contents() << std::endl; std::cout << buffer << std::endl;
buffer.Insert(1, (uint8_t *) "el", 2); buffer.Insert(1, (uint8_t *) "el", 2);
buffer.Append('!'); buffer.Append('!');
std::cout << buffer.Contents() << std::endl; std::cout << buffer << std::endl;
std::cout << "remove end" << std::endl;
buffer.Remove(buffer.Length() - 1); buffer.Remove(buffer.Length() - 1);
std::cout << "remove start" << std::endl;
buffer.Remove(0, 5); buffer.Remove(0, 5);
std::cout << "insert char" << std::endl;
buffer.Insert(0, 'g'); buffer.Insert(0, 'g');
std::cout << "insert chunk" << std::endl;
buffer.Insert(1, (uint8_t *) "oodbye", 6); buffer.Insert(1, (uint8_t *) "oodbye", 6);
std::cout << "cruel" << std::endl; std::cout << buffer << std::endl;
buffer.Insert(9, (uint8_t *)"cruel ", 6); buffer.Insert(9, (uint8_t *)"cruel ", 6);
std::cout << buffer.Contents() << std::endl;
std::cout << buffer << std::endl;
buffer.HexDump(std::cout); buffer.HexDump(std::cout);
std::cout << "reclaim" << std::endl;
buffer.Reclaim(); buffer.Reclaim();
std::cout << "append" << std::endl;
buffer.Append("and now for something completely different..."); buffer.Append("and now for something completely different...");
std::cout << buffer.Contents() << std::endl; std::cout << buffer.Contents() << std::endl;
@ -47,6 +40,12 @@ main(int argc, char *argv[])
buffer.Trim(); buffer.Trim();
std::cout << "Length: " << buffer.Length() << ", capacity " << buffer.Capacity() << std::endl; std::cout << "Length: " << buffer.Length() << ", capacity " << buffer.Capacity() << std::endl;
Buffer buffer2("and now for something completely different...");
assert(buffer == buffer2);
buffer2.Remove(buffer2.Length()-3, 3);
std::cout << buffer << std::endl;
assert(buffer != buffer2);
return 0; return 0;
} }