Compare commits

...

62 Commits

Author SHA1 Message Date
Kyle Isom 6266148eed adding more to intro 2024-02-23 23:41:24 -08:00
K. Isom af61d914f0 add rpm support 2024-02-04 07:01:06 +00:00
Kyle Isom f99d5a8356 Add integer square root to SCMP. 2023-11-09 00:38:01 -08:00
Kyle Isom 94672bba98 SimpleConfig: Put was setting key=key, not key=value. 2023-10-24 13:22:54 -07:00
Kyle Isom 33675c18ec Flags: show default value. 2023-10-24 12:12:17 -07:00
Kyle Isom f76d524999 SimpleConfig: Add missing definition. 2023-10-23 21:51:43 -07:00
Kyle Isom 1bc8a9ad82 SimpleConfig: Add const char * key variants. 2023-10-23 21:47:33 -07:00
Kyle Isom 9a83cf6c08 Add option to set config values. 2023-10-23 21:09:56 -07:00
Kyle Isom 28238ba041 Install headers correctly. 2023-10-23 20:58:23 -07:00
Kyle Isom 044afb9a60 Darwin: don't build .pkg, build stgz and tgz packages. 2023-10-23 03:04:00 -07:00
Kyle Isom 8ba8dee78d Bump patch to cut new packages. 2023-10-22 15:18:56 -07:00
Kyle Isom 7f0a814b3f Vector: IsParallel now works.
There should be a devlog entry shortly describing this, but the
code for IsParallel wasn't working on arm64 (Linux or Darwin).
Floating point math is weird.
2023-10-22 15:18:40 -07:00
Kyle Isom 8868fe40a1 With Trunk in CI disabled, try Alpine again. 2023-10-22 03:24:36 -07:00
Kyle Isom c5308dedba Add SimpleConfig.
https://godocs.io/git.wntrmute.dev/kyle/goutils/config is one of the
more useful Go packages in my standard go library that gets used for
building services. I needed something similar for another Shimmering
Clarity project, and thus I figured I'd add it into SCSL.
2023-10-22 01:45:04 -07:00
Kyle Isom 9a8dc08a4f cppcheck cleanups and additional docs+testing. 2023-10-21 23:33:22 -07:00
Kyle Isom 9faed6a95b phonebook: exit with appropriate code. 2023-10-21 19:58:22 -07:00
Kyle Isom 168ee430f4 Quaternion self test fixed.
The self-test expects a unit quaternion. The Quaternion vector<3>
constructor uses the vector as-is, expecting it to be potentially the
output from an existing Quaternion. MakeQuaternion is the right function
for build a unit quaternion, so the self-test should actually use this.

This error had been hidden due to building with NDEBUG, which ifdefs out
the self-test. The code was probably changed during the refactoring
process.
2023-10-21 19:53:22 -07:00
Kyle Isom f393f8614f Adjust phonebook help usage.
- If a user requests help via the -h flag, the program shouldn't exit
  with an error code, and it should dump information to stdout, not
  stderr.
2023-10-21 19:49:36 -07:00
Kyle Isom 8b63985ac9 Fix flags usage and make Commander Flags capable.
- Programs should exit on Flags parse error.
- Commander now accepts a string vector for interop with Flags.
2023-10-21 19:45:07 -07:00
Kyle Isom 1420ff343d tests: simple test example should be a standalone demo. 2023-10-21 17:45:33 -07:00
Kyle Isom 10888b4872 tests: all tests now use SimpleSuite.
- Also continuing doc updates.
2023-10-21 17:40:01 -07:00
Kyle Isom 0bf4dd54f3 Remove unused Debug header. 2023-10-21 12:34:38 -07:00
Kyle Isom aee337f2e9 clang-tidy fixes, documentation, refactoring. 2023-10-21 02:07:59 -07:00
Kyle Isom 4e83da345f Attempt to fix CircleCI trunk config, round 3. 2023-10-20 21:39:23 -07:00
Kyle Isom 540a0d5b74 Attempt to fix CircleCI trunk config, round 2. 2023-10-20 21:21:39 -07:00
Kyle Isom 2d7a8d5c1d Attempt to fix CircleCI trunk config. 2023-10-20 21:20:40 -07:00
Kyle Isom 0c7fa41cc8 Document and refactor geom code, round 2.
- Doxygenate headers.
- Rename to bring methods and functions in line with everything else.
2023-10-20 21:17:18 -07:00
Kyle Isom 6a421d6adf Document and refactor geom code.
- Doxygenate headers.
- Rename to bring methods and functions in line with everything else.
2023-10-20 20:45:39 -07:00
Kyle Isom 4b1007123a Test suite cleanups, convert Coord2D to Vector<T, 2>.
- The standard SimpleSuite setup now include flags to suppress printing
  the report in addition to silencing the test runs. This is useful in
  automated testing.
- Point2D and Polar2D in Coord2D have been converted from custom types
  to Vector<int, 2> and Vector<double, 2>, respectively.
2023-10-20 19:05:55 -07:00
Kyle Isom 68ed5e0aca Minor code cleanups. 2023-10-20 16:13:49 -07:00
Kyle Isom 2fceae91fd geom: add missing include. 2023-10-20 03:14:12 -07:00
Kyle Isom 1c2f9af9d9 ci: update script name.
This is a change in the upstream dev container.
2023-10-20 03:08:08 -07:00
Kyle Isom e923335396 Alpine image can't run trunk. 2023-10-20 03:06:04 -07:00
Kyle Isom fa8a89625b add trunk config to repo 2023-10-20 03:05:12 -07:00
Kyle Isom b49caa3ec9 Cleaning up and documenting scmp code. 2023-10-20 02:59:36 -07:00
Kyle Isom 4eb4008130 Further code cleanups and documentation.
- Coverity defects.
- Documentation.
2023-10-20 01:31:06 -07:00
Kyle Isom 2a23d2e204 Fix CircleCI build, more coverity fixes. 2023-10-19 22:57:55 -07:00
Kyle Isom a5bb31943c Add trunk to repo, import clang-format. 2023-10-19 22:04:15 -07:00
Kyle Isom f896a108dd Coverity fixes.
Primarily using std::move in more places.
2023-10-19 21:43:38 -07:00
Kyle Isom 9494871145 README.md: use the correct badge for CircleCI. 2023-10-19 21:19:53 -07:00
Kyle Isom 1c6de07b7a test/coord2d: add missing <array> include. 2023-10-19 21:18:08 -07:00
Kyle Isom eaea8c2ab0 Buffer: add explicit/defensive coding checks. 2023-10-19 21:00:35 -07:00
Kyle Isom 6dab443789 Dictionary: add defensive coding checks. 2023-10-19 21:00:21 -07:00
Kyle Isom 0a661a7d70 Close file handle when done.
This also brings the arena to a single-return standard.
2023-10-19 20:55:31 -07:00
Kyle Isom b1bbaebdac Slow working on bringing the old code up to standard.
- Documentation updates - most of the old files use non-Doxygen or
  no/minimal header comments.
- Rework SimpleSuite to be more useful.
- Coverity-surfaced fixes.
2023-10-19 20:32:46 -07:00
Kyle Isom a9991f241a Add badges to README. 2023-10-19 11:16:13 -07:00
Kyle Isom 49eea73449 Arena: remove resource leak. 2023-10-19 10:59:43 -07:00
Kyle Isom 36fe049485 Continuing refactor work. 2023-10-19 00:58:40 -07:00
Kyle Isom 8d02d078e7 refactor namespaces 2023-10-18 23:57:50 -07:00
Kyle Isom 5f3dc6e9f6 Restructure project, start importing sc3 code. 2023-10-18 23:44:05 -07:00
Kyle Isom 3122ed6ac7 Add circleci config. 2023-10-18 17:57:19 -07:00
Kyle Isom 184d468b06 Dictionary conditions reversed.
During a code cleanup, a check for a TLV tag inverted the logic.
2023-10-18 17:35:27 -07:00
Kyle Isom fa1cb59697 Add missing include header.
This was being included transitively.
2023-10-18 16:21:09 -07:00
Kyle Isom a8387f010f Trying to get build working on Fedora. 2023-10-18 16:19:09 -07:00
Kyle Isom 00e9bc0f22 Format README. 2023-10-17 00:45:21 -07:00
Kyle Isom 567f5f9564 gitea doesn't support RST READMEs, so renamed this. 2023-10-17 00:44:40 -07:00
Kyle Isom b7584b06cc Cleanup example program in Flag docs. 2023-10-16 23:58:49 -07:00
Kyle Isom a0cd2ca866 Add trailing newline to cmake config. 2023-10-16 21:23:26 -07:00
Kyle Isom a0edf915ad Remove test prints and fix cursor checks in dictionary. 2023-10-16 17:53:50 -07:00
Kyle Isom 930a2d68f4 Update docs; bump patch version. 2023-10-16 15:03:22 -07:00
Kyle Isom 40d92db968 Bump package version. 2023-10-16 04:02:46 -07:00
Kyle Isom 407ee1c85d Cleanup code and docs; add missing header.
- The source and header files should have standard comment headers.
- Windows support has been removed, as I've decomissioned my Windows
  desktop in favour of a Linux desktop.
- Commander.h wasn't being added to the install directory.
2023-10-16 04:01:35 -07:00
89 changed files with 9051 additions and 1885 deletions

19
.circleci/config.yml Normal file
View File

@ -0,0 +1,19 @@
version: 2.1
jobs:
ctest:
docker:
- image: git.wntrmute.dev/sc/dev:alpine
steps:
- checkout
- run:
name: Setup cmake build
command: cmake-build-and-test.sh
- run:
name: Valgrind checks.
command: cmake-run-valgrind.sh
workflows:
ctest:
jobs:
- ctest

67
.clang-format Normal file
View File

@ -0,0 +1,67 @@
# Generated from CLion C/C++ Code Style settings
BasedOnStyle: LLVM
AccessModifierOffset: -8
AlignAfterOpenBracket: Align
AlignConsecutiveAssignments: Consecutive
AlignOperands: Align
AllowAllArgumentsOnNextLine: false
AllowAllConstructorInitializersOnNextLine: false
AllowAllParametersOfDeclarationOnNextLine: false
AllowShortBlocksOnASingleLine: Always
AllowShortCaseLabelsOnASingleLine: false
AllowShortFunctionsOnASingleLine: All
AllowShortIfStatementsOnASingleLine: Always
AllowShortLambdasOnASingleLine: All
AllowShortLoopsOnASingleLine: true
AlwaysBreakAfterReturnType: TopLevel
AlwaysBreakTemplateDeclarations: Yes
BreakBeforeBraces: Custom
BraceWrapping:
AfterCaseLabel: false
AfterClass: false
AfterControlStatement: Never
AfterEnum: false
AfterFunction: true
AfterNamespace: false
AfterUnion: false
BeforeCatch: false
BeforeElse: false
IndentBraces: false
SplitEmptyFunction: false
SplitEmptyRecord: true
BreakBeforeBinaryOperators: None
BreakBeforeTernaryOperators: true
BreakConstructorInitializers: BeforeColon
BreakInheritanceList: BeforeColon
ColumnLimit: 0
CompactNamespaces: false
ContinuationIndentWidth: 4
IndentCaseLabels: false
IndentPPDirectives: None
IndentWidth: 8
KeepEmptyLinesAtTheStartOfBlocks: true
MaxEmptyLinesToKeep: 2
NamespaceIndentation: None
ObjCSpaceAfterProperty: false
ObjCSpaceBeforeProtocolList: true
QualifierAlignment: Left
PointerAlignment: Right
ReflowComments: false
SpaceAfterCStyleCast: true
SpaceAfterLogicalNot: false
SpaceAfterTemplateKeyword: false
SpaceBeforeAssignmentOperators: true
SpaceBeforeCpp11BracedList: false
SpaceBeforeCtorInitializerColon: true
SpaceBeforeInheritanceColon: true
SpaceBeforeParens: ControlStatements
SpaceBeforeRangeBasedForLoopColon: false
SpaceInEmptyParentheses: false
SpacesBeforeTrailingComments: 0
SpacesInAngles: false
SpacesInCStyleCastParentheses: false
SpacesInContainerLiterals: false
SpacesInParentheses: false
SpacesInSquareBrackets: false
TabWidth: 8
UseTab: ForContinuationAndIndentation

View File

@ -7,11 +7,13 @@ Checks: >-
performance-*,
readability-*,
-bugprone-lambda-function-name,
-bugprone-easily-swappable-parameters,
-bugprone-reserved-identifier,
-cppcoreguidelines-avoid-goto,
-cppcoreguidelines-avoid-magic-numbers,
-cppcoreguidelines-avoid-non-const-global-variables,
-cppcoreguidelines-pro-bounds-array-to-pointer-decay,
-cppcoreguidelines-pro-bounds-pointer-arithmetic,
-cppcoreguidelines-pro-type-vararg,
-google-readability-braces-around-statements,
-google-readability-function-size,
@ -20,14 +22,10 @@ Checks: >-
-modernize-use-nodiscard,
-modernize-use-trailing-return-type,
-performance-unnecessary-value-param,
-readability-magic-numbers,
-readability-identifier-length,
-readability-magic-numbers
CheckOptions:
- key: readability-function-cognitive-complexity.Threshold
value: 100
- key: readability-function-cognitive-complexity.IgnoreMacros
value: true
# Set naming conventions for your style below (there are dozens of naming settings possible):
# See https://clang.llvm.org/extra/clang-tidy/checks/readability/identifier-naming.html
- key: readability-identifier-naming.ClassCase
value: CamelCase
readability-function-cognitive-complexity.Threshold: 100
readability-function-cognitive-complexity.IgnoreMacros: true
readability-identifier-naming.ClassCase: CamelCase

2
.gitignore vendored
View File

@ -1,4 +1,3 @@
.trunk
.vc
.vscode
@ -10,6 +9,7 @@ build
core
core.*
cmake-build-*
compile_commands.json
bufferTest
dictionaryTest

View File

@ -7,6 +7,9 @@
<option name="FUNCTION_BRACE_PLACEMENT" value="2" />
<option name="FUNCTION_TOP_AFTER_RETURN_TYPE_WRAP" value="2" />
</Objective-C>
<clangFormatSettings>
<option name="ENABLED" value="true" />
</clangFormatSettings>
<files>
<extensions>
<pair source="cc" header="h" fileNamingConvention="PASCAL_CASE" />

View File

@ -1,6 +1,7 @@
<component name="InspectionProjectProfileManager">
<profile version="1.0">
<option name="myName" value="Project Default" />
<inspection_tool class="ClangTidy" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="Eslint" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="SpellCheckingInspection" enabled="false" level="TYPO" enabled_by_default="false">
<option name="processCode" value="true" />

View File

@ -1,2 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<module classpath="CMake" type="CPP_MODULE" version="4" />
<module classpath="CMake" type="CPP_MODULE" version="4">
<component name="FacetManager">
<facet type="Python" name="Python facet">
<configuration sdkName="Python 3.10 (scsl)" />
</facet>
</component>
</module>

View File

@ -1,4 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="Black">
<option name="sdkName" value="Python 3.10 (scsl)" />
</component>
<component name="CMakeWorkspace" PROJECT_DIR="$PROJECT_DIR$" />
</project>

8
.trunk/.gitignore vendored Normal file
View File

@ -0,0 +1,8 @@
*out
*logs
*actions
*notifications
*tools
plugins
user_trunk.yaml
user.yaml

27
.trunk/trunk.yaml Normal file
View File

@ -0,0 +1,27 @@
# This file controls the behavior of Trunk: https://docs.trunk.io/cli
# To learn more about the format of this file, see https://docs.trunk.io/reference/trunk-yaml
version: 0.1
cli:
version: 1.17.1
plugins:
sources:
- id: trunk
ref: v1.2.6
uri: https://github.com/trunk-io/plugins
runtimes:
enabled:
- python@3.10.8
lint:
enabled:
- include-what-you-use@0.20
- checkov@2.5.9
- circleci@0.1.29041
- clang-tidy@16.0.3
- git-diff-check
- trufflehog@3.60.0
actions:
disabled:
- trunk-announce
- trunk-check-pre-push
- trunk-fmt-pre-commit
- trunk-upgrade-available

359
Arena.cc
View File

@ -1,359 +0,0 @@
#include <cassert>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#if defined(__posix__) || defined(__linux__) || defined(__APPLE__)
#include <sys/mman.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#define PROT_RW (PROT_WRITE|PROT_READ)
#elif defined(__WIN64__) || defined(__WIN32__) || defined(WIN32)
#include "WinHelpers.h"
#pragma comment(lib, "advapi32.lib")
#endif
#include <ios>
#include "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

View File

@ -1,64 +1,91 @@
cmake_minimum_required(VERSION 3.22)
project(scsl LANGUAGES CXX
VERSION 0.1.1
VERSION 1.1.4
DESCRIPTION "Shimmering Clarity Standard Library")
set(CMAKE_CXX_STANDARD 14)
set(CMAKE_VERBOSE_MAKEFILES TRUE)
set(VERBOSE YES)
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
if (MSVC)
add_compile_options("/W4" "$<$<CONFIG:RELEASE>:/O2>")
# compile options:
# -Wall Default to all errors.
# -Wextra And a few extra.
# -Werror And require them to be fixed to build.
# -Wno-unused-function This is a library. Not every function is used here.
# -Wno-unused-parameter Some functions have parameters defined for compatibility,
# and aren't used in the implementation.
add_compile_options(
"-static"
"-Wall"
"-Wextra"
"-Werror"
"-Wno-unused-function"
"-Wno-unused-parameter"
"-g"
"$<$<CONFIG:RELEASE>:-O2>"
)
#add_link_options("-fsanitize=address")
if ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang")
add_compile_options("-stdlib=libc++")
else ()
# compile options:
# -Wall Default to all errors.
# -Wextra And a few extra.
# -Werror And require them to be fixed to build.
# -Wno-unused-function This is a library. Not every function is used here.
# -Wno-unused-parameter Some functions have parameters defined for compatibility,
# and aren't used in the implementation.
add_compile_options(
"-static"
"-Wall"
"-Wextra"
"-Werror"
"-Wno-unused-function"
"-Wno-unused-parameter"
"$<$<CONFIG:RELEASE>:-O2>"
)
if ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang")
add_compile_options("-stdlib=libc++")
else ()
# nothing special for gcc at the moment
endif ()
# nothing special for gcc At the moment
endif ()
add_compile_definitions(SCSL_DESKTOP_BUILD)
add_compile_definitions(SCSL_VERSION=${PROJECT_VERSION})
set(HEADER_FILES scsl.h
Arena.h
Buffer.h
Dictionary.h
Exceptions.h
Flag.h
StringUtil.h
TLV.h
Test.h
WinHelpers.h)
set(HEADER_FILES
include/scsl/scsl.h
include/scsl/Arena.h
include/scsl/Buffer.h
include/scsl/Commander.h
include/scsl/Dictionary.h
include/scsl/Flags.h
include/scsl/SimpleConfig.h
include/scsl/StringUtil.h
include/scsl/TLV.h
include/scmp/estimation.h
include/scmp/geom.h
include/scmp/scmp.h
include/scmp/Math.h
include/scmp/geom/Coord2D.h
include/scmp/geom/Orientation.h
include/scmp/geom/Quaternion.h
include/scmp/geom/Vector.h
include/scmp/estimation/Madgwick.h
include/sctest/sctest.h
include/sctest/Assert.h
include/sctest/Checks.h
include/sctest/Exceptions.h
include/sctest/Report.h
include/sctest/SimpleSuite.h
)
include_directories(include)
set(SOURCE_FILES
Arena.cc
Buffer.cc
Commander.cc
Commander.h
Dictionary.cc
Exceptions.cc
Flag.cc
StringUtil.cc
TLV.cc
Test.cc
WinHelpers.cc)
src/sl/Arena.cc
src/sl/Buffer.cc
src/sl/Commander.cc
src/sl/Dictionary.cc
src/test/Exceptions.cc
src/sl/Flags.cc
src/sl/SimpleConfig.cc
src/sl/StringUtil.cc
src/sl/TLV.cc
src/scmp/Math.cc
src/scmp/Coord2D.cc
src/scmp/Orientation.cc
src/scmp/Quaternion.cc
src/test/Assert.cc
src/test/Report.cc
src/test/SimpleSuite.cc
)
if (APPLE)
add_library(scsl
@ -70,31 +97,43 @@ add_library(scsl
${SOURCE_FILES} ${HEADER_FILES})
endif()
add_executable(phonebook phonebook.cc)
add_executable(phonebook src/bin/phonebook.cc)
target_link_libraries(phonebook scsl)
include(CTest)
enable_testing()
add_executable(buffer_test bufferTest.cc)
target_link_libraries(buffer_test scsl)
add_test(bufferTest buffer_test)
set(TEST_SOURCES)
macro(generate_test name)
add_executable(test_${name} test/${name}.cc ${TEST_SOURCES} ${ARGN})
target_link_libraries(test_${name} ${PROJECT_NAME})
target_include_directories(test_${name} PRIVATE test)
add_test(test_${name} test_${name})
endmacro()
add_executable(tlv_test tlvTest.cc)
target_link_libraries(tlv_test scsl)
add_test(tlvTest tlv_test)
# core standard library
generate_test(buffer)
generate_test(tlv)
generate_test(dictionary)
generate_test(stringutil)
add_executable(dictionary_test dictionaryTest.cc)
target_link_libraries(dictionary_test scsl)
add_test(dictionaryTest dictionary_test)
# math and physics
generate_test(coord2d)
generate_test(madgwick)
generate_test(math)
generate_test(orientation)
generate_test(quaternion)
generate_test(vector)
add_executable(flag_test flagTest.cc)
target_link_libraries(flag_test scsl)
add_test(flagTest flag_test)
# test tooling
add_executable(flags-demo test/flags.cc)
target_link_libraries(flags-demo ${PROJECT_NAME})
add_executable(stringutil_test stringutil_test.cc)
target_link_libraries(stringutil_test scsl)
add_test(stringutilTest stringutil_test)
add_executable(simple-test-demo test/simple_suite_example.cc)
target_link_libraries(simple-test-demo ${PROJECT_NAME})
add_executable(config-explorer test/config-explorer.cc)
target_link_libraries(config-explorer ${PROJECT_NAME})
include(CMakePackageConfigHelpers)
write_basic_package_version_file(
@ -115,10 +154,10 @@ add_custom_target(deploy-docs
configure_file(scsl.pc.in scsl.pc @ONLY)
install(TARGETS scsl LIBRARY DESTINATION lib)
install(TARGETS phonebook RUNTIME DESTINATION bin)
install(FILES ${HEADER_FILES} DESTINATION include/scsl)
install(DIRECTORY include/ DESTINATION include)
install(FILES scslConfig.cmake DESTINATION share/scsl/cmake)
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/scsl.pc DESTINATION lib/pkgconfig)
include(CMakePack.txt)
include(CMakeDocs.txt)
include(cmake/packaging.cmake)
include(cmake/docs.cmake)
endif()

View File

@ -1,60 +0,0 @@
///
/// \file Commander.cc
/// \author kyle
/// \date 2023-10-10
///
#include <iostream>
#include "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);
}
} // scsl

View File

@ -1,24 +0,0 @@
//
// Created by kyle on 2023-10-10.
//
#include "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());
}
}

View File

@ -1,51 +0,0 @@
//
// Created by kyle on 2023-10-10.
//
#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

134
Flag.h
View File

@ -1,134 +0,0 @@
///
/// \file Flag.h
/// \author kyle
/// \created 2023-10-12
/// \brief Flag declares a command-line flag parser.
///
/// \section COPYRIGHT
///
/// Copyright 2023 K. Isom <kyle@imap.cc>
///
/// Permission to use, copy, modify, and/or distribute this software for
/// any purpose with or without fee is hereby granted, provided that
/// the above copyright notice and this permission notice appear in all /// copies.
///
/// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
/// WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
/// WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
/// AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
/// DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA
/// OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
/// TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
/// PERFORMANCE OF THIS SOFTWARE.
///
#include <cstdint>
#include <functional>
#include <map>
#include <string>
#include <variant>
#include <vector>
namespace scsl {
/// FlagType indicates the value held in a FlagValue.
enum class FlagType : uint8_t {
Unknown = 0,
Boolean = 1,
Integer = 2, ///< int32_t
UnsignedInteger = 3, ///< uint32_t
SizeT = 4, ///< size_t
String = 5,
};
enum class ParseStatus : uint8_t {
Unknown = 0,
OK = 1,
EndOfFlags = 2,
NotRegistered = 3,
NotEnoughArgs = 4,
};
std::string
ParseStatusToString(ParseStatus status);
typedef union {
unsigned int u;
int i;
std::size_t size;
std::string *s;
bool b;
} FlagValue;
typedef struct {
FlagType Type;
bool WasSet;
std::string Name;
std::string Description;
FlagValue Value;
} Flag;
Flag *
NewFlag(FlagType fType, std::string fName, std::string fDescription);
class Flags {
public:
Flags(std::string fName);
Flags(std::string fName, std::string fDescription);
bool Register(std::string fName,
FlagType fType,
std::string fDescription);
bool Register(std::string fName,
bool defaultValue,
std::string fDescription);
bool Register(std::string fName,
int defaultValue,
std::string fDescription);
bool Register(std::string fName,
unsigned int defaultValue,
std::string fDescription);
bool Register(std::string fName,
size_t defaultValue,
std::string fDescription);
bool Register(std::string fName,
std::string defaultValue,
std::string fDescription);
size_t Size();
Flag *Lookup(std::string fName);
bool ValueOf(std::string fName, FlagValue &value);
ParseStatus Parse(int argc, char **argv);
void Usage(std::ostream &os, int exitCode);
size_t NumArgs();
std::vector<std::string> Args();
std::string Arg(int index);
bool GetBool(std::string fName, bool &flagValue);
bool GetUnsignedInteger(std::string fName, unsigned int &flagValue);
bool GetInteger(std::string fName, int &flagValue);
bool GetString(std::string fName, std::string &flagValue);
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

13
LICENSE Normal file
View File

@ -0,0 +1,13 @@
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.

View File

@ -1,39 +1,59 @@
TARGET := klib.a
TESTS := tlv_test dictionary_test
HEADERS := $(wildcard *.h)
SOURCES := $(wildcard *.cc)
OBJS := Arena.o Dictionary.o TLV.o
HEADERS := scsl.h \
Arena.h \
Buffer.h \
Commander.h \
Dictionary.h \
Exceptions.h \
Flag.h \
StringUtil.h \
Test.h \
TLV.h \
WinHelpers.h
SOURCES := Arena.cc \
Buffer.cc \
Commander.cc \
Dictionary.cc \
Exceptions.cc \
Flag.cc \
StringUtil.cc \
Test.cc \
TLV.cc \
WinHelpers.cc
BUILD := DEBUG
OBJS := $(patsubst %.cc,%.o,$(SOURCES))
LIBS := libscsl.a
TARGETS := $(LIBS) phonebook
TESTS := bufferTest dictionaryTest flagTest tlvTest
CXX := clang++
CXXFLAGS := -g -std=c++14 -Werror -Wall -DSCSL_DESKTOP_BUILD
CXXFLAGS := -std=c++14 -Werror -Wall -Wextra -DSCSL_DESKTOP_BUILD \
-DSCSL_BUILD_TYPE=${BUILD}
ifeq ($(BUILD),DEBUG)
CXXFLAGS += -g -fsanitize=address
else
CXXFLAGS += -O2
endif
.PHONY: all
all: $(TARGET) $(TESTS) tags run-tests
all: $(TARGETS) $(TESTS) tags run-tests
tags: $(HEADERS) $(SOURCES)
ctags $(HEADERS) $(SOURCES)
$(TARGET): $(OBJS)
libscsl.a: $(OBJS)
$(AR) rcs $@ $(OBJS)
tlv_test: tlvTest.o $(TARGET)
$(CXX) -o $@ $(CXXFLAGS) tlvTest.o $(TARGET)
dictionary_test: dictionaryTest.o $(TARGET)
$(CXX) -o $@ $(CXXFLAGS) dictionaryTest.o $(TARGET)
.PHONY: print-%
print-%: ; @echo '$(subst ','\'',$*=$($*))'
klib.a: $(OBJS)
%.o: %.cc
$(CXX) -o $@ -c $(CXXFLAGS) $<
.PHONY: clean
clean:
# build outputs
rm -f $(TARGET) $(TESTS) *.o
rm -f $(TARGETS) $(TESTS) *.o
# test miscellaneous
rm -f core core.* tags arena_test.bin
@ -45,3 +65,22 @@ run-tests: $(TESTS)
echo "./$${testbin}" ; \
./$${testbin}; \
done
phonebook: phonebook.o $(LIBS)
$(CXX) -o $@ $(CXXFLAGS) $@.o $(LIBS)
bufferTest: bufferTest.o $(LIBS)
$(CXX) -o $@ $(CXXFLAGS) $@.o $(LIBS)
dictionaryTest: dictionaryTest.o $(LIBS)
$(CXX) -o $@ $(CXXFLAGS) $@.o $(LIBS)
flagTest: flagTest.o $(LIBS)
$(CXX) -o $@ $(CXXFLAGS) $@.o $(LIBS)
tlvTest: tlvTest.o $(LIBS)
$(CXX) -o $@ $(CXXFLAGS) $@.o $(LIBS)
%.o: %.cc
$(CXX) -o $@ -c $(CXXFLAGS) $<

61
README.md Normal file
View File

@ -0,0 +1,61 @@
# scsl : The Shimmering Clarity Standard C++ Library
[![CircleCI](https://dl.circleci.com/status-badge/img/gh/shimmering-clarity/scsl/tree/master.svg?style=svg)](https://dl.circleci.com/status-badge/redirect/gh/shimmering-clarity/scsl/tree/master)
[![image](https://scan.coverity.com/projects/29251/badge.svg)](https://scan.coverity.com/projects/shimmering-clarity-scsl)
scsl is a collection of software I found myself needing to use repeatedly.
Full [Doxygen documentation](https://docs.shimmering-clarity.net/scsl/)
is available.
## Introduction
This is a collection of C++ code that I find useful in building things.
It arose from two main use cases.
### 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.
### 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.
### Finally
I'd been writing Go professionally for a while, but C was my first love. I
recently started a job that is mostly in C++, and the best way for me to
learn is to build a bunch of stuff with it. So, I took a bunch of micro-
controller stuff I'd been writing and started building out some other stuff.
## License
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.

View File

@ -1,165 +0,0 @@
#include <iostream>
#include <sstream>
#include "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)
{
std::string key;
std::string val;
auto pos = line.find(delimiter);
if (pos == std::string::npos) {
key = line;
val = "";
} else {
key = line.substr(0, pos);
val = line.substr(pos + 1, line.size() - pos - 2);
}
TrimWhitespace(key);
TrimWhitespace(val);
return {key, val};
}
std::vector<std::string>
SplitKeyValuePair(std::string line, char delimiter)
{
std::string key;
std::string val;
auto pos = line.find(delimiter);
if (pos == std::string::npos) {
key = line;
val = "";
} else {
key = line.substr(0, pos);
val = line.substr(pos + 1, line.size() - pos - 2);
}
TrimWhitespace(key);
TrimWhitespace(val);
return {key, val};
}
*/
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::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

52
Test.cc
View File

@ -1,52 +0,0 @@
//
// Created by kyle on 2023-10-09.
//
#include "Exceptions.h"
#include "Test.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

43
Test.h
View File

@ -1,43 +0,0 @@
///
/// \file Test.h
/// \author K. Isom <kyle@imap.cc>
/// \date 2023-10-09
/// \brief Test.h implements basic testing tools.
///
#ifndef SCSL_TEST_H
#define SCSL_TEST_H
#include <string>
namespace scsl {
/// TestAssert is a variant on the assert macro. This variant is intended to be
/// a drop-in replacement for the cassert macro: even in release mode, the tests
/// should still run.
///
/// If NDEBUG is set, TestAssert will throw an exception if condition is false.
/// Otherwise, it calls assert after printing the message.
///
/// \param condition If true, TestAssert throws an exception.
void TestAssert(bool condition);
/// TestAssert is a variant on the assert macro.
///
/// If NDEBUG is set, TestAssert will throw an exception if condition is false.
/// Otherwise, it calls assert after printing the message.
///
/// In addition to NDEBUG, SCSL_NOEXCEPT will suppress assertions.
///
/// \throws AssertionFailed
///
/// \param condition The condition to assert.
/// \param message The message that should be displayed if condition is false.
void TestAssert(bool condition, std::string message);
} // namespace scsl
#endif //SCSL_TEST_H

View File

@ -1,142 +0,0 @@
//
// Created by kyle on 2023-10-10.
//
#if defined(__WIN64__) || defined(__WIN32__) || defined(WIN32)
#include "WinHelpers.h"
namespace scsl {
namespace Windows {
int
DisplayWinError(LPTSTR lpszFunction, HANDLE handle)
{
// Retrieve the system error message for the last-error code
DWORD dw = GetLastError();
#ifndef NDEBUG
LPVOID lpMsgBuf;
LPVOID lpDisplayBuf;
FormatMessage(
FORMAT_MESSAGE_ALLOCATE_BUFFER |
FORMAT_MESSAGE_FROM_SYSTEM |
FORMAT_MESSAGE_IGNORE_INSERTS,
NULL,
dw,
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
(LPTSTR) &lpMsgBuf,
0, NULL);
// Display the error message and exit the process
lpDisplayBuf = (LPVOID) LocalAlloc(LMEM_ZEROINIT,
(lstrlen((LPCTSTR) lpMsgBuf) +
lstrlen((LPCTSTR) lpszFunction) +
40) * sizeof(TCHAR));
StringCchPrintf((LPTSTR) lpDisplayBuf,
LocalSize(lpDisplayBuf) / sizeof(TCHAR),
TEXT("%s failed with error %d: %s"),
lpszFunction, dw, lpMsgBuf);
MessageBox(NULL, (LPCTSTR) lpDisplayBuf, TEXT("Error"), MB_OK);
LocalFree(lpMsgBuf);
LocalFree(lpDisplayBuf);
#endif
if ((handle != NULL) && (handle != INVALID_HANDLE_VALUE)) {
CloseHandle(handle);
}
return dw;
}
BOOL SetPrivilege(
HANDLE hToken, // access token handle
LPCTSTR lpszPrivilege, // name of privilege to enable/disable
BOOL bEnablePrivilege // to enable or disable privilege
)
{
TOKEN_PRIVILEGES tp;
LUID luid;
if (!LookupPrivilegeValue(
NULL, // lookup privilege on local system
lpszPrivilege, // privilege to lookup
&luid)) // receives LUID of privilege
{
printf("LookupPrivilegeValue error: %u\n", GetLastError());
return FALSE;
}
tp.PrivilegeCount = 1;
tp.Privileges[0].Luid = luid;
if (bEnablePrivilege)
tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
else
tp.Privileges[0].Attributes = 0;
// Enable the privilege or disable all privileges.
if (!AdjustTokenPrivileges(
hToken,
FALSE,
&tp,
sizeof(TOKEN_PRIVILEGES),
(PTOKEN_PRIVILEGES) NULL,
(PDWORD) NULL)) {
printf("AdjustTokenPrivileges error: %u\n", GetLastError());
return FALSE;
}
if (GetLastError() == ERROR_NOT_ALL_ASSIGNED) {
printf("The token does not have the specified privilege. \n");
return FALSE;
}
return TRUE;
}
HANDLE
CreateFileWindows(const char *path)
{
HANDLE fHandle;
return CreateFileA(
(LPSTR) path,
(GENERIC_READ | GENERIC_WRITE),
(FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE),
NULL,
CREATE_ALWAYS,
FILE_ATTRIBUTE_NORMAL,
NULL);
}
int
CreateFixedSizeFile(const char *path, size_t size)
{
_LARGE_INTEGER fileSize;
fileSize.QuadPart = size;
HANDLE fHandle = CreateFileWindows(path);
if (SetFilePointerEx(fHandle, fileSize, nullptr, FILE_BEGIN) != TRUE) {
return DisplayWinError("SetFilePointerEx", fHandle);
}
if (SetEndOfFile(fHandle) != TRUE) {
return DisplayWinError("SetEndOfFile", fHandle);
}
CloseHandle(fHandle);
return 0;
}
} // namespace Windows
} // namespace scsl
#endif

View File

@ -1,39 +0,0 @@
//
// Created by kyle on 2023-10-10.
//
#ifndef SCSL_WINHELPERS_H
#define SCSL_WINHELPERS_H
#if defined(__WIN64__) || defined(__WIN32__) || defined(WIN32)
#include <Windows.h>
#include <winbase.h>
#include <fileapi.h>
#include <strsafe.h>
namespace scsl {
namespace Windows {
int DisplayWinError(LPTSTR lpszFunction, HANDLE handle);
BOOL SetPrivilege(
HANDLE hToken, // access token handle
LPCTSTR lpszPrivilege, // name of privilege to enable/disable
BOOL bEnablePrivilege // to enable or disable privilege
);
HANDLE CreateFileWindows(const char *path);
int CreateFixedSizeFile(const char *path, size_t size);
} // namespace Windows
} // namespace scsl
#endif // Windows-only guards.
#endif //SCSL_WINHELPERS_H

View File

@ -1,59 +0,0 @@
#include <cassert>
#include <iostream>
#include "Buffer.h"
using namespace scsl;
int
main(int argc, char *argv[])
{
(void) argc;
(void) argv;
Buffer buffer("hlo, world");
Buffer helloWorld("hello, world!");
Buffer goodbyeWorld("goodbye, world");
Buffer goodbyeCruelWorld("goodbye, cruel world");
std::cout << buffer << "\n";
buffer.Insert(1, (uint8_t *) "el", 2);
buffer.Append('!');
assert(buffer == helloWorld);
std::cout << buffer << "\n";
buffer.Remove(buffer.Length() - 1);
std::cout << buffer << "\n";
buffer.Remove(0, 5);
std::cout << buffer << "\n";
buffer.Insert(0, 'g');
std::cout << buffer << "\n";
buffer.Insert(1, (uint8_t *) "oodbye", 6);
std::cout << buffer << "\n";
assert(buffer == goodbyeWorld);
buffer.Insert(9, (uint8_t *)"cruel ", 6);
std::cout << buffer << "\n";
buffer.HexDump(std::cout);
buffer.Reclaim();
buffer.Append("and now for something completely different...");
std::cout << buffer.Contents() << "\n";
std::cout << "Length: " << buffer.Length() << ", capacity " << buffer.Capacity() << "\n";
buffer.Resize(128);
std::cout << "Length: " << buffer.Length() << ", capacity " << buffer.Capacity() << "\n";
buffer.Trim();
std::cout << "Length: " << buffer.Length() << ", capacity " << buffer.Capacity() << "\n";
Buffer buffer2("and now for something completely different...");
assert(buffer == buffer2);
buffer2.Remove(buffer2.Length()-3, 3);
std::cout << buffer << "\n";
assert(buffer != buffer2);
return 0;
}

View File

@ -4,13 +4,13 @@ find_package(Doxygen)
if (${DOXYGEN_FOUND})
set(DOXYGEN_GENERATE_MAN YES)
set(DOXYGEN_GENERATE_LATEX YES)
#set(DOXYGEN_EXTRACT_ALL YES)
set(DOXYGEN_EXTRACT_ALL YES)
message(STATUS "Doxygen found, building docs.")
doxygen_add_docs(scsl_docs
${HEADER_FILES} ${SOURCE_FILES}
${HEADER_FILES}
ALL
USE_STAMP_FILE)
add_dependencies(scsl scsl_docs)
install(DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/html DESTINATION share/doc/scsl)
install(DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/man DESTINATION share)

View File

@ -7,6 +7,9 @@ set(CPACK_PACKAGE_VERSION_MAJOR ${PROJECT_VERSION_MAJOR})
set(CPACK_PACKAGE_VERSION_MINOR ${PROJECT_VERSION_MINOR})
set(CPACK_PACKAGE_VERSION_PATCH ${PROJECT_VERSION_PATCH})
set(CPACK_PACKAGE_FILE_NAME
${PROJECT_NAME}-${PROJECT_VERSION}-${CMAKE_SYSTEM_NAME}-${CMAKE_SYSTEM_ARCH}${CMAKE_HOST_SYSTEM_PROCESSOR})
# Debian settings
set(CPACK_DEBIAN_PACKAGE_MAINTAINER "Shimmering Clarity")
set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "The Shimmering Clarity standard C++ library")
@ -17,9 +20,9 @@ set(CPACK_DEBIAN_PACKAGE_GENERATE_SHLIBS ON)
set(CPACK_DEBIAN_FILE_NAME DEB-DEFAULT)
if(LINUX)
set(CPACK_GENERATOR "DEB;STGZ;TGZ")
set(CPACK_GENERATOR "DEB;RPM;STGZ;TGZ")
elseif(APPLE)
set(CPACK_GENERATOR "productbuild")
set(CPACK_GENERATOR "STGZ;TGZ")
elseif(MSVC OR MSYS OR MINGW)
set(CPACK_GENERATOR "NSIS;ZIP")
else()
@ -35,4 +38,3 @@ set(CPACK_SOURCE_IGNORE_FILES
include (CPack)
add_custom_target(package_docs DEPENDS SCSL_docs package package_source)

View File

@ -1,108 +0,0 @@
#include <iostream>
#include "Arena.h"
#include "Dictionary.h"
#include "Test.h"
#include "testFixtures.h"
using namespace scsl;
constexpr char TEST_KVSTR1[] = "foo";
constexpr uint8_t TEST_KVSTRLEN1 = 3;
constexpr char TEST_KVSTR2[] = "baz";
constexpr uint8_t TEST_KVSTRLEN2 = 3;
constexpr char TEST_KVSTR3[] = "quux";
constexpr uint8_t TEST_KVSTRLEN3 = 4;
constexpr char TEST_KVSTR4[] = "spam";
constexpr uint8_t TEST_KVSTRLEN4 = 4;
constexpr char TEST_KVSTR5[] = "xyzzx";
constexpr uint8_t TEST_KVSTRLEN5 = 5;
constexpr char TEST_KVSTR6[] = "corvid";
constexpr uint8_t TEST_KVSTRLEN6 = 6;
static bool
testSetKV(Dictionary &pb, const char *k, uint8_t kl, const char *v,
uint8_t vl)
{
bool ok;
std::cout << "test Set " << k << "->" << v << "\n";
ok = pb.Set(k, kl, v, vl) == 0;
std::cout << "\tSet complete\n";
return ok;
}
int
main(int argc, const char *argv[])
{
(void) argc;
(void) argv;
Arena arena;
TLV::Record value;
TLV::Record expect;
std::cout << "TESTPROG: " << argv[0] << "\n";
#if defined(__linux__)
if (arena.Create(ARENA_FILE, ARENA_SIZE) == -1) {
abort();
}
#else
if (arena.SetAlloc(ARENA_SIZE) == -1) {
abort();
}
#endif
std::cout << arena << "\n";
TLV::SetRecord(expect, DICTIONARY_TAG_VAL, TEST_KVSTRLEN3, TEST_KVSTR3);
Dictionary dict(arena);
TestAssert(!dict.Contains(TEST_KVSTR2, TEST_KVSTRLEN2));
TestAssert(testSetKV(dict, TEST_KVSTR1, TEST_KVSTRLEN1, TEST_KVSTR3,
TEST_KVSTRLEN3));
std::cout << dict;
TestAssert(testSetKV(dict, TEST_KVSTR2, TEST_KVSTRLEN2, TEST_KVSTR3,
TEST_KVSTRLEN3));
std::cout << dict;
TestAssert(dict.Contains(TEST_KVSTR2, TEST_KVSTRLEN2));
TestAssert(testSetKV(dict, TEST_KVSTR4, TEST_KVSTRLEN4, TEST_KVSTR5,
TEST_KVSTRLEN5));
std::cout << dict;
TestAssert(dict.Lookup(TEST_KVSTR2, TEST_KVSTRLEN2, value));
TestAssert(cmpRecord(value, expect));
std::cout << "test overwriting key" << "\n";
TestAssert(testSetKV(dict, TEST_KVSTR2, TEST_KVSTRLEN2, TEST_KVSTR6,
TEST_KVSTRLEN6));
std::cout << dict;
TLV::SetRecord(expect, DICTIONARY_TAG_VAL, TEST_KVSTRLEN6, TEST_KVSTR6);
std::cout << "\tlookup" << "\n";
TestAssert(dict.Lookup(TEST_KVSTR2, TEST_KVSTRLEN2, value));
std::cout << "\tcompare records" << "\n";
TestAssert(cmpRecord(value, expect));
std::cout << "\tadd new key to dictionary" << "\n";
TestAssert(testSetKV(dict, TEST_KVSTR3, TEST_KVSTRLEN3, TEST_KVSTR5,
TEST_KVSTRLEN5));
std::cout << dict;
TLV::SetRecord(expect, DICTIONARY_TAG_VAL, TEST_KVSTRLEN5, TEST_KVSTR5);
TestAssert(dict.Lookup(TEST_KVSTR4, TEST_KVSTRLEN4, value));
TestAssert(cmpRecord(value, expect));
std::cout << "OK" << "\n";
// Dump the generated arena for inspection later.
#if defined(__linux__)
#else
dict.DumpToFile(ARENA_FILE);
#endif
arena.Clear();
std::cout << dict;
}

View File

@ -1,59 +0,0 @@
//
// Created by kyle on 10/15/23.
//
#include <iostream>
#include "Flag.h"
#include "Test.h"
using namespace scsl;
int
main(int argc, char *argv[])
{
bool testFlag = false;
size_t testSize = 0;
unsigned int testUnsigned = 0;
int testInteger = 0;
std::string testString;
auto flags = new Flags("flag_test", "this is a test of the flag functionality.");
flags->Register("-b", FlagType::Boolean, "test boolean");
flags->Register("-s", FlagType::String, "test string");
flags->Register("-u", (unsigned int)42, "test unsigned integer");
flags->Register("-i", -42, "test integer");
flags->Register("-size", FlagType::SizeT, "test size_t");
TestAssert(flags->Size() == 5, "flags weren't registered");
auto status = flags->Parse(argc, argv);
if (status != ParseStatus::OK) {
std::cerr << "failed to parse flags: " << ParseStatusToString(status) << "\n";
exit(1);
}
auto wasSet = flags->GetBool("-b", testFlag);
std::cout << " (bool) test flag was set: " << wasSet << "\n";
std::cout << " (bool) test flag value: " << testFlag << "\n";
wasSet = flags->GetInteger("-i", testInteger);
std::cout << " (int) test flag was set: " << wasSet << "\n";
std::cout << " (int) test flag value: " << testInteger << "\n";
wasSet = flags->GetUnsignedInteger("-u", testUnsigned);
std::cout << " (uint) test flag was set: " << wasSet << "\n";
std::cout << " (uint) test flag value: " << testUnsigned << "\n";
wasSet = flags->GetSizeT("-size", testSize);
std::cout << "(size_t) test flag was set: " << wasSet << "\n";
std::cout << "(size_t) test flag value: " << testSize << "\n";
wasSet = flags->GetString("-s", testString);
std::cout << "(string) test flag was set: " << wasSet << "\n";
std::cout << "(string) test flag value: " << testString << "\n";
return 0;
}

128
include/scmp/Math.h Normal file
View File

@ -0,0 +1,128 @@
///
/// \file include/scmp/Math.h
/// \author K. Isom <kyle@imap.cc>
/// \date 2017-06-05
/// \brief Common math functions.
///
/// 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.
///
#ifndef SCSL_SCMP_MATH_H
#define SCSL_SCMP_MATH_H
#include <cmath>
#include <vector>
namespace scmp {
/// MAX_RADIAN is a precomputed 2 * M_PI.
constexpr double MAX_RADIAN = 2 * M_PI;
constexpr double MIN_RADIAN = -2 * M_PI;
constexpr double PI_D = 3.141592653589793;
/// Roll m die of n sides, returning a vector of the dice.
std::vector<int> Die(int m, int n);
/// Roll m die of n sides, returning the total of the die.
int DieTotal(int m, int n);
/// Roll m die of n sides, and take the total of the top k die.
int BestDie(int k, int m, int n);
/// \brief Convert radians to degrees.
///
/// \param rads the Angle in radians
/// \return the Angle in degrees.
float RadiansToDegreesF(float rads);
/// \brief Convert radians to degrees.
///
/// \param rads the Angle in radians
/// \return the Angle in degrees.
double RadiansToDegreesD(double rads);
/// \brief Convert degrees to radians.
///
/// \param degrees the Angle in degrees
/// \return the Angle in radians.
float DegreesToRadiansF(float degrees);
/// \brief Convert degrees to radians.
///
/// \param degrees the Angle in degrees
/// \return the Angle in radians.
double DegreesToRadiansD(double degrees);
/// \brief RotateRadians rotates theta0 by theta1 radians, wrapping
/// the result to MIN_RADIAN <= result <= MAX_RADIAN.
///
/// \param theta0
/// \param theta1
/// \return
double RotateRadians(double theta0, double theta1);
/// \brief Get the default epsilon value.
///
/// \param epsilon The variable to store the epsilon value in.
void DefaultEpsilon(double &epsilon);
/// \brief Get the default epsilon value.
///
/// \param epsilon The variable to store the epsilon value in.
void DefaultEpsilon(float &epsilon);
/// \brief Get the default epsilon for integer types.
///
/// \param epsilon The variable to store the epsilon value in.
void DefaultEpsilon(int& epsilon);
/// \brief Return whether the two values of type T are equal to within
/// some tolerance.
///
/// \tparam T The type of value
/// \param a A value of type T used as the left-hand side of an
/// equality check.
/// \param b A value of type T used as the right-hand side of an
/// equality check.
/// \param epsilon The tolerance value.
/// \return Whether the two values are "close enough" to be considered
/// equal.
template <typename T>
static T
WithinTolerance(T a, T b, T epsilon)
{
return std::abs(a - b) <= epsilon;
}
/// \brief Integer square-root.
///
/// \param n A max-value integer whose square root should be returned.
/// \return The square root of $n$.
size_t ISqrt(size_t n);
} // namespace scmp
#endif //SCSL_SCMP_MATH_H

41
include/scmp/estimation.h Normal file
View File

@ -0,0 +1,41 @@
///
/// \file include/scmp/estimation.h
/// \author K. Isom <kyle@imap.cc>
/// \date 2023-10-20
/// \brief Estimate position and orientation.
///
/// \section COPYRIGHT
///
/// Copyright 2023 K. Isom <kyle@imap.cc>
///
/// Permission to use, copy, modify, and/or distribute this software for
/// any purpose with or without fee is hereby granted, provided that
/// the above copyright notice and this permission notice appear in all /// copies.
///
/// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
/// WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
/// WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
/// AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
/// DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA
/// OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
/// TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
/// PERFORMANCE OF THIS SOFTWARE.
///
#include <scmp/estimation/Madgwick.h>
#ifndef SCSL_ESTIMATION_H
#define SCSL_ESTIMATION_H
namespace scmp {
/// \brief Algorithms for estimation position, and system state.
namespace estimation {}
}
#endif // SCSL_ESTIMATION_H

View File

@ -0,0 +1,214 @@
///
/// \file include/scmp/estimation/Madgwick.h
/// \author K. Isom <kyle@imap.cc>
/// \date 2019-08-06
/// \brief Implementation of a Madgwick estimation.
///
/// See https://courses.cs.washington.edu/courses/cse466/14au/labs/l4/madgwick_internal_report.pdf.
///
/// \section COPYRIGHT
///
/// Copyright 2019 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 SCMP_FILTER_MADGWICK_H
#define SCMP_FILTER_MADGWICK_H
#include <scmp/geom/Vector.h>
#include <scmp/geom/Quaternion.h>
/// scmp contains the chimmering clarity math and physics code.
namespace scmp {
namespace estimation {
/// \brief Madgwick implements an efficient Orientation estimation for
/// Intertial Measurement Units (IMUs).
///
/// Madgwick is a novel Orientation estimation applicable to IMUs
/// consisting of tri-Axis gyroscopes and accelerometers, and MARG
/// sensor arrays that also include tri-Axis magnetometers. The MARG
/// implementation incorporates magnetic distortionand gyroscope bias
/// drift compensation.
///
/// It is described in the paper [An efficient Orientation estimation for inertial and inertial/magnetic sensor arrays](http://x-io.co.uk/res/doc/madgwick_internal_report.pdf).
///
/// \tparam T A floating point type.
template <typename T>
class Madgwick {
public:
/// \brief The Madgwick estimation is initialised with an identity MakeQuaternion.
Madgwick() : deltaT(0.0), previousSensorFrame(), sensorFrame()
{};
/// \brief The Madgwick estimation is initialised with a sensor frame.
///
/// \param sf A sensor frame; if zero, the sensor frame will be
/// initialised as an identity MakeQuaternion.
Madgwick(scmp::geom::Vector<T, 3> sf) : deltaT(0.0), previousSensorFrame()
{
if (!sf.IsZero()) {
sensorFrame = scmp::geom::MakeQuaternion<T>(sf, 0.0);
}
}
/// \brief Initialise the estimation with a sensor frame MakeQuaternion.
///
/// \param sf A MakeQuaternion representing the current Orientation.
Madgwick(scmp::geom::Quaternion<T> sf) :
deltaT(0.0), previousSensorFrame(), sensorFrame(sf)
{};
/// \brief Return the current orientation as measured by the
/// estimation.
///
/// \return The current sensor frame.
scmp::geom::Quaternion<T>
Orientation() const
{
return this->sensorFrame;
}
/// \brief Return the estimation's rate of angular change from a
/// sensor frame.
///
/// Return the rate of change of the Orientation of the earth
/// frame with respect to the sensor frame.
///
/// \param gyro A three-dimensional vector containing gyro
/// readings as w_x, w_y, w_z.
/// \return A MakeQuaternion representing the rate of angular
/// change.
scmp::geom::Quaternion<T>
AngularRate(const scmp::geom::Vector<T, 3> &gyro) const
{
return (this->sensorFrame * 0.5) * scmp::geom::Quaternion<T>(gyro, 0.0);
}
/// \brief Update the sensor frame to a new frame.
///
/// \param sf The new sensor frame replacing the previous one.
/// \param delta The time delta since the last update.
void
UpdateFrame(const scmp::geom::Quaternion<T> &sf, T delta)
{
this->previousSensorFrame = this->sensorFrame;
this->sensorFrame = sf;
this->deltaT = delta;
}
/// \brief Update the sensor frame to a new frame.
///
/// \warning The estimation's default Δt must be set before
/// calling this.
///
/// \param sf The new sensor frame replacing the previous one.
void
UpdateFrame(const scmp::geom::Quaternion<T> &sf)
{
this->UpdateFrame(sf, this->deltaT);
}
/// \brief Update the sensor frame with a gyroscope reading.
///
/// This method will assert that the Δt value is not zero
/// within a 100μs tolerance. This assert is compiled out with
/// the compile flag NDEBUG, but may be useful to catch
/// possible errors.
///
/// \param gyro A three-dimensional vector containing gyro
/// readings as w_x, w_y, w_z.
/// \param delta The time step between readings. It must not
/// be zero.
void
UpdateAngularOrientation(const scmp::geom::Vector<T, 3> &gyro, T delta)
{
// Ensure the delta isn't zero within a 100 μs
// tolerance.
if (scmp::WithinTolerance<T>(delta, 0.0, 0.00001)) {
return;
}
scmp::geom::Quaternion<T> q = this->AngularRate(gyro) * delta;
this->UpdateFrame(this->sensorFrame + q, delta);
}
/// \brief Update the sensor frame with a gyroscope reading.
///
/// If no Δt is provided, the estimation's default is used.
///
/// \warning The default Δt must be explicitly set using DeltaT
/// before calling this.
///
/// \param gyro A three-dimensional vector containing gyro
/// readings as w_x, w_y, w_z.
void
UpdateAngularOrientation(const scmp::geom::Vector<T, 3> &gyro)
{
this->UpdateAngularOrientation(gyro, this->deltaT);
}
/// \brief Retrieve a vector of the Euler angles in ZYX
/// Orientation.
///
/// \return A vector of Euler angles as <ψ, θ, ϕ>.
scmp::geom::Vector<T, 3>
Euler()
{
return this->sensorFrame.Euler();
}
/// \brief Set the default Δt.
///
/// \note This must be explicitly called before calling any
/// method which uses the estimation's internal Δt.
///
/// \param newDeltaT The time delta to use when no time delta
/// is provided.
void
DeltaT(T newDeltaT)
{
this->deltaT = newDeltaT;
}
/// \brief Retrieve the estimation's current ΔT.
///
/// \return The current value the estimation will default to using
/// if no time delta is provided.
T DeltaT() { return this->deltaT; }
private:
T deltaT;
scmp::geom::Quaternion<T> previousSensorFrame;
scmp::geom::Quaternion<T> sensorFrame;
};
/// \brief Madgwickd is a shorthand alias for a Madgwick<double>.
using Madgwickd = Madgwick<double>;
/// \brief Madgwickf is a shorthand alias for a Madgwick<float>.
using Madgwickf = Madgwick<float>;
} // namespace estimation
} // namespace scmp
#endif // SCMP_FILTER_MADGWICK_H

43
include/scmp/geom.h Normal file
View File

@ -0,0 +1,43 @@
///
/// \file include/scmp/geom.h
/// \author K. Isom <kyle@imap.cc>
/// \date 2023-10-20
/// \brief Vector-based geometry code.
///
/// 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_GEOM_H
#define SCSL_GEOM_H
#include <scmp/geom/Coord2D.h>
#include <scmp/geom/Orientation.h>
#include <scmp/geom/Quaternion.h>
#include <scmp/geom/Vector.h>
namespace scmp {
/// \brief Geometry-related code.
namespace geom {}
} // namespace scmp
#endif // SCSL_GEOM_H

164
include/scmp/geom/Coord2D.h Executable file
View File

@ -0,0 +1,164 @@
///
/// \file include/scmp/geom/Coord2D.h
/// \author K. Isom <kyle@imap.cc>
/// \date 2017-06-05
/// \brief 2D point and polar coordinate systems.
///
/// \section COPYRIGHT
///
/// 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.
///
#ifndef SCMATH_GEOM_COORD2D_H
#define SCMATH_GEOM_COORD2D_H
#include <cmath>
#include <ostream>
#include <vector>
#include <scmp/geom/Vector.h>
namespace scmp {
namespace geom {
class Point2D;
class Polar2D;
/// \brief Point2D is a cartesian (X,Y) pairing.
class Point2D : public Vector<int, 2> {
public:
/// \brief A Point2D defaults to (0,0).
Point2D();
/// \brief Initialize a Point2D At (_x, _y).
Point2D(int _x, int _y);
/// \brief Initialize a Point2D from a Polar2D coordinate.
Point2D(const Polar2D &pol);
/// \brief Return the X component of the point.
int X() const;
/// \brief Set the X component of the point.
void X(int _x);
/// \brief Return the Y component of the point.
int Y() const;
/// Set the Y component of the point.
void Y(int _y);
/// \brief ToString returns a string in the format (x,y).
std::string ToString();
/// \brief ToPolar converts the Point2D to a polar coordinate
/// in-place.
void ToPolar(Polar2D &);
/// \brief Rotate rotates the point by theta radians.
///
/// \param rotated Stores the rotated point.
/// \param theta The Angle (in radians) to Rotate the point.
void Rotate(Point2D& rotated, double theta);
/// \brief Rotate this point around a series of vertices.
///
/// \param vertices A series of vertices to Rotate this point around.
/// \param theta The Angle to Rotate by.
/// \return A series of rotated points.
std::vector<Point2D> Rotate(std::vector<Polar2D> vertices, double theta);
/// \brief Translate adds this point to the first argument,
/// storing the result in the second argument.
///
/// \param other The point to translate by.
/// \param translated The point to store the translation in.
void Translate(const Point2D &other, Point2D &translated);
/// \brief Distance returns the distance from this point to another.
int Distance(const Point2D &other) const;
friend std::ostream &operator<<(std::ostream &outs, const Point2D &pt);
};
/// \brief Polar2D is a pairing of a radius r and angle θ from some
/// reference point; in this library, it is assumed to be the
/// Cartesian origin (0, 0).
class Polar2D : public Vector<double, 2> {
public:
/// A Polar2D can be initialised as a zeroised polar coordinate, by specifying
/// the radius and Angle directly, or via conversion from a Point2D.
/// \brief Construct a zero polar coordinate.
Polar2D();
/// \brief Construct a polar coordinate from a radius and
/// angle.
///
/// \param _r A radius
/// \param _theta An angle
Polar2D(double _r, double _theta);
/// \brief Construct a polar coordinate from a point.
///
/// This construct uses the origin (0,0) as the reference point.
///
/// \param point A 2D Cartesian point.
Polar2D(const Point2D& point);
/// \brief Return the radius component of this coordinate.
double R() const;
/// \brief Set the radius component of this coordinate.
void R(const double _r);
/// \brief Return the angle component of this coordinate.
double Theta() const;
/// \brief Set the angle component of this coordinate.
void Theta(const double _theta);
/// \brief Return the coordinate in string form.
std::string ToString();
/// \brief Construct a Point2D representing this Polar2D.
void ToPoint(Point2D &point);
/// \brief Rotate polar coordinate by some angle.
///
/// \param rotated The rotated Polar2D will be stored in this
/// coordinate.
/// \param delta The angle to rotate by.
void Rotate(Polar2D &rotated, double delta);
/// \brief Rotate this polar coordinate around a 2D point.
///
/// \param other The reference point.
/// \param result The point where the result will stored.
/// \param delta The angle to rotate by.
void RotateAround(const Point2D &other, Point2D &result, double delta);
friend std::ostream &operator<<(std::ostream &, const Polar2D &);
};
} // end namespace geom
} // end namespace math
#endif

View File

@ -0,0 +1,113 @@
///
/// \file include/scmp/geom/Orientation.h
/// \author K. Isom <kyle@imap.cc>
/// \date 2017-06-05
/// \brief Orientation of vectors w.r.t. a reference plane, assumed to
/// be the Earth.
///
/// 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 <scmp/geom/Vector.h>
#ifndef SCMATH_GEOM_ORIENTATION_H
#define SCMATH_GEOM_ORIENTATION_H
namespace scmp {
namespace geom {
/// \defgroup basis Basis vector indices.
/// The following constants are provided as a convenience for indexing two-
/// and three-dimensional vectors.
/// \ingroup basis
/// \brief Convenience constant for the x index.
constexpr uint8_t BasisX = 0;
/// \ingroup basis
/// \brief Convenience constant for the y index.
constexpr uint8_t BasisY = 1;
/// \ingroup basis
/// \brief Convenience constant for the z index.
constexpr uint8_t BasisZ = 2;
/// \brief Basis2D provides basis vectors for Vector2ds.
static const Vector2D Basis2D[] = {
Vector2D{1, 0},
Vector2D{0, 1},
};
/// \brief Basis2D provides basis vectors for Vector2fs.
static const Vector2F Basis2F[] = {
Vector2F{1, 0},
Vector2F{0, 1},
};
/// \brief Basis2D provides basis vectors for Vector3ds.
static const Vector3D Basis3D[] = {
Vector3D{1, 0, 0},
Vector3D{0, 1, 0},
Vector3D{0, 0, 1},
};
/// \brief Basis2D provides basis vectors for Vector3fs.
static const Vector3F Basis3F[] = {
Vector3F{1, 0, 0},
Vector3F{0, 1, 0},
Vector3F{0, 0, 1},
};
/// \brief Compass heading for a Vector2F.
///
/// \param vec A vector Orientation.
/// \return The compass heading of the vector in radians.
float Heading2F(Vector2F vec);
/// \brief Compass heading for a Vector2D.
///
/// \param vec A vector Orientation.
/// \return The compass heading of the vector in radians.
double Heading2D(Vector2D vec);
/// \brief Compass heading for a Vector2F.
///
/// \param vec A vector Orientation.
/// \return The compass heading of the vector in radians.
float Heading3F(Vector3F vec);
/// Heading3D returns a compass heading for a Vector2F.
///
/// \param vec A vector Orientation.
/// \return The compass heading of the vector in radians.
double Heading3D(Vector3D vec);
} // namespace geom
} // namespace scmp
#endif // SCMATH_GEOM_ORIENTATION_H

View File

@ -0,0 +1,577 @@
///
/// \file include/scmp/geom/Quaternion.h
/// \author K. Isom <kyle@imap.cc>
/// \date 2019-08-05
/// \brief Quaternion implementation suitable for navigation in R3.
///
/// Copyright 2019 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 SCMATH_GEOM_QUATERNION_H
#define SCMATH_GEOM_QUATERNION_H
#include <cassert>
#include <cmath>
#include <initializer_list>
#include <iostream>
#include <ostream>
#include <scmp/Math.h>
#include <scmp/geom/Vector.h>
namespace scmp {
namespace geom {
/// \brief Quaternions provide a representation of Orientation
/// and rotations in three dimensions.
///
/// Quaternions encode rotations in three-dimensional space. While
/// technically a Quaternion is comprised of a real element and a
/// complex vector<3>, for the purposes of this library, it is modeled
/// as a floating point 4D vector of the form <w, x, y, z>, where x, y,
/// and z represent an Axis of rotation in R3 and w the Angle, in
/// radians, of the rotation about that Axis. Where Euler angles are
/// concerned, the ZYX (or yaw, pitch, roll) sequence is used.
///
/// For information on the underlying vector type, see the
/// documentation for scmp::geom::Vector.
///
/// Like vectors, quaternions carry an internal tolerance value ε that
/// is used for floating point comparisons. The math namespace contains
/// the default values used for this; generally, a tolerance of 0.0001
/// is considered appropriate for the uses of this library. The
/// tolerance can be explicitly set with the SetEpsilon method.
template<typename T>
class Quaternion {
public:
/// \brief Construct an identity Quaternion.
Quaternion() : v(Vector<T, 3>{0.0, 0.0, 0.0}), w(1.0)
{
scmp::DefaultEpsilon(this->eps);
v.SetEpsilon(this->eps);
};
/// \brief Construct a Quaternion with an Axis and Angle of
/// rotation.
///
/// A Quaternion may be initialised with a Vector<T, 3> Axis
/// of rotation and an Angle of rotation. This doesn't do the
/// Angle transforms to simplify internal operations.
///
/// \param _axis A three-dimensional vector of the same type as
/// the Quaternion.
/// \param _angle The Angle of rotation about the Axis of
/// rotation.
Quaternion(Vector<T, 3> _axis, T _angle) : v(_axis), w(_angle)
{
this->constrainAngle();
scmp::DefaultEpsilon(this->eps);
v.SetEpsilon(this->eps);
};
/// A Quaternion may be initialised with a Vector<T, 4> comprised of
/// the Axis of rotation followed by the Angle of rotation.
///
/// \param vector A vector in the form <w, x, y, z>.
Quaternion(Vector<T, 4> vector) :
v(Vector<T, 3>{vector[1], vector[2], vector[3]}),
w(vector[0])
{
this->constrainAngle();
scmp::DefaultEpsilon(this->eps);
v.SetEpsilon(this->eps);
}
/// \brief An initializer list containing values for w, x, y,
/// and z.
///
/// \param ilst An initial set of values in the form
/// <w, x, y, z>.
Quaternion(std::initializer_list<T> ilst)
{
auto it = ilst.begin();
this->v = Vector<T, 3>{it[1], it[2], it[3]};
this->w = it[0];
this->constrainAngle();
scmp::DefaultEpsilon(this->eps);
v.SetEpsilon(this->eps);
}
/// \brief Set the comparison tolerance for this Quaternion.
///
/// \param epsilon A tolerance value.
void
SetEpsilon(T epsilon)
{
this->eps = epsilon;
this->v.SetEpsilon(epsilon);
}
/// \brief Return the Axis of rotation of this Quaternion.
///
/// \return The Axis of rotation of this Quaternion.
Vector<T, 3>
Axis() const
{
return this->v;
}
/// \brief Return the Angle of rotation of this Quaternion.
///
/// \return the Angle of rotation of this Quaternion.
T
Angle() const
{
return this->w;
}
/// \brief Compute the Dot product of two quaternions.
///
/// \param other Another Quaternion.
/// \return The Dot product between the two quaternions.
T
Dot(const Quaternion<T> &other) const
{
double innerProduct = this->v[0] * other.v[0];
innerProduct += (this->v[1] * other.v[1]);
innerProduct += (this->v[2] * other.v[2]);
innerProduct += (this->w * other.w);
return innerProduct;
}
/// \brief Compute the Norm of a Quaternion.
///
/// Treating the Quaternion as a Vector<T, 4>, this is the same
/// process as computing the Magnitude.
///
/// \return A non-negative real number.
T
Norm() const
{
T n = 0;
n += (this->v[0] * this->v[0]);
n += (this->v[1] * this->v[1]);
n += (this->v[2] * this->v[2]);
n += (this->w * this->w);
return std::sqrt(n);
}
/// \brief Return the unit Quaternion.
///
/// \return The unit Quaternion.
Quaternion
UnitQuaternion()
{
return *this / this->Norm();
}
/// \brief Compute the Conjugate of a Quaternion.
///
/// \return The Conjugate of this Quaternion.
Quaternion
Conjugate() const
{
return Quaternion(Vector<T, 4>{this->w, -this->v[0], -this->v[1], -this->v[2]});
}
/// \brief Compute the Inverse of a Quaternion.
///
/// \return The Inverse of this Quaternion.
Quaternion
Inverse() const
{
T _norm = this->Norm();
return this->Conjugate() / (_norm * _norm);
}
/// \brief Determine whether this is an identity Quaternion.
///
/// \return true if this is an identity Quaternion.
bool
IsIdentity() const {
return this->v.IsZero() &&
scmp::WithinTolerance(this->w, (T)1.0, this->eps);
}
/// \brief Determine whether this is a unit Quaternion.
///
/// \return true if this is a unit Quaternion.
bool
IsUnitQuaternion() const
{
auto normal = this->Norm();
return scmp::WithinTolerance(normal, (T) 1.0, this->eps);
}
/// \brief Convert to Vector form.
///
/// Return the Quaternion as a Vector<T, 4>, with the Axis of
/// rotation followed by the Angle of rotation.
///
/// \return A vector representation of the Quaternion.
Vector<T, 4>
AsVector() const
{
return Vector<T, 4>{this->w, this->v[0], this->v[1], this->v[2]};
}
/// \brief Rotate Vector vr about this Quaternion.
///
/// \param vr The vector to be rotated.
/// \return The rotated vector.
Vector<T, 3>
Rotate(Vector<T, 3> vr) const
{
return (this->Conjugate() * vr * (*this)).Axis();
}
/// \brief Return Euler angles for this Quaternion.
///
/// Return the Euler angles for this Quaternion as a vector of
/// <yaw, pitch, roll>.
///
/// \warning Users of this function should watch out for gimbal
/// lock.
///
/// \return A vector<T, 3> containing <yaw, pitch, roll>
Vector<T, 3>
Euler() const
{
T yaw, pitch, roll;
T a = this->w, a2 = a * a;
T b = this->v[0], b2 = b * b;
T c = this->v[1], c2 = c * c;
T d = this->v[2], d2 = d * d;
yaw = std::atan2(2 * ((a * b) + (c * d)), a2 - b2 - c2 + d2);
pitch = std::asin(2 * ((b * d) - (a * c)));
roll = std::atan2(2 * ((a * d) + (b * c)), a2 + b2 - c2 - d2);
return Vector<T, 3>{yaw, pitch, roll};
}
/// \brief Quaternion addition.
///
/// \param other The Quaternion to be added with this one.
/// \return The result of adding the two quaternions together.
Quaternion
operator+(const Quaternion<T> &other) const
{
return Quaternion(this->v + other.v, this->w + other.w);
}
/// \brief Quaternion subtraction.
///
/// \param other The Quaternion to be subtracted from this one.
/// \return The result of subtracting the other Quaternion from this one.
Quaternion
operator-(const Quaternion<T> &other) const
{
return Quaternion(this->v - other.v, this->w - other.w);
}
/// \brief Scalar multiplication.
///
/// \param k The scaling value.
/// \return A scaled Quaternion.
Quaternion
operator*(const T k) const
{
return Quaternion(this->v * k, this->w * k);
}
/// \brief Scalar division.
///
/// \param k The scalar divisor.
/// \return A scaled Quaternion.
Quaternion
operator/(const T k) const
{
return Quaternion(this->v / k, this->w / k);
}
/// \brief Quaternion Hamilton multiplication with a three-
/// dimensional vector.
///
/// This is done by treating the vector as a pure Quaternion
/// (e.g. with an Angle of rotation of 0).
///
/// \param vector The vector to multiply with this Quaternion.
/// \return The Hamilton product of the Quaternion and vector.
Quaternion
operator*(const Vector<T, 3> &vector) const
{
return Quaternion(vector * this->w + this->v.Cross(vector),
(T) 0.0);
}
/// \brief Quaternion Hamilton multiplication.
///
/// \param other The other Quaternion to multiply with this one.
/// @result The Hamilton product of the two quaternions.
Quaternion
operator*(const Quaternion<T> &other) const
{
T angle = (this->w * other.w) -
(this->v * other.v);
Vector<T, 3> axis = (other.v * this->w) +
(this->v * other.w) +
(this->v.Cross(other.v));
return Quaternion(axis, angle);
}
/// \brief Quaternion equivalence.
///
/// \param other The Quaternion to check equality against.
/// \return True if the two quaternions are equal within their tolerance.
bool
operator==(const Quaternion<T> &other) const
{
return (this->v == other.v) &&
(scmp::WithinTolerance(this->w, other.w, this->eps));
}
/// \brief Quaternion non-equivalence.
///
/// \param other The Quaternion to check inequality against.
/// \return True if the two quaternions are unequal within their tolerance.
bool
operator!=(const Quaternion<T> &other) const
{
return !(*this == other);
}
/// \brief Output a Quaternion to a stream in the form
/// `a + <i, j, k>`.
///
/// \todo improve the formatting.
///
/// \param outs An output stream
/// \param q A Quaternion
/// \return The output stream
friend std::ostream &
operator<<(std::ostream &outs, const Quaternion<T> &q)
{
outs << q.w << " + " << q.v;
return outs;
}
private:
static constexpr T minRotation = -4 * M_PI;
static constexpr T maxRotation = 4 * M_PI;
Vector<T, 3> v; // Axis of rotation
T w; // Angle of rotation
T eps;
void
constrainAngle()
{
if (this->w < 0.0) {
this->w = std::fmod(this->w, this->minRotation);
}
else {
this->w = std::fmod(this->w, this->maxRotation);
}
}
};
///
/// \defgroup quaternion_aliases Quaternion type aliases.
/// Type aliases are provided for float and double quaternions.
///
/// \ingroup quaternion_aliases
/// \brief Type alias for a float Quaternion.
typedef Quaternion<float> Quaternionf;
/// \ingroup quaternion_aliases
/// \brief Type alias for a double Quaternion.
typedef Quaternion<double> Quaterniond;
/// \brief Convenience Quaternion construction function.
///
/// Return a float Quaternion scaled appropriately from a vector and
/// Angle, e.g.
/// angle = cos(Angle / 2),
/// Axis.UnitVector() * sin(Angle / 2).
///
/// \param axis The Axis of rotation.
/// \param angle The Angle of rotation.
/// \return A Quaternion.
/// \relatesalso Quaternion
Quaternionf MakeQuaternion(Vector3F axis, float angle);
/// \brief Convience Quaternion construction function.
///
/// Return a double Quaternion scaled appropriately from a vector and
/// Angle, e.g.
/// Angle = cos(Angle / 2),
/// Axis.UnitVector() * sin(Angle / 2).
///
/// \param axis The Axis of rotation.
/// \param angle The Angle of rotation.
/// \return A Quaternion.
/// \relatesalso Quaternion
Quaterniond MakeQuaternion(Vector3D axis, double angle);
/// \brief Convience Quaternion construction function.
///
/// Return a double Quaternion scaled appropriately from a vector and
/// Angle, e.g.
/// Angle = cos(Angle / 2),
/// Axis.UnitVector() * sin(Angle / 2).
///
/// \param axis The Axis of rotation.
/// \param angle The Angle of rotation.
/// \return A Quaternion.
/// \relatesalso Quaternion
template <typename T>
Quaternion<T>
MakeQuaternion(Vector<T, 3> axis, T angle)
{
return Quaternion<T>(axis.UnitVector() * std::sin(angle / (T)2.0),
std::cos(angle / (T)2.0));
}
/// \brief COnstruct a Quaternion from Euler angles.
///
/// Given a vector of Euler angles in ZYX sequence (e.g. yaw, pitch,
/// roll), return a Quaternion.
///
/// \param euler A vector Euler Angle in ZYX sequence.
/// \return A Quaternion representation of the Orientation represented
/// by the Euler angles.
/// \relatesalso Quaternion
Quaternionf FloatQuaternionFromEuler(Vector3F euler);
/// \brief COnstruct a Quaternion from Euler angles.
///
/// Given a vector of Euler angles in ZYX sequence (e.g. yaw, pitch,
/// roll), return a Quaternion.
///
/// \param euler A vector Euler Angle in ZYX sequence.
/// \return A Quaternion representation of the Orientation represented
/// by the Euler angles.
/// \relatesalso Quaternion
Quaterniond DoubleQuaternionFromEuler(Vector3D euler);
/// \brief Linear interpolation for two Quaternions.
///
/// LERP computes the linear interpolation of two quaternions At some
/// fraction of the distance between them.
///
/// \tparam T
/// \param p The starting Quaternion.
/// \param q The ending Quaternion.
/// \param t The fraction of the distance between the two quaternions to
/// interpolate.
/// \return A Quaternion representing the linear interpolation of the
/// two quaternions.
template <typename T>
Quaternion<T>
LERP(Quaternion<T> p, Quaternion<T> q, T t)
{
return (p + (q - p) * t).UnitQuaternion();
}
/// \brief Shortest distance spherical linear interpolation.
///
/// ShortestSLERP computes the shortest distance spherical linear
/// interpolation between two unit quaternions At some fraction of the
/// distance between them.
///
/// \tparam T
/// \param p The starting Quaternion.
/// \param q The ending Quaternion.
/// \param t The fraction of the distance between the two quaternions
/// to interpolate.
/// \return A Quaternion representing the shortest path between two
/// quaternions.
template <typename T>
Quaternion<T>
ShortestSLERP(Quaternion<T> p, Quaternion<T> q, T t)
{
assert(p.IsUnitQuaternion());
assert(q.IsUnitQuaternion());
T dp = p.Dot(q);
T sign = dp < 0.0 ? -1.0 : 1.0;
T omega = std::acos(dp * sign);
T sin_omega = std::sin(omega); // Compute once.
if (dp > 0.99999) {
return LERP(p, q * sign, t);
}
return (p * std::sin((1.0 - t) * omega) / sin_omega) +
(q * sign * std::sin(omega*t) / sin_omega);
}
/// \brief Internal consistency check.
///
/// Run a quick self test to exercise basic functionality of the Quaternion
/// class to verify correct operation. Note that if \#NDEBUG is defined, the
/// self test is disabled.
void QuaternionSelfTest();
} // namespace geom
} // namespace wr
#endif // SCMATH_GEOM_QUATERNION_H

505
include/scmp/geom/Vector.h Normal file
View File

@ -0,0 +1,505 @@
///
/// \file include/scmp/geom/Vector.h
/// \author K. Isom <kyle@imap.cc>
/// \date 2017-06-05
/// \brief Linear algebraic vector class.
///
/// 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.
///
#ifndef SCMATH_GEOM_VECTORS_H
#define SCMATH_GEOM_VECTORS_H
#include <array>
#include <cassert>
#include <cmath>
#include <initializer_list>
#include <ostream>
#include <iostream>
#include <scmp/Math.h>
// This implementation is essentially a C++ translation of a Python library
// I wrote for Coursera's "Linear Algebra for Machine Learning" course. Many
// of the test vectors come from quiz questions in the class.
namespace scmp {
namespace geom {
/// \brief Vectors represent a direction and Magnitude.
///
/// Vector provides a standard interface for dimensionless fixed-size
/// vectors. Once instantiated, they cannot be modified.
///
/// Note that while the class is templated, it's intended to be used
/// with floating-point types.
///
/// Vectors can be indexed like arrays, and they contain an epsilon
/// value that defines a tolerance for equality.
template<typename T, size_t N>
class Vector {
public:
/// \brief Construct a unit vector of a given type and size.
Vector()
{
T unitLength = (T) 1.0 / std::sqrt(N);
for (size_t i = 0; i < N; i++) {
this->arr[i] = unitLength;
}
scmp::DefaultEpsilon(this->epsilon);
}
/// \brief Construct a Vector with initial values.
///
/// If given an initializer_list, the vector is created with
/// those values. There must be exactly N elements in the list.
///
/// \param ilst An intializer list with N elements of type T.
Vector(std::initializer_list<T> ilst)
{
assert(ilst.size() == N);
scmp::DefaultEpsilon(this->epsilon);
std::copy(ilst.begin(), ilst.end(), this->arr.begin());
}
/// \brief Return the element At index i.
///
/// \throws std::out_of_range if the index is out of bounds.
///
/// \param index The index of the item to retrieve.
/// \return The value At the index.
T At(size_t index) const
{
if (index > this->arr.size()) {
throw std::out_of_range("index " +
std::to_string(index) + " > " +
std::to_string(this->arr.size()));
}
return this->arr.at(index);
}
/// \brief Set a new value for the vector.
///
/// This is used to modify the vector in place.
///
/// \throws std::out_of_range if the index is out of bounds.
///
/// \param index The index to insert the value At.
/// \param value
void Set(size_t index, T value)
{
if (index > this->arr.size()) {
throw std::out_of_range("index " +
std::to_string(index) + " > " +
std::to_string(this->arr.size()));
}
this->arr[index] = value;
}
/// \brief Compute the length of the vector.
///
/// \return The length of the vector.
T Magnitude() const
{
T result = 0;
for (size_t i = 0; i < N; i++) {
result += (this->arr[i] * this->arr[i]);
}
return std::sqrt(result);
}
/// \brief Set equivalence tolerance.
///
/// Set the tolerance for equality checks. At a minimum, this
/// accounts for systemic errors in floating math arithmetic.
///
/// \param eps is the maximum difference between this vector and
/// another.
void
SetEpsilon(T eps)
{
this->epsilon = eps;
}
/// \brief Determine whether this is a zero vector.
///
/// \return true if the vector is zero.
bool
IsZero() const
{
for (size_t i = 0; i < N; i++) {
if (!scmp::WithinTolerance(this->arr[i], (T) 0.0, this->epsilon)) {
return false;
}
}
return true;
}
/// \brief Obtain the unit vector for this vector.
///
/// \return The unit vector
Vector
UnitVector() const
{
return *this / this->Magnitude();
}
/// \brief Determine if this is a unit vector.
///
/// \return true if the vector is a unit vector.
bool
IsUnitVector() const
{
return scmp::WithinTolerance(this->Magnitude(), (T) 1.0, this->epsilon);
}
/// \brief Compute the Angle between two vectors.
///
/// \param other Another vector.
/// \return The Angle in radians between the two vectors.
T
Angle(const Vector<T, N> &other) const
{
auto unitA = this->UnitVector();
auto unitB = other.UnitVector();
// Can't compute angles with a zero vector.
assert(!this->IsZero());
assert(!other.IsZero());
return static_cast<T>(std::acos(unitA * unitB));
}
/// \brief Determine whether two vectors are parallel.
///
/// \param other Another vector
/// \return True if the Angle between the vectors is zero.
bool
IsParallel(const Vector<T, N> &other) const
{
if (this->IsZero() || other.IsZero()) {
return true;
}
// If the two unit vectors are equal, the two vectors
// lie on the same path.
//
// Context: this used to use Vector::Angle to check for
// a zero angle between the two. However, the vagaries
// of floating point math meant that while this worked
// fine on Linux amd64 builds, it failed on Linux arm64
// and MacOS builds. Parallel float vectors would have
// an angle of ~0.0003 radians, while double vectors
// would have an angle of +NaN. I suspect this is due to
// tiny variations in floating point math, such that a dot
// product of unit vectors would be just a hair over 1,
// e.g. 1.000000001 - which would still fall outside the
// domain of acos.
auto unitA = this->UnitVector();
auto unitB = other.UnitVector();
return unitA == unitB;
}
/// \brief Determine if two vectors are orthogonal or
/// perpendicular to each other.
///
/// \param other Another vector
/// \return True if the two vectors are orthogonal.
bool
IsOrthogonal(const Vector<T, N> &other) const
{
if (this->IsZero() || other.IsZero()) {
return true;
}
return scmp::WithinTolerance(*this * other, (T) 0.0, this->epsilon);
}
/// \brief Project this vector onto some basis vector.
///
/// \param basis The basis vector to be projected onto.
/// \return A vector that is the projection of this onto the basis
/// vector.
Vector
ProjectParallel(const Vector<T, N> &basis) const
{
Vector<T, N> unit_basis = basis.UnitVector();
return unit_basis * (*this * unit_basis);
}
/// \brief Project this vector perpendicularly onto some basis vector.
///
/// This is also called the *rejection* of the vector.
///
/// \param basis The basis vector to be projected onto.
/// \return A vector that is the orthogonal projection of this onto
/// the basis vector.
Vector
ProjectOrthogonal(const Vector<T, N> &basis)
{
Vector<T, N> spar = this->ProjectParallel(basis);
return *this - spar;
}
/// \brief Compute the cross product of two vectors.
///
/// This is only defined over three-dimensional vectors.
///
/// \throw std::out_of_range if this is not a three-dimensional vector.
///
/// \param other Another 3D vector.
/// \return The Cross product vector.
Vector
Cross(const Vector<T, N> &other) const
{
assert(N == 3);
if (N != 3) {
throw std::out_of_range("Cross-product can only called on Vector<T, 3>.");
}
return Vector<T, N>{
(this->arr[1] * other.arr[2]) - (other.arr[1] * this->arr[2]),
-((this->arr[0] * other.arr[2]) - (other.arr[0] * this->arr[2])),
(this->arr[0] * other.arr[1]) - (other.arr[0] * this->arr[1])
};
}
/// \brief Vector addition.
///
/// \param other The vector to be added.
/// \return A new vector that is the result of adding this and the
/// other vector.
Vector
operator+(const Vector<T, N> &other) const
{
Vector<T, N> vec;
for (size_t i = 0; i < N; i++) {
vec.arr[i] = this->arr[i] + other.arr[i];
}
return vec;
}
/// \brief Vector subtraction.
///
/// \param other The vector to be subtracted from this vector.
/// \return A new vector that is the result of subtracting the
/// other vector from this one.
Vector
operator-(const Vector<T, N> &other) const
{
Vector<T, N> vec;
for (size_t i = 0; i < N; i++) {
vec.arr[i] = this->arr[i] - other.arr[i];
}
return vec;
}
/// \brief Scalar multiplication.
///
/// \param k The scaling value.
/// \return A new vector that is this vector scaled by k.
Vector
operator*(const T k) const
{
Vector<T, N> vec;
for (size_t i = 0; i < N; i++) {
vec.arr[i] = this->arr[i] * k;
}
return vec;
}
/// \brief Scalar division.
///
/// \param k The scaling value
/// \return A new vector that is this vector scaled by 1/k.
Vector
operator/(const T k) const
{
Vector<T, N> vec;
for (size_t i = 0; i < N; i++) {
vec.arr[i] = this->arr[i] / k;
}
return vec;
}
/// \brief Compute the Dot product between two vectors.
///
/// \param other The other vector.
/// \return A scalar value that is the Dot product of the two vectors.
T
operator*(const Vector<T, N> &other) const
{
T result = 0;
for (size_t i = 0; i < N; i++) {
result += (this->arr[i] * other.arr[i]);
}
return result;
}
/// \brief Vector equivalence
///
/// \param other The other vector.
/// \return Return true if all the components of both vectors are
/// within the tolerance value.
bool
operator==(const Vector<T, N> &other) const
{
for (size_t i = 0; i < N; i++) {
if (!scmp::WithinTolerance(this->arr[i], other.arr[i], this->epsilon)) {
return false;
}
}
return true;
}
/// \brief Vector non-equivalence.
///
/// \param other The other vector.
/// \return Return true if any of the components of both vectors are
/// not within the tolerance value.
bool
operator!=(const Vector<T, N> &other) const
{
return !(*this == other);
}
/// \brief Array indexing into vector.
///
/// Note that the values of the vector cannot be modified.
/// Instead, something like the following must be done:
///
/// ```
/// Vector3D a {1.0, 2.0, 3.0};
/// Vector3D b {a[0], a[1]*2.0, a[2]};
/// ```
///
/// \param i The component index.
/// \return The value of the vector component At i.
const T &
operator[](size_t i) const
{
return this->arr[i];
}
/// \brief Write a vector a stream in the form "<i, j, ...>".
///
/// \param outs An output stream.
/// \param vec The vector to be formatted.
/// \return The output stream.
friend std::ostream &
operator<<(std::ostream &outs, const Vector<T, N> &vec)
{
outs << "<";
for (size_t i = 0; i < N; i++) {
outs << vec.arr[i];
if (i < (N - 1)) {
outs << ", ";
}
}
outs << ">";
return outs;
}
private:
static const size_t dim = N;
T epsilon;
std::array<T, N> arr;
};
///
/// \defgroup vector_aliases Vector type aliases.
///
/// \ingroup vector_aliases
/// A number of shorthand aliases for vectors are provided. They follow
/// the form of VectorNt, where N is the dimension and t is the type.
/// For example, a 2D float vector is Vector2F.
/// \ingroup vector_aliases
/// \brief Type alias for a two-dimensional float vector.
typedef Vector<float, 2> Vector2F;
/// \ingroup vector_aliases
/// \brief Type alias for a three-dimensional float vector.
typedef Vector<float, 3> Vector3F;
/// \ingroup vector_aliases
/// \brief Type alias for a four-dimensional float vector.
typedef Vector<float, 4> Vector4F;
/// \ingroup vector_aliases
/// \brief Type alias for a two-dimensional double vector.
typedef Vector<double, 2> Vector2D;
/// \ingroup vector_aliases
/// \brief Type alias for a three-dimensional double vector.
typedef Vector<double, 3> Vector3D;
/// \ingroup vector_aliases
/// \brief Type alias for a four-dimensional double vector.
typedef Vector<double, 4> Vector4D;
} // namespace geom
} // namespace scmp
#endif // SCMATH_GEOM_VECTORS_H

34
include/scmp/scmp.h Normal file
View File

@ -0,0 +1,34 @@
///
/// \file include/scmp/scmp.h
/// \author K. Isom <kyle@imap.cc>
/// \date 2023-10-19
/// \brief Aggregated scmp include header.
///
/// 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_SCMP_H
#define SCSL_SCMP_H
/// \brief Shimmering Clarity Math & Physics toolkit.
///
/// The Shimmering Clarity contains code related to math and physics,
/// particularly as relevant to game programming and robotics.
namespace scmp {}
#endif //SCSL_SCMP_H

View File

@ -1,28 +1,37 @@
///
/// \file Arena.h
/// \author K. Isom
/// \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.
///
/// \section PLATFORM SUPPORT
/// 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.
///
/// 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 <iostream>
#include <sys/stat.h>
#include <cstddef>
#include <cstdint>
#include <iostream>
#include <sys/stat.h>
#include "Exceptions.h"
#include "sctest/Exceptions.h"
#if defined(__WIN64__) || defined(__WIN32__) || defined(WIN32)
@ -52,7 +61,7 @@ enum class ArenaType
};
/// Arena is the class that implements a memory arena.
/// \brief Fixed, pre-allocated memory.
///
/// 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
@ -61,7 +70,7 @@ enum class ArenaType
/// 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.
/// arena's life, At which point Destroy should be called.
class Arena {
public:
/// An Arena is initialized with no backing memory.
@ -112,12 +121,7 @@ public:
/// \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.
#if defined(__posix__) || defined(__linux__) || defined(__APPLE__)
int Create(const char *path, size_t fileSize);
#elif defined(__WIN64__) || defined(__WIN32__) || defined(WIN32)
int Create(const char *path, size_t fileSize);
#endif
/// 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

View File

@ -1,5 +1,5 @@
///
/// \file Buffer.h
/// \file include/scsl/Buffer.h
/// \author K. Isom <kyle@imap.cc>
/// \date 2023-10-09
/// \brief Buffer implements basic line buffers.
@ -8,17 +8,32 @@
/// 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 <iostream>
#include <cstdint>
#include <iostream>
namespace scsl {
/// Buffer is a basic line buffer.
/// \brief Basic line buffer.
///
/// The buffer manages its own internal memory, growing and shrinking
/// as needed. Its capacity is separate from its length; the optimal
@ -32,76 +47,89 @@ namespace scsl {
/// 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).
/// \brief Construct an empty buffer with no memory allocated.
Buffer();
/// A Buffer can be constructed with an explicit capacity.
/// \buffer Constructor with explicit memory capacity.
///
/// \param initialCapacity The initial allocation size for the buffer.
/// \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.
/// \brief Construct with a C-style string.
explicit Buffer(const char *s);
/// A Buffer can be initialized with a starting string.
explicit Buffer(const std::string s);
/// \buffer Construct with an initial string.
explicit Buffer(const std::string& s);
~Buffer()
{ this->Reclaim(); }
~Buffer();
/// Contents returns the Buffer's contents.
uint8_t *Contents() const
{ return this->contents; }
/// \brief Retrieve the buffer's contents.
uint8_t *Contents() const;
/// Length returns the length of the data currently stored in the
/// buffer.
size_t Length() const
{ return this->length; };
std::string ToString() const;
/// Capacity returns the amount of memory allocated to the Buffer.
size_t Capacity() const
{ return this->capacity; }
/// \brief The length of data stored in the buffer.
///
/// \return The number of bytes stored in the Buffer.
size_t Length() const;
/// Append copies in a C-style string to the end of the buffer.
/// \brief Return the amount of memory allocated for the
/// Buffer.
size_t Capacity() const;
/// \brief Append 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.
/// Append Append 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);
bool Append(const std::string &s);
/// Append copies in a byte buffer to the end of the buffer.
/// \brief Append 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.
/// \brief Append 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.
/// \brief Insert a C-style string into the buffer at index.
///
/// \note As this is intended for use in text editing, an
/// insert into a buffer after the length will insert
/// spaces before the content.
///
/// \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.
/// \brief Insert a string into the buffer at index.
///
/// \note As this is intended for use in text editing, an
/// insert into a buffer after the length will insert
/// spaces before the content.
///
/// \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);
bool Insert(const size_t index, const std::string &s);
/// Insert copies a uint8_t buffer into the buffer at index.
/// \brief Insert a uint8_t buffer into the buffer at index.
///
/// \note As this is intended for use in text editing, an
/// insert into a buffer after the length will insert
/// spaces before the content.
///
/// \param index The index to insert the buffer at.
/// \param data The buffer to insert.
@ -110,21 +138,25 @@ public:
bool
Insert(const size_t index, const uint8_t *data, const size_t datalen);
/// Insert copies a character into the buffer at index.
/// \brief Insert a character into the buffer at index.
///
/// \note As this is intended for use in text editing, an
/// insert into a buffer after the length will insert
/// spaces before the content.
///
/// \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`.
/// \brief Remove `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.
/// \brief 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.
@ -132,7 +164,7 @@ public:
/* memory management */
/// Resize changes the capacity of the buffer to `newCapacity`.
/// \brief 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.
@ -140,27 +172,23 @@ public:
/// \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.
/// \brief Resize the Buffer capacity 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; }
void DisableAutoTrim();
/// EnableAutoTrim enables automatically trimming memory after
/// calls to #Remove.
void EnableAutoTrim()
{ this->autoTrim = true; }
void EnableAutoTrim();
/// AutoTrimIsEnabled returns true if autotrim is enabled.
///
/// \return #Remove will call Trim.
bool AutoTrimIsEnabled()
{ return this->autoTrim; }
bool AutoTrimIsEnabled();
/// Clear removes the data stored in the buffer. It will not
/// call #Trim; the capacity of the buffer will not be altered.

View File

@ -1,5 +1,5 @@
///
/// \file Commander.h
/// \file include/scsl/Commander.h
/// \author K. Isom <kyle@imap.cc>
/// \date 2023-10-10
/// \brief Subprogram tooling.
@ -12,9 +12,25 @@
/// $ 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 <map>
#include <cstdint>
#include <functional>
#include <map>
#include <string>
#include <vector>
@ -28,15 +44,17 @@ namespace scsl {
/// CommanderFunc describes a function that can be run in Commander.
///
/// It expects an argument count and a list of arguments.
typedef std::function<bool(int, char **)> CommanderFunc;
using CommanderFunc = std::function<bool (std::vector<std::string>)>;
/// \brief Subcommands used by Commander.
///
/// 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 : uint8_t {
enum class Status : int8_t {
/// The subcommand executed correctly.
OK = 0,
/// Not enough arguments were supplied to the subcommand.
@ -54,8 +72,8 @@ public:
/// \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)
Subcommand(std::string name, size_t argc, CommanderFunc func)
: fn(func), requiredArgs(argc), command(name)
{}
/// Name returns the name of this subcommand.
@ -63,16 +81,17 @@ public:
/// Run attempts to run the CommanderFunc for this subcommand.
///
/// \param argc The number of arguments supplied.
/// \param argv The argument list.
/// \param args The argument list.
/// \return A Status type indicating the status of running the command.
Status Run(int argc, char **argv);
Status Run(std::vector<std::string> args);
private:
CommanderFunc fn;
int args;
size_t requiredArgs;
std::string command;
};
/// \brief Subcommander manager for programs.
///
/// Commander collects subcommands and can run the apppropriate one.
///
/// For example:
@ -99,12 +118,13 @@ public:
bool Register(Subcommand scmd);
/// Try to run a subcommand registered with this Commander.
Subcommand::Status Run(std::string command, int argc, char **argv);
Subcommand::Status Run(std::string command, std::vector<std::string> args);
private:
std::map<std::string, Subcommand *> cmap;
};
} // scsl
} // namespace scsl
#endif //SCSL_COMMANDER_H

View File

@ -1,7 +1,24 @@
///
/// \file scsl.h
/// \author kyle
/// \date 2023-10-06
/// \file include/scsl/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.
///
@ -9,6 +26,8 @@
#define SCSL_DICTIONARY_H
#include <cstdint>
#include "Arena.h"
#include "TLV.h"
@ -20,17 +39,7 @@ 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.
///
/// phonebook of SSIDs and WPA keys on a microcontroller. This phonebook had to
/// be stored in persistent NVRAM storage, preëmpting the use of std::map or
/// similar. The hardware in use was also not conducive to more expensive
/// options. It was originally named Phonebook until it was adapted to a more
/// general-purpose data structure.
/// \brief 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

352
include/scsl/Flags.h Normal file
View File

@ -0,0 +1,352 @@
///
/// \file include/scsl/Flags.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;
/// \brief 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;
/// \brief 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);
/// \brief 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);
/// 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,
const char *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

246
include/scsl/SimpleConfig.h Normal file
View File

@ -0,0 +1,246 @@
///
/// \file include/scsl/SimpleConfig.h
/// \author K. Isom <kyle@imap.cc>
/// \date 2023-10-21
/// \brief Simple project configuration.
///
/// This is an implementation of a simple global configuration system
/// for projects based on a Go version I've used successfully in
/// several projects.
///
/// 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_SIMPLECONFIG_H
#define SCSL_SIMPLECONFIG_H
#include <map>
#include <string>
#include <vector>
namespace scsl {
/// \brief SimpleConfig is a basic configuration for projects.
///
/// SimpleConfig is a basic key-value system. It can optionally load
/// key-value pairs from a file, which should consist of key = value
/// lines. Comments may be added by starting the line with a '#'; these
/// lines will be skipped. Comments may have leading whitespace. Any
/// empty lines or lines consisting solely of whitespace will also be
/// skipped.
///
/// When values are retrieved by one of the variants on Get, they are
/// looked up in the following order, assuming `key` and an optional
/// `prefix` set on the config:
///
/// 1. Any cached key-value pairs. Loading a file caches the key-value
/// pairs in the file. The file is not used again, unless another
/// call to Load is made. If the cache has a name for `key`, it will
/// be returned.
/// 2. The value is looked up from the environment. An optional prefix
/// can be set; if so, if there is an environment named
/// `{prefix}{key}`, the value will be cached and it will be
/// returned.
/// 3. If a default value has been provided, it will be cached and
/// returned.
/// 4. If none of the previous steps has provided a value, an empty
/// string will be returned.
///
/// In Go projects, I've used the global config to great success.
/// However, callers may set up an explicit configuration instance.
class SimpleConfig {
public:
#if defined(SCSL_DESKTOP_BUILD)
/// \brief Load key-value pairs from a file.
///
/// \note This operates on the global config.
///
/// \param path The path to a config file.
/// \return 0 if the file was loaded successfully.
static int LoadGlobal(const char *path);
/// \brief Load key-value pairs from a file.
///
/// \note This operates on the global config.
///
/// \param path The path to a config file.
/// \return 0 if the file was loaded successfully.
static int LoadGlobal(std::string &path);
/// \brief Set the prefix in use by the config.
///
/// \note This operates on the global config.
///
/// \param prefix The prefix to prepend to the key when looking
/// up values from the environment.
static void SetPrefixGlobal(const std::string &prefix);
/// \brief Return the keys cached in the config.
///
/// Note that this won't returned any non-cached environment
/// values.
///
/// \note This operates on the global config.
///
/// \return A list of keys stored under the config.
static std::vector<std::string> KeyListGlobal();
/// \brief Get the value stored for the key from the config.
///
/// \note This operates on the global config.
///
/// \param key The key to look up. See the class documentation
/// for information on how this is used.
/// \return The value stored under the key, or an empty string.
static std::string GetGlobal(std::string &key);
/// \brief Get the value stored for the key from the config.
///
/// \note This operates on the global config.
///
/// \param key The key to look up. See the class documentation
/// for information on how this is used.
/// \return The value stored under the key, or an empty string.
static std::string GetGlobal(const char *key);
/// \brief Get the value stored for the key from the config.
///
/// \note This operates on the global config.
///
/// \param key The key to look up. See the class documentation
/// for information on how this is used.
/// \param defaultValue A default value to cache and use if no
/// value is stored under the key.
/// \return The value stored under the key, or the default
/// value.
static std::string GetGlobal(const char *key, const std::string &defaultValue);
/// \brief Get the value stored for the key from the config.
///
/// \note This operates on the global config.
///
/// \param key The key to look up. See the class documentation
/// for information on how this is used.
/// \param defaultValue A default value to cache and use if no
/// value is stored under the key.
/// \return The value stored under the key, or the default
/// value.
static std::string GetGlobal(std::string &key, const std::string &defaultValue);
/// \brief Set a configuration value. This will override any
/// value set.
static void PutGlobal(std::string &key, const std::string &value);
/// \brief Set a configuration value. This will override any
/// value set.
static void PutGlobal(const char *key, const std::string &value);
#endif
/// \brief The constructor doesn't need any initialisation.
SimpleConfig();
/// \brief The constructor can explicitly set the environment
/// prefix.
explicit SimpleConfig(std::string &prefix);
/// \brief Load key-value pairs from a file.
///
/// \param path The path to a config file.
/// \return 0 if the file was loaded successfully.
int Load(const char *path);
/// \brief Load key-value pairs from a file.
///
/// \param path The path to a config file.
/// \return 0 if the file was loaded successfully.
int Load(std::string& path);
/// \brief Set the prefix in use by the config.
///
/// \param prefix The prefix to prepend to the key when looking
/// up values from the environment.
void SetPrefix(const std::string &prefix);
/// \brief Return the keys cached in the config.
///
/// Note that this won't returned any non-cached environment
/// values.
///
/// \return A list of keys stored under the config.
std::vector<std::string> KeyList();
/// \brief Get the value stored for the key from the config.
///
/// \param key The key to look up. See the class documentation
/// for information on how this is used.
/// \return The value stored under the key, or an empty string.
std::string Get(std::string &key);
/// \brief Get the value stored for the key from the config.
///
/// \param key The key to look up. See the class documentation
/// for information on how this is used.
/// \return The value stored under the key, or an empty string.
std::string Get(const char *key);
/// \brief Get the value stored for the key from the config.
///
/// \param key The key to look up. See the class documentation
/// for information on how this is used.
/// \param defaultValue A default value to cache and use if no
/// value is stored under the key.
/// \return The value stored under the key, or the default
/// value.
std::string Get(std::string &key, std::string defaultValue);
/// \brief Get the value stored for the key from the config.
///
/// \param key The key to look up. See the class documentation
/// for information on how this is used.
/// \param defaultValue A default value to cache and use if no
/// value is stored under the key.
/// \return The value stored under the key, or the default
/// value.
std::string Get(const char *key, std::string defaultValue);
/// \brief Set a configuration value. This will override any
/// value set.
///
/// \param key The key to look up. See the class documentation
/// for information on how this is used.
/// \param value The value to set.
void Put(std::string &key, const std::string value);
/// \brief Set a configuration value. This will override any
/// value set.
///
/// \param key The key to look up. See the class documentation
/// for information on how this is used.
/// \param value The value to set.
void Put(const char *key, const std::string value);
private:
std::string envPrefix;
std::map<std::string, std::string> vars;
};
} // namespace scsl
#endif //SCSL_SIMPLECONFIG_H

View File

@ -1,10 +1,9 @@
///
/// \file StringUtil.h
/// \author kyle (kyle@midgard)
/// \created 2023-10-14
/// \brief StringUtil contains string utilities.
/// \file include/scsl/StringUtil.h
/// \author K. Isom <kyle@imap.cc>
/// \date 2023-10-14
/// \brief Utilities for working with strings.
///
/// \section COPYRIGHT
/// Copyright 2023 K. Isom <kyle@imap.cc>
///
/// Permission to use, copy, modify, and/or distribute this software for
@ -33,34 +32,31 @@
namespace scsl {
/// namespace U contains utilities.
namespace U {
/// namespace S contains string-related functions.
namespace S {
/// String-related utility functions.
namespace scstring {
/// Remove any whitespace at the beginning of the string. The string
/// 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
/// 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
/// 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
/// 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
/// 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
/// 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);
@ -85,22 +81,48 @@ 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.
/// \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);
std::vector<std::string> SplitN(std::string s, std::string delimiter, size_t maxCount=0);
//std::vector<std::string> SplitN(std::string, char delimiter, size_t 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);
/// Return a string represention of a string vector in the form [size]{"foo", "bar", ...}.
/// 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 string
} // namespace scsl

View File

@ -1,5 +1,5 @@
///
/// \file TLV.h
/// \file include/scsl/TLV.h
/// \author K. Isom <kyle@imap.cc>
/// \date 2023-10-06
/// \brief TLV.h implements basic tag-length-value records.
@ -14,15 +14,18 @@
#ifndef KIMODEM_TLV_H
#define KIMODEM_TLV_H
#include <cstdint>
#include <array>
#include <cstdint>
#include "Arena.h"
namespace scsl {
/// \brief Tag-length-value record tooling
namespace TLV {
#ifndef TLV_MAX_LEN
static constexpr size_t TLV_MAX_LEN = 253;
#endif
@ -30,7 +33,7 @@ static constexpr size_t TLV_MAX_LEN = 253;
static constexpr uint8_t TAG_EMPTY = 0;
/// Record describes a tag-length-value record.
/// \brief Tag-length-value record with single byte tags and lengths.
///
/// 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.
@ -45,7 +48,7 @@ struct Record {
uint8_t Val[TLV_MAX_LEN];
};
/// WriteToMemory writes the TLV record into the arena at the location pointed
/// WriteToMemory writes the TLV record into the arena At the location pointed
/// to in the arena.
///
/// \param arena The backing memory store.
@ -85,7 +88,7 @@ void DeleteRecord(Arena &arena, uint8_t *cursor);
///
/// \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.
/// 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.
@ -96,7 +99,7 @@ uint8_t *FindTag(Arena &arena, uint8_t *cursor, Record &rec);
///
/// \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.
/// 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.
@ -109,7 +112,7 @@ uint8_t *LocateTag(Arena &arena, uint8_t *cursor, Record &rec);
///
/// \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.
/// 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);

View File

@ -1,10 +1,13 @@
///
/// \file scsl.h
/// \author kyle
/// \created 2023-10-10
/// \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
@ -25,12 +28,15 @@
#define SCSL_SCSL_H
#include <klib/Arena.h>
#include <klib/Buffer.h>
#include <klib/Dictionary.h>
#include <klib/Exceptions.h>
#include <klib/TLV.h>
#include <klib/Test.h>
#include <scsl/Arena.h>
#include <scsl/Buffer.h>
#include <scsl/Commander.h>
#include <scsl/Dictionary.h>
#include <scsl/Exceptions.h>
#include <scsl/Flags.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.
@ -49,7 +55,7 @@ namespace scsl {
///
/// 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 of
/// 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
@ -66,6 +72,12 @@ namespace scsl {
/// working on a graphical editor. For this, I needed some data structures to
/// manage memory in the editor. Thus, Buffer was born.
///
/// \subsection finally Finally
///
/// I'd been writing Go professionally for a while, but C was my first love. I
/// recently started a job that is mostly in C++, and the best way for me to
/// learn is to build a bunch of stuff with it. So, I took a bunch of micro-
/// controller stuff I'd been writing and started building out some other stuff.
}

60
include/sctest/Assert.h Normal file
View File

@ -0,0 +1,60 @@
///
/// \file Assert.h
/// \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.
///
#ifndef SCSL_TEST_H
#define SCSL_TEST_H
#include <string>
namespace sctest {
/// Assert is a variant on the assert macro. This variant is intended to be
/// a drop-in replacement for the cassert macro: even in release mode, the tests
/// should still run.
///
/// If NDEBUG is set, Assert will throw an exception if condition is false.
/// Otherwise, it calls assert after printing the message.
///
/// \param condition If true, Assert throws an exception.
void Assert(bool condition);
/// Assert is a variant on the assert macro.
///
/// If NDEBUG is set, Assert will throw an exception if condition is false.
/// Otherwise, it calls assert after printing the message.
///
/// In addition to NDEBUG, SCSL_NOEXCEPT will suppress assertions.
///
/// \throws AssertionFailed
///
/// \param condition The condition to assert.
/// \param message The message that should be displayed if condition is false.
void Assert(bool condition, std::string message);
} // namespace scsl
#endif //SCSL_TEST_H

54
include/sctest/Checks.h Executable file
View File

@ -0,0 +1,54 @@
///
/// \file Checks.h
/// \author K. Isom <kyle@imap.cc>
/// \date 2017-06-05
/// \brief Provides a number of utility macros for testing.
///
/// 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.
///
#ifndef SCTEST_CHECKS_H
#define SCTEST_CHECKS_H
#include <scmp/Math.h>
namespace sctest {
// The following checks are designed as shortcuts that return false on
// if some condition isn't met.
#define SCTEST_CHECK(x) if (!(x)) { return false; }
#define SCTEST_CHECK_FALSE(x) if ((x)) { return false; }
#define SCTEST_CHECK_EQ(x, y) if ((x) != (y)) { return false; }
#define SCTEST_CHECK_NE(x, y) if ((x) == (y)) { return false; }
#define SCTEST_CHECK_GEQ(x, y) if ((x) < (y)) { return false; }
#define SCTEST_CHECK_LEQ(x, y) if ((x) > (y)) { return false; }
#define SCTEST_CHECK_FEQ(x, y) { float eps; scmp::DefaultEpsilon(eps); if (!scmp::WithinTolerance((x), (y), eps)) { return false; }}
#define SCTEST_CHECK_DEQ(x, y) { double eps; scmp::DefaultEpsilon(eps); if (!scmp::WithinTolerance((x), (y), eps)) { return false; }}
#define SCTEST_CHECK_FEQ_EPS(x, y, eps) { if (!scmp::WithinTolerance<float>((x), (y), (eps))) { return false; }}
#define SCTEST_CHECK_FNE_EPS(x, y, eps) { if (scmp::WithinTolerance<float>((x), (y), (eps))) { return false; }}
#define SCTEST_CHECK_DEQ_EPS(x, y, eps) { if (!scmp::WithinTolerance<double>((x), (y), (eps))) { return false; }}
#define SCTEST_CHECK_DNE_EPS(x, y, eps) { if (scmp::WithinTolerance<double>((x), (y), (eps))) { return false; }}
} // namespace sctest
#endif

View File

@ -0,0 +1,70 @@
///
/// \file include/sctest/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 sctest {
/// \brief 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 sctest
#endif // SCSL_EXCEPTIONS_H

104
include/sctest/Report.h Executable file
View File

@ -0,0 +1,104 @@
///
/// \file include/sctest/Report.h
/// \author K. Isom <kyle@imap.cc>
/// \date 2017-06-05
/// \brief Unit test reporting class.
///
/// 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.
///
#ifndef SCTEST_REPORT_H
#define SCTEST_REPORT_H
#include <chrono>
namespace sctest {
/// \brief A Report holds test run results.
///
/// This is designed to work with SimpleSuite, but might be useful
/// for other things.
class Report {
public:
/// \brief Construct a new Report, zeroed out.
Report();
/// \brief Failing returns the count of failed tests.
///
/// \details If a test is run and expected to pass, but fails,
/// it is marked as failed. If a test is expected to
/// fail, but passes, it is marked as failed.
///
/// \return The number of tests that failed.
size_t Failing() const;
/// \brief The number of tests that have passed successfully.
size_t Passing() const;
/// \brief The number of tests registered.
size_t Total() const;
/// \brief Report a test as having failed.
void Failed();
/// \brief Report a test as having passed.
void Passed();
/// \brief Register more tests in the report.
///
/// This is used to track the total number of tests in the
/// report.
void AddTest(size_t testCount = 0);
/// \brief Reset the internal state.
///
/// All fields in the Report will be zeroed out.
///
/// \param testCount
void Reset(size_t testCount = 0);
/// \brief Mark the start of test runs.
///
/// This is used for tracking how long the tests took to complete.
void Start();
/// \brief Mark the end of test runs.
///
/// This is used for tracking how long the tests took to complete.
void End();
/// \brief Retrieve how long the tests took to run.
///
/// This only makes sense to run after called to Start and End.
///
/// \return The number of milliseconds that have elapsed.
std::chrono::duration<double, std::milli>
Elapsed() const;
private:
size_t failing;
size_t passed;
size_t total;
std::chrono::time_point<std::chrono::steady_clock> start;
std::chrono::time_point<std::chrono::steady_clock> end;
};
std::ostream &operator<<(std::ostream &os, const Report &report);
} // end namespace sctest
#endif

127
include/sctest/SimpleSuite.h Executable file
View File

@ -0,0 +1,127 @@
///
/// \file include/sctest/SimpleSuite.h
/// \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.
///
#ifndef SCTEST_SIMPLESUITE_H
#define SCTEST_SIMPLESUITE_H
#include <functional>
#include <string>
#include <vector>
#include <sctest/Report.h>
namespace sctest {
/// \brief UnitTest describes a single unit test.
///
/// It is a predicate: did the test pass?
struct UnitTest {
/// What name should be shown when running tests?
std::string name;
/// This is the test function to be run.
std::function<bool()> test;
/// This is the value the test returns if it passes.
bool expect;
};
/// \brief SimpleSuite is a test-running harness for simple tests.
///
/// A simple test is defined as a test that takes no arguments and
/// returns a boolean status where true indicates the test has passed.
class SimpleSuite {
public:
SimpleSuite();
/// \brief Silence suppresses output.
void Silence();
/// \brief Define a suite setup function.
///
/// If present, this setup function is called At the start of
/// the Run method, before tests are run. It should be a
/// predicate: if it returns false, tests automatically fail.
void Setup(std::function<bool(void)> setupFn) { fnSetup = setupFn; }
/// \brief Define a teardown function.
///
/// If present, this teardown function is called At the end of
/// the Run method, after all tests have run.
void Teardown(std::function<bool(void)> teardownFn) { fnTeardown = teardownFn; }
/// \brief Register a new simple test.
///
/// \param label The text that will identify test when
/// running.
/// \param test This test should return true if the test has
/// passed.
void AddTest(std::string label, std::function<bool(void)> test);
/// \brief Register a test that is expected to return false.
///
/// \param label The text that will identify test when
/// running.
/// \param test This test should return false if the test has
/// passed.
void AddFailingTest(std::string label, std::function<bool(void)> test);
/// \brief Run all the registered tests.
///
/// \return True if all tests have passed.
bool Run();
/// Reporting methods.
/// \brief Reset clears the report statistics.
///
/// Reset will preserve the setup and teardown functions, just
/// resetting the suite's internal state.
void Reset();
/// \brief Returns true if Run has been called.
bool HasRun() const;
/// \brief Retrieve the test run results.
///
/// The results will only be valid if Run has been called.
Report GetReport();
private:
bool quiet;
std::function<bool(void)> fnSetup, fnTeardown;
std::vector<UnitTest> tests;
// Report functions.
Report report;
bool hasRun; // Have the tests been run yet?
bool hasPassed;
};
std::ostream& operator<<(std::ostream& os, SimpleSuite &suite);
} // namespace sctest
#endif

37
include/sctest/sctest.h Normal file
View File

@ -0,0 +1,37 @@
///
/// \file include/sctest/sctest.h
/// \author K. Isom <kyle@imap.cc>
/// \date 2023-10-20
/// \brief Shimmering Clarity testing code.
///
/// 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 <sctest/Assert.h>
#include <sctest/Checks.h>
#include <sctest/Debug.h>
#include <sctest/Exceptions.h>
#include <sctest/Report.h>
#include <sctest/SimpleSuite.h>
#ifndef SCSL_SCTEST_H
#define SCSL_SCTEST_H
/// \brief Shimmering Clarity testing library.
namespace sctest {}
#endif // SCSL_SCTEST_H

View File

@ -1,2 +1,3 @@
set(SCSL_INCLUDE_DIRS include/scsl)
set(SCSL_LIBRARIES libscsl.a)
set(SCSL_LIBRARIES libscsl.a)

View File

@ -1,56 +1,76 @@
//
// Created by kyle on 2023-10-10.
//
///
/// \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 "Arena.h"
#include "Commander.h"
#include "Dictionary.h"
#include <scsl/Arena.h>
#include <scsl/Commander.h>
#include <scsl/Dictionary.h>
#include <scsl/Flags.h>
using namespace scsl;
static const char *defaultPhonebook = "pb.dat";
static char *pbFile = (char *)defaultPhonebook;
static std::string pbFile(defaultPhonebook);
static Arena arena;
static Dictionary pb(arena);
static bool
listFiles(int argc, char **argv)
listFiles(std::vector<std::string> argv)
{
(void)argc; (void)argv;
(void) argv; // provided for interface compatibility.
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]));
static bool
newPhonebook(std::vector<std::string> argv)
{
auto size = std::stoul(argv[0]);
cout << "[+] create new " << size << "B phonebook '" << pbFile << "'\n";
return arena.Create(pbFile, size) == 0;
return arena.Create(pbFile.c_str(), size) == 0;
}
static bool
delKey(int argc, char **argv)
delKey(std::vector<std::string> 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)
hasKey(std::vector<std::string> argv)
{
(void)argc;
string key = argv[0];
cout << "[+] looking up '" << key << "': ";
@ -60,13 +80,13 @@ hasKey(int argc, char **argv)
}
cout << "not found\n";
return true;
return false;
}
static bool
getKey(int argc, char **argv)
getKey(std::vector<std::string> argv)
{
(void)argc;
TLV::Record rec;
auto key = string(argv[0]);
@ -82,9 +102,8 @@ getKey(int argc, char **argv)
static bool
putKey(int argc, char **argv)
putKey(std::vector<std::string> argv)
{
(void)argc;
auto key = string(argv[0]);
auto val = string(argv[1]);
@ -98,6 +117,7 @@ putKey(int argc, char **argv)
return true;
}
static void
usage(ostream &os, int exc)
{
@ -118,28 +138,36 @@ usage(ostream &os, int exc)
int
main(int argc, char *argv[])
{
int optind = 1;
int retc = 1;
bool help = false;
std::string fileName(pbFile);
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;
}
auto *flags = new scsl::Flags("phonebook",
"A tool for interacting with Arena-backed dictionary files.");
flags->Register("-f", pbFile, "path to a phonebook file");
flags->Register("-h", false, "print a help message");
usage(cerr, 1);
auto parsed = flags->Parse(argc, argv);
if (parsed != scsl::Flags::ParseStatus::OK) {
std::cerr << "Failed to parse flags: "
<< scsl::Flags::ParseStatusToString(parsed) << "\n";
exit(1);
}
if (argc <= 1) {
usage(cout, 0);
flags->GetString("-f", fileName);
flags->GetBool("-h", help);
pbFile = fileName;
if (help) {
usage(std::cout, 0);
}
if (flags->NumArgs() == 0) {
usage(std::cerr, 1);
}
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));
@ -147,18 +175,24 @@ main(int argc, char *argv[])
commander.Register(Subcommand("get", 1, getKey));
commander.Register(Subcommand("put", 2, putKey));
auto command = flags->Arg(0);
if (command != "new") {
cout << "[+] loading phonebook from " << pbFile << "\n";
if (arena.Open(pbFile) != 0) {
if (arena.Open(pbFile.c_str()) != 0) {
cerr << "Failed to open " << pbFile << "\n";
exit(1);
}
}
auto result = commander.Run(command, argc-optind, argv+optind);
auto args = flags->Args();
args.erase(args.begin());
auto result = commander.Run(command, args);
delete flags;
switch (result) {
case Subcommand::Status::OK:
std::cout << "[+] OK\n";
retc = 0;
break;
case Subcommand::Status::NotEnoughArgs:
usage(cerr, 1);
@ -173,4 +207,6 @@ main(int argc, char *argv[])
default:
abort();
}
return retc;
}

230
src/scmp/Coord2D.cc Executable file
View File

@ -0,0 +1,230 @@
///
/// \file include/scmp/geom/Coord2D.h
/// \author K. Isom <kyle@imap.cc>
/// \date 2017-06-05
/// \brief 2D point and polar coordinate systems.
///
/// 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 <cmath>
#include <iostream>
#include <vector>
#include <scmp/Math.h>
#include <scmp/geom/Coord2D.h>
#include <scmp/geom/Orientation.h>
#include <scmp/geom/Vector.h>
namespace scmp {
namespace geom {
//
// Point2D
Point2D::Point2D() : Vector<int, 2>{0, 0}
{
};
Point2D::Point2D(int _x, int _y) : Vector<int, 2>{_x, _y}
{}
Point2D::Point2D(const Polar2D &pol)
: Vector<int, 2>{static_cast<int>(std::rint(std::cos(pol.Theta()) * pol.R())),
static_cast<int>(std::rint(std::sin(pol.Theta()) * pol.R()))}
{}
int
Point2D::X() const
{
return this->At(BasisX);
}
void
Point2D::X(int _x)
{
this->Set(BasisX, _x);
}
int
Point2D::Y() const
{
return this->At(BasisY);
}
void
Point2D::Y(int _y)
{
this->Set(BasisY, _y);
}
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(this->X()) + ", " + std::to_string(this->Y()) + ")";
}
void
Point2D::ToPolar(Polar2D &pol)
{
pol.R(std::sqrt(this->X() * this->X() + this->Y() * this->Y()));
pol.Theta(std::atan2(this->Y(), this->X()));
}
void
Point2D::Rotate(Point2D &pt, double theta)
{
Polar2D pol(*this);
pol.Rotate(pol, theta);
pol.ToPoint(pt);
}
void
Point2D::Translate(const Point2D &origin, Point2D &translated)
{
translated.X(origin.X() + this->X());
translated.Y(origin.Y() + this->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) const
{
auto dx = other.X() - this->X();
auto dy = other.Y() - this->Y();
return static_cast<int>(std::rint(std::sqrt(dx * dx + dy * dy)));
}
// Polar2D
Polar2D::Polar2D() : Vector<double, 2>{0.0, 0.0} {};
Polar2D::Polar2D(double _r, double _theta) : Vector<double, 2>{_r, _theta}
{}
Polar2D::Polar2D(const Point2D &point)
: Vector<double, 2>{std::sqrt((point.X() * point.X()) + (point.Y() * point.Y())),
std::atan2(point.Y(), point.X())}
{}
double
Polar2D::R() const
{
return this->At(0);
}
void
Polar2D::R(const double _r)
{
this->Set(0, _r);
}
double
Polar2D::Theta() const
{
return this->At(1);
}
void
Polar2D::Theta(const double _theta)
{
this->Set(1, _theta);
}
void
Polar2D::ToPoint(Point2D &point)
{
point.Y(static_cast<int>(std::rint(std::sin(this->Theta()) * this->R())));
point.X(static_cast<int>(std::rint(std::cos(this->Theta()) * this->R())));
}
std::string
Polar2D::ToString()
{
return "(" + std::to_string(this->R()) +
", " + std::to_string(this->Theta()) + ")";
}
void
Polar2D::Rotate(Polar2D &rotated, double delta)
{
rotated.R(this->R());
rotated.Theta(RotateRadians(this->Theta(), delta));
}
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 scmp

187
src/scmp/Math.cc Normal file
View File

@ -0,0 +1,187 @@
///
/// \file Math.cc
/// \author K. Isom <kyle@imap.cc>
/// \date 2020-02-26
/// \brief Mathematical convience functions.
///
/// Copyright 2020 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 <functional>
#include <numeric>
#include <random>
#include <vector>
#include <scmp/Math.h>
namespace scmp {
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<>());
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;
}
static constexpr double Epsilon_double = 0.0001;
static constexpr float Epsilon_float = 0.0001;
void
DefaultEpsilon(double& epsilon)
{
epsilon = Epsilon_double;
}
void
DefaultEpsilon(float& epsilon)
{
epsilon = Epsilon_float;
}
void
DefaultEpsilon(int& epsilon)
{
epsilon = 0;
}
size_t
ISqrt(size_t n)
{
if (n < 2) {
return n;
}
size_t start = 0;
size_t end = n / 2;
size_t result = 0;
while (start <= end) {
auto middle = (start + end) >> 1;
result = middle * middle;
if (result == n) {
return middle;
}
if (result < n) {
start = middle + 1;
result = middle;
} else {
end = middle - 1;
result = middle;
}
}
return result;
}
} // namespace scmp

63
src/scmp/Orientation.cc Normal file
View File

@ -0,0 +1,63 @@
///
/// \file src/scmp/geom/Orientation.cc
/// \author K. Isom <kyle@imap.cc>
/// \date 2017-06-05
/// \brief Orientation of vectors w.r.t. a reference plane, assumed to
/// be the Earth.
///
/// 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 <scmp/geom/Vector.h>
#include <scmp/geom/Orientation.h>
namespace scmp {
namespace geom {
float
Heading2F(Vector2F vec)
{
return vec.Angle(Basis2F[BasisX]);
}
float
Heading3F(Vector3F vec)
{
const Vector2F vec2f {vec.At(BasisX), vec.At(BasisY)};
return Heading2F(vec2f);
}
double
Heading2D(Vector2D vec)
{
return vec.Angle(Basis2D[BasisX]);
}
double
Heading3D(Vector3D vec)
{
const Vector2D vec2d {vec.At(BasisX), vec.At(BasisY)};
return Heading2D(vec2d);
}
} // namespace geom
} // namespace scmp

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

@ -0,0 +1,91 @@
#include <iostream>
#include <scmp/geom/Quaternion.h>
namespace scmp {
namespace geom {
Quaternionf
MakeQuaternion(Vector3F axis, float angle)
{
return Quaternionf(axis.UnitVector() * std::sin(angle / 2.0),
std::cos(angle / 2.0));
}
Quaterniond
MakeQuaternion(Vector3D axis, double angle)
{
return Quaterniond(axis.UnitVector() * std::sin(angle / 2.0),
std::cos(angle / 2.0));
}
Quaternionf
FloatQuaternionFromEuler(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
DoubleQuaternionFromEuler(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
QuaternionSelfTest()
{
#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 = MakeQuaternion(yAxis, angle);
Quaternionf q;
Vector3F vr {0.0, 0.0, 1.0};
p.SetEpsilon(0.0001);
assert(p.IsUnitQuaternion());
assert(p.Rotate(v) == vr);
assert(p * q == p);
#endif
}
} // namespace geom
} // namespace scmp

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

@ -0,0 +1,290 @@
///
/// \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 <sys/mman.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <ios>
#include <scsl/Arena.h>
#define PROT_RW (PROT_WRITE|PROT_READ)
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];
this->Clear();
return 0;
}
int
Arena::MemoryMap(int memFileDes, size_t memSize)
{
if (this->size > 0) {
this->Destroy();
}
this->arenaType = ArenaType::MemoryMapped;
this->size = memSize;
this->store = static_cast<uint8_t *>(mmap(nullptr, memSize, PROT_RW, MAP_SHARED,
memFileDes, 0));
if (static_cast<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();
}
this->fd = open(path, O_RDWR);
if (this->fd == -1) {
return -1;
}
if (stat(path, &st) != 0) {
return -1;
}
return this->MemoryMap(this->fd, static_cast<size_t>(st.st_size));
}
int
Arena::Create(const char *path, size_t fileSize)
{
int ret = -1;
if (this->size > 0) {
this->Destroy();
}
auto *fHandle = fopen(path, "w");
if (fHandle != nullptr) {
auto newFileDes = fileno(fHandle);
if (ftruncate(newFileDes, static_cast<off_t>(fileSize)) == 0) {
ret = this->Open(path);
}
close(newFileDes);
fclose(fHandle);
}
return ret;
}
bool
Arena::CursorInArena(const uint8_t *cursor)
{
if (cursor == nullptr) {
return false;
}
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;
case ArenaType::MemoryMapped:
if (munmap(this->store, this->size) == -1) {
abort();
return;
}
if (close(this->fd) == -1) {
abort();
}
this->fd = 0;
break;
default:
#if defined(NDEBUG)
return;
#else
abort();
#endif
}
this->arenaType = ArenaType::Uninit;
this->size = 0;
this->store = nullptr;
}
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;
case ArenaType::MemoryMapped:
os << "mmap/file";
break;
default:
os << "unknown (this is a bug)";
}
os << ">@0x";
os << std::hex << static_cast<void *>(&arena);
os << std::dec;
os << ",store<" << arena.Size() << "B>@";
os << std::hex << cursorString;
os << std::dec;
return os;
}
int
Arena::Write(const char *path)
{
int retc = -1;
FILE *arenaFile = fopen(path, "w");
if (arenaFile == nullptr) {
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

View File

@ -2,16 +2,32 @@
/// \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 <iomanip>
#include "Buffer.h"
#include <scsl/Buffer.h>
namespace scsl {
@ -67,13 +83,46 @@ Buffer::Buffer(const char *data)
}
Buffer::Buffer(const std::string s)
Buffer::Buffer(const std::string& s)
: contents(nullptr), length(0), capacity(0), autoTrim(true)
{
this->Append(s);
}
Buffer::~Buffer()
{
this->Reclaim();
}
uint8_t *
Buffer::Contents() const
{
return this->contents;
}
std::string
Buffer::ToString() const
{
return std::string((const char *)(this->contents));
}
size_t
Buffer::Length() const
{
return this->length;
}
size_t
Buffer::Capacity() const
{
return this->capacity;
}
bool
Buffer::Append(const char *s)
{
@ -84,7 +133,7 @@ Buffer::Append(const char *s)
bool
Buffer::Append(const std::string s)
Buffer::Append(const std::string &s)
{
return this->Append((const uint8_t *) s.c_str(), s.size());
}
@ -93,15 +142,30 @@ Buffer::Append(const std::string s)
bool
Buffer::Append(const uint8_t *data, const size_t datalen)
{
if (datalen == 0) {
return false;
}
auto resized = false;
auto newCap = this->mustGrow(datalen);
if (newCap > 0) {
this->Resize(newCap);
resized = true;
} else if (this->contents == nullptr) {
this->Resize(this->capacity);
resized = true;
}
// If newCap is > 0, memory will be allocated for this->
// contents, and if contents was null, then Resize should force
// it to be allocated. Still, a little defensive coding never
// hurt.
assert(this->contents != nullptr);
if (this->contents == nullptr) {
return false;
}
memcpy(this->contents + this->length, data, datalen);
this->length += datalen;
return resized;
@ -125,7 +189,7 @@ Buffer::Insert(const size_t index, const char *s)
bool
Buffer::Insert(const size_t index, const std::string s)
Buffer::Insert(const size_t index, const std::string &s)
{
return this->Insert(index, (const uint8_t *) s.c_str(), s.size());
}
@ -176,7 +240,13 @@ Buffer::Resize(size_t newCapacity)
auto newContents = new uint8_t[newCapacity];
memset(newContents, 0, newCapacity);
if (this->length > 0) {
// Defensive coding check.
if ((this->length > 0) && (this->contents == nullptr)) {
abort();
}
if (this->length > 0 && this->contents != nullptr) {
memcpy(newContents, this->contents, this->length);
}
@ -203,6 +273,28 @@ Buffer::Trim()
return 0;
}
void
Buffer::DisableAutoTrim()
{
this->autoTrim = false;
}
void
Buffer::EnableAutoTrim()
{
this->autoTrim = true;
}
bool
Buffer::AutoTrimIsEnabled()
{
return this->autoTrim;
}
void
Buffer::Clear()
{
@ -291,7 +383,11 @@ Buffer::shiftRight(size_t offset, size_t delta)
resized = true;
}
if (this->length == 0) return 0;
if (this->length < offset) {
for (size_t i = this->length; i < offset; i++) {
this->contents[i] = ' ';
}
}
memmove(this->contents + (offset + delta), this->contents + offset,

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(std::vector<std::string> args)
{
auto argc = args.size();
if (argc < this->requiredArgs) {
std::cerr << "[!] " << this->command << " expects ";
std::cerr << this->requiredArgs << " args, but was given ";
std::cerr << argc << " args.\n";
return Subcommand::Status::NotEnoughArgs;
}
if (this->fn(args)) {
return Subcommand::Status::OK;
}
return Subcommand::Status::Failed;
}
Commander::Commander()
{
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, std::vector<std::string> args)
{
if (this->cmap.count(command) != 1) {
return Subcommand::Status::CommandNotRegistered;
}
auto scmd = this->cmap[command];
return scmd->Run(std::move(args));
}
} // namespace scsl

View File

@ -1,6 +1,30 @@
#include <cstring>
///
/// \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 "Dictionary.h"
#include <cstring>
#include <scsl/Dictionary.h>
#if defined(SCSL_DESKTOP_BUILD)
#include <iostream>
@ -20,16 +44,13 @@ Dictionary::Lookup(const char *key, uint8_t klen, TLV::Record &res)
if ((klen == res.Len) &&
(memcmp(res.Val, key, klen) == 0)) {
TLV::ReadFromMemory(res, cursor);
if (res.Tag != this->vTag) {
abort();
}
return true;
assert(res.Tag == this->vTag);
return res.Tag == this->vTag;
}
cursor = TLV::FindTag(this->arena, cursor, res);
}
return false;
}
@ -137,6 +158,10 @@ operator<<(std::ostream &os, const Dictionary &dictionary)
uint8_t *cursor = (dictionary.arena).Start();
TLV::Record rec;
if (cursor == nullptr) {
return os;
}
TLV::ReadFromMemory(rec, cursor);
if (rec.Tag == TLV::TAG_EMPTY) {
os << "\t(NONE)" << std::endl;
@ -147,7 +172,11 @@ operator<<(std::ostream &os, const Dictionary &dictionary)
os << "\t" << rec.Val << "->";
cursor = TLV::SkipRecord(rec, cursor);
TLV::ReadFromMemory(rec, cursor);
os << rec.Val << std::endl;
if (cursor == nullptr) {
os << "*** CORRUPT DICTIONARY ***\n";
break;
}
os << rec.Val << "\n";
cursor = TLV::SkipRecord(rec, cursor);
TLV::ReadFromMemory(rec, cursor);
}

View File

@ -1,11 +1,9 @@
///
/// \file Flag.h
/// \author kyle
/// \created 2023-10-12
/// \file src/sl/Flags.cc
/// \author K. Isom <kyle@imap.cc>
/// \date 2023-10-12
/// \brief Flag defines a command-line flag parser.
///
/// \section COPYRIGHT
///
/// Copyright 2023 K. Isom <kyle@imap.cc>
///
/// Permission to use, copy, modify, and/or distribute this software for
@ -22,25 +20,24 @@
/// PERFORMANCE OF THIS SOFTWARE.
///
#include <cstdint>
#include <functional>
#include <map>
#include <cassert>
#include <iostream>
#include <regex>
#include <vector>
#include <utility>
#include "Flag.h"
#include "StringUtil.h"
#include <scsl/Flags.h>
#include <scsl/StringUtil.h>
#include <vector>
namespace scsl {
static const std::regex isFlag("^--?[a-zA-Z0-9][a-zA-Z0-9-_]*$",
std::regex_constants::nosubs);
static const std::regex isFlag("^--?[a-zA-Z0-9][a-zA-Z0-9-_]*$",
std::regex_constants::nosubs | std::regex_constants::optimize);
std::string
ParseStatusToString(ParseStatus status)
Flags::ParseStatusToString(ParseStatus status)
{
switch (status) {
case ParseStatus::OK:
@ -58,29 +55,39 @@ ParseStatusToString(ParseStatus status)
Flag *
NewFlag(FlagType fType, std::string fName, std::string fDescription)
NewFlag(std::string fName, FlagType fType, std::string fDescription)
{
auto flag = new Flag;
auto *flag = new Flag;
flag->Type = fType;
flag->WasSet = false;
flag->Name = fName;
flag->Description = fDescription;
flag->Value = FlagValue{};
flag->Type = fType;
flag->WasSet = false;
flag->Name = std::move(fName);
flag->Description = std::move(fDescription);
flag->Value = FlagValue{};
return flag;
}
Flags::Flags(std::string fName)
: name(fName), description("")
: name(std::move(fName))
{}
Flags::Flags(std::string fName, std::string fDescription)
: name(std::move(fName)), description(std::move(fDescription))
{
}
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;
}
}
@ -95,7 +102,8 @@ Flags::Register(std::string fName, FlagType fType, std::string fDescription)
return false;
}
auto flag = NewFlag(fType, fName, fDescription);
auto *flag = NewFlag(fName, fType, std::move(fDescription));
assert(flag != nullptr);
this->flags[fName] = flag;
return true;
}
@ -104,7 +112,7 @@ Flags::Register(std::string fName, FlagType fType, std::string fDescription)
bool
Flags::Register(std::string fName, bool defaultValue, std::string fDescription)
{
if (!this->Register(fName, FlagType::Boolean, fDescription)) {
if (!this->Register(fName, FlagType::Boolean, std::move(fDescription))) {
return false;
}
@ -116,7 +124,7 @@ Flags::Register(std::string fName, bool defaultValue, std::string fDescription)
bool
Flags::Register(std::string fName, int defaultValue, std::string fDescription)
{
if (!this->Register(fName, FlagType::Integer, fDescription)) {
if (!this->Register(fName, FlagType::Integer, std::move(fDescription))) {
return false;
}
@ -128,9 +136,11 @@ Flags::Register(std::string fName, int defaultValue, std::string fDescription)
bool
Flags::Register(std::string fName, unsigned int defaultValue, std::string fDescription)
{
if (!this->Register(fName, FlagType::UnsignedInteger, fDescription)) {
if (!this->Register(fName, FlagType::UnsignedInteger, std::move(fDescription))) {
return false;
}
assert(this->flags.count(fName) != 0);
assert(this->flags[fName] != nullptr);
this->flags[fName]->Value.u = defaultValue;
return true;
@ -140,7 +150,7 @@ Flags::Register(std::string fName, unsigned int defaultValue, std::string fDescr
bool
Flags::Register(std::string fName, size_t defaultValue, std::string fDescription)
{
if (!this->Register(fName, FlagType::SizeT, fDescription)) {
if (!this->Register(fName, FlagType::SizeT, std::move(fDescription))) {
return false;
}
@ -152,7 +162,19 @@ Flags::Register(std::string fName, size_t defaultValue, std::string fDescription
bool
Flags::Register(std::string fName, std::string defaultValue, std::string fDescription)
{
if (!this->Register(fName, FlagType::String, fDescription)) {
if (!this->Register(fName, FlagType::String, std::move(fDescription))) {
return false;
}
this->flags[fName]->Value.s = new std::string(std::move(defaultValue));
return true;
}
bool
Flags::Register(std::string fName, const char *defaultValue, std::string fDescription)
{
if (!this->Register(fName, FlagType::String, std::move(fDescription))) {
return false;
}
@ -182,7 +204,7 @@ Flags::Lookup(std::string fName)
bool
Flags::ValueOf(std::string fName, FlagValue &value)
{
if (this->flags.count(fName)) {
if (this->flags.count(fName) != 0U) {
return false;
}
@ -191,43 +213,48 @@ Flags::ValueOf(std::string fName, FlagValue &value)
}
ParseStatus
Flags::ParseStatus
Flags::parseArg(int argc, char **argv, int &index)
{
std::string arg(argv[index]);
std::string arg(argv[index]);
scstring::TrimWhitespace(arg);
U::S::TrimWhitespace(arg);
index++;
if (!std::regex_search(arg, isFlag)) {
return ParseStatus::EndOfFlags;
}
index++;
if (this->flags.count(arg) == 0) {
if (arg == "-h" || arg == "--help") {
Usage(std::cout, 0);
}
return ParseStatus::NotRegistered;
}
auto flag = flags[arg];
auto *flag = flags[arg];
if ((flag->Type != FlagType::Boolean) && index == argc) {
return ParseStatus::NotEnoughArgs;
}
switch (flag->Type) {
case FlagType::Boolean:
flag->WasSet = true;
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);
flag->WasSet = true;
flag->Value.i = std::stoi(argv[++index], nullptr, 0);
return ParseStatus::OK;
case FlagType::UnsignedInteger:
flag->WasSet = true;
flag->Value.u = static_cast<unsigned int>(std::stoi(argv[index++], 0, 0));
flag->WasSet = true;
flag->Value.u = static_cast<unsigned int>(std::stoi(argv[index++], nullptr, 0));
return ParseStatus::OK;
case FlagType::String:
flag->WasSet = true;
flag->WasSet = true;
flag->Value.s = new std::string(argv[index++]);
return ParseStatus::OK;
case FlagType::SizeT:
flag->WasSet = true;
flag->WasSet = true;
flag->Value.size = static_cast<size_t>(std::stoull(argv[index++]));
return ParseStatus::OK;
default:
@ -242,10 +269,13 @@ Flags::parseArg(int argc, char **argv, int &index)
}
ParseStatus
Flags::Parse(int argc, char **argv)
Flags::ParseStatus
Flags::Parse(int argc, char **argv, bool skipFirst)
{
int index = 1;
int index = 0;
if (skipFirst) {
index = 1;
}
while (index != argc) {
auto result = this->parseArg(argc, argv, index);
@ -255,7 +285,7 @@ Flags::Parse(int argc, char **argv)
case ParseStatus::EndOfFlags:
while (index < argc) {
this->args.push_back(std::string(argv[index]));
this->args.emplace_back(argv[index]);
index++;
}
continue;
@ -270,13 +300,79 @@ Flags::Parse(int argc, char **argv)
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;
scstring::WriteTabIndented(os, this->description, 72 - indent,
indent / 8, false);
os << "\n\n";
for (const auto &pair: this->flags) {
auto argDesc = pair.second->Description;
if (!argDesc.empty()) {
argDesc += ' ';
}
auto argLine = "\t" + pair.first;
switch (pair.second->Type) {
case FlagType::Boolean:
argLine += "\t\t";
argDesc += "(default=false)";
break;
case FlagType::Integer:
argLine += " int\t\t";
if (pair.second->Value.i != 0) {
argDesc += "(default=" + std::to_string(pair.second->Value.i) + ")";
}
break;
case FlagType::UnsignedInteger:
argLine += " uint\t\t";
if (pair.second->Value.u != 0) {
argDesc += "(default=" + std::to_string(pair.second->Value.u) + ")";
}
break;
case FlagType::SizeT:
argLine += " size_t\t";
if (pair.second->Value.size != 0) {
argDesc += "(default=" + std::to_string(pair.second->Value.size) + ")";
}
break;
case FlagType::String:
argLine += " string\t";
if (pair.second->Value.s != nullptr && !(pair.second->Value.s->empty())) {
argDesc += "(default=" + *(pair.second->Value.s) + ")";
}
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();
scstring::WriteTabIndented(os, argDesc, 72 - indent,
(indent / 8) + 2, false);
}
os << "\n";
exit(exitCode);
}
size_t
Flags::NumArgs()
{
@ -291,14 +387,25 @@ Flags::Args()
}
Flag *
Flags::checkGetArg(std::string fName, FlagType eType)
std::string
Flags::Arg(size_t i)
{
if (this->flags[fName] == 0) {
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] == nullptr) {
return nullptr;
}
auto flag = this->flags[fName];
auto *flag = this->flags[fName];
if (flag == nullptr) {
return nullptr;
}
@ -314,7 +421,7 @@ Flags::checkGetArg(std::string fName, FlagType eType)
bool
Flags::GetBool(std::string fName, bool &flagValue)
{
auto flag = this->checkGetArg(fName, FlagType::Boolean);
auto *flag = this->checkGetArg(fName, FlagType::Boolean);
flagValue = flag->Value.b;
return flag->WasSet;
@ -324,7 +431,7 @@ Flags::GetBool(std::string fName, bool &flagValue)
bool
Flags::GetInteger(std::string fName, int &flagValue)
{
auto flag = this->checkGetArg(fName, FlagType::Integer);
auto *flag = this->checkGetArg(fName, FlagType::Integer);
flagValue = flag->Value.i;
return flag->WasSet;
@ -334,18 +441,17 @@ Flags::GetInteger(std::string fName, int &flagValue)
bool
Flags::GetUnsignedInteger(std::string fName, unsigned int &flagValue)
{
auto flag = this->checkGetArg(fName, FlagType::UnsignedInteger);
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);
auto *flag = this->checkGetArg(fName, FlagType::SizeT);
flagValue = flag->Value.size;
return flag->WasSet;
@ -355,7 +461,7 @@ Flags::GetSizeT(std::string fName, std::size_t &flagValue)
bool
Flags::GetString(std::string fName, std::string &flagValue)
{
auto flag = this->checkGetArg(fName, FlagType::String);
auto *flag = this->checkGetArg(fName, FlagType::String);
if (flag->Value.s == nullptr) {
return false;
@ -366,5 +472,4 @@ Flags::GetString(std::string fName, std::string &flagValue)
}
} // namespace scsl
}// namespace scsl

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

@ -0,0 +1,74 @@
#include <iostream>
#include <string>
#include <vector>
#include <scmp/Math.h>
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]);
}
}
}

246
src/sl/SimpleConfig.cc Normal file
View File

@ -0,0 +1,246 @@
///
/// \file src/sl/SimpleConfig.cc
/// \author K. Isom <kyle@imap.cc>
/// \date 2023-10-21
/// \brief Simple project configuration.
///
/// This is an implementation of a simple global configuration system
/// for projects based on a Go version I've used successfully in
/// several projects.
///
/// 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 <cstdlib>
#include <fstream>
#include <regex>
#include <scsl/SimpleConfig.h>
#include <scsl/StringUtil.h>
namespace scsl {
#if defined(SCSL_DESKTOP_BUILD)
static SimpleConfig globalConfig;
#endif
static constexpr auto regexOpts = std::regex_constants::nosubs |
std::regex_constants::optimize |
std::regex_constants::ECMAScript;
static const std::regex commentLine("^\\s*#.*$", regexOpts);
static const std::regex keyValueLine("^\\s*\\w+\\s*=\\s*\\w+", regexOpts);
int
SimpleConfig::LoadGlobal(const char *path)
{
return globalConfig.Load(path);
}
int
SimpleConfig::LoadGlobal(std::string &path)
{
return globalConfig.Load(path);
}
void
SimpleConfig::SetPrefixGlobal(const std::string &prefix)
{
globalConfig.SetPrefix(prefix);
}
std::vector<std::string>
SimpleConfig::KeyListGlobal()
{
return globalConfig.KeyList();
}
std::string
SimpleConfig::GetGlobal(std::string &key)
{
return globalConfig.Get(key);
}
std::string
SimpleConfig::GetGlobal(const char *key)
{
return globalConfig.Get(key);
}
std::string
SimpleConfig::GetGlobal(std::string &key, const std::string &defaultValue)
{
return globalConfig.Get(key, defaultValue);
}
std::string
SimpleConfig::GetGlobal(const char *key, const std::string &defaultValue)
{
return globalConfig.Get(key, defaultValue);
}
void
SimpleConfig::PutGlobal(std::string &key, const std::string &value)
{
return globalConfig.Put(key, value);
}
void
SimpleConfig::PutGlobal(const char *key, const std::string &value)
{
return globalConfig.Put(key, value);
}
SimpleConfig::SimpleConfig()
{
}
SimpleConfig::SimpleConfig(std::string &prefix)
: envPrefix(prefix)
{
}
int
SimpleConfig::Load(const char *path)
{
auto spath = std::string(path);
return this->Load(spath);
}
int
SimpleConfig::Load(std::string &path)
{
std::ifstream configFile(path);
std::string line;
while (std::getline(configFile, line)) {
scstring::TrimWhitespace(line);
if (line.size() == 0) {
continue;
}
if (std::regex_search(line, commentLine)) {
return -1;
}
if (std::regex_search(line, keyValueLine)) {
auto pair = scstring::SplitKeyValuePair(line, "=");
if (pair.size() < 2) {
return -1;
}
this->vars[pair[0]] = pair[1];
}
}
return 0;
}
void
SimpleConfig::SetPrefix(const std::string &prefix)
{
this->envPrefix = std::move(prefix);
}
std::string
SimpleConfig::Get(std::string &key)
{
return this->Get(key, "");
}
std::string
SimpleConfig::Get(const char *key)
{
auto keyStr = std::string(key);
return this->Get(keyStr, "");
}
std::string
SimpleConfig::Get(std::string &key, std::string defaultValue)
{
if (this->vars.count(key)) {
return this->vars[key];
}
auto envKey = this->envPrefix + key;
const char *envValue = getenv(envKey.c_str());
if (envValue != nullptr) {
this->vars[key] = std::string(envValue);
return this->Get(key);
}
this->vars[key] = std::move(defaultValue);
return this->Get(key);
}
std::string
SimpleConfig::Get(const char *key, std::string defaultValue)
{
auto keyStr = std::string(key);
return this->Get(keyStr, std::move(defaultValue));
}
void
SimpleConfig::Put(std::string &key, const std::string value)
{
this->vars[key] = std::move(value);
}
void
SimpleConfig::Put(const char *key, const std::string value)
{
auto keyStr = std::string(key);
this->vars[std::move(keyStr)] = std::move(value);
}
std::vector<std::string>
SimpleConfig::KeyList()
{
std::vector<std::string> keyList;
std::transform(this->vars.begin(), this->vars.end(), std::back_inserter(keyList),
[](std::pair<std::string, std::string> pair){return pair.first;});
return keyList;
}
} // namespace SimpleConfig

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

@ -0,0 +1,232 @@
///
/// \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 scstring {
std::vector<std::string>
SplitKeyValuePair(std::string line, std::string delimiter)
{
auto pair = SplitN(std::move(line), std::move(delimiter), 2);
if (pair.empty()) {
return {"", ""};
}
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(std::move(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) == 0;
}));
}
void
TrimTrailingWhitespace(std::string &s)
{
s.erase(std::find_if(s.rbegin(), s.rend(), [](unsigned char ch) {
return std::isspace(ch) == 0;
}).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.empty() && 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.empty()) {
continue;
}
if ((wLine.size() + word.size() + 1) > lineLength) {
wrapped.push_back(wLine);
wLine.clear();
}
if (!wLine.empty()) {
wLine += " ";
}
wLine += word;
}
if (!wLine.empty()) {
wrapped.push_back(wLine);
}
return wrapped;
}
void
WriteTabIndented(std::ostream &os, std::vector<std::string> lines,
int tabStop, bool indentFirst)
{
std::string const 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, std::move(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 string
} // namespace scsl

View File

@ -1,10 +1,37 @@
///
/// \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 "TLV.h"
#include <scsl/TLV.h>
using namespace scsl;
#define REC_SIZE(x) ((std::size_t)x.Len + 2)
/// 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 {
@ -74,6 +101,10 @@ SetRecord(Record &rec, uint8_t tag, uint8_t len, const char *val)
void
ReadFromMemory(Record &rec, uint8_t *cursor)
{
assert(cursor != nullptr);
if (cursor == nullptr) {
return;
}
rec.Tag = cursor[0];
rec.Len = cursor[1];
memcpy(rec.Val, cursor + 2, rec.Len);
@ -93,6 +124,10 @@ FindTag(Arena &arena, uint8_t *cursor, Record &rec)
cursor = SkipRecord(rec, cursor);
}
if (!arena.CursorInArena(cursor)) {
cursor = nullptr;
}
return cursor;
}
@ -100,13 +135,20 @@ FindTag(Arena &arena, uint8_t *cursor, Record &rec)
uint8_t *
LocateTag(Arena &arena, uint8_t *cursor, Record &rec)
{
uint8_t tag, len;
uint8_t tag = TAG_EMPTY;
uint8_t len;
if (cursor == nullptr) {
cursor = arena.Start();
}
while ((tag = cursor[0]) != rec.Tag) {
if (!arena.CursorInArena(cursor)) {
cursor = arena.Start();
}
while (arena.CursorInArena(cursor) &&
((tag = cursor[0]) != rec.Tag)) {
assert(arena.CursorInArena(cursor));
len = cursor[1];
if (!spaceAvailable(arena, cursor, len)) {
return nullptr;
@ -115,6 +157,10 @@ LocateTag(Arena &arena, uint8_t *cursor, Record &rec)
cursor += 2;
}
if (!arena.CursorInArena(cursor)) {
return nullptr;
}
if (tag != rec.Tag) {
return nullptr;
}
@ -154,7 +200,7 @@ DeleteRecord(Arena &arena, uint8_t *cursor)
return;
}
uint8_t len = cursor[1] + 2;
uint8_t len = cursor[1] + 2;
uint8_t *stop = arena.Start() + arena.Size();
stop -= len;

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

@ -0,0 +1,70 @@
///
/// \file src/sctest/Assert.cc
/// \author K. Isom <kyle@imap.cc>
/// \date 2023-10-09
/// \brief Assertion tooling useful 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 "sctest/Exceptions.h"
#include <sctest/Assert.h>
#include <cassert>
#include <iostream>
#include <sstream>
namespace sctest {
void
Assert(bool condition, std::string message)
{
#if defined(NDEBUG) || defined(SCSL_NOEXCEPT)
if (!condition) {
throw AssertionFailed(std::move(message));
}
#else
if (!condition) {
std::cerr << message << std::endl;
}
assert(condition);
#endif
}
void
Assert(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 sctest

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

@ -0,0 +1,39 @@
///
/// \file src/test/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 <sctest/Exceptions.h>
namespace sctest {
AssertionFailed::AssertionFailed(std::string message) : msg(std::move(message)) {}
const char *
AssertionFailed::what() const throw()
{
return const_cast<char *>(this->msg.c_str());
}
} // namespace sctest

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

@ -0,0 +1,137 @@
///
/// \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 <iomanip>
#include <ostream>
#include <sctest/Report.h>
namespace sctest {
Report::Report()
{
this->Reset(0);
}
size_t
Report::Failing() const
{
return this->failing;
}
size_t
Report::Passing() const
{
return this->passed;
}
size_t
Report::Total() const
{
return this->total;
}
void
Report::Failed()
{
this->failing++;
}
void
Report::Passed()
{
this->passed++;
}
void
Report::AddTest(size_t testCount)
{
this->total += testCount;
}
void
Report::Reset(size_t testCount)
{
auto now = std::chrono::steady_clock::now();
this->total = testCount;
this->passed = 0;
this->failing = 0;
this->Start();
this->end = now;
}
void
Report::Start()
{
this->start = std::chrono::steady_clock::now();
}
void
Report::End()
{
this->end = std::chrono::steady_clock::now();
}
std::chrono::duration<double, std::milli>
Report::Elapsed() const
{
return this->end - this->start;
}
std::ostream&
operator<<(std::ostream &os, const Report &report)
{
auto elapsed = report.Elapsed();
os << report.Passing() << "/"
<< report.Total() << " tests passed in "
<< std::setw(3) << elapsed.count() << "ms";
auto failed = report.Failing();
if (failed > 0) {
os << " (" << failed << " tests failed)";
}
return os;
}
} // end namespace sctest

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

@ -0,0 +1,149 @@
///
/// \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 <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)
{
this->Reset();
}
void
SimpleSuite::Silence()
{
// Silence will fall.
quiet = true;
}
void
SimpleSuite::AddTest(std::string name, std::function<bool()> test)
{
const UnitTest test_case = {std::move(name), std::move(test), true};
tests.push_back(test_case);
}
void
SimpleSuite::AddFailingTest(std::string name, std::function<bool()> test)
{
const UnitTest test_case = {std::move(name), std::move(test), false};
tests.push_back(test_case);
}
bool
SimpleSuite::Run()
{
report.Reset(this->tests.size());
unless(quiet) { std::cout << "Setting up the tests.\n"; }
unless(fnSetup()) { return false; }
this->hasRun = true;
this->hasPassed = true;
for (size_t i = 0; i < this->report.Total() && this->hasPassed; i++) {
const UnitTest testCase = this->tests.at(i);
unless(quiet) {
std::cout << "[" << i + 1 << "/"
<< this -> report.Total()
<< "] Running test "
<< testCase.name << ": ";
}
this->hasPassed = (testCase.test() == testCase.expect);
if (this->hasPassed) {
report.Passed();
} else {
report.Failed();
}
if (quiet) { continue; }
if (this->hasPassed) {
std::cout << "[PASS]";
} else {
std::cout << "[FAIL]";
}
std::cout << "\n";
}
unless(quiet) { std::cout << "Tearing down the tests.\n"; }
unless(fnTeardown()) { return false; }
report.End();
return this->hasPassed;
}
void
SimpleSuite::Reset()
{
this->report.Reset(0);
this->hasRun = false;
this->hasPassed = false;
}
bool
SimpleSuite::HasRun() const
{
return this->hasRun;
}
Report
SimpleSuite::GetReport()
{
return report;
}
std::ostream &
operator<<(std::ostream &os, SimpleSuite &suite)
{
if (suite.HasRun()) {
os << "OK: " << suite.GetReport();
} else {
os << "Test suite hasn't run.";
}
return os;
}
} // end namespace sctest

View File

@ -1,119 +0,0 @@
///
/// \file stringutil_test.cc
/// \author kyle
/// \created 10/14/23
/// \brief Ensure the stringutil functions work.
///
/// \section COPYRIGHT
/// Copyright 2023 K. Isom <kyle@imap.cc>
///
/// Permission to use, copy, modify, and/or distribute this software for
/// any purpose with or without fee is hereby granted, provided that the
/// above copyright notice and this permission notice appear in all copies.
///
/// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
/// WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
/// WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR
/// BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES
/// OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
/// WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
/// ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
/// SOFTWARE.
///
#include <iostream>
#include <sstream>
#include "StringUtil.h"
#include "Test.h"
using namespace scsl;
static void
TestTrimming(std::string line, std::string lExpected, std::string rExpected, std::string expected)
{
std::string result;
std::string message;
result = U::S::TrimLeadingWhitespaceDup(line);
message = "TrimLeadingDup(\"" + line + "\"): '" + result + "'";
TestAssert(result == lExpected, message);
result = U::S::TrimTrailingWhitespaceDup(line);
message = "TrimTrailingDup(\"" + line + "\"): '" + result + "'";
TestAssert(result == rExpected, message);
result = U::S::TrimWhitespaceDup(line);
message = "TrimDup(\"" + line + "\"): '" + result + "'";
TestAssert(result == expected, message);
result = line;
U::S::TrimLeadingWhitespace(result);
message = "TrimLeadingDup(\"" + line + "\"): '" + result + "'";
TestAssert(result == lExpected, message);
result = line;
U::S::TrimTrailingWhitespace(result);
message = "TrimTrailingDup(\"" + line + "\"): '" + result + "'";
TestAssert(result == rExpected, message);
result = line;
U::S::TrimWhitespace(result);
message = "TrimDup(\"" + line + "\"): '" + result + "'";
TestAssert(result == expected, message);
}
static std::string
vec2string(std::vector<std::string> v)
{
std::stringstream ss;
ss << "(";
ss << v.size();
ss << ")";
ss << "{";
for (size_t i = 0; i < v.size(); i++) {
if (i > 0) ss << ", ";
ss << v[i];
}
ss << "}";
return ss.str();
}
static void
TestSplit(std::string line, std::string delim, size_t maxCount, std::vector<std::string> expected)
{
std::cout << "test split\n";
std::cout << "\t line: \"" << line << "\"\n";
std::cout << "\t delim: \"" << delim << "\"\n";
std::cout << "\t count: " << maxCount << "\n";
std::cout << "\texpect: " << vec2string(expected) << "\n";
auto result = U::S::SplitN(line, delim, maxCount);
std::cout << "\tresult: " << U::S::VectorToString(result) << "\n";
TestAssert(result == expected, U::S::VectorToString(result));
std::cout << "OK!\n";
}
int
main()
{
TestTrimming(" foo\t ", "foo\t ", " foo", "foo");
TestTrimming(" foo\tbar ", "foo\tbar ", " foo\tbar", "foo\tbar");
TestSplit("abc:def:ghij:klm", ":", 0,
std::vector<std::string>{"abc", "def", "ghij", "klm"});
TestSplit("abc:def:ghij:klm", ":", 3,
std::vector<std::string>{"abc", "def", "ghij:klm"});
TestSplit("abc:def:ghij:klm", ":", 2,
std::vector<std::string>{"abc", "def:ghij:klm"});
TestSplit("abc:def:ghij:klm", ":", 1,
std::vector<std::string>{"abc:def:ghij:klm"});
TestSplit("abc::def:ghi", ":", 0,
std::vector<std::string>{"abc", "", "def", "ghi"});
}

163
test/buffer.cc Normal file
View File

@ -0,0 +1,163 @@
///
/// \file test/buffer.cc
/// \author K.Isom <kyle@imap.cc>
/// \date 2017-06-05
/// \brief Unit tests on the scsl::Buffer class.
///
/// \section COPYRIGHT
///
/// Copyright 2023 K. Isom <kyle@imap.cc>
///
/// Permission to use, copy, modify, and/or distribute this software for
/// any purpose with or without fee is hereby granted, provided that
/// the above copyright notice and this permission notice appear in all /// copies.
///
/// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
/// WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
/// WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
/// AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
/// DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA
/// OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
/// TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
/// PERFORMANCE OF THIS SOFTWARE.
///
#include <iostream>
#include <scsl/Buffer.h>
#include <scsl/Flags.h>
#include <sctest/Checks.h>
#include <sctest/SimpleSuite.h>
using namespace scsl;
bool
bufferTest()
{
Buffer buffer("hlo, world");
Buffer helloWorld("hello, world!");
Buffer goodbyeWorld("goodbye, world");
Buffer goodbyeCruelWorld("goodbye, cruel world");
buffer.Insert(1, (uint8_t *) "el", 2);
SCTEST_CHECK_EQ(buffer.Length(), 12);
buffer.Append('!');
SCTEST_CHECK_EQ(buffer, helloWorld);
buffer.Remove(buffer.Length() - 1);
SCTEST_CHECK_EQ(buffer.Length(), 12);
buffer.Remove(0, 5);
buffer.Insert(0, 'g');
buffer.Insert(1, (uint8_t *) "oodbye", 6);
SCTEST_CHECK_EQ(buffer, goodbyeWorld);
buffer.Insert(9, (uint8_t *)"cruel ", 6);
buffer.Reclaim();
SCTEST_CHECK_EQ(buffer.Length(), 0);
SCTEST_CHECK_EQ(buffer.Capacity(), 0);
buffer.Append("and now for something completely different...");
Buffer buffer2("and now for something completely different...");
SCTEST_CHECK_EQ(buffer, buffer2);
buffer2.Remove(buffer2.Length()-3, 3);
SCTEST_CHECK_NE(buffer, buffer2);
return true;
}
bool
testBufferTrimming()
{
const std::string contents = "and now for something completely different...";
Buffer buffer(contents);
buffer.Resize(128);
SCTEST_CHECK_EQ(buffer.Capacity(), 128);
buffer.Trim();
SCTEST_CHECK_EQ(buffer.Capacity(), 64);
buffer.DisableAutoTrim();
buffer.Resize(128);
SCTEST_CHECK_EQ(buffer.Capacity(), 128);
buffer.Remove(buffer.Length() - 1);
SCTEST_CHECK_EQ(buffer.Capacity(), 128);
buffer.Append('.');
SCTEST_CHECK_EQ(buffer.Capacity(), 128);
buffer.EnableAutoTrim();
buffer.Remove(buffer.Length() - 1);
SCTEST_CHECK_EQ(buffer.Capacity(), 64);
return true;
}
bool
testInserts()
{
const std::string contents = "and now for something completely different...";
const std::string expected1 = " and now for something completely different...";
const std::string hello = "hello";
const std::string world = "world";
const std::string helloWorld = "hello world";
Buffer buffer(64);
// Insert shouldn't resize the buffer.
SCTEST_CHECK_FALSE(buffer.Insert(5, contents));
SCTEST_CHECK_EQ(buffer.ToString(), expected1);
buffer.Clear();
SCTEST_CHECK_EQ(buffer.Length(), 0);
buffer.Append(hello);
SCTEST_CHECK_EQ(buffer.ToString(), hello);
buffer.Insert(7, world);
SCTEST_CHECK_EQ(buffer.ToString(), helloWorld);
return true;
}
int
main(int argc, char *argv[])
{
auto noReport = false;
auto quiet = false;
auto flags = new scsl::Flags("test_buffer",
"This test validates the Buffer class.");
flags->Register("-n", false, "don't print the report");
flags->Register("-q", false, "suppress test output");
auto parsed = flags->Parse(argc, argv);
if (parsed != scsl::Flags::ParseStatus::OK) {
std::cerr << "Failed to parse flags: "
<< scsl::Flags::ParseStatusToString(parsed) << "\n";
exit(1);
}
sctest::SimpleSuite suite;
flags->GetBool("-n", noReport);
flags->GetBool("-q", quiet);
if (quiet) {
suite.Silence();
}
suite.AddTest("bufferTest", bufferTest);
suite.AddTest("trimTest", testBufferTrimming);
suite.AddTest("insertTest", testInserts);
delete flags;
auto result = suite.Run();
if (!noReport) { std::cout << suite.GetReport() << "\n"; }
return result ? 0 : 1;
}

81
test/config-explorer.cc Normal file
View File

@ -0,0 +1,81 @@
///
/// \file test/config-explorer.cc
/// \author K. Isom <kyle@imap.cc>
/// \date 2023-10-21
/// \brief Commandline tools for interacting with simple configurations.
///
/// 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>
#include <scsl/Flags.h>
#include <scsl/SimpleConfig.h>
int
main(int argc, char *argv[])
{
int retc = 1;
bool listKeys;
std::string fileName;
std::string prefix;
std::string defaultValue;
auto *flags = new scsl::Flags("config-explorer",
"interact with a simple configuration system");
flags->Register("-d", "", "set a default value");
flags->Register("-f", scsl::FlagType::String, "path to a configuration file");
flags->Register("-l", false, "list cached keys at the end");
flags->Register("-p", "CX_",
"prefix for configuration environment variables");
auto parsed = flags->Parse(argc, argv);
if (parsed != scsl::Flags::ParseStatus::OK) {
std::cerr << "Failed to parse flags: "
<< scsl::Flags::ParseStatusToString(parsed) << "\n";
exit(1);
}
flags->GetString("-d", defaultValue);
flags->GetString("-f", fileName);
flags->GetBool("-l", listKeys);
flags->GetString("-p", prefix);
scsl::SimpleConfig::SetPrefixGlobal(prefix);
if (!fileName.empty()) {
if (scsl::SimpleConfig::LoadGlobal(fileName) != 0) {
std::cerr << "[!] failed to load " << fileName << "\n";
return retc;
}
}
for (auto &key : flags->Args()) {
auto val = scsl::SimpleConfig::GetGlobal(key, defaultValue);
std::cout << key << ": " << val << "\n";
}
if (listKeys) {
std::cout << "[+] cached keys\n";
for (auto &key : scsl::SimpleConfig::KeyListGlobal()) {
std::cout << "\t- " << key << "\n";
}
}
delete flags;
return retc;
}

295
test/coord2d.cc Executable file
View File

@ -0,0 +1,295 @@
///
/// \file test/coord2d.cc
/// \author K.Isom <kyle@imap.cc>
/// \date 2017-06-05
/// \brief Unit tests on 2D geometry code.
///
/// \section COPYRIGHT
///
/// 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 <array>
#include <iostream>
#include <vector>
#include <scsl/Flags.h>
#include <scmp/Math.h>
#include <scmp/geom/Coord2D.h>
#include <sctest/Checks.h>
#include <sctest/SimpleSuite.h>
using namespace scmp::geom;
using namespace sctest;
namespace {
#define CHECK_ROTATE(theta, expected) if (!scmp::WithinTolerance(scmp::RotateRadians((double)theta, 0), (double)expected, (double)0.0001)) { \
std::cerr << "Expected " << theta << " to wrap to " << expected << "\n"; \
std::cerr << " have " << scmp::RotateRadians(theta, 0) << "\n"; \
return false; \
}
bool
geomValidateAngularRotation()
{
CHECK_ROTATE(0, 0);
CHECK_ROTATE(M_PI/4, M_PI/4);
CHECK_ROTATE(M_PI/2, M_PI/2);
CHECK_ROTATE(3 * M_PI / 4, 3 * M_PI / 4);
CHECK_ROTATE(M_PI, M_PI);
CHECK_ROTATE(5 * M_PI / 4, -3 * M_PI / 4);
CHECK_ROTATE(3 * M_PI / 2, -(M_PI / 2));
CHECK_ROTATE(7 * M_PI / 4, -(M_PI / 4));
CHECK_ROTATE(4 * M_PI, 0)
return true;
}
bool
geomConversionIdentities()
{
const std::array<Point2D,4> points = {
Point2D(1, 0),
Point2D(0, 1),
Point2D(-1, 0),
Point2D(0, -1)
};
const std::array<Polar2D,4> polars = {
Polar2D(1, 0),
Polar2D(1, scmp::DegreesToRadiansD(90)),
Polar2D(1, scmp::DegreesToRadiansD(180)),
Polar2D(1, scmp::DegreesToRadiansD(-90)),
};
for (auto i = 0; i < 4; i++) {
const Polar2D pol(points.at(i));
if (pol != polars.at(i)) {
std::cerr << "! measured value outside tolerance ("
<< i << ")\n";
std::cerr << " " << points.at(i) << "" << pol
<< "" << polars.at(i) << "\n";
return false;
}
const Point2D point(pol);
SCTEST_CHECK(point == points.at(i));
}
Point2D point(3, 5);
Point2D point2;
Polar2D polar;
Polar2D polar2;
point.ToPolar(polar);
polar.ToPoint(point2);
SCTEST_CHECK_EQ(point, point2);
point2.ToPolar(polar2);
SCTEST_CHECK_EQ(polar, polar2);
return true;
}
bool
geomVerifyBasicProperties()
{
const Point2D pt1(1, 1);
const Point2D pt2(2, 2);
const Point2D pt3(3, 3);
SCTEST_CHECK((pt1 + pt2) == pt3);
SCTEST_CHECK((pt3 - pt2) == pt1);
// commutative
SCTEST_CHECK((pt1 + pt2) == (pt2 + pt1));
SCTEST_CHECK((pt1 + pt3) == (pt3 + pt1));
SCTEST_CHECK((pt2 + pt3) == (pt3 + pt2));
// associative
SCTEST_CHECK(((pt1 + pt2) + pt3) == (pt1 + (pt2 + pt3)));
// transitive
const Point2D pt4(1, 1);
const Point2D pt5(1, 1);
SCTEST_CHECK(pt1 == pt4);
SCTEST_CHECK(pt4 == pt5);
SCTEST_CHECK(pt1 == pt5);
// scaling
const Point2D pt6(2, 3);
const Point2D pt7(8, 12);
SCTEST_CHECK((pt6 * 4) == pt7);
return true;
}
bool
geomComparePoint2D()
{
const Point2D pt1(1, 1);
const Point2D pt2(1, 1);
const Point2D pt3(0, 1);
SCTEST_CHECK(pt1 == pt2);
SCTEST_CHECK_FALSE(pt2 == pt3);
return true;
}
bool
geomRotatePoint2D()
{
std::array<Point2D, 4> vertices = {
Point2D(1, 0), // θ = 0
Point2D(0, 1), // θ = π/2
Point2D(-1, 0), // θ = π
Point2D(0, -1) // θ = 3π/2
};
for (auto i = 0; i < 4; i++) {
auto first = i % 4;
auto expected = (i + 1) % 4;
Point2D vertex;
vertices.at(first).Rotate(vertex, 1.5708);
if (vertex != vertices.at(expected)) {
std::cerr << "expected: " << expected << "\n";
std::cerr << " have: " << vertex << "\n";
return false;
}
}
return true;
}
bool
geomRotatePointsAboutOrigin()
{
Point2D origin(3, 3);
double theta = 0;
const std::vector<Polar2D> vertices {
Polar2D(2, 0),
Polar2D(1.41421, 2.35619),
Polar2D(1.41421, -2.35619)
};
// expected coordinates with no rotation
std::vector<Point2D> rotated0 {
Point2D(5, 3),
Point2D(2, 4),
Point2D(2, 2)
};
auto rotated = origin.Rotate(vertices, theta);
for (auto i = 0; i < 3; i++) {
SCTEST_CHECK(rotated.at(i) == rotated0.at(i));
}
// expected after 90° rotation
theta = scmp::DegreesToRadiansD(90);
std::vector<Point2D> rotated90 {
Point2D(3, 5),
Point2D(2, 2),
Point2D(4, 2)
};
rotated = origin.Rotate(vertices, theta);
for (auto i = 0; i < 3; i++) {
SCTEST_CHECK(rotated.at(i) == rotated90.at(i));
}
// expected after 180° rotation
theta = scmp::DegreesToRadiansD(180);
std::vector<Point2D> rotated180 {
Point2D(1, 3),
Point2D(4, 2),
Point2D(4, 4)
};
rotated = origin.Rotate(vertices, theta);
for (auto i = 0; i < 3; i++) {
SCTEST_CHECK(rotated.at(i) == rotated180.at(i));
}
return true;
}
bool
pointDistances()
{
const Point2D origin;
const Point2D y2(0, 2);
const Point2D x2(2, 0);
const int dist2 = 2;
const int dist10 = 10;
const Point2D deg45{8, 6};
SCTEST_CHECK_EQ(y2.Distance(origin), dist2);
SCTEST_CHECK_EQ(x2.Distance(origin), dist2);
SCTEST_CHECK_EQ(deg45.Distance(origin), dist10);
return true;
};
} // anonymous namespace
int
main(int argc, char *argv[])
{
auto noReport = false;
auto quiet = false;
auto flags = new scsl::Flags("test_orientation",
"This test validates various orientation-related components in scmp.");
flags->Register("-n", false, "don't print the report");
flags->Register("-q", false, "suppress test output");
auto parsed = flags->Parse(argc, argv);
if (parsed != scsl::Flags::ParseStatus::OK) {
std::cerr << "Failed to parse flags: "
<< scsl::Flags::ParseStatusToString(parsed) << "\n";
exit(1);
}
SimpleSuite suite;
flags->GetBool("-n", noReport);
flags->GetBool("-q", quiet);
if (quiet) {
suite.Silence();
}
suite.AddTest("geomValidateAngularRotation", geomValidateAngularRotation);
suite.AddTest("geomConversionIdentities", geomConversionIdentities);
suite.AddTest("geomVerifyBasicProperties", geomVerifyBasicProperties);
suite.AddTest("geomComparePoint2D", geomComparePoint2D);
suite.AddTest("geomRotatePoint2D", geomRotatePoint2D);
suite.AddTest("geomRotatePointsAboutOrigin", geomRotatePointsAboutOrigin);
suite.AddTest("pointDistances", pointDistances);
delete flags;
auto result = suite.Run();
if (!noReport) { std::cout << suite.GetReport() << "\n"; }
return result ? 0 : 1;
}

142
test/dictionary.cc Normal file
View File

@ -0,0 +1,142 @@
///
/// \file test/dictionary.cc
/// \author K.Isom <kyle@imap.cc>
/// \date 2023-10-05
/// \brief Unit tests on the scsl::Dictionary class.
///
/// \section COPYRIGHT
///
/// Copyright 2023 K. Isom <kyle@imap.cc>
///
/// Permission to use, copy, modify, and/or distribute this software for
/// any purpose with or without fee is hereby granted, provided that
/// the above copyright notice and this permission notice appear in all /// copies.
///
/// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
/// WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
/// WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
/// AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
/// DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA
/// OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
/// TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
/// PERFORMANCE OF THIS SOFTWARE.
///
#include <iostream>
#include <scsl/Arena.h>
#include <scsl/Dictionary.h>
#include <scsl/Flags.h>
#include <sctest/SimpleSuite.h>
#include <sctest/Checks.h>
#include "test_fixtures.h"
using namespace scsl;
using namespace sctest;
constexpr char TEST_KVSTR1[] = "foo";
constexpr uint8_t TEST_KVSTRLEN1 = 3;
constexpr char TEST_KVSTR2[] = "baz";
constexpr uint8_t TEST_KVSTRLEN2 = 3;
constexpr char TEST_KVSTR3[] = "quux";
constexpr uint8_t TEST_KVSTRLEN3 = 4;
constexpr char TEST_KVSTR4[] = "spam";
constexpr uint8_t TEST_KVSTRLEN4 = 4;
constexpr char TEST_KVSTR5[] = "xyzzx";
constexpr uint8_t TEST_KVSTRLEN5 = 5;
constexpr char TEST_KVSTR6[] = "corvid";
constexpr uint8_t TEST_KVSTRLEN6 = 6;
static bool
testSetKV(Dictionary &pb, const char *k, uint8_t kl, const char *v,
uint8_t vl)
{
return pb.Set(k, kl, v, vl) == 0;
}
bool
dictionaryTest()
{
Arena arena;
TLV::Record value;
TLV::Record expect;
if (arena.Create(ARENA_FILE, ARENA_SIZE) == -1) {
abort();
}
TLV::SetRecord(expect, DICTIONARY_TAG_VAL, TEST_KVSTRLEN3, TEST_KVSTR3);
Dictionary dict(arena);
SCTEST_CHECK_FALSE(dict.Contains(TEST_KVSTR2, TEST_KVSTRLEN2));
SCTEST_CHECK(testSetKV(dict, TEST_KVSTR1, TEST_KVSTRLEN1, TEST_KVSTR3,
TEST_KVSTRLEN3));
SCTEST_CHECK(testSetKV(dict, TEST_KVSTR2, TEST_KVSTRLEN2, TEST_KVSTR3,
TEST_KVSTRLEN3));
SCTEST_CHECK(dict.Contains(TEST_KVSTR2, TEST_KVSTRLEN2));
SCTEST_CHECK(testSetKV(dict, TEST_KVSTR4, TEST_KVSTRLEN4, TEST_KVSTR5,
TEST_KVSTRLEN5));
SCTEST_CHECK(dict.Lookup(TEST_KVSTR2, TEST_KVSTRLEN2, value));
SCTEST_CHECK(cmpRecord(value, expect));
SCTEST_CHECK(testSetKV(dict, TEST_KVSTR2, TEST_KVSTRLEN2, TEST_KVSTR6,
TEST_KVSTRLEN6));
TLV::SetRecord(expect, DICTIONARY_TAG_VAL, TEST_KVSTRLEN6, TEST_KVSTR6);
SCTEST_CHECK(dict.Lookup(TEST_KVSTR2, TEST_KVSTRLEN2, value));
SCTEST_CHECK(cmpRecord(value, expect));
SCTEST_CHECK(testSetKV(dict, TEST_KVSTR3, TEST_KVSTRLEN3, TEST_KVSTR5,
TEST_KVSTRLEN5));
TLV::SetRecord(expect, DICTIONARY_TAG_VAL, TEST_KVSTRLEN5, TEST_KVSTR5);
SCTEST_CHECK(dict.Lookup(TEST_KVSTR4, TEST_KVSTRLEN4, value));
SCTEST_CHECK(cmpRecord(value, expect));
arena.Write("pb.dat");
arena.Clear();
return true;
}
int
main(int argc, char *argv[])
{
auto noReport = false;
auto quiet = false;
auto flags = new scsl::Flags("test_dictionary",
"This test validates the Dictionary class.");
flags->Register("-n", false, "don't print the report");
flags->Register("-q", false, "suppress test output");
auto parsed = flags->Parse(argc, argv);
if (parsed != scsl::Flags::ParseStatus::OK) {
std::cerr << "Failed to parse flags: "
<< scsl::Flags::ParseStatusToString(parsed) << "\n";
exit(1);
}
sctest::SimpleSuite suite;
flags->GetBool("-n", noReport);
flags->GetBool("-q", quiet);
if (quiet) {
suite.Silence();
}
suite.AddTest("dictionaryTest", dictionaryTest);
delete flags;
auto result = suite.Run();
if (!noReport) { std::cout << suite.GetReport() << "\n"; }
return result ? 0 : 1;
}

84
test/flags.cc Normal file
View File

@ -0,0 +1,84 @@
///
/// \file test/flags.cc
/// \author K.Isom <kyle@imap.cc>
/// \date 2017-06-05
/// \brief Demonstration of the scsl::Flags class.
///
/// \note This isn't a test program, and won't be run as part of a
/// normal ctest run. It's meant to show how flags work in
/// practice.
///
/// \section COPYRIGHT
///
/// 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 <iostream>
#include <scsl/Flags.h>
#include <sctest/Assert.h>
using namespace scsl;
int
main(int argc, char *argv[])
{
bool testFlag = false;
size_t testSize = 0;
unsigned int testUnsigned = 0;
int testInteger = 0;
std::string testString;
auto flags = new Flags("flag_test", "this is a test of the flag functionality. This line is particularly long to make sure the wrapping works.");
flags->Register("-b", FlagType::Boolean, "test boolean");
flags->Register("-s", FlagType::String, "test string");
flags->Register("-u", (unsigned int)42, "test unsigned integer with a long description line. This should trigger multiline text-wrapping.");
flags->Register("-i", -42, "test integer");
flags->Register("-size", FlagType::SizeT, "test size_t");
sctest::Assert(flags->Size() == 5, "flags weren't registered");
auto status = flags->Parse(argc, argv);
if (status != Flags::ParseStatus::OK) {
std::cerr << "failed to parse flags: "
<< Flags::ParseStatusToString(status) << "\n";
exit(1);
}
auto wasSet = flags->GetBool("-b", testFlag);
std::cout << " (bool) test flag was set: " << wasSet << "\n";
std::cout << " (bool) test flag value: " << testFlag << "\n";
wasSet = flags->GetInteger("-i", testInteger);
std::cout << " (int) test flag was set: " << wasSet << "\n";
std::cout << " (int) test flag value: " << testInteger << "\n";
wasSet = flags->GetUnsignedInteger("-u", testUnsigned);
std::cout << " (uint) test flag was set: " << wasSet << "\n";
std::cout << " (uint) test flag value: " << testUnsigned << "\n";
wasSet = flags->GetSizeT("-size", testSize);
std::cout << "(size_t) test flag was set: " << wasSet << "\n";
std::cout << "(size_t) test flag value: " << testSize << "\n";
wasSet = flags->GetString("-s", testString);
std::cout << "(string) test flag was set: " << wasSet << "\n";
std::cout << "(string) test flag value: " << testString << "\n";
delete flags;
return 0;
}

279
test/madgwick.cc Normal file
View File

@ -0,0 +1,279 @@
#include <cmath>
#include <sstream>
#include <scsl/Flags.h>
#include <scmp/geom/Vector.h>
#include <scmp/geom/Quaternion.h>
#include <scmp/Math.h>
#include <scmp/estimation/Madgwick.h>
#include <sctest/Assert.h>
#include <sctest/Checks.h>
#include <sctest/SimpleSuite.h>
using namespace std;
using namespace scmp;
bool
SimpleAngularOrientationFloat()
{
estimation::Madgwickf estimation;
const geom::Vector3F gyro{0.174533, 0.0, 0.0}; // 10° X rotation.
const geom::Quaternionf frame20Deg{0.984808, 0.173648, 0, 0}; // 20° final Orientation.
const float delta = 0.00917; // assume 109 updates per second, as per the paper.
const float twentyDegrees = scmp::DegreesToRadiansF(20.0);
// The paper specifies a minimum of 109 IMU readings to stabilize; for
// two seconds, that means 218 updates.
for (int i = 0; i < 218; i++) {
estimation.UpdateAngularOrientation(gyro, delta);
}
SCTEST_CHECK_EQ(estimation.Orientation(), frame20Deg);
auto euler = estimation.Euler();
SCTEST_CHECK_FEQ_EPS(euler[0], twentyDegrees, 0.01);
SCTEST_CHECK_FEQ_EPS(euler[1], 0.0, 0.01);
SCTEST_CHECK_FEQ_EPS(euler[2], 0.0, 0.01);
return true;
}
bool
SimpleAngularOrientationFloatDefaultDT()
{
estimation::Madgwickf mflt;
const geom::Vector3F gyro{0.174533, 0.0, 0.0}; // 10° X rotation.
const geom::Quaternionf frame20Deg{0.984808, 0.173648, 0, 0}; // 20° final Orientation.
const float delta = 0.00917; // assume 109 updates per second, as per the paper.
const float twentyDegrees = scmp::DegreesToRadiansF(20.0);
mflt.DeltaT(delta);
// The paper specifies a minimum of 109 IMU readings to stabilize; for
// two seconds, that means 218 updates.
for (int i = 0; i < 218; i++) {
mflt.UpdateAngularOrientation(gyro);
}
SCTEST_CHECK_EQ(mflt.Orientation(), frame20Deg);
auto euler = mflt.Euler();
SCTEST_CHECK_FEQ_EPS(euler[0], twentyDegrees, 0.01);
SCTEST_CHECK_FEQ_EPS(euler[1], 0.0, 0.01);
SCTEST_CHECK_FEQ_EPS(euler[2], 0.0, 0.01);
return true;
}
bool
VerifyUpdateWithZeroDeltaTFails()
{
estimation::Madgwickf mflt;
const geom::Vector3F gyro{0.174533, 0.0, 0.0}; // 10° X rotation.
const geom::Quaternionf frame20Deg{0.984808, 0.173648, 0, 0}; // 20° final Orientation.
const float twentyDegrees = scmp::DegreesToRadiansF(20.0);
// The paper specifies a minimum of 109 IMU readings to stabilize; for
// two seconds, that means 218 updates.
for (int i = 0; i < 218; i++) {
mflt.UpdateAngularOrientation(gyro);
}
SCTEST_CHECK_EQ(mflt.Orientation(), frame20Deg);
auto euler = mflt.Euler();
SCTEST_CHECK_FEQ_EPS(euler[0], twentyDegrees, 0.01);
SCTEST_CHECK_FEQ_EPS(euler[1], 0.0, 0.01);
SCTEST_CHECK_FEQ_EPS(euler[2], 0.0, 0.01);
return true;
}
bool
SimpleAngularOrientationDouble()
{
estimation::Madgwickd mflt;
const geom::Vector3D gyro{0.174533, 0.0, 0.0}; // 10° X rotation.
const geom::Quaterniond frame20Deg{0.984808, 0.173648, 0, 0}; // 20° final Orientation.
const double delta = 0.00917; // assume 109 updates per second, as per the paper.
const double twentyDegrees = scmp::DegreesToRadiansD(20.0);
// The paper specifies a minimum of 109 IMU readings to stabilize; for
// two seconds, that means 218 updates.
for (int i = 0; i < 218; i++) {
mflt.UpdateAngularOrientation(gyro, delta);
}
SCTEST_CHECK_EQ(mflt.Orientation(), frame20Deg);
auto euler = mflt.Euler();
SCTEST_CHECK_DEQ_EPS(euler[0], twentyDegrees, 0.01);
SCTEST_CHECK_DEQ_EPS(euler[1], 0.0, 0.01);
SCTEST_CHECK_DEQ_EPS(euler[2], 0.0, 0.01);
return true;
}
bool
SimpleAngularOrientation2InitialVector3f()
{
const geom::Vector3F initialFrame{0, 0, 0};
estimation::Madgwickf mflt(initialFrame);
const geom::Vector3F gyro{0.174533, 0.0, 0.0}; // 10° X rotation.
const geom::Quaternionf frame20Deg{0.984808, 0.173648, 0, 0}; // 20° final Orientation.
const float delta = 0.00917; // assume 109 updates per second, as per the paper.
const float twentyDegrees = scmp::DegreesToRadiansF(20.0);
// The paper specifies a minimum of 109 IMU readings to stabilize; for
// two seconds, that means 218 updates.
for (int i = 0; i < 218; i++) {
mflt.UpdateAngularOrientation(gyro, delta);
}
SCTEST_CHECK_EQ(mflt.Orientation(), frame20Deg);
auto euler = mflt.Euler();
SCTEST_CHECK_FEQ_EPS(euler[0], twentyDegrees, 0.01);
SCTEST_CHECK_FEQ_EPS(euler[1], 0.0, 0.01);
SCTEST_CHECK_FEQ_EPS(euler[2], 0.0, 0.01);
return true;
}
bool
SimpleAngularOrientation2InitialQuaternionf()
{
const auto initialFrame = geom::FloatQuaternionFromEuler({0, 0, 0});
estimation::Madgwickf mflt(initialFrame);
const geom::Vector3F gyro{0.174533, 0.0, 0.0}; // 10° X rotation.
const geom::Quaternionf frame20Deg{0.984808, 0.173648, 0, 0}; // 20° final Orientation.
const float delta = 0.00917; // assume 109 updates per second, as per the paper.
const float twentyDegrees = scmp::DegreesToRadiansF(20.0);
// The paper specifies a minimum of 109 IMU readings to stabilize; for
// two seconds, that means 218 updates.
for (int i = 0; i < 218; i++) {
mflt.UpdateAngularOrientation(gyro, delta);
}
SCTEST_CHECK_EQ(mflt.Orientation(), frame20Deg);
auto euler = mflt.Euler();
SCTEST_CHECK_FEQ_EPS(euler[0], twentyDegrees, 0.01);
SCTEST_CHECK_FEQ_EPS(euler[1], 0.0, 0.01);
SCTEST_CHECK_FEQ_EPS(euler[2], 0.0, 0.01);
return true;
}
bool
SimpleAngularOrientation2InitialVector3d()
{
const geom::Vector3D initialFrame{0, 0, 0};
estimation::Madgwickd mflt(initialFrame);
const geom::Vector3D gyro{0.174533, 0.0, 0.0}; // 10° X rotation.
const geom::Quaterniond frame20Deg{0.984808, 0.173648, 0, 0}; // 20° final Orientation.
const double delta = 0.00917; // assume 109 updates per second, as per the paper.
const double twentyDegrees = scmp::DegreesToRadiansD(20.0);
// The paper specifies a minimum of 109 IMU readings to stabilize; for
// two seconds, that means 218 updates.
for (int i = 0; i < 218; i++) {
mflt.UpdateAngularOrientation(gyro, delta);
}
SCTEST_CHECK_EQ(mflt.Orientation(), frame20Deg);
auto euler = mflt.Euler();
SCTEST_CHECK_DEQ_EPS(euler[0], twentyDegrees, 0.01);
SCTEST_CHECK_DEQ_EPS(euler[1], 0.0, 0.01);
SCTEST_CHECK_DEQ_EPS(euler[2], 0.0, 0.01);
return true;
}
bool
SimpleAngularOrientation2InitialQuaterniond()
{
const auto initialFrame = geom::DoubleQuaternionFromEuler({0, 0, 0});
estimation::Madgwickd mflt(initialFrame);
const geom::Vector3D gyro{0.174533, 0.0, 0.0}; // 10° X rotation.
const geom::Quaterniond frame20Deg{0.984808, 0.173648, 0, 0}; // 20° final Orientation.
const double delta = 0.00917; // assume 109 updates per second, as per the paper.
const double twentyDegrees = scmp::DegreesToRadiansD(20.0);
// The paper specifies a minimum of 109 IMU readings to stabilize; for
// two seconds, that means 218 updates.
for (int i = 0; i < 218; i++) {
mflt.UpdateAngularOrientation(gyro, delta);
}
SCTEST_CHECK_EQ(mflt.Orientation(), frame20Deg);
auto euler = mflt.Euler();
SCTEST_CHECK_DEQ_EPS(euler[0], twentyDegrees, 0.01);
SCTEST_CHECK_DEQ_EPS(euler[1], 0.0, 0.01);
SCTEST_CHECK_DEQ_EPS(euler[2], 0.0, 0.01);
return true;
}
int
main(int argc, char **argv)
{
auto quiet = false;
auto noReport = false;
auto *flags = new scsl::Flags("test_madgwick",
"This test validates the Madgwick estimation code");
flags->Register("-n", false, "don't print the report");
flags->Register("-q", false, "suppress test output");
auto parsed = flags->Parse(argc, argv);
if (parsed != scsl::Flags::ParseStatus::OK) {
std::cerr << "Failed to parse flags: "
<< scsl::Flags::ParseStatusToString(parsed) << "\n";
exit(1);
}
sctest::SimpleSuite suite;
flags->GetBool("-n", noReport);
flags->GetBool("-q", quiet);
if (quiet) {
suite.Silence();
}
suite.AddTest("SimpleAngularOrientationFloat",
SimpleAngularOrientationFloat);
suite.AddTest("SimpleAngularOrientationFloatDefaultDT",
SimpleAngularOrientationFloatDefaultDT);
suite.AddFailingTest("VerifyUpdateWithZeroDeltaTFails",
VerifyUpdateWithZeroDeltaTFails);
suite.AddTest("SimpleAngularOrientationDouble",
SimpleAngularOrientationDouble);
suite.AddTest("SimpleAngularOrientationFloat (inital vector3f)",
SimpleAngularOrientation2InitialVector3f);
suite.AddTest("SimpleAngularOrientationDouble (inital vector3d)",
SimpleAngularOrientation2InitialVector3d);
suite.AddTest("SimpleAngularOrientationFloat (inital MakeQuaternion)",
SimpleAngularOrientation2InitialQuaternionf);
suite.AddTest("SimpleAngularOrientationDouble (inital MakeQuaternion)",
SimpleAngularOrientation2InitialQuaterniond);
delete flags;
auto result = suite.Run();
if (!noReport) { std::cout << suite.GetReport() << "\n"; }
return result ? 0 : 1;
}

186
test/math.cc Normal file
View File

@ -0,0 +1,186 @@
///
/// \file test/math.cc
/// \author K. Isom <kyle@imap.cc>
/// \date 2023-10-20
/// \brief Unit tests for math functions.
///
/// 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.
///
#include <iostream>
#include <scsl/Flags.h>
#include <scmp/Math.h>
#include <sctest/Checks.h>
#include <sctest/SimpleSuite.h>
namespace {
bool
BestDie()
{
// Theoretically, this could fail. The odds of that happening
// with 100 die and a proper RNG is 0.99999998792533,
// practically 100%. At 1000 die, it's virtually guaranteed.
auto n = scmp::BestDie(1000, 6, 1);
SCTEST_CHECK_EQ(n, 6);
return true;
}
bool
DieTotal()
{
auto n = scmp::DieTotal(100, 6);
SCTEST_CHECK_GEQ(n, 100);
SCTEST_CHECK_LEQ(n, 600);
return true;
}
bool
WithinToleranceFloat()
{
float x = 0.1235;
float y = 0.1236;
float eps = 0.0;
float expected = 0.1234;
scmp::DefaultEpsilon(eps);
SCTEST_CHECK_FEQ_EPS(x, expected, eps);
SCTEST_CHECK_FNE_EPS(y, expected, eps);
return true;
}
bool
WithinToleranceDouble()
{
double x = 0.12348;
double y = 0.1236;
double eps = 0.0;
double expected = 0.12345;
scmp::DefaultEpsilon(eps);
SCTEST_CHECK_DEQ_EPS(x, expected, eps);
SCTEST_CHECK_DNE_EPS(y, expected, eps);
return true;
}
bool
RotateRadians()
{
double theta0 = 0.0;
double theta1 = scmp::PI_D;
auto rotated = scmp::RotateRadians(theta0, theta1);
SCTEST_CHECK_DEQ(rotated, theta1);
rotated = scmp::RotateRadians(rotated, theta1);
SCTEST_CHECK_DEQ(rotated, theta0);
theta1 = scmp::PI_D * 3 / 2;
rotated = scmp::RotateRadians(theta0, theta1);
SCTEST_CHECK_DEQ(rotated, -scmp::PI_D / 2);
rotated = scmp::RotateRadians(rotated, theta1);
SCTEST_CHECK_DEQ(rotated, scmp::PI_D);
return true;
}
bool
IntegerSquareRoot()
{
static std::vector<size_t> ns{
// standard integer roots
4, 9, 16, 25, 36, 49, 64, 81, 100, 121, 144,
// a few float cases
42, 90, 92
};
static std::vector<size_t> expected{
// standard integer roots
2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12,
// a few float cases
6, 9, 10
};
SCTEST_CHECK_EQ(ns.size(), expected.size());
for (size_t i = 0; i < ns.size(); i++) {
auto root = scmp::ISqrt(ns.at(i));
SCTEST_CHECK_EQ(root, expected.at(i));
}
return true;
}
} // anonymous namespace
int
main(int argc, char *argv[])
{
auto noReport = false;
auto quiet = false;
auto flags = new scsl::Flags("test_orientation",
"This test validates various orientation-related components in scmp.");
flags->Register("-n", false, "don't print the report");
flags->Register("-q", false, "suppress test output");
auto parsed = flags->Parse(argc, argv);
if (parsed != scsl::Flags::ParseStatus::OK) {
std::cerr << "Failed to parse flags: "
<< scsl::Flags::ParseStatusToString(parsed) << "\n";
exit(1);
}
sctest::SimpleSuite suite;
flags->GetBool("-n", noReport);
flags->GetBool("-q", quiet);
if (quiet) {
suite.Silence();
}
suite.AddTest("BestDie", BestDie);
suite.AddTest("DieTotal", DieTotal);
suite.AddTest("WithinToleranceFloat", WithinToleranceFloat);
suite.AddTest("WithinToleranceDouble", WithinToleranceDouble);
suite.AddTest("RotateRadians", RotateRadians);
suite.AddTest("IntegerSquareRoot", IntegerSquareRoot);
delete flags;
auto result = suite.Run();
if (!noReport) { std::cout << suite.GetReport() << "\n"; }
return result ? 0 : 1;
}

123
test/orientation.cc Normal file
View File

@ -0,0 +1,123 @@
#include <scsl/Flags.h>
#include <scmp/Math.h>
#include <scmp/geom/Vector.h>
#include <scmp/geom/Orientation.h>
#include <sctest/Checks.h>
#include <sctest/SimpleSuite.h>
using namespace std;
using namespace scmp;
using namespace sctest;
namespace {
bool
UnitConversions_RadiansToDegreesF()
{
for (int i = 0; i < 360; i++) {
auto rads = scmp::DegreesToRadiansF(i);
auto deg = scmp::RadiansToDegreesF(rads);
SCTEST_CHECK_FEQ(deg, static_cast<float>(i));
}
return true;
}
bool
UnitConversions_RadiansToDegreesD()
{
for (int i = 0; i < 360; i++) {
auto deg = static_cast<double>(i);
SCTEST_CHECK_DEQ(scmp::RadiansToDegreesD(scmp::DegreesToRadiansD(deg)), deg);
}
return true;
}
bool
Orientation2f_Heading()
{
geom::Vector2F const a{2.0, 2.0};
SCTEST_CHECK_FEQ(geom::Heading2F(a), scmp::DegreesToRadiansF(45));
return true;
}
bool
Orientation3f_Heading()
{
geom::Vector3F const a{2.0, 2.0, 2.0};
SCTEST_CHECK_FEQ(geom::Heading3F(a), scmp::DegreesToRadiansF(45));
return true;
}
bool
Orientation2d_Heading()
{
geom::Vector2D const a{2.0, 2.0};
return scmp::WithinTolerance(geom::Heading2D(a), scmp::DegreesToRadiansD(45), 0.000001) != 0.0;
}
bool
Orientation3d_Heading()
{
geom::Vector3D const a{2.0, 2.0, 2.0};
return scmp::WithinTolerance(geom::Heading3D(a), scmp::DegreesToRadiansD(45), 0.000001) != 0.0;
}
} // anonymous namespace
int
main(int argc, char *argv[])
{
auto noReport = false;
auto quiet = false;
auto *flags = new scsl::Flags("test_orientation",
"This test validates various orientation-related components in scmp.");
flags->Register("-n", false, "don't print the report");
flags->Register("-q", false, "suppress test output");
auto parsed = flags->Parse(argc, argv);
if (parsed != scsl::Flags::ParseStatus::OK) {
std::cerr << "Failed to parse flags: "
<< scsl::Flags::ParseStatusToString(parsed) << "\n";
exit(1);
}
SimpleSuite suite;
flags->GetBool("-n", noReport);
flags->GetBool("-q", quiet);
if (quiet) {
suite.Silence();
}
suite.AddTest("UnitConversions_RadiansToDegreesF", UnitConversions_RadiansToDegreesF);
suite.AddTest("UnitConversions_RadiansToDegreesD", UnitConversions_RadiansToDegreesD);
suite.AddTest("Orientation2f_Heading", Orientation2f_Heading);
suite.AddTest("Orientation3f_Heading", Orientation3f_Heading);
suite.AddTest("Orientation2d_Heading", Orientation2d_Heading);
suite.AddTest("Orientation3d_Heading", Orientation3d_Heading);
delete flags;
auto result = suite.Run();
if (!noReport) { std::cout << suite.GetReport() << "\n"; }
return result ? 0 : 1;
}

493
test/quaternion.cc Normal file
View File

@ -0,0 +1,493 @@
#include <cmath>
#include <sstream>
#include <scsl/Flags.h>
#include <scmp/geom/Quaternion.h>
#include <sctest/Checks.h>
#include <sctest/SimpleSuite.h>
using namespace std;
using namespace scmp;
using namespace sctest;
static bool
Quaternion_SelfTest()
{
geom::QuaternionSelfTest();
return true;
}
static bool
Quaterniond_Addition()
{
geom::Quaterniond p(geom::Vector4D {3.0, 1.0, -2.0, 1.0});
geom::Quaterniond q(geom::Vector4D {2.0, -1.0, 2.0, 3.0});
geom::Quaterniond expected(geom::Vector4D{5.0, 0.0, 0.0, 4.0});
SCTEST_CHECK_EQ(p + q, expected);
SCTEST_CHECK_EQ(expected - q, p);
SCTEST_CHECK_NE(expected - q, q); // exercise !=
return true;
}
static bool
Quaterniond_Conjugate()
{
geom::Quaterniond p {2.0, 3.0, 4.0, 5.0};
geom::Quaterniond q {2.0, -3.0, -4.0, -5.0};
SCTEST_CHECK_EQ(p.Conjugate(), q);
return true;
}
static bool
Quaterniond_Euler()
{
geom::Quaterniond p = geom::MakeQuaternion(
geom::Vector3D{5.037992718099102, 6.212303632611285, 1.7056797335843106}, M_PI / 4.0);
geom::Quaterniond q = geom::DoubleQuaternionFromEuler(p.Euler());
SCTEST_CHECK_EQ(p, q);
return true;
}
static bool
Quaterniond_Identity()
{
geom::Quaterniond p {3.0, 1.0, -2.0, 1.0};
geom::Quaterniond q;
SCTEST_CHECK(q.IsIdentity());
SCTEST_CHECK_EQ(p * q, p);
return true;
}
static bool
Quaterniond_Inverse()
{
geom::Quaterniond p {2.0, 3.0, 4.0, 5.0};
geom::Quaterniond q {0.03704, -0.05556, -0.07407, -0.09259};
SCTEST_CHECK_EQ(p.Inverse(), q);
return true;
}
static bool
Quaterniond_Norm()
{
geom::Quaterniond p {5.563199889674063, 0.9899139811480784, 9.387110042325054, 6.161341707794767};
double norm = 12.57016663729933;
SCTEST_CHECK_DEQ(p.Norm(), norm);
return true;
}
static bool
Quaterniond_Product()
{
geom::Quaterniond p {3.0, 1.0, -2.0, 1.0};
geom::Quaterniond q {2.0, -1.0, 2.0, 3.0};
geom::Quaterniond expected {8.0, -9.0, -2.0, 11.0};
SCTEST_CHECK_EQ(p * q, expected);
return true;
}
static bool
Quaterniond_Rotate()
{
// This test aims to Rotate a vector v using a MakeQuaternion.
// c.f. https://math.stackexchange.com/questions/40164/how-do-you-rotate-a-vector-by-a-unit-quaternion
// If we assume a standard IMU frame of reference following the
// right hand rule:
// + The x Axis points toward magnetic north
// + The y Axis points toward magnentic west
// + The z Axis points toward the sky
// Given a vector pointing due north, rotating by 90º about
// the y-Axis should leave us pointing toward the sky.
geom::Vector3D v {1.0, 0.0, 0.0}; // a vector pointed north
geom::Vector3D yAxis {0.0, 1.0, 0.0}; // a vector representing the y Axis.
double angle = M_PI / 2; // 90º rotation
// A MakeQuaternion representing a 90º rotation about the y Axis.
geom::Quaterniond p = geom::MakeQuaternion(yAxis, angle);
geom::Vector3D vr {0.0, 0.0, 1.0}; // expected rotated vector.
// A rotation quaternion should be a unit MakeQuaternion.
SCTEST_CHECK(p.IsUnitQuaternion());
SCTEST_CHECK_EQ(p.Rotate(v), vr);
return true;
}
static bool
Quaterniond_ShortestSLERP()
{
// Our starting point is an Orientation that is yawed 45° - our
// Orientation is pointed π/4 radians in the X Axis.
geom::Quaterniond p {0.92388, 0.382683, 0, 0};
// Our ending point is an Orientation that is yawed -45° - or
// pointed -π/4 radians in the X Axis.
geom::Quaterniond q {0.92388, -0.382683, 0, 0};
// The halfway point should be oriented midway about the X Axis. It turns
// out this is an identity MakeQuaternion.
geom::Quaterniond r;
SCTEST_CHECK_EQ(geom::ShortestSLERP(p, q, 0.0), p);
SCTEST_CHECK_EQ(geom::ShortestSLERP(p, q, 1.0), q);
SCTEST_CHECK_EQ(geom::ShortestSLERP(p, q, 0.5), r);
return true;
}
static bool
Quaterniond_ShortestSLERP2()
{
// Start with an Orientation pointing forward, all Euler angles
// set to 0.
geom::Quaterniond start {1.0, 0.0, 0.0, 0.0};
// The goal is to end up face up, or 90º pitch (still facing forward).
geom::Quaterniond end {0.707107, 0, -0.707107, 0};
// Halfway to the endpoint should be a 45º pitch.
geom::Quaterniond halfway {0.92388, 0, -0.382683, 0};
// 2/3 of the way should be 60º pitch.
geom::Quaterniond twoThirds {0.866025, 0, -0.5, 0};
SCTEST_CHECK_EQ(ShortestSLERP(start, end, 0.0), start);
SCTEST_CHECK_EQ(ShortestSLERP(start, end, 1.0), end);
SCTEST_CHECK_EQ(ShortestSLERP(start, end, 0.5), halfway);
SCTEST_CHECK_EQ(ShortestSLERP(start, end, 2.0/3.0), twoThirds);
return true;
}
static bool
Quaterniond_Unit()
{
geom::Quaterniond q {0.0, 0.5773502691896258, 0.5773502691896258, 0.5773502691896258};
SCTEST_CHECK(q.IsUnitQuaternion());
return true;
}
static bool
Quaterniond_UtilityCreator()
{
geom::Vector3D v {1.0, 1.0, 1.0};
double w = M_PI;
geom::Quaterniond p = geom::MakeQuaternion(v, w);
geom::Quaterniond q {0.0, 0.5773502691896258, 0.5773502691896258, 0.5773502691896258};
SCTEST_CHECK_EQ(p, q);
return true;
}
static bool
Quaternionf_Addition()
{
geom::Quaternionf p {3.0, 1.0, -2.0, 1.0};
geom::Quaternionf q {2.0, -1.0, 2.0, 3.0};
geom::Quaternionf expected {5.0, 0.0, 0.0, 4.0};
SCTEST_CHECK_EQ(p + q, expected);
SCTEST_CHECK_EQ(expected - q, p);
SCTEST_CHECK_NE(expected - q, q); // exercise !=
return true;
}
static bool
Quaternionf_Conjugate()
{
geom::Quaternionf p {2.0, 3.0, 4.0, 5.0};
geom::Quaternionf q {2.0, -3.0, -4.0, -5.0};
SCTEST_CHECK_EQ(p.Conjugate(), q);
return true;
}
static bool
Quaternionf_Euler()
{
geom::Quaternionf p = geom::MakeQuaternion(
geom::Vector3F{5.037992718099102, 6.212303632611285, 1.7056797335843106}, M_PI / 4.0);
geom::Quaternionf q = geom::FloatQuaternionFromEuler(p.Euler());
SCTEST_CHECK_EQ(p, q);
return true;
}
static bool
Quaternionf_Identity()
{
geom::Quaternionf p {1.0, 3.0, 1.0, -2.0};
geom::Quaternionf q;
SCTEST_CHECK_EQ(p * q, p);
return true;
}
static bool
Quaternionf_Inverse()
{
geom::Quaternionf p {2.0, 3.0, 4.0, 5.0};
geom::Quaternionf q {0.03704, -0.05556, -0.07407, -0.09259};
SCTEST_CHECK_EQ(p.Inverse(), q);
return true;
}
static bool
Quaternionf_Norm()
{
geom::Quaternionf p {0.9899139811480784, 9.387110042325054, 6.161341707794767, 5.563199889674063};
float norm = 12.57016663729933;
SCTEST_CHECK_FEQ(p.Norm(), norm);
return true;
}
static bool
Quaternionf_Product()
{
geom::Quaternionf p {3.0, 1.0, -2.0, 1.0};
geom::Quaternionf q {2.0, -1.0, 2.0, 3.0};
geom::Quaternionf expected {8.0, -9.0, -2.0, 11.0};
SCTEST_CHECK_EQ(p * q, expected);
return true;
}
static bool
Quaternionf_Rotate()
{
geom::Vector3F v {1.0, 0.0, 0.0};
geom::Vector3F yAxis {0.0, 1.0, 0.0};
float angle = M_PI / 2;
geom::Quaternionf p = geom::MakeQuaternion(yAxis, angle);
geom::Vector3F vr {0.0, 0.0, 1.0};
SCTEST_CHECK(p.IsUnitQuaternion());
SCTEST_CHECK_EQ(p.Rotate(v), vr);
return true;
}
static bool
Quaternionf_ShortestSLERP()
{
// Our starting point is an Orientation that is yawed 45° - our
// Orientation is pointed π/4 radians in the X Axis.
geom::Quaternionf p {0.92388, 0.382683, 0, 0};
// Our ending point is an Orientation that is yawed -45° - or
// pointed -π/4 radians in the X Axis.
geom::Quaternionf q {0.92388, -0.382683, 0, 0};
// The halfway point should be oriented midway about the X Axis. It turns
// out this is an identity MakeQuaternion.
geom::Quaternionf r;
SCTEST_CHECK_EQ(geom::ShortestSLERP(p, q, (float)0.0), p);
SCTEST_CHECK_EQ(geom::ShortestSLERP(p, q, (float)1.0), q);
SCTEST_CHECK_EQ(geom::ShortestSLERP(p, q, (float)0.5), r);
return true;
}
static bool
Quaternionf_ShortestSLERP2()
{
// Start with an Orientation pointing forward, all Euler angles
// set to 0.
geom::Quaternionf start {1.0, 0.0, 0.0, 0.0};
// The goal is to end up face up, or 90º pitch (still facing forward).
geom::Quaternionf end {0.707107, 0, -0.707107, 0};
// Halfway to the endpoint should be a 45º pitch.
geom::Quaternionf halfway {0.92388, 0, -0.382683, 0};
// 2/3 of the way should be 60º pitch.
geom::Quaternionf twoThirds {0.866025, 0, -0.5, 0};
SCTEST_CHECK_EQ(ShortestSLERP(start, end, (float)0.0), start);
SCTEST_CHECK_EQ(ShortestSLERP(start, end, (float)1.0), end);
SCTEST_CHECK_EQ(ShortestSLERP(start, end, (float)0.5), halfway);
SCTEST_CHECK_EQ(ShortestSLERP(start, end, (float)(2.0/3.0)), twoThirds);
return true;
}
static bool
Quaternionf_Unit()
{
geom::Quaternionf q {0.0, 0.5773502691896258, 0.5773502691896258, 0.5773502691896258};
SCTEST_CHECK(q.IsUnitQuaternion());
return true;
}
static bool
Quaternionf_UtilityCreator()
{
geom::Vector3F v {1.0, 1.0, 1.0};
float w = M_PI;
geom::Quaternionf p = geom::MakeQuaternion(v, w);
geom::Quaternionf q {0.0, 0.5773502691896258, 0.5773502691896258, 0.5773502691896258};
SCTEST_CHECK_EQ(p, q);
return true;
}
static bool
QuaternionMiscellaneous_SanityChecks()
{
geom::Vector4D q {4.0, 1.0, 2.0, 3.0};
geom::Vector3D v {1.0, 2.0, 3.0};
double w = 4.0;
geom::Quaterniond p(q);
geom::Quaterniond u = p.UnitQuaternion();
SCTEST_CHECK_EQ(p.Axis(), v);
SCTEST_CHECK_DEQ(p.Angle(), w);
SCTEST_CHECK(u.IsUnitQuaternion());
return true;
}
static bool
QuaternionMiscellaneous_OutputStream()
{
geom::Quaternionf p {4.0, 1.0, 2.0, 3.0};
geom::Quaterniond q {4.0, 1.0, 2.0, 3.0};
stringstream ss;
ss << p;
SCTEST_CHECK_EQ(ss.str(), "4 + <1, 2, 3>");
ss.str("");
ss << q;
SCTEST_CHECK_EQ(ss.str(), "4 + <1, 2, 3>");
return true;
}
static bool
QuaternionMiscellanous_InitializerConstructor()
{
geom::Quaternionf p {1.0, 1.0, 1.0, 1.0};
geom::Quaternionf q(geom::Vector4F {1.0, 1.0, 1.0, 1.0});
SCTEST_CHECK_EQ(p, q);
SCTEST_CHECK_FEQ(p.Norm(), (float)2.0);
return true;
}
int
main(int argc, char *argv[])
{
auto noReport = false;
auto quiet = false;
auto flags = new scsl::Flags("test_quaternion",
"This test validates the Quaternion class.");
flags->Register("-n", false, "don't print the report");
flags->Register("-q", false, "suppress test output");
auto parsed = flags->Parse(argc, argv);
if (parsed != scsl::Flags::ParseStatus::OK) {
std::cerr << "Failed to parse flags: "
<< scsl::Flags::ParseStatusToString(parsed) << "\n";
exit(1);
}
SimpleSuite suite;
flags->GetBool("-n", noReport);
flags->GetBool("-q", quiet);
if (quiet) {
suite.Silence();
}
suite.AddTest("QuaternionSelfTest", Quaternion_SelfTest);
suite.AddTest("QuaternionMiscellanous_InitializerConstructor",
QuaternionMiscellanous_InitializerConstructor);
suite.AddTest("QuaternionMiscellaneous_SanityChecks",
QuaternionMiscellaneous_SanityChecks);
suite.AddTest("QuaternionMiscellaneous_OutputStream",
QuaternionMiscellaneous_OutputStream);
suite.AddTest("Quaterniond_Addition", Quaterniond_Addition);
suite.AddTest("Quaterniond_Conjugate", Quaterniond_Conjugate);
suite.AddTest("Quaterniond_Euler", Quaterniond_Euler);
suite.AddTest("Quaterniond_Identity", Quaterniond_Identity);
suite.AddTest("Quaterniond_Inverse", Quaterniond_Inverse);
suite.AddTest("Quaterniond_Norm", Quaterniond_Norm);
suite.AddTest("Quaterniond_Product", Quaterniond_Product);
suite.AddTest("Quaterniond_Rotate", Quaterniond_Rotate);
suite.AddTest("Quaterniond_ShortestSLERP", Quaterniond_ShortestSLERP);
suite.AddTest("Quaterniond_ShortestSLERP2", Quaterniond_ShortestSLERP2);
suite.AddTest("Quaterniond_Unit", Quaterniond_Unit);
suite.AddTest("Quaterniond_UtilityCreator", Quaterniond_UtilityCreator);
suite.AddTest("Quaternionf_Addition", Quaternionf_Addition);
suite.AddTest("Quaternionf_Conjugate", Quaternionf_Conjugate);
suite.AddTest("Quaternionf_Euler", Quaternionf_Euler);
suite.AddTest("Quaternionf_Identity", Quaternionf_Identity);
suite.AddTest("Quaternionf_Inverse", Quaternionf_Inverse);
suite.AddTest("Quaternionf_Norm", Quaternionf_Norm);
suite.AddTest("Quaternionf_Product", Quaternionf_Product);
suite.AddTest("Quaternionf_Rotate", Quaternionf_Rotate);
suite.AddTest("Quaternionf_ShortestSLERP", Quaternionf_ShortestSLERP);
suite.AddTest("Quaternionf_ShortestSLERP2", Quaternionf_ShortestSLERP2);
suite.AddTest("Quaternionf_Unit", Quaternionf_Unit);
suite.AddTest("Quaternionf_UtilityCreator", Quaternionf_UtilityCreator);
delete flags;
auto result = suite.Run();
if (!noReport) { std::cout << suite.GetReport() << "\n"; }
return result ? 0 : 1;
}

89
test/simple_suite_example.cc Executable file
View File

@ -0,0 +1,89 @@
///
/// \file test/simple_suite_example.cc
/// \author K. Isom <kyle@imap.cc>
/// \date 2017-06-05
///
/// simple_suite_example demonstrates the usage of the SimpleSuite test class
/// and serves to unit test the unit tester (qui custodiet ipsos custodes)?
///
///
/// 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 <iostream>
#include <scsl/Flags.h>
#include <sctest/SimpleSuite.h>
static bool
prepareTests()
{
std::cout << "time passes...\n";
std::cout << "tests are ready.\n";
return true;
}
static bool
destroyTests()
{
std::cout << "time passes...\n" ;
std::cout << "tests have been destroyed.\n";
return true;
}
static bool addOne() { return 1 + 1 == 2; }
static bool four() { return 2 + 2 == 4; }
static bool nope() { return 2 + 2 == 5; }
int
main(int argc, char *argv[])
{
auto noReport = false;
auto quiet = false;
auto flags = new scsl::Flags("test_orientation",
"This test validates various orientation-related components in scmp.");
flags->Register("-n", false, "don't print the report");
flags->Register("-q", false, "suppress test output");
auto parsed = flags->Parse(argc, argv);
if (parsed != scsl::Flags::ParseStatus::OK) {
std::cerr << "Failed to parse flags: "
<< scsl::Flags::ParseStatusToString(parsed) << "\n";
exit(1);
}
sctest::SimpleSuite suite;
flags->GetBool("-n", noReport);
flags->GetBool("-q", quiet);
if (quiet) {
suite.Silence();
}
suite.Setup(prepareTests);
suite.Teardown(destroyTests);
suite.AddTest("1 + 1", addOne);
suite.AddTest("fourness", four);
suite.AddFailingTest("self-evident truth", nope);
delete flags;
auto result = suite.Run();
if (!noReport) { std::cout << suite.GetReport() << "\n"; }
return result ? 0 : 1;
}

182
test/stringutil.cc Normal file
View File

@ -0,0 +1,182 @@
///
/// \file test/stringutil_test.cc
/// \author kyle
/// \date 10/14/23
/// \brief Ensure the stringutil functions work.
///
/// 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 <functional>
#include <iostream>
#include <scsl/Flags.h>
#include <scsl/StringUtil.h>
#include <sctest/Assert.h>
#include <sctest/SimpleSuite.h>
using namespace scsl;
namespace {
void
TestTrimming(std::string line, std::string lExpected, std::string rExpected, std::string expected)
{
std::string result;
std::string message;
result = scstring::TrimLeadingWhitespaceDup(line);
message = "TrimLeadingDup(\"" + line + "\"): '" + result + "'";
sctest::Assert(result == lExpected, message);
result = scstring::TrimTrailingWhitespaceDup(line);
message = "TrimTrailingDup(\"" + line + "\"): '" + result + "'";
sctest::Assert(result == rExpected, message);
result = scstring::TrimWhitespaceDup(line);
message = "TrimDup(\"" + line + "\"): '" + result + "'";
sctest::Assert(result == expected, message);
result = line;
scstring::TrimLeadingWhitespace(result);
message = "TrimLeadingDup(\"" + line + "\"): '" + result + "'";
sctest::Assert(result == lExpected, message);
result = line;
scstring::TrimTrailingWhitespace(result);
message = "TrimTrailingDup(\"" + line + "\"): '" + result + "'";
sctest::Assert(result == rExpected, message);
result = line;
scstring::TrimWhitespace(result);
message = "TrimDup(\"" + line + "\"): '" + result + "'";
sctest::Assert(result == expected, message);
}
std::function<bool()>
TestSplit(std::string line, std::string delim, size_t maxCount, std::vector<std::string> expected)
{
return [line, delim, maxCount, expected]() {
return scstring::SplitN(line, delim, maxCount) == expected;
};
}
bool
TestSplitChar()
{
auto expected = std::vector<std::string>{"hello", "world"};
const auto *inputLine = "hello=world\n";
auto actual = scstring::SplitKeyValuePair(inputLine, '=');
return actual == expected;
}
bool
TestWrapping()
{
std::string testLine = "A much longer line, something that can be tested with WrapText. ";
testLine += "Does it handle puncuation? I hope so.";
std::vector<std::string> expected{
"A much longer",
"line, something",
"that can be",
"tested with",
"WrapText. Does",
"it handle",
"puncuation? I",
"hope so.",
};
auto wrapped = scstring::WrapText(testLine, 16);
if (wrapped.size() != expected.size()) {
std::cerr << scstring::VectorToString(wrapped)
<< " != "
<< scstring::VectorToString(expected)
<< "\n";
}
for (size_t i = 0; i < wrapped.size(); i++) {
if (wrapped[i] == expected[i]) {
continue;
}
std::cerr << "[" << i << "] \"" << wrapped[i] << "\" != \""
<< expected[i] << "\"\n";
return false;
}
// scstring::WriteTabIndented(std::cout, wrapped, 4, true);
return true;
}
} // anonymous namespace
int
main(int argc, char *argv[])
{
auto noReport = false;
auto quiet = false;
auto *flags = new scsl::Flags("test_orientation",
"This test validates various orientation-related components in scmp.");
flags->Register("-n", false, "don't print the report");
flags->Register("-q", false, "suppress test output");
auto parsed = flags->Parse(argc, argv);
if (parsed != scsl::Flags::ParseStatus::OK) {
std::cerr << "Failed to parse flags: "
<< scsl::Flags::ParseStatusToString(parsed) << "\n";
}
sctest::SimpleSuite suite;
flags->GetBool("-n", noReport);
flags->GetBool("-q", quiet);
if (quiet) {
suite.Silence();
}
TestTrimming(" foo\t ", "foo\t ", " foo", "foo");
TestTrimming(" foo\tbar ", "foo\tbar ", " foo\tbar", "foo\tbar");
suite.AddTest("SplitN(0)", TestSplit("abc:def:ghij:klm", ":", 0,
std::vector<std::string>{"abc", "def", "ghij", "klm"}));
suite.AddTest("SplitN(3)", TestSplit("abc:def:ghij:klm", ":", 3,
std::vector<std::string>{"abc", "def", "ghij:klm"}));
suite.AddTest("SplitN(2)", TestSplit("abc:def:ghij:klm", ":", 2,
std::vector<std::string>{"abc", "def:ghij:klm"}));
suite.AddTest("SplitN(1)", TestSplit("abc:def:ghij:klm", ":", 1,
std::vector<std::string>{"abc:def:ghij:klm"}));
suite.AddTest("SplitN(0) with empty element",
TestSplit("abc::def:ghi", ":", 0,
std::vector<std::string>{"abc", "", "def", "ghi"}));
suite.AddTest("TestSplitKV(char)", TestSplitChar);
suite.AddTest("TextWrapping", TestWrapping);
delete flags;
auto result = suite.Run();
if (!noReport) { std::cout << suite.GetReport() << "\n"; }
return result ? 0 : 1;
}

View File

@ -3,7 +3,7 @@
#include <string.h>
#include "TLV.h"
#include <scsl/TLV.h>
#define ARENA_SIZE 128

172
test/tlv.cc Normal file
View File

@ -0,0 +1,172 @@
///
/// \file test/tlv.cc
/// \author K. Isom <kyle@imap.cc>
/// \date 2023-10-05
/// \brief Unit tests for the TLV namespace.
///
/// simple_suite_example demonstrates the usage of the SimpleSuite test class
/// and serves to unit test the unit tester (qui custodiet ipsos custodes)?
///
/// \section COPYRIGHT
///
/// Copyright 2023 K. Isom <kyle@imap.cc>
///
/// Permission to use, copy, modify, and/or distribute this software for
/// any purpose with or without fee is hereby granted, provided that the
/// above copyright notice and this permission notice appear in all copies.
///
/// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
/// WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
/// WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR
/// BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES
/// OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
/// WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
/// ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
/// SOFTWARE.
///
#include <exception>
#include <iostream>
#include <scsl/Arena.h>
#include <scsl/Flags.h>
#include <scsl/TLV.h>
#include <sctest/Checks.h>
#include <sctest/SimpleSuite.h>
#include "test_fixtures.h"
using namespace scsl;
static uint8_t arenaBuffer[ARENA_SIZE];
bool
runTLVTest(Arena &backend)
{
TLV::Record rec1, rec2, rec3, rec4;
uint8_t *cursor = nullptr;
TLV::SetRecord(rec1, 1, TEST_STRLEN1, TEST_STR1);
TLV::SetRecord(rec2, 2, TEST_STRLEN2, TEST_STR2);
TLV::SetRecord(rec3, 1, TEST_STRLEN4, TEST_STR4);
rec4.Tag = 1;
cursor = TLV::WriteToMemory(backend, cursor, rec1);
SCTEST_CHECK_NE(cursor, nullptr);
cursor = TLV::WriteToMemory(backend, cursor, rec2);
SCTEST_CHECK_NE(cursor, nullptr);
cursor = TLV::WriteToMemory(backend, cursor, rec3);
SCTEST_CHECK_NE(cursor, nullptr);
// the cursor should point At the next record,
// and rec4 should contain the same data as rec1.
cursor = TLV::FindTag(backend, nullptr, rec4);
SCTEST_CHECK_NE(cursor, nullptr);
SCTEST_CHECK_NE(cursor, backend.Start());
SCTEST_CHECK(cmpRecord(rec1, rec4));
cursor = TLV::FindTag(backend, cursor, rec4);
SCTEST_CHECK_NE(cursor, nullptr);
SCTEST_CHECK(cmpRecord(rec3, rec4));
TLV::SetRecord(rec4, 3, TEST_STRLEN3, TEST_STR3);
SCTEST_CHECK(TLV::WriteToMemory(backend, nullptr, rec4));
rec4.Tag = 2;
cursor = TLV::FindTag(backend, nullptr, rec4);
SCTEST_CHECK_NE(cursor, nullptr);
TLV::DeleteRecord(backend, cursor);
SCTEST_CHECK_EQ(cursor[0], 3);
rec4.Tag = 3;
cursor = nullptr;
return true;
}
bool
tlvTestSuite(ArenaType arenaType)
{
Arena backend;
switch (arenaType) {
case ArenaType::Static:
if (backend.SetStatic(arenaBuffer, ARENA_SIZE) != 0) {
std::cerr << "[!] failed to set up statically-allocated arena\n";
return false;
}
break;
case ArenaType::Alloc:
if (backend.SetAlloc(ARENA_SIZE) != 0) {
std::cerr << "[!] failed to set up dynamically-allocated arena\n";
return false;
}
break;
case ArenaType::MemoryMapped:
if (backend.Create(ARENA_FILE, ARENA_SIZE)) {
std::cerr << "[!] failed to set up memory-mapped arena\n";
return false;
}
break;
default:
std::cerr << "[!] " << static_cast<int>(arenaType) << " is invalid for this test.\n";
return false;
}
auto result = runTLVTest(backend);
if (!result) {
std::cerr << "[!] suite failed with " << backend << "\n";
}
backend.Destroy();
return result;
}
std::function<bool()>
buildTestSuite(ArenaType arenaType)
{
return [arenaType](){
return tlvTestSuite(arenaType);
};
}
int
main(int argc, char *argv[])
{
auto noReport = false;
auto quiet = false;
auto flags = new scsl::Flags("test_tlv",
"This test validates various TLV-related components in scsl.");
flags->Register("-n", false, "don't print the report");
flags->Register("-q", false, "suppress test output");
auto parsed = flags->Parse(argc, argv);
if (parsed != scsl::Flags::ParseStatus::OK) {
std::cerr << "Failed to parse flags: "
<< scsl::Flags::ParseStatusToString(parsed) << "\n";
exit(1);
}
sctest::SimpleSuite suite;
flags->GetBool("-n", noReport);
flags->GetBool("-q", quiet);
if (quiet) {
suite.Silence();
}
suite.AddTest("ArenaStatic", buildTestSuite(ArenaType::Static));
suite.AddTest("ArenaAlloc", buildTestSuite(ArenaType::Alloc));
suite.AddTest("ArenaFile", buildTestSuite(ArenaType::MemoryMapped));
delete flags;
auto result = suite.Run();
if (!noReport) { std::cout << suite.GetReport() << "\n"; }
return result ? 0 : 1;
}

514
test/vector.cc Normal file
View File

@ -0,0 +1,514 @@
//
// Project: scccl
// File: test/math/geom2d_test.cpp
// Author: Kyle Isom
// Date: 2020-02-19
//
// vector runs a set of unit tests on the vector parts of the
// math::geom namespace.
//
// Copyright 2020 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 <iostream>
#include <sstream>
#include <scmp/geom/Vector.h>
#include <sctest/SimpleSuite.h>
#include <sctest/Checks.h>
#include "scsl/Flags.h"
using namespace scmp;
using namespace sctest;
using namespace std;
static bool
Vector3Miscellaneous_ExtractionOperator3d()
{
geom::Vector3D vec {1.0, 2.0, 3.0};
stringstream vecBuffer;
vecBuffer << vec;
SCTEST_CHECK_EQ(vecBuffer.str(), "<1, 2, 3>");
return true;
}
static bool
Vector3Miscellaneous_ExtractionOperator3f()
{
geom::Vector3F vec {1.0, 2.0, 3.0};
stringstream vecBuffer;
vecBuffer << vec;
SCTEST_CHECK_EQ(vecBuffer.str(), "<1, 2, 3>");
return true;
}
static bool
Vector3Miscellaneous_SetEpsilon() {
geom::Vector3F a {1.0, 1.0, 1.0};
geom::Vector3F b;
a.SetEpsilon(1.1);
SCTEST_CHECK_EQ(a, b);
return true;
}
static bool
Vector3FloatTests_Magnitude()
{
geom::Vector3F v3f {1.0, -2.0, 3.0};
const float expected = 3.74165738677394;
SCTEST_CHECK_FEQ(v3f.Magnitude(), expected);
return true;
}
static bool
Vector3FloatTests_Equality()
{
geom::Vector3F a {1.0, 2.0, 3.0};
geom::Vector3F b {1.0, 2.0, 3.0};
geom::Vector3F c {1.0, 2.0, 1.0};
SCTEST_CHECK_EQ(a, b);
SCTEST_CHECK_EQ(b, a);
SCTEST_CHECK_NE(a, c);
SCTEST_CHECK_NE(b, c);
return true;
}
static bool
Vector3FloatTests_Addition()
{
geom::Vector3F a {1.0, 2.0, 3.0};
geom::Vector3F b {4.0, 5.0, 6.0};
geom::Vector3F expected {5.0, 7.0, 9.0};
SCTEST_CHECK_EQ(a+b, expected);
return true;
}
static bool
Vector3FloatTests_Subtraction()
{
geom::Vector3F a {1.0, 2.0, 3.0};
geom::Vector3F b {4.0, 5.0, 6.0};
geom::Vector3F c {5.0, 7.0, 9.0};
SCTEST_CHECK_EQ(c-b, a);
return true;
}
static bool
Vector3FloatTests_ScalarMultiplication()
{
geom::Vector3F a {1.0, 2.0, 3.0};
geom::Vector3F expected {3.0, 6.0, 9.0};
SCTEST_CHECK_EQ(a * 3.0, expected);
return true;
}
static bool
Vector3FloatTests_ScalarDivision()
{
geom::Vector3F a {1.0, 2.0, 3.0};
geom::Vector3F b {3.0, 6.0, 9.0};
SCTEST_CHECK_EQ(b / 3.0, a);
return true;
}
static bool
Vector3FloatTests_DotProduct()
{
geom::Vector3F a {1.0, 2.0, 3.0};
geom::Vector3F b {4.0, 5.0, 6.0};
SCTEST_CHECK_FEQ(a * b, (float)32.0);
return true;
}
static bool
Vector3FloatTests_UnitVector()
{
// Test values randomly generated and calculated with numpy.
geom::Vector3F vec3 {5.320264018493507, 5.6541812891273935, 1.9233435162644652};
geom::Vector3F unit {0.6651669556972103, 0.7069150218815566, 0.24046636539587804};
geom::Vector3F unit2;
SCTEST_CHECK_EQ(vec3.UnitVector(), unit);
SCTEST_CHECK_FALSE(vec3.IsUnitVector());
SCTEST_CHECK(unit.IsUnitVector());
SCTEST_CHECK(unit2.IsUnitVector());
return true;
}
static bool
Vector3FloatTests_Angle()
{
geom::Vector3F a {0.3977933061361172, 8.053980094436525, 8.1287759943773};
geom::Vector3F b {9.817895298608196, 4.034166890407462, 4.37628316513266};
geom::Vector3F c {7.35, 0.221, 5.188};
geom::Vector3F d {2.751, 8.259, 3.985};
SCTEST_CHECK_FEQ(a.Angle(b), (float)0.9914540426033251);
if (!scmp::WithinTolerance(c.Angle(d), (float)1.052, (float)0.001)) {
return false;
}
return true;
}
static bool
Vector3FloatTests_ParallelOrthogonalVectors()
{
geom::Vector3F a {-2.029, 9.97, 4.172};
geom::Vector3F b {-9.231, -6.639, -7.245};
geom::Vector3F c {-2.328, -7.284, -1.214};
geom::Vector3F d {-1.821, 1.072, -2.94};
geom::Vector3F e {-2.0, 1.0, 3.0};
geom::Vector3F f {-6.0, 3.0, 9.0};
geom::Vector3F zeroVector {0.0, 0.0, 0.0};
SCTEST_CHECK_FALSE(a.IsParallel(b));
SCTEST_CHECK_FALSE(a.IsOrthogonal(b));
SCTEST_CHECK_FALSE(c.IsParallel(d));
SCTEST_CHECK(c.IsOrthogonal(d));
SCTEST_CHECK(e.IsParallel(f));
SCTEST_CHECK_FALSE(e.IsOrthogonal(f));
SCTEST_CHECK(zeroVector.IsZero());
SCTEST_CHECK(c.IsParallel(zeroVector));
SCTEST_CHECK(c.IsOrthogonal(zeroVector));
return true;
}
static bool
Vector3FloatTests_Projections()
{
geom::Vector3F a {4.866769214609107, 6.2356222686140566, 9.140878417029711};
geom::Vector3F b {6.135533104801077, 8.757851406697895, 0.6738031370548048};
geom::Vector3F c {4.843812341655318, 6.9140509888133055, 0.5319465962229454};
geom::Vector3F d {0.02295687295378901, -0.6784287201992489, 8.608931820806765};
SCTEST_CHECK_EQ(a.ProjectParallel(b), c);
SCTEST_CHECK_EQ(a.ProjectOrthogonal(b), d);
return true;
}
static bool
Vector3FloatTests_CrossProduct()
{
geom::Vector3F a {8.462, 7.893, -8.187};
geom::Vector3F b {6.984, -5.975, 4.778};
geom::Vector3F c {-11.2046, -97.6094, -105.685};
c.SetEpsilon(0.001);
SCTEST_CHECK_EQ(c, a.Cross(b));
return true;
}
static bool
Vector3DoubleTests_Magnitude()
{
geom::Vector3D v3d{1.0, -2.0, 3.0};
const double expected = 3.74165738677394;
SCTEST_CHECK_DEQ(v3d.Magnitude(), expected);
return true;
}
static bool
Vector3DoubleTests_Equality()
{
geom::Vector3D a {1.0, 2.0, 3.0};
geom::Vector3D b {1.0, 2.0, 3.0};
geom::Vector3D c {1.0, 2.0, 1.0};
SCTEST_CHECK_EQ(a, b);
SCTEST_CHECK_EQ(b, a);
SCTEST_CHECK_NE(a, c);
SCTEST_CHECK_NE(b, c);
return true;
}
static bool
Vector3DoubleTests_Addition()
{
geom::Vector3D a {1.0, 2.0, 3.0};
geom::Vector3D b {4.0, 5.0, 6.0};
geom::Vector3D expected {5.0, 7.0, 9.0};
SCTEST_CHECK_EQ(a+b, expected);
return true;
}
static bool
Vector3DoubleTests_Subtraction()
{
geom::Vector3D a {1.0, 2.0, 3.0};
geom::Vector3D b {4.0, 5.0, 6.0};
geom::Vector3D c {5.0, 7.0, 9.0};
SCTEST_CHECK_EQ(c-b, a);
return true;
}
static bool
Vector3DoubleTests_ScalarMultiplication()
{
geom::Vector3D a {1.0, 2.0, 3.0};
geom::Vector3D expected {3.0, 6.0, 9.0};
SCTEST_CHECK_EQ(a * 3.0, expected);
return true;
}
static bool
Vector3DoubleTests_ScalarDivision()
{
geom::Vector3D a {1.0, 2.0, 3.0};
geom::Vector3D b {3.0, 6.0, 9.0};
SCTEST_CHECK_EQ(b / 3.0, a);
return true;
}
static bool
Vector3DoubleTests_DotProduct()
{
geom::Vector3D a {1.0, 2.0, 3.0};
geom::Vector3D b {4.0, 5.0, 6.0};
SCTEST_CHECK_DEQ(a * b, 32.0);
return true;
}
static bool
Vector3DoubleTests_UnitVector()
{
// Test values randomly generated and calculated with numpy.
geom::Vector3D vec3 {5.320264018493507, 5.6541812891273935, 1.9233435162644652};
geom::Vector3D unit {0.6651669556972103, 0.7069150218815566, 0.24046636539587804};
geom::Vector3D unit2;
SCTEST_CHECK_EQ(vec3.UnitVector(), unit);
SCTEST_CHECK_FALSE(vec3.IsUnitVector());
SCTEST_CHECK(unit.IsUnitVector());
SCTEST_CHECK(unit2.IsUnitVector());
return true;
}
static bool
Vector3DoubleTests_Angle()
{
geom::Vector3D a {0.3977933061361172, 8.053980094436525, 8.1287759943773};
geom::Vector3D b {9.817895298608196, 4.034166890407462, 4.37628316513266};
geom::Vector3D c {7.35, 0.221, 5.188};
geom::Vector3D d {2.751, 8.259, 3.985};
SCTEST_CHECK_DEQ(a.Angle(b), 0.9914540426033251);
if (!scmp::WithinTolerance(c.Angle(d), (double)1.052, (double)0.001)) {
return false;
}
return true;
}
static bool
Vector3DoubleTests_ParallelOrthogonalVectors()
{
geom::Vector3D a {-2.029, 9.97, 4.172};
geom::Vector3D b {-9.231, -6.639, -7.245};
geom::Vector3D c {-2.328, -7.284, -1.214};
geom::Vector3D d {-1.821, 1.072, -2.94};
geom::Vector3D e {-2.0, 1.0, 3.0};
geom::Vector3D f {-6.0, 3.0, 9.0};
geom::Vector3D zeroVector {0.0, 0.0, 0.0};
SCTEST_CHECK_FALSE(a.IsParallel(b));
SCTEST_CHECK_FALSE(a.IsOrthogonal(b));
SCTEST_CHECK_FALSE(c.IsParallel(d));
SCTEST_CHECK(c.IsOrthogonal(d));
SCTEST_CHECK(e.IsParallel(f));
SCTEST_CHECK_FALSE(e.IsOrthogonal(f));
SCTEST_CHECK(zeroVector.IsZero());
SCTEST_CHECK(c.IsParallel(zeroVector));
SCTEST_CHECK(c.IsOrthogonal(zeroVector));
return true;
}
static bool
Vector3DoubleTests_Projections()
{
geom::Vector3D a {4.866769214609107, 6.2356222686140566, 9.140878417029711};
geom::Vector3D b {6.135533104801077, 8.757851406697895, 0.6738031370548048};
geom::Vector3D c {4.843812341655318, 6.9140509888133055, 0.5319465962229454};
geom::Vector3D d {0.02295687295378901, -0.6784287201992489, 8.608931820806765};
SCTEST_CHECK_EQ(a.ProjectParallel(b), c);
SCTEST_CHECK_EQ(a.ProjectOrthogonal(b), d);
return true;
}
static bool
Vector3DoubleTests_CrossProduct()
{
geom::Vector3D a {8.462, 7.893, -8.187};
geom::Vector3D b {6.984, -5.975, 4.778};
geom::Vector3D c {-11.2046, -97.6094, -105.685};
c.SetEpsilon(0.001); // double trouble
SCTEST_CHECK_EQ(c, a.Cross(b));
return true;
}
int
main(int argc, char *argv[])
{
auto noReport = false;
auto quiet = false;
auto flags = new scsl::Flags("test_vector",
"This test validates the vector implementation.");
flags->Register("-n", false, "don't print the report");
flags->Register("-q", false, "suppress test output");
auto parsed = flags->Parse(argc, argv);
if (parsed != scsl::Flags::ParseStatus::OK) {
std::cerr << "Failed to parse flags: "
<< scsl::Flags::ParseStatusToString(parsed) << "\n";
exit(1);
}
SimpleSuite suite;
flags->GetBool("-n", noReport);
flags->GetBool("-q", quiet);
if (quiet) {
suite.Silence();
}
suite.AddTest("Vector3Miscellaneous_ExtractionOperator3d",
Vector3Miscellaneous_ExtractionOperator3d);
suite.AddTest("Vector3Miscellaneous_ExtractionOperator3f",
Vector3Miscellaneous_ExtractionOperator3f);
suite.AddTest("Vector3Miscellaneous_SetEpsilon",
Vector3Miscellaneous_SetEpsilon);
suite.AddTest("Vector3FloatTests_Magnitude",
Vector3FloatTests_Magnitude);
suite.AddTest("Vector3FloatTests_Equality",
Vector3FloatTests_Equality);
suite.AddTest("Vector3FloatTests_Addition",
Vector3FloatTests_Addition);
suite.AddTest("Vector3FloatTests_Subtraction",
Vector3FloatTests_Subtraction);
suite.AddTest("Vector3FloatTests_ScalarMultiplication",
Vector3FloatTests_ScalarMultiplication);
suite.AddTest("Vector3FloatTests_ScalarDivision",
Vector3FloatTests_ScalarDivision);
suite.AddTest("Vector3FloatTests_DotProduct",
Vector3FloatTests_DotProduct);
suite.AddTest("Vector3FloatTests_UnitVector",
Vector3FloatTests_UnitVector);
suite.AddTest("Vector3FloatTests_Angle",
Vector3FloatTests_Angle);
suite.AddTest("Vector3FloatTests_ParallelOrthogonalVectors",
Vector3FloatTests_ParallelOrthogonalVectors);
suite.AddTest("Vector3FloatTests_Projections",
Vector3FloatTests_Projections);
suite.AddTest("Vector3FloatTests_CrossProduct",
Vector3FloatTests_CrossProduct);
suite.AddTest("Vector3DoubleTests_Magnitude",
Vector3DoubleTests_Magnitude);
suite.AddTest("Vector3DoubleTests_Equality",
Vector3DoubleTests_Equality);
suite.AddTest("Vector3DoubleTests_Addition",
Vector3DoubleTests_Addition);
suite.AddTest("Vector3DoubleTests_Subtraction",
Vector3DoubleTests_Subtraction);
suite.AddTest("Vector3DoubleTests_ScalarMultiplication",
Vector3DoubleTests_ScalarMultiplication);
suite.AddTest("Vector3DoubleTests_ScalarDivision",
Vector3DoubleTests_ScalarDivision);
suite.AddTest("Vector3DoubleTests_DotProduct",
Vector3DoubleTests_DotProduct);
suite.AddTest("Vector3DoubleTests_UnitVector",
Vector3DoubleTests_UnitVector);
suite.AddTest("Vector3DoubleTests_Angle",
Vector3DoubleTests_Angle);
suite.AddTest("Vector3DoubleTests_ParallelOrthogonalVectors",
Vector3DoubleTests_ParallelOrthogonalVectors);
suite.AddTest("Vector3DoubleTests_Projections",
Vector3DoubleTests_Projections);
suite.AddTest("Vector3DoubleTests_CrossProduct",
Vector3DoubleTests_CrossProduct);
delete flags;
auto result = suite.Run();
if (!noReport) { std::cout << suite.GetReport() << "\n"; }
return result ? 0 : 1;
}

View File

@ -1,121 +0,0 @@
#include <cassert>
#include <cstring>
#include <exception>
#include <iostream>
#include "Arena.h"
#include "Test.h"
#include "TLV.h"
#include "testFixtures.h"
using namespace scsl;
static uint8_t arenaBuffer[ARENA_SIZE];
void
tlvTestSuite(Arena &backend)
{
TLV::Record rec1, rec2, rec3, rec4;
uint8_t *cursor = nullptr;
std::cout << "\tSetting first three records." << "\n";
TLV::SetRecord(rec1, 1, TEST_STRLEN1, TEST_STR1);
TLV::SetRecord(rec2, 2, TEST_STRLEN2, TEST_STR2);
TLV::SetRecord(rec3, 1, TEST_STRLEN4, TEST_STR4);
rec4.Tag = 1;
std::cout << "\twriting new rec1" << "\n";
assert(TLV::WriteToMemory(backend, cursor, rec1) != nullptr);
std::cout << "\twriting new rec2" << "\n";
assert((cursor = TLV::WriteToMemory(backend, cursor, rec2)) != nullptr);
std::cout << "\twriting new rec3" << "\n";
assert(TLV::WriteToMemory(backend, cursor, rec3) != nullptr);
cursor = nullptr;
// the cursor should point at the next record,
// and rec4 should contain the same data as rec1.
std::cout << "\tFindTag 1" << "\n";
cursor = TLV::FindTag(backend, cursor, rec4);
assert(cursor != nullptr);
assert(cursor != backend.Start());
assert(cmpRecord(rec1, rec4));
std::cout << "\tFindTag 2" << "\n";
cursor = TLV::FindTag(backend, cursor, rec4);
assert(cursor != nullptr);
assert(cmpRecord(rec3, rec4));
TLV::SetRecord(rec4, 3, TEST_STRLEN3, TEST_STR3);
assert(TLV::WriteToMemory(backend, nullptr, rec4));
rec4.Tag = 2;
cursor = TLV::FindTag(backend, nullptr, rec4);
assert(cursor != nullptr);
TLV::DeleteRecord(backend, cursor);
assert(cursor[0] == 3);
}
bool
runSuite(Arena &backend, const char *label)
{
std::exception exc;
std::cout << backend << "\n";
std::cout << "running test suite " << label << ": ";
try {
tlvTestSuite(backend);
} catch (std::exception &exc){
std::cout << "FAILED: " << exc.what() << "\n";
return false;
}
std::cout << "OK" << "\n";
std::cout << "\tdestroying arena: ";
backend.Destroy();
std::cout << "OK" << "\n";
return true;
}
int
main(int argc, const char *argv[])
{
(void)argc; (void)argv;
Arena arenaStatic;
Arena arenaMem;
std::cout << "TESTPROG: " << argv[0] << "\n";
if (-1 == arenaStatic.SetStatic(arenaBuffer, ARENA_SIZE)) {
abort();
} else if (!runSuite(arenaStatic, "arenaStatic")) {
abort();
}
arenaStatic.Clear();
Arena arenaFile;
auto status = arenaFile.Create(ARENA_FILE, ARENA_SIZE);
if (status != 0) {
std::cerr << "Create failed with error " << status << "\n";
abort();
} else if (!runSuite(arenaFile, "arenaFile")) {
abort();
}
if (-1 == arenaMem.SetAlloc(ARENA_SIZE)) {
abort();
} else if (!runSuite(arenaMem, "arenaMem")) {
abort();
}
arenaMem.Clear();
std::cout << "OK" << "\n";
return 0;
}

446
trunk Executable file
View File

@ -0,0 +1,446 @@
#!/bin/bash
###############################################################################
# #
# Setup #
# #
###############################################################################
set -euo pipefail
readonly TRUNK_LAUNCHER_VERSION="1.2.7" # warning: this line is auto-updated
readonly SUCCESS_MARK="\033[0;32m✔\033[0m"
readonly FAIL_MARK="\033[0;31m✘\033[0m"
readonly PROGRESS_MARKS=("⡿" "⢿" "⣻" "⣽" "⣾" "⣷" "⣯" "⣟")
# This is how mktemp(1) decides where to create stuff in tmpfs.
readonly TMPDIR="${TMPDIR:-/tmp}"
KERNEL=$(uname | tr "[:upper:]" "[:lower:]")
if [[ ${KERNEL} == mingw64* || ${KERNEL} == msys* ]]; then
KERNEL="mingw"
fi
readonly KERNEL
MACHINE=$(uname -m)
if [[ $MACHINE == "aarch64" ]]; then
MACHINE="arm64";
fi
readonly MACHINE
PLATFORM="${KERNEL}-${MACHINE}"
readonly PLATFORM
PLATFORM_UNDERSCORE="${KERNEL}_${MACHINE}"
readonly PLATFORM_UNDERSCORE
# https://en.wikipedia.org/wiki/ANSI_escape_code#CSI_(Control_Sequence_Introducer)_sequences
# [nF is "cursor previous line" and moves to the beginning of the nth previous line
# [0K is "erase display" and clears from the cursor to the end of the screen
readonly CLEAR_LAST_MSG="\033[1F\033[0K"
if [[ ! -z ${CI:-} && "${CI}" = true && -z ${TRUNK_LAUNCHER_QUIET:-} ]]; then
TRUNK_LAUNCHER_QUIET=1
else
TRUNK_LAUNCHER_QUIET=${TRUNK_LAUNCHER_QUIET:-${TRUNK_QUIET:-false}}
fi
readonly TRUNK_LAUNCHER_DEBUG
if [[ ${TRUNK_LAUNCHER_QUIET} != false ]]; then
exec 3>&1 4>&2 &>/dev/null
fi
TRUNK_CACHE="${TRUNK_CACHE:-}"
if [[ -n ${TRUNK_CACHE} ]]; then
:
elif [[ -n ${XDG_CACHE_HOME:-} ]]; then
TRUNK_CACHE="${XDG_CACHE_HOME}/trunk"
else
TRUNK_CACHE="${HOME}/.cache/trunk"
fi
readonly TRUNK_CACHE
readonly CLI_DIR="${TRUNK_CACHE}/cli"
mkdir -p "${CLI_DIR}"
# platform check
readonly MINIMUM_MACOS_VERSION="10.15"
check_darwin_version() {
local osx_version
osx_version="$(sw_vers -productVersion)"
# trunk-ignore-begin(shellcheck/SC2312): the == will fail if anything inside the $() fails
if [[ "$(printf "%s\n%s\n" "${MINIMUM_MACOS_VERSION}" "${osx_version}" |
sort --version-sort |
head -n 1)" == "${MINIMUM_MACOS_VERSION}"* ]]; then
return
fi
# trunk-ignore-end(shellcheck/SC2312)
echo -e "${FAIL_MARK} Trunk requires at least MacOS ${MINIMUM_MACOS_VERSION}" \
"(yours is ${osx_version}). See https://docs.trunk.io for more info."
exit 1
}
if [[ ${PLATFORM} == "darwin-x86_64" || ${PLATFORM} == "darwin-arm64" ]]; then
check_darwin_version
elif [[ ${PLATFORM} == "linux-x86_64" || ${PLATFORM} == "linux-arm64" || ${PLATFORM} == "windows-x86_64" || ${PLATFORM} == "mingw-x86_64" ]]; then
:
else
echo -e "${FAIL_MARK} Trunk is only supported on Linux (x64_64, arm64), MacOS (x86_64, arm64), and Windows (x86_64)." \
"See https://docs.trunk.io for more info."
exit 1
fi
TRUNK_TMPDIR="${TMPDIR}/trunk-$(
set -e
id -u
)/launcher_logs"
readonly TRUNK_TMPDIR
mkdir -p "${TRUNK_TMPDIR}"
# For the `mv $TOOL_TMPDIR/trunk $TOOL_DIR` to be atomic (i.e. just inode renames), the source and destination filesystems need to be the same
TOOL_TMPDIR=$(mktemp -d "${CLI_DIR}/tmp.XXXXXXXXXX")
readonly TOOL_TMPDIR
cleanup() {
rm -rf "${TOOL_TMPDIR}"
if [[ $1 == "0" ]]; then
rm -rf "${TRUNK_TMPDIR}"
fi
}
trap 'cleanup $?' EXIT
# e.g. 2022-02-16-20-40-31-0800
dt_str() { date +"%Y-%m-%d-%H-%M-%S%z"; }
LAUNCHER_TMPDIR="${TOOL_TMPDIR}/launcher"
readonly LAUNCHER_TMPDIR
mkdir -p "${LAUNCHER_TMPDIR}"
if [[ -n ${TRUNK_LAUNCHER_DEBUG:-} ]]; then
set -x
fi
# launcher awk
#
# BEGIN{ORS="";}
# use "" as the output record separator
# ORS defaults to "\n" for bwk, which results in
# $(printf "foo bar" | awk '{print $2}') == "bar\n"
#
# {gsub(/\r/, "", $0)}
# for every input record (i.e. line), the regex "\r" should be replaced with ""
# This is necessary to handle CRLF files in a portable fashion.
#
# Some StackOverflow answers suggest using RS="\r?\n" to handle CRLF files (RS is the record
# separator, i.e. the line delimiter); unfortunately, original-awk only allows single-character
# values for RS (see https://www.gnu.org/software/gawk/manual/gawk.html#awk-split-records).
lawk() {
awk 'BEGIN{ORS="";}{gsub(/\r/, "", $0)}'"${1}" "${@:2}"
}
awk_test() {
# trunk-ignore-begin(shellcheck/SC2310,shellcheck/SC2312)
# SC2310 and SC2312 are about set -e not propagating to the $(); if that happens, the string
# comparison will fail and we'll claim the user's awk doesn't work
if [[ $(
set -e
printf 'k1: v1\n \tk2: v2\r\n' | lawk '/[ \t]+k2:/{print $2}'
) == 'v2' &&
$(
set -e
printf 'k1: v1\r\n\t k2: v2\r\n' | lawk '/[ \t]+k2:/{print $2}'
) == 'v2' ]]; then
return
fi
# trunk-ignore-end(shellcheck/SC2310,shellcheck/SC2312)
echo -e "${FAIL_MARK} Trunk does not work with your awk;" \
"please report this at https://slack.trunk.io."
echo -e "Your version of awk is:"
awk --version || awk -Wversion
exit 1
}
awk_test
readonly CURL_FLAGS="${CURL_FLAGS:- -vvv --max-time 120 --retry 3 --fail}"
readonly WGET_FLAGS="${WGET_FLAGS:- --verbose --tries=3 --limit-rate=10M}"
TMP_DOWNLOAD_LOG="${TRUNK_TMPDIR}/download-$(
set -e
dt_str
).log"
readonly TMP_DOWNLOAD_LOG
# Detect whether we should use wget or curl.
if command -v wget &>/dev/null; then
download_cmd() {
local url="${1}"
local output_to="${2}"
# trunk-ignore-begin(shellcheck/SC2312): we don't care if wget --version errors
cat >>"${TMP_DOWNLOAD_LOG}" <<EOF
Using wget to download '${url}' to '${output_to}'
Is Trunk up?: https://status.trunk.io
WGET_FLAGS: ${WGET_FLAGS}
wget --version:
$(wget --version 2>&1)
EOF
# trunk-ignore-end(shellcheck/SC2312)
# Support BusyBox wget
if wget --help 2>&1 | grep BusyBox; then
wget "${url}" -O "${output_to}" 2>>"${TMP_DOWNLOAD_LOG}" &
else
# trunk-ignore(shellcheck/SC2086): we deliberately don't quote WGET_FLAGS
wget ${WGET_FLAGS} "${url}" --output-document "${output_to}" 2>>"${TMP_DOWNLOAD_LOG}" &
fi
}
elif command -v curl &>/dev/null; then
download_cmd() {
local url="${1}"
local output_to="${2}"
# trunk-ignore-begin(shellcheck/SC2312): we don't care if curl --version errors
cat >>"${TMP_DOWNLOAD_LOG}" <<EOF
Using curl to download '${url}' to '${output_to}'
Is Trunk up?: https://status.trunk.io
CURL_FLAGS: ${CURL_FLAGS}
curl --version:
$(curl --version)
EOF
# trunk-ignore-end(shellcheck/SC2312)
# trunk-ignore(shellcheck/SC2086): we deliberately don't quote CURL_FLAGS
curl ${CURL_FLAGS} "${url}" --output "${output_to}" 2>>"${TMP_DOWNLOAD_LOG}" &
}
else
download_cmd() {
echo -e "${FAIL_MARK} Cannot download '${url}'; please install curl or wget."
exit 1
}
fi
download_url() {
local url="${1}"
local output_to="${2}"
local progress_message="${3:-}"
if [[ -n ${progress_message} ]]; then
echo -e "${PROGRESS_MARKS[0]} ${progress_message}..."
fi
download_cmd "${url}" "${output_to}"
local download_pid="$!"
local i_prog=0
while [[ -d "/proc/${download_pid}" && -n ${progress_message} ]]; do
echo -e "${CLEAR_LAST_MSG}${PROGRESS_MARKS[${i_prog}]} ${progress_message}..."
sleep 0.2
i_prog=$(((i_prog + 1) % ${#PROGRESS_MARKS[@]}))
done
local download_log
if ! wait "${download_pid}"; then
download_log="${TRUNK_TMPDIR}/launcher-download-$(
set -e
dt_str
).log"
mv "${TMP_DOWNLOAD_LOG}" "${download_log}"
echo -e "${CLEAR_LAST_MSG}${FAIL_MARK} ${progress_message}... FAILED (see ${download_log})"
echo -e "Please check your connection and try again." \
"If you continue to see this error message," \
"consider reporting it to us at https://slack.trunk.io."
exit 1
fi
if [[ -n ${progress_message} ]]; then
echo -e "${CLEAR_LAST_MSG}${SUCCESS_MARK} ${progress_message}... done"
fi
}
# sha256sum is in coreutils, so we prefer that over shasum, which is installed with perl
if command -v sha256sum &>/dev/null; then
:
elif command -v shasum &>/dev/null; then
sha256sum() { shasum -a 256 "$@"; }
else
sha256sum() {
echo -e "${FAIL_MARK} Cannot compute sha256; please install sha256sum or shasum"
exit 1
}
fi
###############################################################################
# #
# CLI resolution functions #
# #
###############################################################################
trunk_yaml_abspath() {
local repo_head
local cwd
if repo_head=$(git rev-parse --show-toplevel 2>/dev/null); then
echo "${repo_head}/.trunk/trunk.yaml"
elif [[ -f .trunk/trunk.yaml ]]; then
cwd="$(pwd)"
echo "${cwd}/.trunk/trunk.yaml"
else
echo ""
fi
}
read_cli_version_from() {
local config_abspath="${1}"
local cli_version
cli_version="$(
set -e
lawk '/[ \t]+version:/{print $2; exit;}' "${config_abspath}"
)"
if [[ -z ${cli_version} ]]; then
echo -e "${FAIL_MARK} Invalid .trunk/trunk.yaml, no cli version found." \
"See https://docs.trunk.io for more info." >&2
exit 1
fi
echo "${cli_version}"
}
download_cli() {
local dl_version="${1}"
local expected_sha256="${2}"
local actual_sha256
readonly TMP_INSTALL_DIR="${LAUNCHER_TMPDIR}/install"
mkdir -p "${TMP_INSTALL_DIR}"
TRUNK_NEW_URL_VERSION=0.10.2-beta.1
if sort --help 2>&1 | grep BusyBox; then
readonly URL="https://trunk.io/releases/${dl_version}/trunk-${dl_version}-${PLATFORM}.tar.gz"
else
if [[ "$(printf "%s\n%s\n" "${TRUNK_NEW_URL_VERSION}" "${dl_version}" |
sort --version-sort |
head -n 1 || true)" == "${TRUNK_NEW_URL_VERSION}"* ]]; then
readonly URL="https://trunk.io/releases/${dl_version}/trunk-${dl_version}-${PLATFORM}.tar.gz"
else
readonly URL="https://trunk.io/releases/trunk-${dl_version}.${KERNEL}.tar.gz"
fi
fi
readonly DOWNLOAD_TAR_GZ="${TMP_INSTALL_DIR}/download-${dl_version}.tar.gz"
download_url "${URL}" "${DOWNLOAD_TAR_GZ}" "Downloading Trunk ${dl_version}"
if [[ -n ${expected_sha256:-} ]]; then
local verifying_text="Verifying Trunk sha256..."
echo -e "${PROGRESS_MARKS[0]} ${verifying_text}"
actual_sha256="$(
set -e
sha256sum "${DOWNLOAD_TAR_GZ}" | lawk '{print $1}'
)"
if [[ ${actual_sha256} != "${expected_sha256}" ]]; then
echo -e "${CLEAR_LAST_MSG}${FAIL_MARK} ${verifying_text} FAILED"
echo "Expected sha256: ${expected_sha256}"
echo " Actual sha256: ${actual_sha256}"
exit 1
fi
echo -e "${CLEAR_LAST_MSG}${SUCCESS_MARK} ${verifying_text} done"
fi
local unpacking_text="Unpacking Trunk..."
echo -e "${PROGRESS_MARKS[0]} ${unpacking_text}"
tar --strip-components=1 -C "${TMP_INSTALL_DIR}" -xf "${DOWNLOAD_TAR_GZ}"
echo -e "${CLEAR_LAST_MSG}${SUCCESS_MARK} ${unpacking_text} done"
rm -f "${DOWNLOAD_TAR_GZ}"
mkdir -p "${TOOL_DIR}"
readonly OLD_TOOL_DIR="${CLI_DIR}/${version}"
# Create a backwards compatability link for old versions of trunk that want to write their
# crashpad_handlers to that dir.
if [[ ! -e ${OLD_TOOL_DIR} ]]; then
ln -sf "${TOOL_PART}" "${OLD_TOOL_DIR}"
fi
mv -n "${TMP_INSTALL_DIR}/trunk" "${TOOL_DIR}/" || true
rm -rf "${TMP_INSTALL_DIR}"
}
###############################################################################
# #
# CLI resolution #
# #
###############################################################################
CONFIG_ABSPATH="$(
set -e
trunk_yaml_abspath
)"
readonly CONFIG_ABSPATH
version="${TRUNK_CLI_VERSION:-}"
if [[ -n ${version:-} ]]; then
:
elif [[ -f ${CONFIG_ABSPATH} ]]; then
version="$(
set -e
read_cli_version_from "${CONFIG_ABSPATH}"
)"
version_sha256="$(
set -e
lawk "/${PLATFORM_UNDERSCORE}:/"'{print $2}' "${CONFIG_ABSPATH}"
)"
else
readonly LATEST_FILE="${LAUNCHER_TMPDIR}/latest"
download_url "https://trunk.io/releases/latest" "${LATEST_FILE}"
version=$(
set -e
lawk '/version:/{print $2}' "${LATEST_FILE}"
)
version_sha256=$(
set -e
lawk "/${PLATFORM_UNDERSCORE}:/"'{print $2}' "${LATEST_FILE}"
)
fi
readonly TOOL_PART="${version}-${PLATFORM}"
readonly TOOL_DIR="${CLI_DIR}/${TOOL_PART}"
if [[ ! -e ${TOOL_DIR}/trunk ]]; then
download_cli "${version}" "${version_sha256:-}"
echo # add newline between launcher and CLI output
fi
if [[ ${TRUNK_LAUNCHER_QUIET} != false ]]; then
exec 1>&3 3>&- 2>&4 4>&-
fi
###############################################################################
# #
# CLI invocation #
# #
###############################################################################
if [[ -n ${LATEST_FILE:-} ]]; then
mv -n "${LATEST_FILE}" "${TOOL_DIR}/version" >/dev/null 2>&1 || true
fi
# NOTE: exec will overwrite the process image, so trap will not catch the exit signal.
# Therefore, run cleanup manually here.
cleanup 0
exec \
env TRUNK_LAUNCHER_VERSION="${TRUNK_LAUNCHER_VERSION}" \
env TRUNK_LAUNCHER_PATH="${BASH_SOURCE[0]}" \
"${TOOL_DIR}/trunk" "$@"