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

194
src/bin/phonebook.cc Normal file
View File

@@ -0,0 +1,194 @@
///
/// \file phonebook.cc
/// \author K. Isom <kyle@imap.cc>
/// \date 2023-10-10
/// \brief Commandline tools for interacting with dictionary data file.
///
/// 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 <iostream>
#include <string>
using namespace std;
#include <scsl/Arena.h>
#include <scsl/Commander.h>
#include <scsl/Dictionary.h>
using namespace scsl;
static const char *defaultPhonebook = "pb.dat";
static char *pbFile = (char *)defaultPhonebook;
static Arena arena;
static Dictionary pb(arena);
static bool
listFiles(int argc, char **argv)
{
(void)argc; (void)argv;
cout << "[+] keys in '" << pbFile << "':\n";
cout << pb;
return true;
}
static bool
newPhonebook(int argc, char **argv)
{
(void)argc;
auto size = std::stoul(string(argv[0]));
cout << "[+] create new " << size << "B phonebook '" << pbFile << "'\n";
return arena.Create(pbFile, size) == 0;
}
static bool
delKey(int argc, char **argv)
{
(void)argc;
string key = argv[0];
cout << "[+] deleting key '" << key << "'\n";
return pb.Delete(key.c_str(), key.size());
}
static bool
hasKey(int argc, char **argv)
{
(void)argc;
string key = argv[0];
cout << "[+] looking up '" << key << "': ";
if (pb.Contains(key.c_str(), key.size())) {
cout << "found\n";
return true;
}
cout << "not found\n";
return true;
}
static bool
getKey(int argc, char **argv)
{
(void)argc;
TLV::Record rec;
auto key = string(argv[0]);
cout << "[+] key '" << key << "' ";
if (!pb.Lookup(key.c_str(), key.size(), rec)) {
cout << "not found\n";
return false;
}
cout << "-> " << rec.Val << "\n";
return true;
}
static bool
putKey(int argc, char **argv)
{
(void)argc;
auto key = string(argv[0]);
auto val = string(argv[1]);
cout << "[+] setting '" << key << "' -> '" << val << "': ";
if (pb.Set(key.c_str(), key.size(), val.c_str(), val.size()) != 0) {
cout << "failed\n";
return false;
}
cout << "set\n";
return true;
}
static void
usage(ostream &os, int exc)
{
os << "phonebook is a tool for interacting with phonebook files.\n";
os << "\nThe default filename is pb.dat.\n\n";
os << "Usage:\n";
os << "\tphonebook [-f file] list\n";
os << "\tphonebook [-f file] new size\n";
os << "\tphonebook [-f file] del key\n";
os << "\tphonebook [-f file] has key\n";
os << "\tphonebook [-f file] get key\n";
os << "\tphonebook [-f file] put key value\n";
os << "\n";
exit(exc);
}
int
main(int argc, char *argv[])
{
int optind = 1;
for (optind = 1; optind < argc; optind++) {
auto arg = string(argv[optind]);
if (arg[0] != '-') break;
if (arg == "-h") usage(cout, 0);
if (arg == "-f") {
pbFile = argv[optind+1];
optind++;
continue;
}
usage(cerr, 1);
}
if (argc <= 1) {
usage(cout, 0);
}
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));
if (command != "new") {
cout << "[+] loading phonebook from " << pbFile << "\n";
if (arena.Open(pbFile) != 0) {
cerr << "Failed to open " << pbFile << "\n";
exit(1);
}
}
auto result = commander.Run(command, argc-optind, argv+optind);
switch (result) {
case Subcommand::Status::OK:
std::cout << "[+] OK\n";
break;
case Subcommand::Status::NotEnoughArgs:
usage(cerr, 1);
break;
case Subcommand::Status::Failed:
cerr << "[!] '"<< command << "' failed\n";
break;
case Subcommand::Status::CommandNotRegistered:
cerr << "[!] '" << command << "' not registered.\n";
usage(cerr, 1);
break;
default:
abort();
}
}

181
src/scmp/coord2d.cc Executable file
View File

@@ -0,0 +1,181 @@
//
// Project: scccl
// File: src/math/geom2d.cpp
// Author: Kyle Isom
// Date: 2017-06-05
// Namespace: math::geom
//
// geom2d.cpp contains the implementation of 2D geometry in the math::geom
// namespace.
//
// Copyright 2017 Kyle Isom <kyle@imap.cc>
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include <cmath>
#include <iostream>
#include <vector>
#include <scccl/math/math.h>
#include <scccl/math/geom/coord2d.h>
// coord2d.cpp contains 2D geometric functions and data structures, such as
// cartesian and polar coordinates and rotations.
// TODO: deprecate Point2D in favour of Vector
namespace scmath {
namespace geom {
//
// Point2D
Point2D::Point2D(const Polar2D &pol)
: x(std::rint(std::cos(pol.theta) * pol.r)),
y(std::rint(std::sin(pol.theta) * pol.r)) {}
std::ostream&
operator<<(std::ostream& outs, const Point2D& pt)
{
outs << "(" << std::to_string(pt.x) << ", " << std::to_string(pt.y) << ")";
return outs;
}
std::string
Point2D::ToString()
{
return "(" + std::to_string(x) + ", " + std::to_string(y) + ")";
}
void
Point2D::ToPolar(Polar2D& pol)
{
pol.r = std::sqrt((x * x) + (y * y));
pol.theta = std::atan2(y, x);
}
void
Point2D::Rotate(Point2D& pt, double theta)
{
Polar2D pol(*this);
pol.Rotate(pol, theta);
pol.ToPoint(pt);
}
bool
Point2D::operator==(const Point2D& rhs) const
{
return (x == rhs.x) && (y == rhs.y);
}
void
Point2D::Translate(const Point2D& origin, Point2D &translated)
{
translated.x = origin.x + x;
translated.y = origin.y + y;
}
std::vector<Point2D>
Point2D::Rotate(std::vector<Polar2D> vertices, double theta)
{
std::vector<Point2D> rotated;
for (auto v : vertices) {
Point2D p;
v.RotateAround(*this, p, theta);
rotated.push_back(p) ;
}
return rotated;
}
int
Point2D::Distance(const Point2D& other)
{
auto dx = other.x - x;
auto dy = other.y - y;
return std::sqrt(dx * dx + dy + dy);
}
// Polar2D
Polar2D::Polar2D(const Point2D &pt)
: r(std::sqrt((pt.x * pt.x) + (pt.y * pt.y))),
theta(std::atan2(pt.y, pt.x)) {}
void
Polar2D::ToPoint(Point2D& pt)
{
pt.y = std::rint(std::sin(theta) * r);
pt.x = std::rint(std::cos(theta) * r);
}
std::string
Polar2D::ToString()
{
return "(" + std::to_string(r) + ", " + std::to_string(theta) + ")";
}
void
Polar2D::Rotate(Polar2D& rot, double delta)
{
rot.r = r;
rot.theta = RotateRadians(theta, delta);
}
bool
Polar2D::operator==(const Polar2D& rhs) const
{
static double eps = 0.0;
if (eps == 0.0) {
scmath::DefaultEpsilon(eps);
}
return scmath::WithinTolerance(r, rhs.r, eps) &&
scmath::WithinTolerance(theta, rhs.theta, eps);
}
void
Polar2D::RotateAround(const Point2D &origin, Point2D &point, double delta)
{
Polar2D rot;
this->Rotate(rot, delta);
rot.ToPoint(point);
point.Translate(origin, point);
}
std::ostream&
operator<<(std::ostream& outs, const Polar2D& pol)
{
outs << "(" << pol.r << ", " << pol.theta << ")";
return outs;
}
} // end namespace geom
} // end namespace math

128
src/scmp/math.cc Normal file
View File

@@ -0,0 +1,128 @@
#include <algorithm>
#include <functional>
#include <numeric>
#include <random>
#include <vector>
#include <scccl/math/math.h>
namespace scmath {
std::vector<int>
Die(int m, int n)
{
std::uniform_int_distribution<> die(1, n);
std::random_device rd;
std::vector<int> dice;
int i = 0;
for (i = 0; i < m; i++) {
dice.push_back(die(rd));
}
return dice;
}
int
BestDie(int k, int m, int n)
{
auto dice = Die(m, n);
if (k < m) {
std::sort(dice.begin(), dice.end(), std::greater<int>());
dice.resize(static_cast<size_t>(k));
}
return std::accumulate(dice.begin(), dice.end(), 0);
}
int
DieTotal(int m, int n)
{
std::uniform_int_distribution<> die(1, n);
std::random_device rd;
int i = 0, total = 0;
for (i = 0; i < m; i++) {
total += die(rd);
}
return total;
}
float
RadiansToDegreesF(float rads)
{
return rads * (180.0 / M_PI);
}
double
RadiansToDegreesD(double rads)
{
return rads * (180.0 / M_PI);
}
float
DegreesToRadiansF(float degrees)
{
return degrees * M_PI / 180.0;
}
double
DegreesToRadiansD(double degrees)
{
return degrees * M_PI / 180.0;
}
double
RotateRadians(double theta0, double theta1)
{
auto dtheta = theta0 + theta1;
if (dtheta > M_PI) {
dtheta -= MAX_RADIAN;
} else if (dtheta < -M_PI) {
dtheta += MAX_RADIAN;
}
if ((dtheta < -M_PI) || (dtheta > M_PI)) {
return RotateRadians(dtheta, 0);
}
return dtheta;
}
const double Epsilon_double = 0.0001;
const float Epsilon_float = 0.0001;
void
DefaultEpsilon(double &epsilon)
{
epsilon = Epsilon_double;
}
void
DefaultEpsilon(float &epsilon)
{
epsilon = Epsilon_float;
}
} // namespace math

19
src/scmp/motion2d.cc Normal file
View File

@@ -0,0 +1,19 @@
#include <cmath>
#include <scccl/phys/basic/motion2d.h>
namespace scphys {
namespace basic {
scmath::geom::Vector2d
Acceleration(double speed, double heading)
{
auto dx = std::cos(heading) * speed;
auto dy = std::sin(heading) * speed;
return scmath::geom::Vector2d({dx, dy});
}
} // namespace basic
} // namespace phys

40
src/scmp/orientation.cc Normal file
View File

@@ -0,0 +1,40 @@
#include <scccl/math/geom/vector.h>
#include <scccl/math/geom/orientation.h>
namespace scmath {
namespace geom {
float
Heading2f(Vector2f vec)
{
return vec.angle(Basis2f[Basis_x]);
}
float
Heading3f(Vector3f vec)
{
Vector2f vec2f {vec[0], vec[1]};
return Heading2f(vec2f);
}
double
Heading2d(Vector2d vec)
{
return vec.angle(Basis2d[Basis_x]);
}
double
Heading3d(Vector3d vec)
{
Vector2d vec2d {vec[0], vec[1]};
return Heading2d(vec2d);
}
} // namespace geom
} // namespace math

91
src/scmp/quaternion.cc Normal file
View File

@@ -0,0 +1,91 @@
#include <iostream>
#include <scccl/math/geom/quaternion.h>
namespace scmath {
namespace geom {
Quaternionf
quaternionf(Vector3f axis, float angle)
{
return Quaternionf(axis.unitVector() * std::sin(angle / 2.0),
std::cos(angle / 2.0));
}
Quaterniond
quaterniond(Vector3d axis, double angle)
{
return Quaterniond(axis.unitVector() * std::sin(angle / 2.0),
std::cos(angle / 2.0));
}
Quaternionf
quaternionf_from_euler(Vector3f euler)
{
float x, y, z, w;
euler = euler / 2.0;
float cos_yaw = std::cos(euler[0]);
float cos_pitch = std::cos(euler[1]);
float cos_roll = std::cos(euler[2]);
float sin_yaw = std::sin(euler[0]);
float sin_pitch = std::sin(euler[1]);
float sin_roll = std::sin(euler[2]);
x = (sin_yaw * cos_pitch * cos_roll) + (cos_yaw * sin_pitch * sin_roll);
y = (sin_yaw * cos_pitch * sin_roll) - (cos_yaw * sin_pitch * cos_roll);
z = (cos_yaw * cos_pitch * sin_roll) + (sin_yaw * sin_pitch * cos_roll);
w = (cos_yaw * cos_pitch * cos_roll) - (sin_yaw * sin_pitch * sin_roll);
return Quaternionf(Vector4f{w, x, y, z});
}
Quaterniond
quaterniond_from_euler(Vector3d euler)
{
double x, y, z, w;
euler = euler / 2.0;
double cos_yaw = std::cos(euler[0]);
double cos_pitch = std::cos(euler[1]);
double cos_roll = std::cos(euler[2]);
double sin_yaw = std::sin(euler[0]);
double sin_pitch = std::sin(euler[1]);
double sin_roll = std::sin(euler[2]);
x = (sin_yaw * cos_pitch * cos_roll) + (cos_yaw * sin_pitch * sin_roll);
y = (sin_yaw * cos_pitch * sin_roll) - (cos_yaw * sin_pitch * cos_roll);
z = (cos_yaw * cos_pitch * sin_roll) + (sin_yaw * sin_pitch * cos_roll);
w = (cos_yaw * cos_pitch * cos_roll) - (sin_yaw * sin_pitch * sin_roll);
return Quaterniond(Vector4d{w, x, y, z});
}
void
Quaternion_SelfTest()
{
#ifndef NDEBUG
Vector3f v {1.0, 0.0, 0.0};
Vector3f yAxis {0.0, 1.0, 0.0};
float angle = M_PI / 2;
Quaternionf p = quaternionf(yAxis, angle);
Quaternionf q;
Vector3f vr {0.0, 0.0, 1.0};
assert(p.isUnitQuaternion());
std::cerr << p.rotate(v) << std::endl;
assert(p.rotate(v) == vr);
assert(p * q == p);
#endif
}
} // namespace geom
} // namespace math

377
src/sl/Arena.cc Normal file
View File

@@ -0,0 +1,377 @@
///
/// \file Arena.cc
/// \author K. Isom <kyle@imap.cc>
/// \date 2023-10-06
/// \brief Memory management using an arena.
///
/// 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 <cstdio>
#include <cstdlib>
#include <cstring>
#if defined(__posix__) || defined(__linux__) || defined(__APPLE__)
#include <sys/mman.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#define PROT_RW (PROT_WRITE|PROT_READ)
#endif
#include <ios>
#include <scsl/Arena.h>
namespace scsl {
Arena::Arena()
: store(nullptr), size(0), fd(0), arenaType(ArenaType::Uninit)
{
}
Arena::~Arena()
{
this->Destroy();
}
int
Arena::SetStatic(uint8_t *mem, size_t memSize)
{
this->store = mem;
this->size = memSize;
this->arenaType = ArenaType::Static;
return 0;
}
int
Arena::SetAlloc(size_t allocSize)
{
if (this->size > 0) {
this->Destroy();
}
this->arenaType = ArenaType::Alloc;
this->size = allocSize;
this->store = new uint8_t[allocSize];
if (this->store == nullptr) {
return -1;
}
this->Clear();
return 0;
}
#if defined(__posix__) || defined(__linux__) || defined(__APPLE__)
int
Arena::MemoryMap(int memFileDes, size_t memSize)
{
if (this->size > 0) {
this->Destroy();
}
this->arenaType = ArenaType::MemoryMapped;
this->size = memSize;
this->store = (uint8_t *) mmap(NULL, memSize, PROT_RW, MAP_SHARED,
memFileDes, 0);
if ((void *) this->store == MAP_FAILED) {
return -1;
}
this->fd = memFileDes;
return 0;
}
int
Arena::Open(const char *path)
{
struct stat st{};
if (this->size > 0) {
this->Destroy();
}
if (stat(path, &st) != 0) {
return -1;
}
this->fd = open(path, O_RDWR);
if (this->fd == -1) {
return -1;
}
return this->MemoryMap(this->fd, (size_t) st.st_size);
}
int
Arena::Create(const char *path, size_t fileSize)
{
FILE *fHandle = nullptr;
int newFileDes = 0;
if (this->size > 0) {
this->Destroy();
}
fHandle = fopen(path, "w");
if (fHandle == nullptr) {
return -1;
}
newFileDes = fileno(fHandle);
if (ftruncate(newFileDes, fileSize) == -1) {
return -1;
}
close(newFileDes);
return this->Open(path);
}
#elif defined(__WIN64__) || defined(__WIN32__) || defined(WIN32)
int
Arena::Open(const char *path)
{
HANDLE fHandle;
DWORD fRead = 0;
size_t fSize;
size_t fRemaining;
auto *cursor = this->store;
OVERLAPPED overlap = {0};
fHandle = CreateFileA(
(LPSTR) path,
GENERIC_READ,
(FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE),
NULL,
OPEN_ALWAYS,
FILE_ATTRIBUTE_NORMAL,
NULL);
if (fHandle == INVALID_HANDLE_VALUE) {
return Windows::DisplayWinError("CreateFileA", NULL);
}
if (SetFilePointer(fHandle, 0, 0, FILE_BEGIN) != 0) {
return Windows::DisplayWinError("SetFilePointer", fHandle);
}
if (GetFileSizeEx(fHandle, reinterpret_cast<PLARGE_INTEGER>(&fSize)) !=
TRUE) {
return Windows::DisplayWinError("GetFileSizeEx", fHandle);
}
this->SetAlloc(fSize);
cursor = this->NewCursor();
this->store[0] = 1;
fRemaining = fSize;
while (fRemaining != 0) {
overlap.Offset = (fSize - fRemaining);
if (ReadFile(fHandle, cursor, fSize - 1,
&fRead,
&overlap) != TRUE) {
auto errorCode = GetLastError();
if (errorCode != ERROR_HANDLE_EOF) {
this->Destroy();
return Windows::DisplayWinError("ReadFile", fHandle);
}
break;
}
cursor += fRead;
fRemaining -= fRead;
}
CloseHandle(fHandle);
return 0;
}
int
Arena::Create(const char *path, size_t fileSize)
{
auto errorCode = Windows::CreateFixedSizeFile(path, fileSize);
if (errorCode != 0) {
return errorCode;
}
return this->Open(path);
}
#endif
bool
Arena::CursorInArena(const uint8_t *cursor)
{
if (cursor < this->store) {
return false;
}
if (cursor >= this->End()) {
return false;
}
return true;
}
/*
* ClearArena clears the memory being used, removing any data
* present. It does not free the memory; it is effectively a
* wrapper around memset.
*/
void
Arena::Clear()
{
if (this->size == 0) {
return;
}
memset(this->store, 0, this->size);
}
void
Arena::Destroy()
{
if (this->arenaType == ArenaType::Uninit) {
return;
}
switch (this->arenaType) {
case ArenaType::Static:
break;
case ArenaType::Alloc:
delete[] this->store;
break;
#if defined(__posix__) || defined(__linux__) || defined(__APPLE__)
case ArenaType::MemoryMapped:
if (munmap(this->store, this->size) == -1) {
abort();
return;
}
if (close(this->fd) == -1) {
abort();
}
this->fd = 0;
break;
#endif
default:
#if defined(NDEBUG)
return;
#else
abort();
#endif
}
this->arenaType = ArenaType::Uninit;
this->size = 0;
this->store = nullptr;
return;
}
std::ostream &
operator<<(std::ostream &os, Arena &arena)
{
auto cursor = arena.Start();
char cursorString[33] = {0};
snprintf(cursorString, 32, "%#016llx",
(long long unsigned int) cursor);
os << "Arena<";
switch (arena.Type()) {
case ArenaType::Uninit:
os << "uninitialized";
break;
case ArenaType::Static:
os << "static";
break;
case ArenaType::Alloc:
os << "allocated";
break;
#if defined(__posix__) || defined(__linux__) || defined(__APPLE__)
case ArenaType::MemoryMapped:
os << "mmap/file";
break;
#endif
default:
os << "unknown (this is a bug)";
}
os << ">@0x";
os << std::hex << (uintptr_t) &arena;
os << std::dec;
os << ",store<" << arena.Size() << "B>@";
os << std::hex << cursorString;
os << std::dec;
return os;
}
int
Arena::Write(const char *path)
{
FILE *arenaFile = nullptr;
int retc = -1;
#if defined(__posix__) || defined(__linux__) || defined(__APPLE__)
arenaFile = fopen(path, "w");
if (arenaFile == nullptr) {
#else
if (fopen_s(&arenaFile, path, "w") != 0) {
#endif
return -1;
}
if (fwrite(this->store, sizeof(*this->store), this->size,
arenaFile) == this->size) {
retc = 0;
}
if (fclose(arenaFile) != 0) {
return -1;
}
return retc;
}
uint8_t &
Arena::operator[](size_t index)
{
if (index > this->size) {
#if defined(SCSL_DESKTOP_BUILD) and !defined(SCSL_NOEXCEPT)
throw std::range_error("index out of range");
#else
abort();
#endif
}
return this->store[index];
}
} // namespace scsl

379
src/sl/Buffer.cc Normal file
View File

@@ -0,0 +1,379 @@
///
/// \file Buffer.cc
/// \author K. Isom <kyle@imap.cc>
/// \date 2023-10-09
/// \brief Buffer implements basic line buffers.
///
/// 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 <cstring>
#include <iomanip>
#include <ios>
#include <iostream>
#include <scsl/Buffer.h>
namespace scsl {
/// The defaultCapacity for a new Buffer is a reasonably arbitrary starting
/// point.
constexpr size_t defaultCapacity = 32;
/// maxReasonableLine is the longest a reasonable line could be. It assumes
/// something like a long, unprettified JSON strong or the like.
constexpr size_t maxReasonableLine = 8192;
static size_t
nearestPower(size_t x)
{
if (x == 0) {
return 0;
}
x--;
x |= x >> 1;
x |= x >> 2;
x |= x >> 4;
x |= x >> 8;
x |= x >> 16;
x |= x >> 32;
return x + 1;
}
Buffer::Buffer()
: contents(nullptr), length(0), capacity(0), autoTrim(true)
{
this->Resize(defaultCapacity);
}
Buffer::Buffer(size_t initialCapacity)
: contents(nullptr), length(0), capacity(0), autoTrim(true)
{
this->Resize(initialCapacity);
}
Buffer::Buffer(const char *data)
: contents(nullptr), length(0), capacity(0), autoTrim(true)
{
size_t datalen = strnlen(data, maxReasonableLine);
this->Append((uint8_t *) data, datalen);
}
Buffer::Buffer(const std::string s)
: contents(nullptr), length(0), capacity(0), autoTrim(true)
{
this->Append(s);
}
bool
Buffer::Append(const char *s)
{
size_t slen = strnlen(s, maxReasonableLine);
return this->Append((uint8_t *) s, slen);
}
bool
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 newCap = this->mustGrow(datalen);
if (newCap > 0) {
this->Resize(newCap);
resized = true;
}
assert(this->contents != nullptr);
memcpy(this->contents + this->length, data, datalen);
this->length += datalen;
return resized;
}
bool
Buffer::Append(const uint8_t c)
{
return this->Append(&c, 1);
}
bool
Buffer::Insert(const size_t index, const char *s)
{
size_t slen = strnlen(s, maxReasonableLine);
return this->Insert(index, (uint8_t *) (s), slen);
}
bool
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);
memcpy(this->contents + index, data, datalen);
this->length += datalen;
return resized;
}
bool
Buffer::Insert(const size_t index, const uint8_t c)
{
return this->Insert(index, &c, 1);
}
bool
Buffer::Remove(const size_t index, const size_t count)
{
auto resized = this->shiftLeft(index, count);
this->length -= count;
return resized;
}
bool
Buffer::Remove(size_t index)
{
return this->Remove(index, 1);
}
void
Buffer::Resize(size_t newCapacity)
{
if (newCapacity < this->length) {
newCapacity = nearestPower(this->length + newCapacity);
}
auto newContents = new uint8_t[newCapacity];
memset(newContents, 0, newCapacity);
if (this->length > 0) {
memcpy(newContents, this->contents, this->length);
}
if (this->length > 0) {
delete[] this->contents;
this->contents = nullptr;
}
this->contents = newContents;
this->capacity = newCapacity;
}
size_t
Buffer::Trim()
{
size_t projectedCapacity = nearestPower(this->length);
assert(projectedCapacity >= length);
if (projectedCapacity < this->capacity) {
this->Resize(projectedCapacity);
return this->Capacity();
}
return 0;
}
void
Buffer::Clear()
{
if (this->length == 0) {
return;
}
memset(this->contents, 0, this->length);
this->length = 0;
}
void
Buffer::Reclaim()
{
this->Clear();
if (this->contents == nullptr) {
assert(this->length == 0);
assert(this->capacity == 0);
return;
}
delete[] this->contents;
this->contents = nullptr;
this->capacity = 0;
}
size_t
Buffer::mustGrow(size_t delta) const
{
if ((delta + this->length) < this->capacity) {
return 0;
}
auto newCapacity = delta + this->length;
return nearestPower(newCapacity);
}
void
Buffer::HexDump(std::ostream &os)
{
#ifndef NDEBUG
size_t index = 0;
os << std::hex;
os << std::setfill('0');
for (index = 0; index < this->length; index++) {
bool eol = (index % 16) == 0;
if (eol && (index > 0)) {
os << std::endl;
}
if (eol) {
os << std::setw(8);
os << index << " ";
os << std::setw(2);
}
os << (unsigned short) this->contents[index];
if ((index % 15) != 0 || (index == 0)) {
os << " ";
}
}
if ((index % 16) != 0) {
os << std::endl;
}
os << std::setw(0) << std::dec;
#else
(void)os;
#endif
}
bool
Buffer::shiftRight(size_t offset, size_t delta)
{
auto resized = false;
auto newCap = this->mustGrow(delta);
if (newCap > 0) {
this->Resize(newCap);
resized = true;
}
if (this->length == 0) return 0;
memmove(this->contents + (offset + delta), this->contents + offset,
this->length);
return resized;
}
bool
Buffer::shiftLeft(size_t offset, size_t delta)
{
if (delta == 0) {
return false;
}
if ((offset+delta) > this->length) {
abort();
}
for (auto i = offset; i <= (this->length-delta); i++) {
this->contents[i] = this->contents[i+delta];
}
for (auto i = this->length-delta; i < this->length; i++) {
this->contents[i] = 0;
}
if (this->AutoTrimIsEnabled()) {
return this->Trim() != 0;
}
return false;
}
uint8_t &
Buffer::operator[](size_t index)
{
if (index > this->length) {
#if defined(SCSL_DESKTOP_BUILD) and !defined(SCSL_NOEXCEPT)
throw std::range_error("array index out of bounds");
#else
abort();
#endif
}
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 scsl

78
src/sl/Commander.cc Normal file
View File

@@ -0,0 +1,78 @@
///
/// \file Commander.cc
/// \author K. Isom <kyle@imap.cc>
/// \date 2023-10-10
/// \brief Subprogram tooling.
///
/// 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 <iostream>
#include <scsl/Commander.h>
namespace scsl {
Subcommand::Status
Subcommand::Run(int argc, char **argv)
{
if (argc < this->args) {
std::cerr << "[!] " << this->command << " expects ";
std::cerr << this->args << " args, but was given ";
std::cerr << argc << " args.\n";
return Subcommand::Status::NotEnoughArgs;
}
if (this->fn(argc, argv)) {
return Subcommand::Status::OK;
}
return Subcommand::Status::Failed;
}
Commander::Commander()
: cmap()
{
this->cmap.clear();
}
bool
Commander::Register(Subcommand scmd)
{
if (this->cmap.count(scmd.Name()) > 0) {
return false;
}
auto *pScmd = new Subcommand(scmd);
this->cmap[scmd.Name()] = pScmd;
return true;
}
Subcommand::Status
Commander::Run(std::string command, int argc, char **argv)
{
if (this->cmap.count(command) != 1) {
return Subcommand::Status::CommandNotRegistered;
}
auto scmd = this->cmap[command];
return scmd->Run(argc, argv);
}
} // namespace scsl

188
src/sl/Dictionary.cc Normal file
View File

@@ -0,0 +1,188 @@
///
/// \file Dictionary.cc
/// \author K.Isom <kyle@imap.cc>
/// \date 2023-10-05
/// \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.
///
#include <cassert>
#include <cstdlib>
#include <cstring>
#include <scsl/Dictionary.h>
#if defined(SCSL_DESKTOP_BUILD)
#include <iostream>
#endif
namespace scsl {
bool
Dictionary::Lookup(const char *key, uint8_t klen, TLV::Record &res)
{
res.Tag = this->kTag;
uint8_t *cursor = TLV::FindTag(this->arena, nullptr, res);
while (cursor != nullptr) {
if ((klen == res.Len) &&
(memcmp(res.Val, key, klen) == 0)) {
TLV::ReadFromMemory(res, cursor);
assert(res.Tag == this->vTag);
return res.Tag == this->vTag;
}
cursor = TLV::FindTag(this->arena, cursor, res);
}
return false;
}
int
Dictionary::Set(const char *key, uint8_t klen, const char *val, uint8_t vlen)
{
TLV::Record rec;
uint8_t *cursor = nullptr;
SetRecord(rec, this->kTag, klen, key);
cursor = this->seek(key, klen);
if (cursor != nullptr) {
TLV::DeleteRecord(this->arena, cursor);
TLV::DeleteRecord(this->arena, cursor);
}
if (!spaceAvailable(klen, vlen)) {
return -1;
}
cursor = TLV::WriteToMemory(this->arena, nullptr, rec);
if (cursor == nullptr) {
return -1;
}
SetRecord(rec, this->vTag, vlen, val);
if (TLV::WriteToMemory(this->arena, nullptr, rec) == nullptr) {
return -1;
}
return 0;
}
/// seek searches the Dictionary for the key.
uint8_t *
Dictionary::seek(const char *key, uint8_t klen)
{
TLV::Record rec;
rec.Tag = this->kTag;
uint8_t *cursor = TLV::LocateTag(this->arena, nullptr, rec);
while (cursor != nullptr) {
if ((klen == rec.Len) && (this->kTag == rec.Tag)) {
if (memcmp(rec.Val, key, klen) == 0) {
return cursor;
}
}
cursor = TLV::SkipRecord(rec, cursor);
cursor = TLV::LocateTag(this->arena, cursor, rec);
}
return nullptr;
}
bool
Dictionary::Contains(const char *key, uint8_t klen)
{
return this->seek(key, klen) != nullptr;
}
bool
Dictionary::Delete(const char *key, uint8_t klen)
{
auto *cursor = this->seek(key, klen);
if (cursor == nullptr) {
return false;
}
TLV::DeleteRecord(this->arena, cursor);
TLV::DeleteRecord(this->arena, cursor);
return true;
}
bool
Dictionary::spaceAvailable(uint8_t klen, uint8_t vlen)
{
size_t required = 0;
uintptr_t remaining = 0;
uint8_t *cursor = nullptr;
cursor = TLV::FindEmpty(this->arena, nullptr);
if (cursor == nullptr) {
return false;
}
required += klen + 2;
required += vlen + 2;
remaining = (uintptr_t)cursor - (uintptr_t) arena.Start();
remaining = arena.Size() - remaining;
return ((size_t)remaining >= required);
}
std::ostream &
operator<<(std::ostream &os, const Dictionary &dictionary)
{
#if defined(SCSL_DESKTOP_BUILD)
uint8_t *cursor = (dictionary.arena).Start();
TLV::Record rec;
TLV::ReadFromMemory(rec, cursor);
if (rec.Tag == TLV::TAG_EMPTY) {
os << "\t(NONE)" << std::endl;
return os;
}
while ((cursor != nullptr) && (rec.Tag != TLV::TAG_EMPTY)) {
os << "\t" << rec.Val << "->";
cursor = TLV::SkipRecord(rec, cursor);
TLV::ReadFromMemory(rec, cursor);
os << rec.Val << std::endl;
cursor = TLV::SkipRecord(rec, cursor);
TLV::ReadFromMemory(rec, cursor);
}
#endif
return os;
}
int
Dictionary::DumpToFile(const char *path)
{
return this->arena.Write(path);
}
} // namespace scsl

39
src/sl/Exceptions.cc Normal file
View File

@@ -0,0 +1,39 @@
///
/// \file Exceptions.cc
/// \author K. Isom <kyle@imap.cc>
/// \date 2023-10-10
/// \brief Custom exceptions 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.
///
#include <scsl/Exceptions.h>
namespace scsl {
AssertionFailed::AssertionFailed(std::string message) : msg(message) {}
const char *
AssertionFailed::what() const throw()
{
return const_cast<char *>(this->msg.c_str());
}
}

444
src/sl/Flag.cc Normal file
View File

@@ -0,0 +1,444 @@
///
/// \file Flag.cc
/// \author K. Isom <kyle@imap.cc>
/// \date 2023-10-12
/// \brief Flag defines 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 <iostream>
#include <regex>
#include <vector>
#include <scsl/Flag.h>
#include <scsl/StringUtil.h>
namespace scsl {
static const std::regex isFlag("^--?[a-zA-Z0-9][a-zA-Z0-9-_]*$",
std::regex_constants::nosubs);
std::string
Flags::ParseStatusToString(ParseStatus status)
{
switch (status) {
case ParseStatus::OK:
return "OK";
case ParseStatus::EndOfFlags:
return "end of flags";
case ParseStatus::NotRegistered:
return "flag not registered";
case ParseStatus::NotEnoughArgs:
return "not enough args passed to flags";
default:
return "unknown/unspecified parse error";
}
}
Flag *
NewFlag(std::string fName, FlagType fType, std::string fDescription)
{
auto flag = new Flag;
flag->Type = fType;
flag->WasSet = false;
flag->Name = fName;
flag->Description = fDescription;
flag->Value = FlagValue{};
return flag;
}
Flags::Flags(std::string fName)
: name(fName), description("")
{
}
Flags::Flags(std::string fName, std::string fDescription)
: name(fName), description(fDescription)
{
}
Flags::~Flags()
{
for (auto flag : this->flags) {
if (flag.second->Type == FlagType::String) {
delete flag.second->Value.s;
}
delete flag.second;
}
}
bool
Flags::Register(std::string fName, FlagType fType, std::string fDescription)
{
if (!std::regex_search(fName, isFlag) || fName == "-h") {
return false;
}
if (this->flags.count(fName) != 0) {
return false;
}
auto flag = NewFlag(fName, fType, fDescription);
this->flags[fName] = flag;
return true;
}
bool
Flags::Register(std::string fName, bool defaultValue, std::string fDescription)
{
if (!this->Register(fName, FlagType::Boolean, fDescription)) {
return false;
}
this->flags[fName]->Value.b = defaultValue;
return true;
}
bool
Flags::Register(std::string fName, int defaultValue, std::string fDescription)
{
if (!this->Register(fName, FlagType::Integer, fDescription)) {
return false;
}
this->flags[fName]->Value.i = defaultValue;
return true;
}
bool
Flags::Register(std::string fName, unsigned int defaultValue, std::string fDescription)
{
if (!this->Register(fName, FlagType::UnsignedInteger, fDescription)) {
return false;
}
this->flags[fName]->Value.u = defaultValue;
return true;
}
bool
Flags::Register(std::string fName, size_t defaultValue, std::string fDescription)
{
if (!this->Register(fName, FlagType::SizeT, fDescription)) {
return false;
}
this->flags[fName]->Value.size = defaultValue;
return true;
}
bool
Flags::Register(std::string fName, std::string defaultValue, std::string fDescription)
{
if (!this->Register(fName, FlagType::String, fDescription)) {
return false;
}
this->flags[fName]->Value.s = new std::string(defaultValue);
return true;
}
size_t
Flags::Size()
{
return this->flags.size();
}
Flag *
Flags::Lookup(std::string fName)
{
if (this->flags.count(fName) == 0) {
return nullptr;
}
return this->flags[fName];
}
bool
Flags::ValueOf(std::string fName, FlagValue &value)
{
if (this->flags.count(fName)) {
return false;
}
value = this->flags[fName]->Value;
return true;
}
Flags::ParseStatus
Flags::parseArg(int argc, char **argv, int &index)
{
std::string arg(argv[index]);
U::S::TrimWhitespace(arg);
index++;
if (!std::regex_search(arg, isFlag)) {
return ParseStatus::EndOfFlags;
}
if (this->flags.count(arg) == 0) {
if (arg == "-h" || arg == "--help") {
Usage(std::cout, 0);
}
return ParseStatus::NotRegistered;
}
auto flag = flags[arg];
if ((flag->Type != FlagType::Boolean) && index == argc) {
return ParseStatus::NotEnoughArgs;
}
switch (flag->Type) {
case FlagType::Boolean:
flag->WasSet = true;
flag->Value.b = true;
return ParseStatus::OK;
case FlagType::Integer:
flag->WasSet = true;
flag->Value.i = std::stoi(argv[++index], 0, 0);
return ParseStatus::OK;
case FlagType::UnsignedInteger:
flag->WasSet = true;
flag->Value.u = static_cast<unsigned int>(std::stoi(argv[index++], 0, 0));
return ParseStatus::OK;
case FlagType::String:
flag->WasSet = true;
flag->Value.s = new std::string(argv[index++]);
return ParseStatus::OK;
case FlagType::SizeT:
flag->WasSet = true;
flag->Value.size = static_cast<size_t>(std::stoull(argv[index++]));
return ParseStatus::OK;
default:
#if defined(NDEBUG) or defined(SCSL_NOEXCEPT)
return ParseStatus::Unknown;
#else
throw std::runtime_error("unhandled type");
#endif
}
return ParseStatus::OK;
}
Flags::ParseStatus
Flags::Parse(int argc, char **argv, bool skipFirst)
{
int index = 0;
if (skipFirst) {
index = 1;
}
while (index != argc) {
auto result = this->parseArg(argc, argv, index);
switch (result) {
case ParseStatus::OK:
continue;
case ParseStatus::EndOfFlags:
while (index < argc) {
this->args.push_back(std::string(argv[index]));
index++;
}
continue;
case ParseStatus::NotEnoughArgs:
case ParseStatus::NotRegistered:
// fall through //
return result;
default:
#if defined(NDEBUG) or defined(SCSL_NOEXCEPT)
return ParseStatus::Unknown;
#else
throw std::runtime_error("unhandled parse state");
#endif
}
}
return ParseStatus::OK;
}
void
Flags::Usage(std::ostream &os, int exitCode)
{
os << this->name << ":\t";
auto indent = this->name.size() + 7;
U::S::WriteTabIndented(os, description, 72 - indent, indent / 8, false);
os << "\n\n";
for (const auto &pair : this->flags) {
auto argLine = "\t" + pair.first;
switch (pair.second->Type) {
case FlagType::Boolean:
argLine += "\t\t";
break;
case FlagType::Integer:
argLine += "int\t\t";
break;
case FlagType::UnsignedInteger:
argLine += "uint\t\t";
break;
case FlagType::SizeT:
argLine += "size_t\t";
break;
case FlagType::String:
argLine += "string\t";
break;
case FlagType::Unknown:
// fallthrough
default:
#ifdef SCSL_NOEXCEPT
abort();
#else
throw std::runtime_error("unhandled parsing error - this is a bug");
#endif
break;
}
os << argLine;
indent = argLine.size();
U::S::WriteTabIndented(os, pair.second->Description,
72-indent, (indent/8)+2, false);
}
os << "\n";
exit(exitCode);
}
size_t
Flags::NumArgs()
{
return this->args.size();
}
std::vector<std::string>
Flags::Args()
{
return this->args;
}
std::string
Flags::Arg(size_t i)
{
if (i >= this->args.size()) {
throw std::out_of_range("index is out of range");
}
return this->args[i];
}
Flag *
Flags::checkGetArg(std::string fName, FlagType eType)
{
if (this->flags[fName] == 0) {
return nullptr;
}
auto flag = this->flags[fName];
if (flag == nullptr) {
return nullptr;
}
if (flag->Type != eType) {
return nullptr;
}
return flag;
}
bool
Flags::GetBool(std::string fName, bool &flagValue)
{
auto flag = this->checkGetArg(fName, FlagType::Boolean);
flagValue = flag->Value.b;
return flag->WasSet;
}
bool
Flags::GetInteger(std::string fName, int &flagValue)
{
auto flag = this->checkGetArg(fName, FlagType::Integer);
flagValue = flag->Value.i;
return flag->WasSet;
}
bool
Flags::GetUnsignedInteger(std::string fName, unsigned int &flagValue)
{
auto flag = this->checkGetArg(fName, FlagType::UnsignedInteger);
flagValue = flag->Value.u;
return flag->WasSet;
}
bool
Flags::GetSizeT(std::string fName, std::size_t &flagValue)
{
auto flag = this->checkGetArg(fName, FlagType::SizeT);
flagValue = flag->Value.size;
return flag->WasSet;
}
bool
Flags::GetString(std::string fName, std::string &flagValue)
{
auto flag = this->checkGetArg(fName, FlagType::String);
if (flag->Value.s == nullptr) {
return false;
}
flagValue = *(flag->Value.s);
return flag->WasSet;
}
} // namespace scsl

76
src/sl/Roll.cc Normal file
View File

@@ -0,0 +1,76 @@
#include <iostream>
#include <string>
#include <vector>
#include <scccl/math/math.h>
using namespace std;
using namespace scmath;
static void
rollDie(char *s)
{
int m = 0, n = 0;
int i = 0;
bool readSides = false;
while (s[i] != '\0') {
if (s[i] != 'd' && !isdigit(s[i])) {
cerr << "Invalid die specification!" << endl;
return;
}
if (readSides) {
if (s[i] == 'd') {
cerr << "Invalid die specification!" << endl;
return;
}
n *= 10;
n += (s[i] - 0x30);
} else {
if (s[i] == 'd') {
readSides = true;
} else {
m *= 10;
m += (s[i] - 0x30);
}
}
i++;
}
if (m == 0) {
m = 1;
}
cout << s << ": " << DieTotal(m, n) << endl;
}
static void
rollPlayer()
{
vector<string> statNames = {"STR", "CON", "DEX", "INT", "PER"};
vector<int> statRolls;
for (size_t i = 0; i < statNames.size(); i++) {
statRolls.push_back(BestDie(3, 4, 6));
}
for (size_t i = 0; i < statNames.size(); i++) {
cout << statNames[i] << ": " << statRolls[i] << endl;
}
}
int
main(int argc, char *argv[])
{
for (int i = 1; i < argc; i++) {
if (string(argv[i]) == "player") {
rollPlayer();
} else {
rollDie(argv[i]);
}
}
}

235
src/sl/StringUtil.cc Normal file
View File

@@ -0,0 +1,235 @@
///
/// \file StringUtil.cc
/// \author K. Isom <kyle@imap.cc>
/// \date 2023-10-12
/// \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 <cassert>
#include <iostream>
#include <sstream>
#include <scsl/StringUtil.h>
namespace scsl {
/// namespace U contains utilities.
namespace U {
/// namespace S contains string-related functions.
namespace S {
std::vector<std::string>
SplitKeyValuePair(std::string line, std::string delimiter)
{
auto pair = SplitN(line, delimiter, 2);
if (pair.size() == 0) {
return {"", ""};
} else if (pair.size() == 1) {
return {pair[0], ""};
}
assert(pair.size() == 2);
auto key = pair[0];
auto val = pair[1];
TrimWhitespace(key);
TrimWhitespace(val);
return {key, val};
}
std::vector<std::string>
SplitKeyValuePair(std::string line, char delimiter)
{
std::string sDelim;
sDelim.push_back(delimiter);
return SplitKeyValuePair(line, sDelim);
}
void
TrimLeadingWhitespace(std::string &s)
{
s.erase(s.begin(),
std::find_if(s.begin(), s.end(),
[](unsigned char ch) {
return !std::isspace(ch);
}));
}
void
TrimTrailingWhitespace(std::string &s)
{
s.erase(std::find_if(s.rbegin(), s.rend(), [](unsigned char ch) {
return !std::isspace(ch);
}).base(), s.end());
}
void
TrimWhitespace(std::string &s)
{
TrimLeadingWhitespace(s);
TrimTrailingWhitespace(s);
}
std::string
TrimLeadingWhitespaceDup(std::string s)
{
TrimLeadingWhitespace(s);
return s;
}
std::string
TrimTrailingWhitespaceDup(std::string s)
{
TrimTrailingWhitespace(s);
return s;
}
std::string
TrimWhitespaceDup(std::string s)
{
TrimWhitespace(s);
return s;
}
std::vector<std::string>
SplitN(std::string s, std::string delim, size_t maxCount)
{
std::vector<std::string> parts;
size_t ss = 0;
size_t se = 0;
for (ss = 0; s.size() != 0 && ss < s.size(); ss++) {
se = s.find(delim, ss);
if ((maxCount > 0) && (parts.size() == maxCount - 1)) {
se = s.size();
} else if (se == std::string::npos) {
se = s.size();
}
auto length = se - ss;
parts.push_back(s.substr(ss, length));
ss = se;
}
return parts;
}
std::vector<std::string>
WrapText(std::string line, size_t lineLength)
{
std::vector<std::string> wrapped;
auto parts = SplitN(line, " ", 0);
for (auto &part: parts) {
TrimWhitespace(part);
}
std::string wLine;
for (auto word: parts) {
if (word.size() == 0) {
continue;
}
if ((wLine.size() + word.size() + 1) > lineLength) {
wrapped.push_back(wLine);
wLine.clear();
}
if (wLine.size() > 0) {
wLine += " ";
}
wLine += word;
}
if (wLine.size() > 0) {
wrapped.push_back(wLine);
}
return wrapped;
}
void
WriteTabIndented(std::ostream &os, std::vector<std::string> lines,
int tabStop, bool indentFirst)
{
std::string indent(tabStop, '\t');
for (size_t i = 0; i < lines.size(); i++) {
if (i > 0 || indentFirst) {
os << indent;
}
os << lines[i] << "\n";
}
}
void
WriteTabIndented(std::ostream &os, std::string line, size_t maxLength,
int tabStop, bool indentFirst)
{
auto lines = WrapText(line, maxLength);
WriteTabIndented(os, lines, tabStop, indentFirst);
}
std::ostream &
VectorToString(std::ostream &os, const std::vector<std::string> &svec)
{
os << "(";
os << svec.size();
os << ")";
os << "{";
for (size_t i = 0; i < svec.size(); i++) {
if (i > 0) { os << ", "; }
os << svec[i];
}
os << "}";
return os;
}
std::string
VectorToString(const std::vector<std::string> &svec)
{
std::stringstream ss;
VectorToString(ss, svec);
return ss.str();
}
} // namespace S
} // namespace U
} // namespace scsl

215
src/sl/TLV.cc Normal file
View File

@@ -0,0 +1,215 @@
///
/// \file TLV.cc
/// \author K. Isom <kyle@imap.cc>
/// \date 2023-10-06
/// \brief Tag-Length-Value records built on Arena.
///
/// 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 <cstring>
#include <scsl/TLV.h>
using namespace scsl;
/// REC_SIZE calculates the total length of a TLV record, including the
/// two byte header.
#define REC_SIZE(x) ((std::size_t)x.Len + 2)
namespace scsl {
namespace TLV {
static bool
spaceAvailable(Arena &arena, uint8_t *cursor, uint8_t len)
{
if (cursor == nullptr) {
return false;
}
return arena.CursorInArena(cursor + len);
}
static inline void
clearUnused(Record &rec)
{
uint8_t trail = TLV_MAX_LEN - rec.Len;
memset(rec.Val + rec.Len, 0, trail);
}
uint8_t *
WriteToMemory(Arena &arena, uint8_t *cursor, Record &rec)
{
// If cursor is nullptr, the user needs us to select an empty
// slot for the record. If we can't find one, that's an
// error.
//
// If, however, the user gives us a cursor, we'll trust it
// (though spaceAvailable will sanity check that cursor).
if (cursor == nullptr) {
cursor = FindEmpty(arena, cursor);
if (cursor == nullptr) {
return nullptr;
}
}
if (!arena.CursorInArena(cursor)) {
return nullptr;
}
if (!spaceAvailable(arena, cursor, rec.Len)) {
return nullptr;
}
memcpy(cursor, &rec, REC_SIZE(rec));
cursor = SkipRecord(rec, cursor);
return cursor;
}
void
SetRecord(Record &rec, uint8_t tag, uint8_t len, const char *val)
{
rec.Tag = tag;
rec.Len = len;
memcpy(rec.Val, val, len);
clearUnused(rec);
}
void
ReadFromMemory(Record &rec, uint8_t *cursor)
{
rec.Tag = cursor[0];
rec.Len = cursor[1];
memcpy(rec.Val, cursor + 2, rec.Len);
clearUnused(rec);
}
/*
* returns a pointer to memory where the record was found,
* e.g. FindTag(...)[0] is the tag of the found record.
*/
uint8_t *
FindTag(Arena &arena, uint8_t *cursor, Record &rec)
{
cursor = LocateTag(arena, cursor, rec);
if (rec.Tag != TAG_EMPTY) {
cursor = SkipRecord(rec, cursor);
if (!arena.CursorInArena(cursor)) {
cursor = nullptr;
}
}
return cursor;
}
uint8_t *
LocateTag(Arena &arena, uint8_t *cursor, Record &rec)
{
uint8_t tag, len;
if (!arena.CursorInArena(cursor)) {
cursor = nullptr;
}
if (cursor == nullptr) {
cursor = arena.Start();
}
while (((tag = cursor[0]) != rec.Tag) &&
(arena.CursorInArena(cursor))) {
assert(arena.CursorInArena(cursor));
len = cursor[1];
if (!spaceAvailable(arena, cursor, len)) {
return nullptr;
}
cursor += len;
cursor += 2;
}
if (!arena.CursorInArena(cursor)) {
return nullptr;
}
if (tag != rec.Tag) {
return nullptr;
}
if (tag != TAG_EMPTY) {
ReadFromMemory(rec, cursor);
}
return cursor;
}
uint8_t *
FindEmpty(Arena &arena, uint8_t *cursor)
{
Record rec;
rec.Tag = TAG_EMPTY;
return FindTag(arena, cursor, rec);
}
uint8_t *
SkipRecord(Record &rec, uint8_t *cursor)
{
return (uint8_t *) ((uintptr_t) cursor + rec.Len + 2);
}
void
DeleteRecord(Arena &arena, uint8_t *cursor)
{
if (cursor == nullptr) {
return;
}
if (!arena.CursorInArena(cursor)) {
return;
}
uint8_t len = cursor[1] + 2;
uint8_t *stop = arena.Start() + arena.Size();
stop -= len;
while (cursor != stop) {
cursor[0] = cursor[len];
cursor++;
}
stop += len;
while (cursor != stop) {
cursor[0] = 0;
cursor++;
}
}
} // namespace TLV
} // namespace scsl

70
src/test/Assert.cc Normal file
View File

@@ -0,0 +1,70 @@
///
/// \file Test.cc
/// \author K. Isom <kyle@imap.cc>
/// \date 2023-10-09
/// \brief Tooling to assist in building 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.
///
#include <scsl/Exceptions.h>
#include <sctest/Assert.h>
#include <cassert>
#include <iostream>
#include <sstream>
namespace scsl {
void
TestAssert(bool condition, std::string message)
{
#if defined(NDEBUG) || defined(SCSL_NOEXCEPT)
if (!condition) {
throw AssertionFailed(message);
}
#else
if (!condition) {
std::cerr << message << std::endl;
}
assert(condition);
#endif
}
void
TestAssert(bool condition)
{
#if defined(NDEBUG)
if (condition) {
return;
}
#if defined(SCSL_NOEXCEPT)
std::cerr << "Assertion failed!\n";
#else
std::stringstream msg;
msg << "assertion failed at " << __FILE__ << ":" << __LINE__;
throw AssertionFailed(msg.str());
#endif
#else
assert(condition);
#endif
}
} // namespace scsl

37
src/test/Report.cc Normal file
View File

@@ -0,0 +1,37 @@
///
/// \file src/test/Report.cpp
/// \author Kyle Isom
/// \date 2017-06-07
///
/// \brief Defines a Report structure that contains information about
/// the results of unit tests.
///
/// Copyright 2017 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 <chrono>
#include <sctest/Report.h>
namespace sctest {
_Report::_Report()
: Failing (0), Total(0), Start(std::chrono::steady_clock::now()),
End(std::chrono::steady_clock::now()), Duration(0) {}
} // end namespace test

108
src/test/SimpleSuite.cc Executable file
View File

@@ -0,0 +1,108 @@
///
/// \file SimpleSuite.cc
/// \author K. Isom <kyle@imap.cc>
/// \date 2017-06-05
/// \brief Defines a simple unit testing framework.
///
/// Copyright 2017 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 <chrono>
#include <iostream>
#include <sctest/SimpleSuite.h>
namespace sctest {
#define unless(cond) if (!(cond))
static bool
stub()
{ return true; }
SimpleSuite::SimpleSuite()
: quiet(false), fnSetup(stub), fnTeardown(stub), tests(),
report(), hasRun(false)
{
}
void
SimpleSuite::AddTest(std::string name, std::function<bool()> test)
{
TestCase test_case = {name, test};
tests.push_back(test_case);
}
void
SimpleSuite::AddFailingTest(std::string name, std::function<bool()> test)
{
// auto ntest = [&test]() { return !test(); };
TestCase test_case = {name, [&test]() { return !test(); }};
tests.push_back(test_case);
}
bool
SimpleSuite::Run()
{
report.Start = std::chrono::steady_clock::now();
unless(quiet) { std::cout << "Setting up the tests.\n"; }
unless(fnSetup()) { return false; }
// Reset the failed test counts.
report.Failing = 0;
bool result = true;
hasRun = true;
report.Total = tests.size();
for (size_t i = 0; i < report.Total && result; i++) {
TestCase tc = tests.at(i);
unless(quiet) {
std::cout << "[" << i + 1 << "/" << report.Total << "] Running test " << tc.name << ": ";
}
result = tc.test();
if (quiet) { continue; }
if (result) {
std::cout << "[PASS]";
} else {
std::cout << "[FAIL]";
report.Failing++;
}
std::cout << "\n";
}
unless(quiet) { std::cout << "Tearing down the tests.\n"; }
unless(fnTeardown()) { return false; }
report.End = std::chrono::steady_clock::now();
return result;
}
Report
SimpleSuite::GetReport()
{
return report;
}
} // end namespace sctest