Restructure project, start importing sc3 code.

This commit is contained in:
2023-10-18 23:44:05 -07:00
parent 3122ed6ac7
commit 5f3dc6e9f6
46 changed files with 2300 additions and 66 deletions

232
include/scsl/Arena.h Normal file
View File

@@ -0,0 +1,232 @@
///
/// \file Arena.h
/// \author K. Isom <kyle@imap.cc>
/// \date 2023-10-06
/// \brief Memory management using an arena.
///
/// Arena defines a memory management backend for pre-allocating memory.
///
/// 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 PLATFORM SUPPORT
///
/// Arena will build on the major platforms, but memory-mapped files are only
/// supported on Unix-like systems. File I/O on Windows, for example, reads the
/// file into an allocated arena. See Arena::Open for more details.
#ifndef KIMODEM_ARENA_H
#define KIMODEM_ARENA_H
#include <cstddef>
#include <cstdint>
#include <iostream>
#include <sys/stat.h>
#include "Exceptions.h"
#if defined(__WIN64__) || defined(__WIN32__) || defined(WIN32)
#include <Windows.h>
#include <fileapi.h>
#endif
namespace scsl {
/// \enum ArenaType
///
/// ArenaType describes the type of \class Arena.
enum class ArenaType
: uint8_t {
/// Uninit is an unintialized arena.
Uninit,
/// Static is an arena backed by a static block of memory.
Static,
/// Alloc is an arena backed by allocated memory.
Alloc,
/// MemoryMapped is an arena backed by a memory-mapped file.
MemoryMapped,
};
/// Arena is the class that implements a memory arena.
///
/// The Arena uses the concept of a cursor to point to memory in the arena. The
/// #Start and #End methods return pointers to the start and end of the
/// arena memory.
///
/// The arena should be initialized with one of the Set methods (SetStatic,
/// SetAlloc) or one of the file-based options (Create, Open, MemoryMap). At
/// this point, no further memory management should be done until the end of the
/// arena's life, at which point Destroy should be called.
class Arena {
public:
/// An Arena is initialized with no backing memory.
Arena();
~Arena();
/// SetStatic points the arena to a chunk of memory. That memory is
/// intended to be statically allocated, e.g. via a global `static
/// uint8_t memory[memSize];`. If the arena is already backed, then
/// #Destroy will be called first.
///
/// \param mem A pointer to a section of memory, preferably statically
/// allocated.
/// \param memSize The size of the memory section.
/// \return Returns 0 on success and -1 on error.
int SetStatic(uint8_t *mem, size_t memSize);
/// SetAlloc allocates a chunk of memory for the arena; the arena takes
/// ownership. If the arena is already backed, then #Destroy will be
/// called first.
///
/// \param allocSize The size of memory to allocate.
/// \return Returns 0 on success and -1 on error.
int SetAlloc(size_t allocSize);
/// MemoryMap points the arena to a memory-mapped file. This is
/// currently only supported on Linux. If the arena is already backed,
/// then #Destroy will be called first.
///
/// \param memFileDes File descriptor to map into memory.
/// \param memSize The size of memory to map.
/// \return Returns 0 on success and -1 on error.
#if defined(__posix__) || defined(__linux__) || defined(__APPLE__)
int MemoryMap(int memFileDes, size_t memSize);
#else
int MemoryMap(int memFileDes, size_t memSize)
{ (void)memFileDes; (void)memSize; throw NotImplemented("WIN32"); }
#endif
/// Create creates a new file, truncating it if it already exists. On
/// Unix-based platforms, the arena will be backed by a memory via
/// #MemoryMap. On other platforms (e.g. Windows), the arena will read
/// the file into an allocated arena, calling #SetAlloc.
///
/// \param path The path to the file that should be created.
/// \param fileSize The size of the file to create.
/// \return Returns 0 on success and -1 on error.
int Create(const char *path, size_t fileSize);
/// Open reads a file into the arena; the file must already exist. On
/// Unix-based platforms, the arena will be backed by a memory via
/// #MemoryMap. On other platforms (e.g. Windows), the arena will read
/// the file into an allocated arena, calling #SetAlloc. On these
/// platforms, in order to persist changes, #Write must be called to
/// sync changes to disk.
///
/// \param path The path to the file to be loaded.
/// \return Returns 0 on success and -1 on error.
int Open(const char *path);
/// Start returns a pointer to the start of the memory in the arena.
///
/// \return A pointer to the start of the arena memory.
uint8_t *Start() const
{ return this->store; }
/// End returns a pointer to the end of the arena memory.
///
/// \return A pointer to the end of the arena memory.
uint8_t *End()
{ return this->store + this->size; }
/// CursorInArena checks whether the cursor is still in the arena.
///
/// \param cursor A pointer that ostensibly points to the arena's
/// memory.
/// \return True if the cursor is still in the arena.
bool CursorInArena(const uint8_t *cursor);
/// Returns the current size of the arena.
///
/// \return The size of the arena.
size_t Size() const
{ return this->size; }
/// Type returns an ArenaType describing the arena.
///
/// \return An ArenaType describing the backing memory for the arena.
ArenaType Type() const
{ return this->arenaType; }
/// Ready returns whether the arena is initialized.
bool Ready() const
{ return this->Type() != ArenaType::Uninit; };
/// Clear zeroizes the memory in the arena.
void Clear();
/// Destroy removes any backing memory (e.g. from SetAlloc or
/// MemoryMap). This does not call Clear; if the arena was backed by a
/// file that should be persisted, it would wipe out the file.
void Destroy();
/// Write dumps the arena to a file suitable for loading by Open.
///
/// \warning DANGER: if arena is memory-mapped, DO NOT WRITE TO THE
/// BACKING FILE!
///
/// \param path
/// \return Returns 0 on success and -1 on error.
int Write(const char *path);
/// This operator allows the data in the arena 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);
private:
uint8_t *store;
size_t size;
int fd;
ArenaType arenaType;
};
/// Write an Arena out to the output stream.
///
/// The resulting output looks something like
/// ```
/// Arena<allocated>@0x7fff91dfad70,store<128B>@0x0055d6c5881ec0.
/// ^ ^ ^ ^
/// | +- base memory | +- store memory
/// +- arena type +- arena size
/// ```
///
/// \param os
/// \param arena
/// \return
std::ostream &operator<<(std::ostream &os, Arena &arena);
} // namespace scsl
#endif

232
include/scsl/Buffer.h Normal file
View File

@@ -0,0 +1,232 @@
///
/// \file Buffer.h
/// \author K. Isom <kyle@imap.cc>
/// \date 2023-10-09
/// \brief Buffer implements basic line buffers.
///
/// 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.
///
/// 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.
///
#ifndef KGE_BUFFER_H
#define KGE_BUFFER_H
#include <cstdint>
#include <iostream>
namespace scsl {
/// 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 {
public:
/// A Buffer can be constructed empty, with no memory allocated (yet).
Buffer();
/// A Buffer can be constructed with an explicit capacity.
///
/// \param initialCapacity The initial allocation size for the buffer.
explicit Buffer(size_t initialCapacity);
/// A Buffer can be initialized with a starting C-style string.
explicit Buffer(const char *s);
/// A Buffer can be initialized with a starting string.
explicit Buffer(const std::string s);
~Buffer()
{ 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 */
/// 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);
private:
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;
size_t length;
size_t capacity;
bool autoTrim;
};
/// The << operator is overloaded to write out the contents of the Buffer.
std::ostream &operator<<(std::ostream &os, const Buffer &buf);
/// Two Buffers are not equal if their lengths differ or if their contents
/// differ.
inline bool operator!=(const Buffer &lhs, const Buffer &rhs) { return !(lhs == rhs); };
} // namespace scsl
#endif // KGE_BUFFER_H

127
include/scsl/Commander.h Normal file
View File

@@ -0,0 +1,127 @@
///
/// \file Commander.h
/// \author K. Isom <kyle@imap.cc>
/// \date 2023-10-10
/// \brief Subprogram tooling.
///
/// Commander is tooling for creating subcommand interfaces for command-line
/// programs. For an example, see phonebook.cc.
///
/// The basic idea is to enable writing programs of the form
/// ```
/// $ some_tool subcommand args...
/// ```
///
/// 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 <cstdint>
#include <functional>
#include <map>
#include <string>
#include <vector>
#ifndef SCSL_COMMANDER_H
#define SCSL_COMMANDER_H
namespace scsl {
/// CommanderFunc describes a function that can be run in Commander.
///
/// It expects an argument count and a list of arguments.
using CommanderFunc = std::function<bool (int, char **)>;
/// Subcommands are the individual commands for the program. A Subcommand
/// will check that it has enough arguments before running its function.
class Subcommand {
public:
/// Status describes the results of running a Subcommand.
enum class Status : int8_t {
/// The subcommand executed correctly.
OK = 0,
/// Not enough arguments were supplied to the subcommand.
NotEnoughArgs = 1,
/// The subcommand failed to run correctly.
Failed = 2,
/// The subcommand hasn't been registered in a Commander
/// instance.
CommandNotRegistered = 3,
};
/// A Subcommand is initialized with a name, the number of arguments
/// it requires, and a function to run.
///
/// \param name The subcommand name; this is the name that will select this command.
/// \param argc The minimum number of arguments required by this subcommand.
/// \param func A valid CommanderFunc.
Subcommand(std::string name, int argc, CommanderFunc func)
: fn(func), args(argc), command(name)
{}
/// Name returns the name of this subcommand.
std::string Name() { return this->command; }
/// Run attempts to run the CommanderFunc for this subcommand.
///
/// \param argc The number of arguments supplied.
/// \param argv The argument list.
/// \return A Status type indicating the status of running the command.
Status Run(int argc, char **argv);
private:
CommanderFunc fn;
int args;
std::string command;
};
/// Commander collects subcommands and can run the apppropriate one.
///
/// For example:
/// ```
/// auto command = string(argv[optind++]);
/// Commander commander;
///
/// commander.Register(Subcommand("list", 0, listFiles));
/// commander.Register(Subcommand("new", 1, newPhonebook));
/// commander.Register(Subcommand("del", 1, delKey));
/// commander.Register(Subcommand("has", 1, hasKey));
/// commander.Register(Subcommand("get", 1, getKey));
/// commander.Register(Subcommand("put", 2, putKey));
///
/// auto result = commander.Run(command, argc-optind, argv+optind);
/// ```
///
class Commander {
public:
/// A Commander is initialized empty.
Commander();
/// Register adds the subcommand. It will be copied into the Commander.
bool Register(Subcommand scmd);
/// Try to run a subcommand registered with this Commander.
Subcommand::Status Run(std::string command, int argc, char **argv);
private:
std::map<std::string, Subcommand *> cmap;
};
} // namespace scsl
#endif //SCSL_COMMANDER_H

142
include/scsl/Dictionary.h Normal file
View File

@@ -0,0 +1,142 @@
///
/// \file Dictionary.h
/// \author kyle (kyle@imap.cc)
/// \date 2023-10-12
/// \brief Key-value store built on top of Arena and TLV.
///
///
/// 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.
///
#ifndef SCSL_DICTIONARY_H
#define SCSL_DICTIONARY_H
#include <cstdint>
#include "Arena.h"
#include "TLV.h"
static constexpr uint8_t DICTIONARY_TAG_KEY = 1;
static constexpr uint8_t DICTIONARY_TAG_VAL = 2;
namespace scsl {
/*
* A Dictionary is a collection of key-value pairs, similar to how
* a dictionary is a mapping of names to definitions.
*/
/// Dictionary implements a key-value store on top of Arena and TLV::Record.
///
/// Keys and vales are stored as sequential pairs of TLV records; they are
/// expected to contain string values but this isn't necessarily the case. The
/// tag values default to a tag of DICTIONARY_TAG_KEY, and values to a tag of
/// DICTIONARY_TAG_VAL.
class Dictionary {
public:
/// A Dictionary can be initialized with just a backing Arena.
///
/// \param arena The backing arena for the Dictionary.
Dictionary(Arena &arena) :
arena(arena),
kTag(DICTIONARY_TAG_KEY),
vTag(DICTIONARY_TAG_VAL)
{};
/// A Dictionary can also be configured with custom key and value types.
///
/// \param arena The backing arena for the Dictionary.
/// \param kt The value to use for key tags.
/// \param vt The value to use for val tags.
Dictionary(Arena &arena, uint8_t kt, uint8_t vt) :
arena(arena),
kTag(kt),
vTag(vt)
{};
/// Lookup checks to see if the Dictionary has a value under key.
///
/// \param key The key to search for.
/// \param klen The length of the key.
/// \param res The TLV::Record to store the value in;
/// \return True if the key was found, false otherwise.
bool Lookup(const char *key, uint8_t klen, TLV::Record &res);
/// Set adds a pairing for key → value in the Dictionary.
///
/// If the key is already present in the dictionary, both the
/// key and value are deleted, and a new pair is insert.
///
/// \warning If the key is present, but there isn't enough space to
/// store the new Val, the Dictionary will not contain either key
/// or value.
///
///
/// \param key The key to associate.
/// \param klen The length of the key.
/// \param val The value to associate.
/// \param vlen The length of the value.
/// \return Returns 0 on success and -1 on failure.
int Set(const char *key, uint8_t klen, const char *val,
uint8_t vlen);
/// Contains checks the dictionary to see if it contains a given key.
///
/// \param key The key to look up.
/// \param klen The length of the key.
/// \return True if the key is in the Dictionary, otherwise false.
bool Contains(const char *key, uint8_t klen);
/// Delete removes the key from the Dictionary.
///
/// \param key The key to look up.
/// \param klen The length of the key.
/// \return True if the key was removed, otherwise false.
bool Delete(const char *key, uint8_t klen);
/// DumpToFile is a wrapper aorund a call to Arena::Write on the
/// underlying Arena.
///
/// \param path The path to the dumped file.
/// \return 0 on success, -1 on failure.
int DumpToFile(const char *path);
/// operator<< writes the key pairs phonto the output stream.
///
/// \param os The output stream to write to.
/// \param dictionary The dictionary to write out.
/// \return The output stream is returned.
friend std::ostream &operator<<(std::ostream &os,
const Dictionary &dictionary);
private:
uint8_t *seek(const char *key, uint8_t klen);
bool spaceAvailable(uint8_t klen, uint8_t vlen);
Arena &arena;
uint8_t kTag;
uint8_t vTag;
};
} // namespace scsl
#endif

69
include/scsl/Exceptions.h Normal file
View File

@@ -0,0 +1,69 @@
///
/// \file Exceptions.h
/// \author K. Isom <kyle@imap.cc>
/// \date 2023-10-10
/// \brief Custom exceptions for use in SCSL used in writing test programs.
///
/// 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.
///
#ifndef SCSL_EXCEPTIONS_H
#define SCSL_EXCEPTIONS_H
#include <exception>
#include <string>
namespace scsl {
/// NotImplemented is an exception reserved for unsupported platforms.
///
/// It is used to mark functionality included for compatibility, and useful for
/// debugging.
class NotImplemented : public std::exception {
public:
/// NotImplemented exceptions are constructed with a platform name.
explicit NotImplemented(const char *pl) : platform((char *)pl) {}
/// what returns a message naming the platform.
const char *what() const throw() {
return this->platform;
}
private:
char *platform;
};
/// AssertionFailed indicates that some invariant didn't hold.
class AssertionFailed : public std::exception {
public:
/// AssertionFailed is constructed with a message describing what
/// failed.
explicit AssertionFailed(std::string message);
/// what returns a message describing the exception.
const char *what() const throw();
private:
std::string msg;
};
} // namespace scsl
#endif //SCSL_EXCEPTIONS_H

341
include/scsl/Flag.h Normal file
View File

@@ -0,0 +1,341 @@
///
/// \file Flag.h
/// \author K. Isom <kyle@imap.cc>
/// \date 2023-10-12
/// \brief Flag declares a command-line flag parser.
///
/// 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 <cstdint>
#include <functional>
#include <map>
#include <string>
#include <variant>
#include <vector>
namespace scsl {
/// FlagType indicates the value held in a FlagValue.
///
/// \todo When C++17 support is more common, switch to `std::variant`.
enum class FlagType : uint8_t {
Unknown = 0, ///< Unsupported value type.
Boolean = 1, ///< bool
Integer = 2, ///< int32_t
UnsignedInteger = 3, ///< uint32_t
SizeT = 4, ///< size_t
String = 5, ///< std::string
};
/// FlagValue holds the value of a command line flag.
typedef union {
unsigned int u; ///< FlagType::UnsignedInteger
int i; ///< FlagType::Integer
std::size_t size; ///< FlagType::SizeT
std::string *s; ///< FlagType::String
bool b; ///< FlagType::Boolean
} FlagValue;
/// Flag describes an individual command-line flag.
typedef struct {
FlagType Type; ///< The type of the value in the flag.
bool WasSet; ///< The flag was set on the command-line.
std::string Name; ///< The name of the flag.
std::string Description; ///< A description of the flag.
FlagValue Value; ///< The flag's value.
} Flag;
/// NewFlag is a helper function for constructing a new flag.
///
/// \param fName The name of the flag.
/// \param fType The type of the flag.
/// \param fDescription A description of the flag.
/// \return A pointer to a flag.
Flag *NewFlag(std::string fName, FlagType fType, std::string fDescription);
/// Flags provides a basic facility for processing command line flags.
///
/// Any remaining arguments after the args are added to the parser as
/// arguments that can be accessed with NumArgs, Args, and Arg.
///
/// \note The parser automatically handles the -h and --help flags by
/// calling Usage(std::cout, 0). The user can override this flag
/// and handle providing help on their own.
///
/// \details
///
/// Arguments are named with their leading dash, e.g. "-f". For example,
///
/// ```c++
/// flags.Register("-f", FlagType::String, "Path to a configuration file.");
/// ```
/// \section Usage
///
/// A short example program is below:
///
/// ```c++
/// int
/// main(int argc, char *argv[])
/// {
/// std::string server = "service.example.com";
/// unsigned int port = 1234;
///
/// auto flags = new scsl::Flags("example-client",
/// "This interacts with the example.com service.");
/// flags->Register("-p", port, "server port");
/// flags->Register("-s", server, "hostname to connect to");
///
/// auto status = flags->Parse(argc, argv);
/// if (status != ParseStatus::OK) {
/// std::cerr << "failed to parse flags: "
/// << scsl::Flags::ParseStatusToString(status)
/// << "\n";
/// exit(1);
/// }
///
/// auto wasSet = flags->GetString("-s", server);
/// if (wasSet) {
/// std::cout << "hostname override: " << server << "\n";
/// }
///
/// wasSet = flags->GetUnsignedInteger("-p", port);
/// if (wasSet) {
/// std::cout << "port override: " << port << "\n";
/// }
///
/// std::cout << "connecting to " << server << ":" << port << "\n";
/// for (size_t i = 0; i < flags.NumArgs(); i++) {
/// std::cout << "\tExecuting command " << flags.Arg(i) << "\n";
/// }
/// return 0;
/// }
/// ```
///
class Flags {
public:
/// ParseStatus describes the result of parsing command-line
/// arguments.
enum class ParseStatus : uint8_t {
/// An unknown parsing error occurred. This is a bug,
/// and users should never see this.
Unknown = 0,
/// Parsing succeeded.
OK = 1,
/// This is an internal status marking the end of
/// command line flags.
EndOfFlags = 2,
/// The command line flag provided isn't registered.
NotRegistered = 3,
/// Not enough arguments were provided to a flag that
/// takes an argument. For example, if "-p" expects
/// a number, and the program was called with just
/// `./program -p`, this error will be reported.
NotEnoughArgs = 4,
};
/// ParseStatusToString returns a string message describing the
/// result of parsing command line args.
static std::string ParseStatusToString(ParseStatus status);
/// Create a new flags parser for the named program.
///
/// \param fName The program name,
Flags(std::string fName);
/// Create a new flags parser for the named program.
///
/// \param fName The program name.
/// \param fDescription A short description of the program.
Flags(std::string fName, std::string fDescription);
~Flags();
/// Register a new command line flag.
///
/// \param fName The name of the flag, including a leading dash.
/// \param fType The type of the argument to parse.
/// \param fDescription A description of the flag.
/// \return True if the flag was registered, false if the flag could
/// not be registered (e.g. a duplicate flag was registered).
bool Register(std::string fName,
FlagType fType,
std::string fDescription);
/// Register a new boolean command line flag with a default value.
///
/// \note For booleans, it only makes sense to pass a false default
/// value, as there is no way to set a boolean flag to false.
/// This form is provided for compatibility with the other
/// variants on this method.
///
/// \param fName The name of the flag, including a leading dash.
/// \param defaultValue The default value for the flag.
/// \param fDescription A short description of the flag.
/// \return True if the flag was registered, false if the flag could
/// not be registered (e.g. a duplicate flag was registered).
bool Register(std::string fName,
bool defaultValue,
std::string fDescription);
/// Register a new integer command line flag with a default value.
///
/// \param fName The name of the flag, including a leading dash.
/// \param defaultValue The default value for the flag.
/// \param fDescription A short description of the flag.
/// \return True if the flag was registered, false if the flag could
/// not be registered (e.g. a duplicate flag was registered).
bool Register(std::string fName,
int defaultValue,
std::string fDescription);
/// Register a new unsigned integer command line flag with a default
/// value.
///
/// \param fName The name of the flag, including a leading dash.
/// \param defaultValue The default value for the flag.
/// \param fDescription A short description of the flag.
/// \return True if the flag was registered, false if the flag could
/// not be registered (e.g. a duplicate flag was registered).
bool Register(std::string fName,
unsigned int defaultValue,
std::string fDescription);
/// Register a new size_t command line flag with a default value.
///
/// \param fName The name of the flag, including a leading dash.
/// \param defaultValue The default value for the flag.
/// \param fDescription A short description of the flag.
/// \return True if the flag was registered, false if the flag could
/// not be registered (e.g. a duplicate flag was registered).
bool Register(std::string fName,
size_t defaultValue,
std::string fDescription);
/// Register a new string command line flag with a default value.
///
/// \param fName The name of the flag, including a leading dash.
/// \param defaultValue The default value for the flag.
/// \param fDescription A short description of the flag.
/// \return True if the flag was registered, false if the flag could
/// not be registered (e.g. a duplicate flag was registered).
bool Register(std::string fName,
std::string defaultValue,
std::string fDescription);
/// Return the number of registered flags.
size_t Size();
/// Lookup a flag.
///
/// \param fName The flag name.
/// \return A pointer to flag if registered, or nullptr if the flag
/// wasn't registered.
Flag *Lookup(std::string fName);
/// ValueOf returns the value of the flag in the
bool ValueOf(std::string fName, FlagValue &value);
/// Process a list of arguments into flags.
///
/// \param argc The number of arguments.
/// \param argv The arguments passed to the program.
/// \param skipFirst Flags expects to receive arguments directly
/// from those passed to `main`, and defaults to skipping
/// the first argument. Set to false if this is not the
/// case.
/// \return
ParseStatus Parse(int argc, char **argv, bool skipFirst=true);
/// Print a usage message to the output stream.
void Usage(std::ostream &os, int exitCode);
/// Return the number of arguments processed. These are any
/// remaining elements in argv that are not flags.
size_t NumArgs();
/// Return the arguments as a vector.
std::vector<std::string> Args();
/// Return a particular argument.
///
/// \param index The argument number to extract.
/// \return The argument at index i. If the index is greater than
/// the number of arguments present, an out_of_range
/// exception is thrown.
std::string Arg(size_t index);
/// Retrieve the state of a boolean flag.
///
/// \param fName The flag to look up.
/// \param flagValue The value to store.
/// \return True if the value was set, or false if the value isn't
/// a boolean or if it wasn't set.
bool GetBool(std::string fName, bool &flagValue);
/// Retrieve the value of an unsigned integer flag.
///
/// \param fName The flag to look up.
/// \param flagValue The value to store.
/// \return True if the value was set, or false if the value isn't
/// an unsigned integer or if it wasn't set.
bool GetUnsignedInteger(std::string fName, unsigned int &flagValue);
/// Retrieve the value of an integer flag.
///
/// \param fName The flag to look up.
/// \param flagValue The value to store.
/// \return True if the value was set, or false if the value isn't
/// an integer or if it wasn't set.
bool GetInteger(std::string fName, int &flagValue);
/// Retrieve the value of a string flag.
///
/// \param fName The flag to look up.
/// \param flagValue The value to store.
/// \return True if the value was set, or false if the value isn't
/// a string or if it wasn't set.
bool GetString(std::string fName, std::string &flagValue);
/// Retrieve the value of a size_t flag.
///
/// \param fName The flag to look up.
/// \param flagValue The value to store.
/// \return True if the value was set, or false if the value isn't
/// a size_t or if it wasn't set.
bool GetSizeT(std::string fName, std::size_t &flagValue);
private:
ParseStatus parseArg(int argc, char **argv, int &index);
Flag *checkGetArg(std::string fName, FlagType eType);
std::string name;
std::string description;
std::vector<std::string> args;
std::map<std::string, Flag *> flags;
};
} // namespace scsl

135
include/scsl/StringUtil.h Normal file
View File

@@ -0,0 +1,135 @@
///
/// \file StringUtil.h
/// \author K. Isom <kyle@imap.cc>
/// \date 2023-10-14
/// \brief Utilities for working with strings.
///
/// 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 <algorithm>
#include <string>
#include <vector>
#ifndef STRINGUTIL_H
#define STRINGUTIL_H
namespace scsl {
/// namespace U contains utilities.
namespace U {
/// namespace S contains string-related functions.
namespace S {
/// Remove any whitespace at the beginning of the string. The string
/// is modified in-place.
void TrimLeadingWhitespace(std::string &s);
/// Remove any whitespace at the end of the string. The string is
/// modified in-place.
void TrimTrailingWhitespace(std::string &s);
/// Remove any whitespace at the beginning and end of the string. The
/// string is modified in-place.
void TrimWhitespace(std::string &s);
/// Remove any whitespace at the beginning of the string. The original
/// string isn't modified, and a copy is returned.
std::string TrimLeadingWhitespaceDup(std::string s);
/// Remove any whitespace at the end of the string. The original string
/// isn't modified, and a copy is returned.
std::string TrimTrailingWhitespaceDup(std::string s);
/// Remove any whitespace at the beginning and end of the string. The
/// original string isn't modified, and a copy is returned.
std::string TrimWhitespaceDup(std::string s);
/// Split a line into key and value pairs. If the delimiter isn't found,
/// the line is returned as the first element in the pair, and the second
/// element will be empty.
///
/// \param line A string representing a line in a file.
/// \param delimiter The string delimiter between the key and value.
/// \return The key and value, or {line, ""}.
std::vector<std::string> SplitKeyValuePair(std::string line, std::string delimiter);
/// Split a line into key and value pairs. If the delimiter isn't found,
/// the line is returned as the first element in the pair, and the second
/// element will be empty.
///
/// \param line A string representing a line in a file.
/// \param delimiter The character delimiter between the key and value.
/// \return The key and value.
std::vector<std::string> SplitKeyValuePair(std::string line, char delimiter);
/// Split a string into parts based on the delimiter.
///
/// \param s The string that should be split.
/// \param delimiter The string that delimits the parts of the string.
/// \param maxCount The maximum number of parts to split. If 0, there is no
/// limit to the number of parts.
/// \return A vector containing all the parts of the string.
std::vector<std::string> SplitN(std::string, std::string delimiter, size_t maxCount=0);
/// WrapText is a very simple wrapping function that breaks the line into
/// lines of at most lineLength characters. It does this by breaking the
/// line into individual words (splitting on whitespace).
std::vector<std::string> WrapText(std::string line, size_t lineLength);
/// Write out a vector of lines indented with tabs.
///
/// \param os The output stream to write to.
/// \param lines The lines of text to write.
/// \param tabStop The number of tabs to indent.
/// \param indentFirst Whether the first line should be indented.
void WriteTabIndented(std::ostream &os, std::vector<std::string> lines,
int tabStop, bool indentFirst);
/// Wrap a line, then output it to a stream.
///
/// \param os The output stream to write to.
/// \param line The line to wrap and output.
/// \param maxLength The maximum length of each section of text.
/// \param tabStop The number of tabs to indent.
/// \param indentFirst Whether the first line should be indented.
void WriteTabIndented(std::ostream &os, std::string line, size_t maxLength,
int tabStop, bool indentFirst);
/// Write a string vector to the output stream in the same format as
/// VectorToString.
std::ostream &VectorToString(std::ostream &os, const std::vector<std::string> &svec);
/// Return a string represention of a string vector in the form
/// [size]{"foo", "bar", ...}.
std::string VectorToString(const std::vector<std::string> &svec);
} // namespace S
} // namespace U
} // namespace scsl
#endif // STRINGUTIL_H

130
include/scsl/TLV.h Normal file
View File

@@ -0,0 +1,130 @@
///
/// \file TLV.h
/// \author K. Isom <kyle@imap.cc>
/// \date 2023-10-06
/// \brief TLV.h implements basic tag-length-value records.
///
/// TLV implements tag-length-value (TLV) records. Each record can have
/// a maximum length of 253 bytes; each TLV record occupies a fixed 255
/// bytes in memory. TLV records don't allocate memory.
///
/// This system uses an Arena as a backing store.
///
#ifndef KIMODEM_TLV_H
#define KIMODEM_TLV_H
#include <array>
#include <cstdint>
#include "Arena.h"
namespace scsl {
namespace TLV {
#ifndef TLV_MAX_LEN
static constexpr size_t TLV_MAX_LEN = 253;
#endif
static constexpr uint8_t TAG_EMPTY = 0;
/// Record describes a tag-length-value record.
///
/// TLV records occupy a fixed size in memory, which can be controlled with the
/// TLV_MAX_LEN define. If this isn't defined, it defaults to a size of 253.
/// When writen to an Arena, it occupies Len + 2 bytes. The strings
/// are not null-terminated in the arena.
struct Record {
/// A Tag is used to identify the type of this record.
uint8_t Tag;
/// Len describes the number of bytes stored in #Val.
uint8_t Len;
/// Val contains the data in the record.
uint8_t Val[TLV_MAX_LEN];
};
/// WriteToMemory writes the TLV record into the arena at the location pointed
/// to in the arena.
///
/// \param arena The backing memory store.
/// \param cursor Pointer into the arena's memory.
/// \param rec A TLV record to be serialized.
/// \return A pointer the memory after the record.
uint8_t *WriteToMemory(Arena &arena, uint8_t *cursor, Record &rec);
/// ReadFromMemory reads a record from the memory pointed to by the cursor.
///
/// \param rec The TLV record to be filled in.
/// \param cursor A pointer into an arena's memory store.
void ReadFromMemory(Record &rec, uint8_t *cursor);
/// SetRecord sets a record.
///
/// \param rec The record to be set.
/// \param tag The record's tag.
/// \param length The record's length.
/// \param data The data to fill the record with.
void SetRecord(Record &rec, uint8_t tag, uint8_t length, const char *data);
/// DeleteRecord removes the record from the arena. All records ahead of this
/// record are shifted backwards so that there are no gaps.
void DeleteRecord(Arena &arena, uint8_t *cursor);
/*
* returns a pointer to memory where the record was found,
* e.g. LocateTag(...)[0] is the tag of the found record.
* FindTag will call LocateTag and then SkipRecord if the
* tag was found.
*/
/// FindTag finds the next occurrence of the record's tag.
///
/// The record must have a tag set, which tells FindTag which tag to look for.
/// If found, it fills the record. \see LocateTag.
///
/// \param arena The backing memory for the TLV store.
/// \param cursor A pointer to memory inside the arena; if it's NULL, the
/// search starts at the beginning of the arena.
/// \param rec The record to be filled.
/// \return If the tag is found, a cursor pointing to the next record is
/// returned; otherwise nullptr is returned.
uint8_t *FindTag(Arena &arena, uint8_t *cursor, Record &rec);
/// LocateTag operates similarly to FindTag, but the cursor points to the
/// beginning of the found record.
///
/// \param arena The backing memory for the TLV store.
/// \param cursor A pointer to memory inside the arena; if it's NULL, the
/// search starts at the beginning of the arena.
/// \param rec The record to be filled.
/// \return If the tag is found, a cursor pointing to the record is
/// returned; otherwise nullptr is returned.
uint8_t *LocateTag(Arena &arena, uint8_t *cursor, Record &rec);
/// FindEmpty finds a pointer the next available empty space.
///
/// \return A cursor to the start of empty space in the arena, or nullptr
/// if there is no more empty space available.
///
/// \param arena The backing memory for the TLV store.
/// \param cursor A pointer to memory inside the arena; if it's NULL, the
/// search starts at the beginning of the arena.
/// \return If the arena has space available, a cursor pointing the start
/// of empty space; otherwise, nullptr is returned.
uint8_t *FindEmpty(Arena &arena, uint8_t *cursor);
/// SkipRecord skips the cursor to the next record.
///
/// \param rec The record that should be skipped.
/// \param cursor A pointer to the record in the arena.
/// \return The pointer to the next record in the arena.
uint8_t *SkipRecord(Record &rec, uint8_t *cursor);
} // namespace TLV
} // namespace scsl
#endif

80
include/scsl/scsl.h Normal file
View File

@@ -0,0 +1,80 @@
///
/// \file scsl.h
/// \author kyle (kyle@imap.cc)
/// \date 2023-10-10
/// \brief scsl is my collection of C++ data structures and code.
///
/// scsl.h is a utility header that includes all of SCSL.
///
/// \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.
///
#ifndef SCSL_SCSL_H
#define SCSL_SCSL_H
#include <scsl/Arena.h>
#include <scsl/Buffer.h>
#include <scsl/Commander.h>
#include <scsl/Dictionary.h>
#include <scsl/Exceptions.h>
#include <scsl/Flag.h>
#include <scsl/StringUtil.h>
#include <scsl/TLV.h>
#include <scsl/Test.h>
/// scsl is the top-level namespace containing all the code in this library.
namespace scsl {
/// \mainpage scsl documentation
///
/// \section Introduction
///
/// This is a collection of data structures and subroutines that I find
/// useful in building things.
///
/// This library arose from two main use cases.
///
/// \subsection kimodem The modem
///
/// On the one hand, I was building a wireless modem for some Z80 computers I
/// have. I needed to be able to store a phonebook of SSIDs and WPA keys, as
/// well as short names to host:port descriptors. I had a limited amount of
/// persistent NVRAM storage and no SD card or other removeable media, so
/// typical desktop-oriented serialization mechanisms weren't going to really
/// work well. Furthermore, when working with microcontrollers, I prefer not to
/// dynamically allocate memory as much as possible. This led to building out
/// Arena, TLV::Record to store the records, and finally Dictionary to make use
/// of both of them.
///
/// Closely related to this, I've been working on building an ARM-based handheld
/// computer, for which I would also need a memory arena.
///
/// \subsection textedit The text editors
///
/// Some time ago, I wrote a console text editor of my own; then later, started
/// working on a graphical editor. For this, I needed some data structures to
/// manage memory in the editor. Thus, Buffer was born.
///
}
#endif // SCSL_SCSL_H