Compare commits
1 Commits
Author | SHA1 | Date |
---|---|---|
|
0071b84ab5 |
|
@ -1,19 +0,0 @@
|
||||||
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
|
|
|
@ -1,67 +0,0 @@
|
||||||
# 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
|
|
16
.clang-tidy
16
.clang-tidy
|
@ -7,13 +7,11 @@ Checks: >-
|
||||||
performance-*,
|
performance-*,
|
||||||
readability-*,
|
readability-*,
|
||||||
-bugprone-lambda-function-name,
|
-bugprone-lambda-function-name,
|
||||||
-bugprone-easily-swappable-parameters,
|
|
||||||
-bugprone-reserved-identifier,
|
-bugprone-reserved-identifier,
|
||||||
-cppcoreguidelines-avoid-goto,
|
-cppcoreguidelines-avoid-goto,
|
||||||
-cppcoreguidelines-avoid-magic-numbers,
|
-cppcoreguidelines-avoid-magic-numbers,
|
||||||
-cppcoreguidelines-avoid-non-const-global-variables,
|
-cppcoreguidelines-avoid-non-const-global-variables,
|
||||||
-cppcoreguidelines-pro-bounds-array-to-pointer-decay,
|
-cppcoreguidelines-pro-bounds-array-to-pointer-decay,
|
||||||
-cppcoreguidelines-pro-bounds-pointer-arithmetic,
|
|
||||||
-cppcoreguidelines-pro-type-vararg,
|
-cppcoreguidelines-pro-type-vararg,
|
||||||
-google-readability-braces-around-statements,
|
-google-readability-braces-around-statements,
|
||||||
-google-readability-function-size,
|
-google-readability-function-size,
|
||||||
|
@ -22,10 +20,14 @@ Checks: >-
|
||||||
-modernize-use-nodiscard,
|
-modernize-use-nodiscard,
|
||||||
-modernize-use-trailing-return-type,
|
-modernize-use-trailing-return-type,
|
||||||
-performance-unnecessary-value-param,
|
-performance-unnecessary-value-param,
|
||||||
-readability-identifier-length,
|
-readability-magic-numbers,
|
||||||
-readability-magic-numbers
|
|
||||||
|
|
||||||
CheckOptions:
|
CheckOptions:
|
||||||
readability-function-cognitive-complexity.Threshold: 100
|
- key: readability-function-cognitive-complexity.Threshold
|
||||||
readability-function-cognitive-complexity.IgnoreMacros: true
|
value: 100
|
||||||
readability-identifier-naming.ClassCase: CamelCase
|
- 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
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
.trunk
|
||||||
.vc
|
.vc
|
||||||
.vscode
|
.vscode
|
||||||
|
|
||||||
|
@ -9,7 +10,6 @@ build
|
||||||
core
|
core
|
||||||
core.*
|
core.*
|
||||||
cmake-build-*
|
cmake-build-*
|
||||||
compile_commands.json
|
|
||||||
|
|
||||||
bufferTest
|
bufferTest
|
||||||
dictionaryTest
|
dictionaryTest
|
||||||
|
|
|
@ -7,9 +7,6 @@
|
||||||
<option name="FUNCTION_BRACE_PLACEMENT" value="2" />
|
<option name="FUNCTION_BRACE_PLACEMENT" value="2" />
|
||||||
<option name="FUNCTION_TOP_AFTER_RETURN_TYPE_WRAP" value="2" />
|
<option name="FUNCTION_TOP_AFTER_RETURN_TYPE_WRAP" value="2" />
|
||||||
</Objective-C>
|
</Objective-C>
|
||||||
<clangFormatSettings>
|
|
||||||
<option name="ENABLED" value="true" />
|
|
||||||
</clangFormatSettings>
|
|
||||||
<files>
|
<files>
|
||||||
<extensions>
|
<extensions>
|
||||||
<pair source="cc" header="h" fileNamingConvention="PASCAL_CASE" />
|
<pair source="cc" header="h" fileNamingConvention="PASCAL_CASE" />
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
<component name="InspectionProjectProfileManager">
|
<component name="InspectionProjectProfileManager">
|
||||||
<profile version="1.0">
|
<profile version="1.0">
|
||||||
<option name="myName" value="Project Default" />
|
<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="Eslint" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||||
<inspection_tool class="SpellCheckingInspection" enabled="false" level="TYPO" enabled_by_default="false">
|
<inspection_tool class="SpellCheckingInspection" enabled="false" level="TYPO" enabled_by_default="false">
|
||||||
<option name="processCode" value="true" />
|
<option name="processCode" value="true" />
|
||||||
|
|
|
@ -1,8 +1,2 @@
|
||||||
<?xml version="1.0" encoding="UTF-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>
|
|
|
@ -1,7 +1,4 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<project version="4">
|
<project version="4">
|
||||||
<component name="Black">
|
|
||||||
<option name="sdkName" value="Python 3.10 (scsl)" />
|
|
||||||
</component>
|
|
||||||
<component name="CMakeWorkspace" PROJECT_DIR="$PROJECT_DIR$" />
|
<component name="CMakeWorkspace" PROJECT_DIR="$PROJECT_DIR$" />
|
||||||
</project>
|
</project>
|
|
@ -1,8 +0,0 @@
|
||||||
*out
|
|
||||||
*logs
|
|
||||||
*actions
|
|
||||||
*notifications
|
|
||||||
*tools
|
|
||||||
plugins
|
|
||||||
user_trunk.yaml
|
|
||||||
user.yaml
|
|
|
@ -1,27 +0,0 @@
|
||||||
# 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
|
|
|
@ -0,0 +1,359 @@
|
||||||
|
#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
|
|
@ -1,37 +1,28 @@
|
||||||
///
|
///
|
||||||
/// \file Arena.h
|
/// \file Arena.h
|
||||||
/// \author K. Isom <kyle@imap.cc>
|
/// \author K. Isom
|
||||||
/// \date 2023-10-06
|
/// \date 2023-10-06
|
||||||
/// \brief Memory management using an arena.
|
/// \brief Memory management using an arena.
|
||||||
///
|
///
|
||||||
/// Arena defines a memory management backend for pre-allocating memory.
|
/// Arena defines a memory management backend for pre-allocating memory.
|
||||||
///
|
///
|
||||||
/// Copyright 2023 K. Isom <kyle@imap.cc>
|
/// \section PLATFORM SUPPORT
|
||||||
///
|
|
||||||
/// 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
|
#ifndef KIMODEM_ARENA_H
|
||||||
#define KIMODEM_ARENA_H
|
#define KIMODEM_ARENA_H
|
||||||
|
|
||||||
|
|
||||||
#include <cstddef>
|
|
||||||
#include <cstdint>
|
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <sys/stat.h>
|
#include <sys/stat.h>
|
||||||
|
#include <cstddef>
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
#include "sctest/Exceptions.h"
|
#include "Exceptions.h"
|
||||||
|
|
||||||
|
|
||||||
#if defined(__WIN64__) || defined(__WIN32__) || defined(WIN32)
|
#if defined(__WIN64__) || defined(__WIN32__) || defined(WIN32)
|
||||||
|
@ -61,7 +52,7 @@ enum class ArenaType
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
/// \brief Fixed, pre-allocated memory.
|
/// Arena is the class that implements a memory arena.
|
||||||
///
|
///
|
||||||
/// The Arena uses the concept of a cursor to point to memory in the arena. The
|
/// 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
|
/// #Start and #End methods return pointers to the start and end of the
|
||||||
|
@ -70,7 +61,7 @@ enum class ArenaType
|
||||||
/// The arena should be initialized with one of the Set methods (SetStatic,
|
/// The arena should be initialized with one of the Set methods (SetStatic,
|
||||||
/// SetAlloc) or one of the file-based options (Create, Open, MemoryMap). At
|
/// 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
|
/// 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 {
|
class Arena {
|
||||||
public:
|
public:
|
||||||
/// An Arena is initialized with no backing memory.
|
/// An Arena is initialized with no backing memory.
|
||||||
|
@ -121,7 +112,12 @@ public:
|
||||||
/// \param path The path to the file that should be created.
|
/// \param path The path to the file that should be created.
|
||||||
/// \param fileSize The size of the file to create.
|
/// \param fileSize The size of the file to create.
|
||||||
/// \return Returns 0 on success and -1 on error.
|
/// \return Returns 0 on success and -1 on error.
|
||||||
|
#if defined(__posix__) || defined(__linux__) || defined(__APPLE__)
|
||||||
int Create(const char *path, size_t fileSize);
|
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
|
/// 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
|
/// Unix-based platforms, the arena will be backed by a memory via
|
|
@ -2,32 +2,16 @@
|
||||||
/// \file Buffer.cc
|
/// \file Buffer.cc
|
||||||
/// \author K. Isom <kyle@imap.cc>
|
/// \author K. Isom <kyle@imap.cc>
|
||||||
/// \date 2023-10-09
|
/// \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 <cassert>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
#include <iomanip>
|
|
||||||
#include <ios>
|
#include <ios>
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
|
#include <iomanip>
|
||||||
|
|
||||||
#include <scsl/Buffer.h>
|
#include "Buffer.h"
|
||||||
|
|
||||||
|
|
||||||
namespace scsl {
|
namespace scsl {
|
||||||
|
|
||||||
|
@ -83,46 +67,13 @@ 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)
|
: contents(nullptr), length(0), capacity(0), autoTrim(true)
|
||||||
{
|
{
|
||||||
this->Append(s);
|
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
|
bool
|
||||||
Buffer::Append(const char *s)
|
Buffer::Append(const char *s)
|
||||||
{
|
{
|
||||||
|
@ -133,7 +84,7 @@ Buffer::Append(const char *s)
|
||||||
|
|
||||||
|
|
||||||
bool
|
bool
|
||||||
Buffer::Append(const std::string &s)
|
Buffer::Append(const std::string s)
|
||||||
{
|
{
|
||||||
return this->Append((const uint8_t *) s.c_str(), s.size());
|
return this->Append((const uint8_t *) s.c_str(), s.size());
|
||||||
}
|
}
|
||||||
|
@ -142,30 +93,15 @@ Buffer::Append(const std::string &s)
|
||||||
bool
|
bool
|
||||||
Buffer::Append(const uint8_t *data, const size_t datalen)
|
Buffer::Append(const uint8_t *data, const size_t datalen)
|
||||||
{
|
{
|
||||||
if (datalen == 0) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto resized = false;
|
auto resized = false;
|
||||||
auto newCap = this->mustGrow(datalen);
|
auto newCap = this->mustGrow(datalen);
|
||||||
|
|
||||||
if (newCap > 0) {
|
if (newCap > 0) {
|
||||||
this->Resize(newCap);
|
this->Resize(newCap);
|
||||||
resized = true;
|
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);
|
assert(this->contents != nullptr);
|
||||||
if (this->contents == nullptr) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
memcpy(this->contents + this->length, data, datalen);
|
memcpy(this->contents + this->length, data, datalen);
|
||||||
this->length += datalen;
|
this->length += datalen;
|
||||||
return resized;
|
return resized;
|
||||||
|
@ -189,7 +125,7 @@ Buffer::Insert(const size_t index, const char *s)
|
||||||
|
|
||||||
|
|
||||||
bool
|
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());
|
return this->Insert(index, (const uint8_t *) s.c_str(), s.size());
|
||||||
}
|
}
|
||||||
|
@ -240,13 +176,7 @@ Buffer::Resize(size_t newCapacity)
|
||||||
auto newContents = new uint8_t[newCapacity];
|
auto newContents = new uint8_t[newCapacity];
|
||||||
|
|
||||||
memset(newContents, 0, 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);
|
memcpy(newContents, this->contents, this->length);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -273,28 +203,6 @@ Buffer::Trim()
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void
|
|
||||||
Buffer::DisableAutoTrim()
|
|
||||||
{
|
|
||||||
this->autoTrim = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void
|
|
||||||
Buffer::EnableAutoTrim()
|
|
||||||
{
|
|
||||||
this->autoTrim = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
bool
|
|
||||||
Buffer::AutoTrimIsEnabled()
|
|
||||||
{
|
|
||||||
return this->autoTrim;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void
|
void
|
||||||
Buffer::Clear()
|
Buffer::Clear()
|
||||||
{
|
{
|
||||||
|
@ -383,11 +291,7 @@ Buffer::shiftRight(size_t offset, size_t delta)
|
||||||
resized = true;
|
resized = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this->length < offset) {
|
if (this->length == 0) return 0;
|
||||||
for (size_t i = this->length; i < offset; i++) {
|
|
||||||
this->contents[i] = ' ';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
memmove(this->contents + (offset + delta), this->contents + offset,
|
memmove(this->contents + (offset + delta), this->contents + offset,
|
|
@ -1,5 +1,5 @@
|
||||||
///
|
///
|
||||||
/// \file include/scsl/Buffer.h
|
/// \file Buffer.h
|
||||||
/// \author K. Isom <kyle@imap.cc>
|
/// \author K. Isom <kyle@imap.cc>
|
||||||
/// \date 2023-10-09
|
/// \date 2023-10-09
|
||||||
/// \brief Buffer implements basic line buffers.
|
/// \brief Buffer implements basic line buffers.
|
||||||
|
@ -8,32 +8,17 @@
|
||||||
/// editing. It allocates memory in powers of two, and will grow or shrink
|
/// editing. It allocates memory in powers of two, and will grow or shrink
|
||||||
/// as needed.
|
/// 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
|
#ifndef KGE_BUFFER_H
|
||||||
#define KGE_BUFFER_H
|
#define KGE_BUFFER_H
|
||||||
|
|
||||||
#include <cstdint>
|
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
|
|
||||||
namespace scsl {
|
namespace scsl {
|
||||||
|
|
||||||
/// \brief Basic line buffer.
|
/// Buffer is a basic line buffer.
|
||||||
///
|
///
|
||||||
/// The buffer manages its own internal memory, growing and shrinking
|
/// The buffer manages its own internal memory, growing and shrinking
|
||||||
/// as needed. Its capacity is separate from its length; the optimal
|
/// as needed. Its capacity is separate from its length; the optimal
|
||||||
|
@ -47,89 +32,76 @@ namespace scsl {
|
||||||
/// memory if possible, but only if #AutoTrimIsEnabled (it is by default).
|
/// memory if possible, but only if #AutoTrimIsEnabled (it is by default).
|
||||||
class Buffer {
|
class Buffer {
|
||||||
public:
|
public:
|
||||||
/// \brief Construct an empty buffer with no memory allocated.
|
/// A Buffer can be constructed empty, with no memory allocated (yet).
|
||||||
Buffer();
|
Buffer();
|
||||||
|
|
||||||
/// \buffer Constructor with explicit memory capacity.
|
/// A Buffer can be constructed with an explicit capacity.
|
||||||
///
|
///
|
||||||
/// \param initialCapacity The initial allocation size for the
|
/// \param initialCapacity The initial allocation size for the buffer.
|
||||||
/// buffer.
|
|
||||||
explicit Buffer(size_t initialCapacity);
|
explicit Buffer(size_t initialCapacity);
|
||||||
|
|
||||||
/// \brief Construct with a C-style string.
|
/// A Buffer can be initialized with a starting C-style string.
|
||||||
explicit Buffer(const char *s);
|
explicit Buffer(const char *s);
|
||||||
|
|
||||||
/// \buffer Construct with an initial string.
|
/// A Buffer can be initialized with a starting string.
|
||||||
explicit Buffer(const std::string& s);
|
explicit Buffer(const std::string s);
|
||||||
|
|
||||||
~Buffer();
|
~Buffer()
|
||||||
|
{ this->Reclaim(); }
|
||||||
|
|
||||||
/// \brief Retrieve the buffer's contents.
|
/// Contents returns the Buffer's contents.
|
||||||
uint8_t *Contents() const;
|
uint8_t *Contents() const
|
||||||
|
{ return this->contents; }
|
||||||
|
|
||||||
std::string ToString() const;
|
/// Length returns the length of the data currently stored in the
|
||||||
|
/// buffer.
|
||||||
|
size_t Length() const
|
||||||
|
{ return this->length; };
|
||||||
|
|
||||||
/// \brief The length of data stored in the buffer.
|
/// Capacity returns the amount of memory allocated to the Buffer.
|
||||||
///
|
size_t Capacity() const
|
||||||
/// \return The number of bytes stored in the Buffer.
|
{ return this->capacity; }
|
||||||
size_t Length() const;
|
|
||||||
|
|
||||||
/// \brief Return the amount of memory allocated for the
|
/// Append copies in a C-style string to the end of the buffer.
|
||||||
/// Buffer.
|
|
||||||
size_t Capacity() const;
|
|
||||||
|
|
||||||
/// \brief Append a C-style string to the end of the buffer.
|
|
||||||
///
|
///
|
||||||
/// \param s The string to append.
|
/// \param s The string to append.
|
||||||
/// \return True if the Buffer was resized.
|
/// \return True if the Buffer was resized.
|
||||||
bool Append(const char *s);
|
bool Append(const char *s);
|
||||||
|
|
||||||
/// Append Append a string to the end of the buffer.
|
/// Append copies in a string to the end of the buffer.
|
||||||
///
|
///
|
||||||
/// \param s The string to append.
|
/// \param s The string to append.
|
||||||
/// \return True if the Buffer was resized.
|
/// \return True if the Buffer was resized.
|
||||||
bool Append(const std::string &s);
|
bool Append(const std::string s);
|
||||||
|
|
||||||
/// \brief Append a byte buffer to the end of the buffer.
|
/// Append copies in a byte buffer to the end of the buffer.
|
||||||
///
|
///
|
||||||
/// \param data The byte buffer to insert.
|
/// \param data The byte buffer to insert.
|
||||||
/// \param datalen The length of the byte buffer.
|
/// \param datalen The length of the byte buffer.
|
||||||
/// \return True if the Buffer was resized.
|
/// \return True if the Buffer was resized.
|
||||||
bool Append(const uint8_t *data, const size_t datalen);
|
bool Append(const uint8_t *data, const size_t datalen);
|
||||||
|
|
||||||
/// \brief Append a single character to the end of the buffer.
|
/// Append copies a single character to the end of the buffer.
|
||||||
///
|
///
|
||||||
/// \param c The character to append.
|
/// \param c The character to append.
|
||||||
/// \return True if the Buffer was resized.
|
/// \return True if the Buffer was resized.
|
||||||
bool Append(const uint8_t c);
|
bool Append(const uint8_t c);
|
||||||
|
|
||||||
/// \brief Insert a C-style string into the buffer at index.
|
/// Insert copies 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 index The index to insert the string at.
|
||||||
/// \param s The string to insert.
|
/// \param s The string to insert.
|
||||||
/// \return True if the Buffer was resized.
|
/// \return True if the Buffer was resized.
|
||||||
bool Insert(const size_t index, const char *s);
|
bool Insert(const size_t index, const char *s);
|
||||||
|
|
||||||
/// \brief Insert a string into the buffer at index.
|
/// Insert copies 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 index The index the string should be inserted at.
|
||||||
/// \param s The string to insert.
|
/// \param s The string to insert.
|
||||||
/// \return True if the Buffer was resized.
|
/// \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);
|
||||||
|
|
||||||
/// \brief Insert a uint8_t buffer into the buffer at index.
|
/// Insert copies 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 index The index to insert the buffer at.
|
||||||
/// \param data The buffer to insert.
|
/// \param data The buffer to insert.
|
||||||
|
@ -138,25 +110,21 @@ public:
|
||||||
bool
|
bool
|
||||||
Insert(const size_t index, const uint8_t *data, const size_t datalen);
|
Insert(const size_t index, const uint8_t *data, const size_t datalen);
|
||||||
|
|
||||||
/// \brief Insert a character into the buffer at index.
|
/// Insert copies 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 index The index to insert the character at.
|
||||||
/// \param c The character to insert.
|
/// \param c The character to insert.
|
||||||
/// \return True if the Buffer was resized.
|
/// \return True if the Buffer was resized.
|
||||||
bool Insert(const size_t index, const uint8_t c);
|
bool Insert(const size_t index, const uint8_t c);
|
||||||
|
|
||||||
/// \brief Remove `count` bytes from the buffer at `index`.
|
/// Remove removes `count` bytes from the buffer at `index`.
|
||||||
///
|
///
|
||||||
/// \param index The starting index to remove bytes from.
|
/// \param index The starting index to remove bytes from.
|
||||||
/// \param count The number of bytes to remove.
|
/// \param count The number of bytes to remove.
|
||||||
/// \return True if the Buffer was resized.
|
/// \return True if the Buffer was resized.
|
||||||
bool Remove(const size_t index, const size_t count);
|
bool Remove(const size_t index, const size_t count);
|
||||||
|
|
||||||
/// \brief Remove removes a single byte from the buffer.
|
/// Remove removes a single byte from the buffer.
|
||||||
///
|
///
|
||||||
/// \param index The index pointing to the byte to be removed.
|
/// \param index The index pointing to the byte to be removed.
|
||||||
/// \return True if the Buffer was resized.
|
/// \return True if the Buffer was resized.
|
||||||
|
@ -164,7 +132,7 @@ public:
|
||||||
|
|
||||||
/* memory management */
|
/* memory management */
|
||||||
|
|
||||||
/// \brief Changes the capacity of the buffer to `newCapacity`.
|
/// Resize changes the capacity of the buffer to `newCapacity`.
|
||||||
///
|
///
|
||||||
/// If newCapacity is less than the length of the Buffer, it
|
/// If newCapacity is less than the length of the Buffer, it
|
||||||
/// will remove enough bytes from the end to make this happen.
|
/// will remove enough bytes from the end to make this happen.
|
||||||
|
@ -172,23 +140,27 @@ public:
|
||||||
/// \param newCapacity The new capacity for the Buffer.
|
/// \param newCapacity The new capacity for the Buffer.
|
||||||
void Resize(size_t newCapacity);
|
void Resize(size_t newCapacity);
|
||||||
|
|
||||||
/// \brief Resize the Buffer capacity based on its length.
|
/// Trim will resize the Buffer to an appropriate size based on
|
||||||
|
/// its length.
|
||||||
///
|
///
|
||||||
/// \return The new capacity of the Buffer.
|
/// \return The new capacity of the Buffer.
|
||||||
size_t Trim();
|
size_t Trim();
|
||||||
|
|
||||||
/// DisableAutoTrim prevents the #Buffer from automatically
|
/// DisableAutoTrim prevents the #Buffer from automatically
|
||||||
/// trimming memory after a call to #Remove.
|
/// trimming memory after a call to #Remove.
|
||||||
void DisableAutoTrim();
|
void DisableAutoTrim()
|
||||||
|
{ this->autoTrim = false; }
|
||||||
|
|
||||||
/// EnableAutoTrim enables automatically trimming memory after
|
/// EnableAutoTrim enables automatically trimming memory after
|
||||||
/// calls to #Remove.
|
/// calls to #Remove.
|
||||||
void EnableAutoTrim();
|
void EnableAutoTrim()
|
||||||
|
{ this->autoTrim = true; }
|
||||||
|
|
||||||
/// AutoTrimIsEnabled returns true if autotrim is enabled.
|
/// AutoTrimIsEnabled returns true if autotrim is enabled.
|
||||||
///
|
///
|
||||||
/// \return #Remove will call Trim.
|
/// \return #Remove will call Trim.
|
||||||
bool AutoTrimIsEnabled();
|
bool AutoTrimIsEnabled()
|
||||||
|
{ return this->autoTrim; }
|
||||||
|
|
||||||
/// Clear removes the data stored in the buffer. It will not
|
/// Clear removes the data stored in the buffer. It will not
|
||||||
/// call #Trim; the capacity of the buffer will not be altered.
|
/// call #Trim; the capacity of the buffer will not be altered.
|
|
@ -4,13 +4,13 @@ find_package(Doxygen)
|
||||||
if (${DOXYGEN_FOUND})
|
if (${DOXYGEN_FOUND})
|
||||||
set(DOXYGEN_GENERATE_MAN YES)
|
set(DOXYGEN_GENERATE_MAN YES)
|
||||||
set(DOXYGEN_GENERATE_LATEX YES)
|
set(DOXYGEN_GENERATE_LATEX YES)
|
||||||
set(DOXYGEN_EXTRACT_ALL YES)
|
#set(DOXYGEN_EXTRACT_ALL YES)
|
||||||
message(STATUS "Doxygen found, building docs.")
|
message(STATUS "Doxygen found, building docs.")
|
||||||
|
|
||||||
doxygen_add_docs(scsl_docs
|
doxygen_add_docs(scsl_docs
|
||||||
${HEADER_FILES}
|
${HEADER_FILES} ${SOURCE_FILES}
|
||||||
ALL
|
|
||||||
USE_STAMP_FILE)
|
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}/html DESTINATION share/doc/scsl)
|
||||||
install(DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/man DESTINATION share)
|
install(DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/man DESTINATION share)
|
||||||
|
|
169
CMakeLists.txt
169
CMakeLists.txt
|
@ -1,91 +1,64 @@
|
||||||
cmake_minimum_required(VERSION 3.22)
|
cmake_minimum_required(VERSION 3.22)
|
||||||
project(scsl LANGUAGES CXX
|
project(scsl LANGUAGES CXX
|
||||||
VERSION 1.1.4
|
VERSION 0.1.1
|
||||||
DESCRIPTION "Shimmering Clarity Standard Library")
|
DESCRIPTION "Shimmering Clarity Standard Library")
|
||||||
|
|
||||||
set(CMAKE_CXX_STANDARD 14)
|
set(CMAKE_CXX_STANDARD 14)
|
||||||
set(CMAKE_VERBOSE_MAKEFILES TRUE)
|
set(CMAKE_VERBOSE_MAKEFILES TRUE)
|
||||||
set(VERBOSE YES)
|
set(VERBOSE YES)
|
||||||
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
|
|
||||||
|
|
||||||
# compile options:
|
if (MSVC)
|
||||||
# -Wall Default to all errors.
|
add_compile_options("/W4" "$<$<CONFIG:RELEASE>:/O2>")
|
||||||
# -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 ()
|
else ()
|
||||||
# nothing special for gcc At the moment
|
# compile options:
|
||||||
endif ()
|
# -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 ()
|
||||||
|
endif ()
|
||||||
add_compile_definitions(SCSL_DESKTOP_BUILD)
|
add_compile_definitions(SCSL_DESKTOP_BUILD)
|
||||||
add_compile_definitions(SCSL_VERSION=${PROJECT_VERSION})
|
add_compile_definitions(SCSL_VERSION=${PROJECT_VERSION})
|
||||||
|
|
||||||
set(HEADER_FILES
|
set(HEADER_FILES scsl.h
|
||||||
include/scsl/scsl.h
|
Arena.h
|
||||||
include/scsl/Arena.h
|
Buffer.h
|
||||||
include/scsl/Buffer.h
|
Dictionary.h
|
||||||
include/scsl/Commander.h
|
Exceptions.h
|
||||||
include/scsl/Dictionary.h
|
Flag.h
|
||||||
include/scsl/Flags.h
|
StringUtil.h
|
||||||
include/scsl/SimpleConfig.h
|
TLV.h
|
||||||
include/scsl/StringUtil.h
|
Test.h
|
||||||
include/scsl/TLV.h
|
WinHelpers.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
|
set(SOURCE_FILES
|
||||||
src/sl/Arena.cc
|
Arena.cc
|
||||||
src/sl/Buffer.cc
|
Buffer.cc
|
||||||
src/sl/Commander.cc
|
Commander.cc
|
||||||
src/sl/Dictionary.cc
|
Commander.h
|
||||||
src/test/Exceptions.cc
|
Dictionary.cc
|
||||||
src/sl/Flags.cc
|
Exceptions.cc
|
||||||
src/sl/SimpleConfig.cc
|
Flag.cc
|
||||||
src/sl/StringUtil.cc
|
StringUtil.cc
|
||||||
src/sl/TLV.cc
|
TLV.cc
|
||||||
|
Test.cc
|
||||||
src/scmp/Math.cc
|
WinHelpers.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)
|
if (APPLE)
|
||||||
add_library(scsl
|
add_library(scsl
|
||||||
|
@ -97,43 +70,31 @@ add_library(scsl
|
||||||
${SOURCE_FILES} ${HEADER_FILES})
|
${SOURCE_FILES} ${HEADER_FILES})
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
add_executable(phonebook src/bin/phonebook.cc)
|
add_executable(phonebook phonebook.cc)
|
||||||
target_link_libraries(phonebook scsl)
|
target_link_libraries(phonebook scsl)
|
||||||
|
|
||||||
include(CTest)
|
include(CTest)
|
||||||
enable_testing()
|
enable_testing()
|
||||||
|
|
||||||
set(TEST_SOURCES)
|
add_executable(buffer_test bufferTest.cc)
|
||||||
macro(generate_test name)
|
target_link_libraries(buffer_test scsl)
|
||||||
add_executable(test_${name} test/${name}.cc ${TEST_SOURCES} ${ARGN})
|
add_test(bufferTest buffer_test)
|
||||||
target_link_libraries(test_${name} ${PROJECT_NAME})
|
|
||||||
target_include_directories(test_${name} PRIVATE test)
|
|
||||||
add_test(test_${name} test_${name})
|
|
||||||
endmacro()
|
|
||||||
|
|
||||||
# core standard library
|
add_executable(tlv_test tlvTest.cc)
|
||||||
generate_test(buffer)
|
target_link_libraries(tlv_test scsl)
|
||||||
generate_test(tlv)
|
add_test(tlvTest tlv_test)
|
||||||
generate_test(dictionary)
|
|
||||||
generate_test(stringutil)
|
|
||||||
|
|
||||||
# math and physics
|
add_executable(dictionary_test dictionaryTest.cc)
|
||||||
generate_test(coord2d)
|
target_link_libraries(dictionary_test scsl)
|
||||||
generate_test(madgwick)
|
add_test(dictionaryTest dictionary_test)
|
||||||
generate_test(math)
|
|
||||||
generate_test(orientation)
|
|
||||||
generate_test(quaternion)
|
|
||||||
generate_test(vector)
|
|
||||||
|
|
||||||
# test tooling
|
add_executable(flag_test flagTest.cc)
|
||||||
add_executable(flags-demo test/flags.cc)
|
target_link_libraries(flag_test scsl)
|
||||||
target_link_libraries(flags-demo ${PROJECT_NAME})
|
add_test(flagTest flag_test)
|
||||||
|
|
||||||
add_executable(simple-test-demo test/simple_suite_example.cc)
|
add_executable(stringutil_test stringutil_test.cc)
|
||||||
target_link_libraries(simple-test-demo ${PROJECT_NAME})
|
target_link_libraries(stringutil_test scsl)
|
||||||
|
add_test(stringutilTest stringutil_test)
|
||||||
add_executable(config-explorer test/config-explorer.cc)
|
|
||||||
target_link_libraries(config-explorer ${PROJECT_NAME})
|
|
||||||
|
|
||||||
include(CMakePackageConfigHelpers)
|
include(CMakePackageConfigHelpers)
|
||||||
write_basic_package_version_file(
|
write_basic_package_version_file(
|
||||||
|
@ -154,10 +115,10 @@ add_custom_target(deploy-docs
|
||||||
configure_file(scsl.pc.in scsl.pc @ONLY)
|
configure_file(scsl.pc.in scsl.pc @ONLY)
|
||||||
install(TARGETS scsl LIBRARY DESTINATION lib)
|
install(TARGETS scsl LIBRARY DESTINATION lib)
|
||||||
install(TARGETS phonebook RUNTIME DESTINATION bin)
|
install(TARGETS phonebook RUNTIME DESTINATION bin)
|
||||||
install(DIRECTORY include/ DESTINATION include)
|
install(FILES ${HEADER_FILES} DESTINATION include/scsl)
|
||||||
install(FILES scslConfig.cmake DESTINATION share/scsl/cmake)
|
install(FILES scslConfig.cmake DESTINATION share/scsl/cmake)
|
||||||
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/scsl.pc DESTINATION lib/pkgconfig)
|
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/scsl.pc DESTINATION lib/pkgconfig)
|
||||||
|
|
||||||
include(cmake/packaging.cmake)
|
include(CMakePack.txt)
|
||||||
include(cmake/docs.cmake)
|
include(CMakeDocs.txt)
|
||||||
endif()
|
endif()
|
||||||
|
|
|
@ -7,9 +7,6 @@ set(CPACK_PACKAGE_VERSION_MAJOR ${PROJECT_VERSION_MAJOR})
|
||||||
set(CPACK_PACKAGE_VERSION_MINOR ${PROJECT_VERSION_MINOR})
|
set(CPACK_PACKAGE_VERSION_MINOR ${PROJECT_VERSION_MINOR})
|
||||||
set(CPACK_PACKAGE_VERSION_PATCH ${PROJECT_VERSION_PATCH})
|
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
|
# Debian settings
|
||||||
set(CPACK_DEBIAN_PACKAGE_MAINTAINER "Shimmering Clarity")
|
set(CPACK_DEBIAN_PACKAGE_MAINTAINER "Shimmering Clarity")
|
||||||
set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "The Shimmering Clarity standard C++ library")
|
set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "The Shimmering Clarity standard C++ library")
|
||||||
|
@ -20,9 +17,9 @@ set(CPACK_DEBIAN_PACKAGE_GENERATE_SHLIBS ON)
|
||||||
set(CPACK_DEBIAN_FILE_NAME DEB-DEFAULT)
|
set(CPACK_DEBIAN_FILE_NAME DEB-DEFAULT)
|
||||||
|
|
||||||
if(LINUX)
|
if(LINUX)
|
||||||
set(CPACK_GENERATOR "DEB;RPM;STGZ;TGZ")
|
set(CPACK_GENERATOR "DEB;STGZ;TGZ")
|
||||||
elseif(APPLE)
|
elseif(APPLE)
|
||||||
set(CPACK_GENERATOR "STGZ;TGZ")
|
set(CPACK_GENERATOR "productbuild")
|
||||||
elseif(MSVC OR MSYS OR MINGW)
|
elseif(MSVC OR MSYS OR MINGW)
|
||||||
set(CPACK_GENERATOR "NSIS;ZIP")
|
set(CPACK_GENERATOR "NSIS;ZIP")
|
||||||
else()
|
else()
|
||||||
|
@ -38,3 +35,4 @@ set(CPACK_SOURCE_IGNORE_FILES
|
||||||
|
|
||||||
include (CPack)
|
include (CPack)
|
||||||
add_custom_target(package_docs DEPENDS SCSL_docs package package_source)
|
add_custom_target(package_docs DEPENDS SCSL_docs package package_source)
|
||||||
|
|
|
@ -0,0 +1,60 @@
|
||||||
|
///
|
||||||
|
/// \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
|
|
@ -1,5 +1,5 @@
|
||||||
///
|
///
|
||||||
/// \file include/scsl/Commander.h
|
/// \file Commander.h
|
||||||
/// \author K. Isom <kyle@imap.cc>
|
/// \author K. Isom <kyle@imap.cc>
|
||||||
/// \date 2023-10-10
|
/// \date 2023-10-10
|
||||||
/// \brief Subprogram tooling.
|
/// \brief Subprogram tooling.
|
||||||
|
@ -12,25 +12,9 @@
|
||||||
/// $ some_tool subcommand args...
|
/// $ some_tool subcommand args...
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
/// Copyright 2023 K. Isom <kyle@imap.cc>
|
|
||||||
///
|
|
||||||
/// Permission to use, copy, modify, and/or distribute this software for
|
|
||||||
/// any purpose with or without fee is hereby granted, provided that
|
|
||||||
/// the above copyright notice and this permission notice appear in all /// copies.
|
|
||||||
///
|
|
||||||
/// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
|
|
||||||
/// WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
|
|
||||||
/// WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
|
|
||||||
/// AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
|
|
||||||
/// DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA
|
|
||||||
/// OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
|
|
||||||
/// TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
|
||||||
/// PERFORMANCE OF THIS SOFTWARE.
|
|
||||||
///
|
|
||||||
|
|
||||||
#include <cstdint>
|
|
||||||
#include <functional>
|
|
||||||
#include <map>
|
#include <map>
|
||||||
|
#include <functional>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
|
@ -44,17 +28,15 @@ namespace scsl {
|
||||||
/// CommanderFunc describes a function that can be run in Commander.
|
/// CommanderFunc describes a function that can be run in Commander.
|
||||||
///
|
///
|
||||||
/// It expects an argument count and a list of arguments.
|
/// It expects an argument count and a list of arguments.
|
||||||
using CommanderFunc = std::function<bool (std::vector<std::string>)>;
|
typedef std::function<bool(int, char **)> CommanderFunc;
|
||||||
|
|
||||||
|
|
||||||
/// \brief Subcommands used by Commander.
|
|
||||||
///
|
|
||||||
/// Subcommands are the individual commands for the program. A Subcommand
|
/// Subcommands are the individual commands for the program. A Subcommand
|
||||||
/// will check that it has enough arguments before running its function.
|
/// will check that it has enough arguments before running its function.
|
||||||
class Subcommand {
|
class Subcommand {
|
||||||
public:
|
public:
|
||||||
/// Status describes the results of running a Subcommand.
|
/// Status describes the results of running a Subcommand.
|
||||||
enum class Status : int8_t {
|
enum class Status : uint8_t {
|
||||||
/// The subcommand executed correctly.
|
/// The subcommand executed correctly.
|
||||||
OK = 0,
|
OK = 0,
|
||||||
/// Not enough arguments were supplied to the subcommand.
|
/// Not enough arguments were supplied to the subcommand.
|
||||||
|
@ -72,8 +54,8 @@ public:
|
||||||
/// \param name The subcommand name; this is the name that will select this command.
|
/// \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 argc The minimum number of arguments required by this subcommand.
|
||||||
/// \param func A valid CommanderFunc.
|
/// \param func A valid CommanderFunc.
|
||||||
Subcommand(std::string name, size_t argc, CommanderFunc func)
|
Subcommand(std::string name, int argc, CommanderFunc func)
|
||||||
: fn(func), requiredArgs(argc), command(name)
|
: fn(func), args(argc), command(name)
|
||||||
{}
|
{}
|
||||||
|
|
||||||
/// Name returns the name of this subcommand.
|
/// Name returns the name of this subcommand.
|
||||||
|
@ -81,17 +63,16 @@ public:
|
||||||
|
|
||||||
/// Run attempts to run the CommanderFunc for this subcommand.
|
/// Run attempts to run the CommanderFunc for this subcommand.
|
||||||
///
|
///
|
||||||
/// \param args The argument list.
|
/// \param argc The number of arguments supplied.
|
||||||
|
/// \param argv The argument list.
|
||||||
/// \return A Status type indicating the status of running the command.
|
/// \return A Status type indicating the status of running the command.
|
||||||
Status Run(std::vector<std::string> args);
|
Status Run(int argc, char **argv);
|
||||||
private:
|
private:
|
||||||
CommanderFunc fn;
|
CommanderFunc fn;
|
||||||
size_t requiredArgs;
|
int args;
|
||||||
std::string command;
|
std::string command;
|
||||||
};
|
};
|
||||||
|
|
||||||
/// \brief Subcommander manager for programs.
|
|
||||||
///
|
|
||||||
/// Commander collects subcommands and can run the apppropriate one.
|
/// Commander collects subcommands and can run the apppropriate one.
|
||||||
///
|
///
|
||||||
/// For example:
|
/// For example:
|
||||||
|
@ -118,13 +99,12 @@ public:
|
||||||
bool Register(Subcommand scmd);
|
bool Register(Subcommand scmd);
|
||||||
|
|
||||||
/// Try to run a subcommand registered with this Commander.
|
/// Try to run a subcommand registered with this Commander.
|
||||||
Subcommand::Status Run(std::string command, std::vector<std::string> args);
|
Subcommand::Status Run(std::string command, int argc, char **argv);
|
||||||
private:
|
private:
|
||||||
std::map<std::string, Subcommand *> cmap;
|
std::map<std::string, Subcommand *> cmap;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
} // scsl
|
||||||
} // namespace scsl
|
|
||||||
|
|
||||||
|
|
||||||
#endif //SCSL_COMMANDER_H
|
#endif //SCSL_COMMANDER_H
|
|
@ -1,30 +1,6 @@
|
||||||
///
|
|
||||||
/// \file Dictionary.cc
|
|
||||||
/// \author K.Isom <kyle@imap.cc>
|
|
||||||
/// \date 2023-10-05
|
|
||||||
/// \brief Key-value store built on top of Arena and TLV.
|
|
||||||
///
|
|
||||||
/// Copyright 2023 K. Isom <kyle@imap.cc>
|
|
||||||
///
|
|
||||||
/// Permission to use, copy, modify, and/or distribute this software for
|
|
||||||
/// any purpose with or without fee is hereby granted, provided that
|
|
||||||
/// the above copyright notice and this permission notice appear in all /// copies.
|
|
||||||
///
|
|
||||||
/// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
|
|
||||||
/// WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
|
|
||||||
/// WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
|
|
||||||
/// AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
|
|
||||||
/// DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA
|
|
||||||
/// OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
|
|
||||||
/// TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
|
||||||
/// PERFORMANCE OF THIS SOFTWARE.
|
|
||||||
///
|
|
||||||
|
|
||||||
#include <cassert>
|
|
||||||
#include <cstdlib>
|
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
|
#include <cstdlib>
|
||||||
#include <scsl/Dictionary.h>
|
#include "Dictionary.h"
|
||||||
|
|
||||||
#if defined(SCSL_DESKTOP_BUILD)
|
#if defined(SCSL_DESKTOP_BUILD)
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
|
@ -44,13 +20,16 @@ Dictionary::Lookup(const char *key, uint8_t klen, TLV::Record &res)
|
||||||
if ((klen == res.Len) &&
|
if ((klen == res.Len) &&
|
||||||
(memcmp(res.Val, key, klen) == 0)) {
|
(memcmp(res.Val, key, klen) == 0)) {
|
||||||
TLV::ReadFromMemory(res, cursor);
|
TLV::ReadFromMemory(res, cursor);
|
||||||
assert(res.Tag == this->vTag);
|
if (res.Tag != this->vTag) {
|
||||||
return res.Tag == this->vTag;
|
abort();
|
||||||
|
}
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
cursor = TLV::FindTag(this->arena, cursor, res);
|
cursor = TLV::FindTag(this->arena, cursor, res);
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -158,10 +137,6 @@ operator<<(std::ostream &os, const Dictionary &dictionary)
|
||||||
uint8_t *cursor = (dictionary.arena).Start();
|
uint8_t *cursor = (dictionary.arena).Start();
|
||||||
TLV::Record rec;
|
TLV::Record rec;
|
||||||
|
|
||||||
if (cursor == nullptr) {
|
|
||||||
return os;
|
|
||||||
}
|
|
||||||
|
|
||||||
TLV::ReadFromMemory(rec, cursor);
|
TLV::ReadFromMemory(rec, cursor);
|
||||||
if (rec.Tag == TLV::TAG_EMPTY) {
|
if (rec.Tag == TLV::TAG_EMPTY) {
|
||||||
os << "\t(NONE)" << std::endl;
|
os << "\t(NONE)" << std::endl;
|
||||||
|
@ -172,11 +147,7 @@ operator<<(std::ostream &os, const Dictionary &dictionary)
|
||||||
os << "\t" << rec.Val << "->";
|
os << "\t" << rec.Val << "->";
|
||||||
cursor = TLV::SkipRecord(rec, cursor);
|
cursor = TLV::SkipRecord(rec, cursor);
|
||||||
TLV::ReadFromMemory(rec, cursor);
|
TLV::ReadFromMemory(rec, cursor);
|
||||||
if (cursor == nullptr) {
|
os << rec.Val << std::endl;
|
||||||
os << "*** CORRUPT DICTIONARY ***\n";
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
os << rec.Val << "\n";
|
|
||||||
cursor = TLV::SkipRecord(rec, cursor);
|
cursor = TLV::SkipRecord(rec, cursor);
|
||||||
TLV::ReadFromMemory(rec, cursor);
|
TLV::ReadFromMemory(rec, cursor);
|
||||||
}
|
}
|
|
@ -1,24 +1,7 @@
|
||||||
///
|
///
|
||||||
/// \file include/scsl/Dictionary.h
|
/// \file scsl.h
|
||||||
/// \author kyle (kyle@imap.cc)
|
/// \author kyle
|
||||||
/// \date 2023-10-12
|
/// \date 2023-10-06
|
||||||
/// \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.
|
|
||||||
///
|
///
|
||||||
|
|
||||||
|
|
||||||
|
@ -26,8 +9,6 @@
|
||||||
#define SCSL_DICTIONARY_H
|
#define SCSL_DICTIONARY_H
|
||||||
|
|
||||||
|
|
||||||
#include <cstdint>
|
|
||||||
|
|
||||||
#include "Arena.h"
|
#include "Arena.h"
|
||||||
#include "TLV.h"
|
#include "TLV.h"
|
||||||
|
|
||||||
|
@ -39,7 +20,17 @@ static constexpr uint8_t DICTIONARY_TAG_VAL = 2;
|
||||||
namespace scsl {
|
namespace scsl {
|
||||||
|
|
||||||
|
|
||||||
/// \brief Key-value store on top of Arena and TLV::Record.
|
/*
|
||||||
|
* 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.
|
||||||
///
|
///
|
||||||
/// Keys and vales are stored as sequential pairs of TLV records; they are
|
/// 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
|
/// expected to contain string values but this isn't necessarily the case. The
|
|
@ -0,0 +1,24 @@
|
||||||
|
//
|
||||||
|
// 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());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,51 @@
|
||||||
|
//
|
||||||
|
// 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
|
|
@ -1,9 +1,11 @@
|
||||||
///
|
///
|
||||||
/// \file src/sl/Flags.cc
|
/// \file Flag.h
|
||||||
/// \author K. Isom <kyle@imap.cc>
|
/// \author kyle
|
||||||
/// \date 2023-10-12
|
/// \created 2023-10-12
|
||||||
/// \brief Flag defines a command-line flag parser.
|
/// \brief Flag defines a command-line flag parser.
|
||||||
///
|
///
|
||||||
|
/// \section COPYRIGHT
|
||||||
|
///
|
||||||
/// Copyright 2023 K. Isom <kyle@imap.cc>
|
/// Copyright 2023 K. Isom <kyle@imap.cc>
|
||||||
///
|
///
|
||||||
/// Permission to use, copy, modify, and/or distribute this software for
|
/// Permission to use, copy, modify, and/or distribute this software for
|
||||||
|
@ -20,24 +22,25 @@
|
||||||
/// PERFORMANCE OF THIS SOFTWARE.
|
/// PERFORMANCE OF THIS SOFTWARE.
|
||||||
///
|
///
|
||||||
|
|
||||||
#include <cassert>
|
|
||||||
#include <iostream>
|
|
||||||
#include <regex>
|
|
||||||
#include <utility>
|
|
||||||
|
|
||||||
#include <scsl/Flags.h>
|
#include <cstdint>
|
||||||
#include <scsl/StringUtil.h>
|
#include <functional>
|
||||||
|
#include <map>
|
||||||
|
#include <regex>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
|
#include "Flag.h"
|
||||||
|
#include "StringUtil.h"
|
||||||
|
|
||||||
|
|
||||||
namespace scsl {
|
namespace scsl {
|
||||||
|
|
||||||
|
|
||||||
static const std::regex isFlag("^--?[a-zA-Z0-9][a-zA-Z0-9-_]*$",
|
static const std::regex isFlag("^--?[a-zA-Z0-9][a-zA-Z0-9-_]*$",
|
||||||
std::regex_constants::nosubs | std::regex_constants::optimize);
|
std::regex_constants::nosubs);
|
||||||
|
|
||||||
std::string
|
std::string
|
||||||
Flags::ParseStatusToString(ParseStatus status)
|
ParseStatusToString(ParseStatus status)
|
||||||
{
|
{
|
||||||
switch (status) {
|
switch (status) {
|
||||||
case ParseStatus::OK:
|
case ParseStatus::OK:
|
||||||
|
@ -55,39 +58,29 @@ Flags::ParseStatusToString(ParseStatus status)
|
||||||
|
|
||||||
|
|
||||||
Flag *
|
Flag *
|
||||||
NewFlag(std::string fName, FlagType fType, std::string fDescription)
|
NewFlag(FlagType fType, std::string fName, std::string fDescription)
|
||||||
{
|
{
|
||||||
auto *flag = new Flag;
|
auto flag = new Flag;
|
||||||
|
|
||||||
flag->Type = fType;
|
flag->Type = fType;
|
||||||
flag->WasSet = false;
|
flag->WasSet = false;
|
||||||
flag->Name = std::move(fName);
|
flag->Name = fName;
|
||||||
flag->Description = std::move(fDescription);
|
flag->Description = fDescription;
|
||||||
flag->Value = FlagValue{};
|
flag->Value = FlagValue{};
|
||||||
|
|
||||||
return flag;
|
return flag;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
Flags::Flags(std::string fName)
|
Flags::Flags(std::string fName)
|
||||||
: name(std::move(fName))
|
: name(fName), description("")
|
||||||
{}
|
|
||||||
|
|
||||||
|
|
||||||
Flags::Flags(std::string fName, std::string fDescription)
|
|
||||||
: name(std::move(fName)), description(std::move(fDescription))
|
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
Flags::~Flags()
|
Flags::Flags(std::string fName, std::string fDescription)
|
||||||
|
: name(fName), description(fDescription)
|
||||||
{
|
{
|
||||||
for (auto &flag: this->flags) {
|
|
||||||
if (flag.second->Type == FlagType::String) {
|
|
||||||
delete flag.second->Value.s;
|
|
||||||
}
|
|
||||||
delete flag.second;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -102,8 +95,7 @@ Flags::Register(std::string fName, FlagType fType, std::string fDescription)
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto *flag = NewFlag(fName, fType, std::move(fDescription));
|
auto flag = NewFlag(fType, fName, fDescription);
|
||||||
assert(flag != nullptr);
|
|
||||||
this->flags[fName] = flag;
|
this->flags[fName] = flag;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -112,7 +104,7 @@ Flags::Register(std::string fName, FlagType fType, std::string fDescription)
|
||||||
bool
|
bool
|
||||||
Flags::Register(std::string fName, bool defaultValue, std::string fDescription)
|
Flags::Register(std::string fName, bool defaultValue, std::string fDescription)
|
||||||
{
|
{
|
||||||
if (!this->Register(fName, FlagType::Boolean, std::move(fDescription))) {
|
if (!this->Register(fName, FlagType::Boolean, fDescription)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -124,7 +116,7 @@ Flags::Register(std::string fName, bool defaultValue, std::string fDescription)
|
||||||
bool
|
bool
|
||||||
Flags::Register(std::string fName, int defaultValue, std::string fDescription)
|
Flags::Register(std::string fName, int defaultValue, std::string fDescription)
|
||||||
{
|
{
|
||||||
if (!this->Register(fName, FlagType::Integer, std::move(fDescription))) {
|
if (!this->Register(fName, FlagType::Integer, fDescription)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -136,11 +128,9 @@ Flags::Register(std::string fName, int defaultValue, std::string fDescription)
|
||||||
bool
|
bool
|
||||||
Flags::Register(std::string fName, unsigned int defaultValue, std::string fDescription)
|
Flags::Register(std::string fName, unsigned int defaultValue, std::string fDescription)
|
||||||
{
|
{
|
||||||
if (!this->Register(fName, FlagType::UnsignedInteger, std::move(fDescription))) {
|
if (!this->Register(fName, FlagType::UnsignedInteger, fDescription)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
assert(this->flags.count(fName) != 0);
|
|
||||||
assert(this->flags[fName] != nullptr);
|
|
||||||
|
|
||||||
this->flags[fName]->Value.u = defaultValue;
|
this->flags[fName]->Value.u = defaultValue;
|
||||||
return true;
|
return true;
|
||||||
|
@ -150,7 +140,7 @@ Flags::Register(std::string fName, unsigned int defaultValue, std::string fDescr
|
||||||
bool
|
bool
|
||||||
Flags::Register(std::string fName, size_t defaultValue, std::string fDescription)
|
Flags::Register(std::string fName, size_t defaultValue, std::string fDescription)
|
||||||
{
|
{
|
||||||
if (!this->Register(fName, FlagType::SizeT, std::move(fDescription))) {
|
if (!this->Register(fName, FlagType::SizeT, fDescription)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -162,19 +152,7 @@ Flags::Register(std::string fName, size_t defaultValue, std::string fDescription
|
||||||
bool
|
bool
|
||||||
Flags::Register(std::string fName, std::string defaultValue, std::string fDescription)
|
Flags::Register(std::string fName, std::string defaultValue, std::string fDescription)
|
||||||
{
|
{
|
||||||
if (!this->Register(fName, FlagType::String, std::move(fDescription))) {
|
if (!this->Register(fName, FlagType::String, 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;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -204,7 +182,7 @@ Flags::Lookup(std::string fName)
|
||||||
bool
|
bool
|
||||||
Flags::ValueOf(std::string fName, FlagValue &value)
|
Flags::ValueOf(std::string fName, FlagValue &value)
|
||||||
{
|
{
|
||||||
if (this->flags.count(fName) != 0U) {
|
if (this->flags.count(fName)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -213,48 +191,43 @@ Flags::ValueOf(std::string fName, FlagValue &value)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
Flags::ParseStatus
|
|
||||||
|
ParseStatus
|
||||||
Flags::parseArg(int argc, char **argv, int &index)
|
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)) {
|
if (!std::regex_search(arg, isFlag)) {
|
||||||
return ParseStatus::EndOfFlags;
|
return ParseStatus::EndOfFlags;
|
||||||
}
|
}
|
||||||
|
|
||||||
index++;
|
|
||||||
if (this->flags.count(arg) == 0) {
|
if (this->flags.count(arg) == 0) {
|
||||||
if (arg == "-h" || arg == "--help") {
|
|
||||||
Usage(std::cout, 0);
|
|
||||||
}
|
|
||||||
return ParseStatus::NotRegistered;
|
return ParseStatus::NotRegistered;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto *flag = flags[arg];
|
auto flag = flags[arg];
|
||||||
if ((flag->Type != FlagType::Boolean) && index == argc) {
|
|
||||||
return ParseStatus::NotEnoughArgs;
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (flag->Type) {
|
switch (flag->Type) {
|
||||||
case FlagType::Boolean:
|
case FlagType::Boolean:
|
||||||
flag->WasSet = true;
|
flag->WasSet = true;
|
||||||
flag->Value.b = true;
|
flag->Value.b = true;
|
||||||
return ParseStatus::OK;
|
return ParseStatus::OK;
|
||||||
case FlagType::Integer:
|
case FlagType::Integer:
|
||||||
flag->WasSet = true;
|
flag->WasSet = true;
|
||||||
flag->Value.i = std::stoi(argv[++index], nullptr, 0);
|
flag->Value.i = std::stoi(argv[++index], 0, 0);
|
||||||
return ParseStatus::OK;
|
return ParseStatus::OK;
|
||||||
case FlagType::UnsignedInteger:
|
case FlagType::UnsignedInteger:
|
||||||
flag->WasSet = true;
|
flag->WasSet = true;
|
||||||
flag->Value.u = static_cast<unsigned int>(std::stoi(argv[index++], nullptr, 0));
|
flag->Value.u = static_cast<unsigned int>(std::stoi(argv[index++], 0, 0));
|
||||||
return ParseStatus::OK;
|
return ParseStatus::OK;
|
||||||
case FlagType::String:
|
case FlagType::String:
|
||||||
flag->WasSet = true;
|
flag->WasSet = true;
|
||||||
flag->Value.s = new std::string(argv[index++]);
|
flag->Value.s = new std::string(argv[index++]);
|
||||||
return ParseStatus::OK;
|
return ParseStatus::OK;
|
||||||
case FlagType::SizeT:
|
case FlagType::SizeT:
|
||||||
flag->WasSet = true;
|
flag->WasSet = true;
|
||||||
flag->Value.size = static_cast<size_t>(std::stoull(argv[index++]));
|
flag->Value.size = static_cast<size_t>(std::stoull(argv[index++]));
|
||||||
return ParseStatus::OK;
|
return ParseStatus::OK;
|
||||||
default:
|
default:
|
||||||
|
@ -269,13 +242,10 @@ Flags::parseArg(int argc, char **argv, int &index)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
Flags::ParseStatus
|
ParseStatus
|
||||||
Flags::Parse(int argc, char **argv, bool skipFirst)
|
Flags::Parse(int argc, char **argv)
|
||||||
{
|
{
|
||||||
int index = 0;
|
int index = 1;
|
||||||
if (skipFirst) {
|
|
||||||
index = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
while (index != argc) {
|
while (index != argc) {
|
||||||
auto result = this->parseArg(argc, argv, index);
|
auto result = this->parseArg(argc, argv, index);
|
||||||
|
@ -285,7 +255,7 @@ Flags::Parse(int argc, char **argv, bool skipFirst)
|
||||||
|
|
||||||
case ParseStatus::EndOfFlags:
|
case ParseStatus::EndOfFlags:
|
||||||
while (index < argc) {
|
while (index < argc) {
|
||||||
this->args.emplace_back(argv[index]);
|
this->args.push_back(std::string(argv[index]));
|
||||||
index++;
|
index++;
|
||||||
}
|
}
|
||||||
continue;
|
continue;
|
||||||
|
@ -300,79 +270,13 @@ Flags::Parse(int argc, char **argv, bool skipFirst)
|
||||||
throw std::runtime_error("unhandled parse state");
|
throw std::runtime_error("unhandled parse state");
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return ParseStatus::OK;
|
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
|
size_t
|
||||||
Flags::NumArgs()
|
Flags::NumArgs()
|
||||||
{
|
{
|
||||||
|
@ -387,25 +291,14 @@ Flags::Args()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
std::string
|
|
||||||
Flags::Arg(size_t i)
|
|
||||||
{
|
|
||||||
if (i >= this->args.size()) {
|
|
||||||
throw std::out_of_range("index is out of range");
|
|
||||||
}
|
|
||||||
|
|
||||||
return this->args[i];
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
Flag *
|
Flag *
|
||||||
Flags::checkGetArg(std::string &fName, FlagType eType)
|
Flags::checkGetArg(std::string fName, FlagType eType)
|
||||||
{
|
{
|
||||||
if (this->flags[fName] == nullptr) {
|
if (this->flags[fName] == 0) {
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto *flag = this->flags[fName];
|
auto flag = this->flags[fName];
|
||||||
if (flag == nullptr) {
|
if (flag == nullptr) {
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
@ -421,7 +314,7 @@ Flags::checkGetArg(std::string &fName, FlagType eType)
|
||||||
bool
|
bool
|
||||||
Flags::GetBool(std::string fName, bool &flagValue)
|
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;
|
flagValue = flag->Value.b;
|
||||||
return flag->WasSet;
|
return flag->WasSet;
|
||||||
|
@ -431,7 +324,7 @@ Flags::GetBool(std::string fName, bool &flagValue)
|
||||||
bool
|
bool
|
||||||
Flags::GetInteger(std::string fName, int &flagValue)
|
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;
|
flagValue = flag->Value.i;
|
||||||
return flag->WasSet;
|
return flag->WasSet;
|
||||||
|
@ -441,17 +334,18 @@ Flags::GetInteger(std::string fName, int &flagValue)
|
||||||
bool
|
bool
|
||||||
Flags::GetUnsignedInteger(std::string fName, unsigned int &flagValue)
|
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;
|
flagValue = flag->Value.u;
|
||||||
return flag->WasSet;
|
return flag->WasSet;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
bool
|
bool
|
||||||
Flags::GetSizeT(std::string fName, std::size_t &flagValue)
|
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;
|
flagValue = flag->Value.size;
|
||||||
return flag->WasSet;
|
return flag->WasSet;
|
||||||
|
@ -461,7 +355,7 @@ Flags::GetSizeT(std::string fName, std::size_t &flagValue)
|
||||||
bool
|
bool
|
||||||
Flags::GetString(std::string fName, std::string &flagValue)
|
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) {
|
if (flag->Value.s == nullptr) {
|
||||||
return false;
|
return false;
|
||||||
|
@ -472,4 +366,5 @@ Flags::GetString(std::string fName, std::string &flagValue)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}// namespace scsl
|
} // namespace scsl
|
||||||
|
|
|
@ -0,0 +1,134 @@
|
||||||
|
///
|
||||||
|
/// \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
|
75
Makefile
75
Makefile
|
@ -1,59 +1,39 @@
|
||||||
HEADERS := scsl.h \
|
TARGET := klib.a
|
||||||
Arena.h \
|
TESTS := tlv_test dictionary_test
|
||||||
Buffer.h \
|
HEADERS := $(wildcard *.h)
|
||||||
Commander.h \
|
SOURCES := $(wildcard *.cc)
|
||||||
Dictionary.h \
|
OBJS := Arena.o Dictionary.o TLV.o
|
||||||
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++
|
CXX := clang++
|
||||||
CXXFLAGS := -std=c++14 -Werror -Wall -Wextra -DSCSL_DESKTOP_BUILD \
|
CXXFLAGS := -g -std=c++14 -Werror -Wall -DSCSL_DESKTOP_BUILD
|
||||||
-DSCSL_BUILD_TYPE=${BUILD}
|
|
||||||
ifeq ($(BUILD),DEBUG)
|
|
||||||
CXXFLAGS += -g -fsanitize=address
|
|
||||||
else
|
|
||||||
CXXFLAGS += -O2
|
|
||||||
endif
|
|
||||||
|
|
||||||
.PHONY: all
|
.PHONY: all
|
||||||
all: $(TARGETS) $(TESTS) tags run-tests
|
all: $(TARGET) $(TESTS) tags run-tests
|
||||||
|
|
||||||
tags: $(HEADERS) $(SOURCES)
|
tags: $(HEADERS) $(SOURCES)
|
||||||
ctags $(HEADERS) $(SOURCES)
|
ctags $(HEADERS) $(SOURCES)
|
||||||
|
|
||||||
libscsl.a: $(OBJS)
|
$(TARGET): $(OBJS)
|
||||||
$(AR) rcs $@ $(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-%
|
.PHONY: print-%
|
||||||
print-%: ; @echo '$(subst ','\'',$*=$($*))'
|
print-%: ; @echo '$(subst ','\'',$*=$($*))'
|
||||||
|
|
||||||
klib.a: $(OBJS)
|
klib.a: $(OBJS)
|
||||||
|
|
||||||
|
%.o: %.cc
|
||||||
|
$(CXX) -o $@ -c $(CXXFLAGS) $<
|
||||||
|
|
||||||
.PHONY: clean
|
.PHONY: clean
|
||||||
clean:
|
clean:
|
||||||
# build outputs
|
# build outputs
|
||||||
rm -f $(TARGETS) $(TESTS) *.o
|
rm -f $(TARGET) $(TESTS) *.o
|
||||||
|
|
||||||
# test miscellaneous
|
# test miscellaneous
|
||||||
rm -f core core.* tags arena_test.bin
|
rm -f core core.* tags arena_test.bin
|
||||||
|
@ -65,22 +45,3 @@ run-tests: $(TESTS)
|
||||||
echo "./$${testbin}" ; \
|
echo "./$${testbin}" ; \
|
||||||
./$${testbin}; \
|
./$${testbin}; \
|
||||||
done
|
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
61
README.md
|
@ -1,61 +0,0 @@
|
||||||
# scsl : The Shimmering Clarity Standard C++ Library
|
|
||||||
|
|
||||||
[](https://dl.circleci.com/status-badge/redirect/gh/shimmering-clarity/scsl/tree/master)
|
|
||||||
|
|
||||||
[](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.
|
|
||||||
|
|
|
@ -1,3 +1,16 @@
|
||||||
|
scsl : The Shimmering Clarity Standard C++ Library
|
||||||
|
==================================================
|
||||||
|
|
||||||
|
scsl is a collection of software I found myself needing to use repeatedly.
|
||||||
|
|
||||||
|
Full `Doxygen documentation`_ is available.
|
||||||
|
|
||||||
|
.. _Doxygen documentation: https://docs.shimmering-clarity.net/scsl/
|
||||||
|
|
||||||
|
|
||||||
|
License
|
||||||
|
-------
|
||||||
|
|
||||||
Copyright 2023 K. Isom <kyle@imap.cc>
|
Copyright 2023 K. Isom <kyle@imap.cc>
|
||||||
|
|
||||||
Permission to use, copy, modify, and/or distribute this software for any
|
Permission to use, copy, modify, and/or distribute this software for any
|
||||||
|
@ -11,3 +24,4 @@ ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||||
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||||
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
|
|
@ -0,0 +1,165 @@
|
||||||
|
#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
|
|
@ -1,9 +1,10 @@
|
||||||
///
|
///
|
||||||
/// \file include/scsl/StringUtil.h
|
/// \file StringUtil.h
|
||||||
/// \author K. Isom <kyle@imap.cc>
|
/// \author kyle (kyle@midgard)
|
||||||
/// \date 2023-10-14
|
/// \created 2023-10-14
|
||||||
/// \brief Utilities for working with strings.
|
/// \brief StringUtil contains string utilities.
|
||||||
///
|
///
|
||||||
|
/// \section COPYRIGHT
|
||||||
/// Copyright 2023 K. Isom <kyle@imap.cc>
|
/// Copyright 2023 K. Isom <kyle@imap.cc>
|
||||||
///
|
///
|
||||||
/// Permission to use, copy, modify, and/or distribute this software for
|
/// Permission to use, copy, modify, and/or distribute this software for
|
||||||
|
@ -32,31 +33,34 @@
|
||||||
|
|
||||||
namespace scsl {
|
namespace scsl {
|
||||||
|
|
||||||
/// String-related utility functions.
|
/// namespace U contains utilities.
|
||||||
namespace scstring {
|
namespace U {
|
||||||
|
|
||||||
|
/// namespace S contains string-related functions.
|
||||||
|
namespace S {
|
||||||
|
|
||||||
|
|
||||||
/// 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.
|
/// is modified in-place.
|
||||||
void TrimLeadingWhitespace(std::string &s);
|
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.
|
/// modified in-place.
|
||||||
void TrimTrailingWhitespace(std::string &s);
|
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.
|
/// string is modified in-place.
|
||||||
void TrimWhitespace(std::string &s);
|
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.
|
/// string isn't modified, and a copy is returned.
|
||||||
std::string TrimLeadingWhitespaceDup(std::string s);
|
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.
|
/// isn't modified, and a copy is returned.
|
||||||
std::string TrimTrailingWhitespaceDup(std::string s);
|
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.
|
/// original string isn't modified, and a copy is returned.
|
||||||
std::string TrimWhitespaceDup(std::string s);
|
std::string TrimWhitespaceDup(std::string s);
|
||||||
|
|
||||||
|
@ -81,48 +85,22 @@ std::vector<std::string> SplitKeyValuePair(std::string line, char delimiter);
|
||||||
|
|
||||||
/// Split a string into parts based on the 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 delimiter The string that delimits the parts of the string.
|
||||||
/// \param maxCount The maximum number of parts to split. If 0, there is no
|
/// \param maxCount The maximum number of parts to split. If 0, there is no limit
|
||||||
/// limit to the number of parts.
|
/// to the number of parts.
|
||||||
/// \return A vector containing all the parts of the string.
|
/// \return A vector containing all the parts of the string.
|
||||||
std::vector<std::string> SplitN(std::string s, std::string delimiter, size_t maxCount=0);
|
std::vector<std::string> SplitN(std::string, std::string delimiter, size_t maxCount=0);
|
||||||
|
|
||||||
/// WrapText is a very simple wrapping function that breaks the line into
|
//std::vector<std::string> SplitN(std::string, char delimiter, size_t size_t maxCount=0);
|
||||||
/// lines of At most lineLength characters. It does this by breaking the
|
|
||||||
/// line into individual words (splitting on whitespace).
|
|
||||||
std::vector<std::string> WrapText(std::string& line, size_t lineLength);
|
|
||||||
|
|
||||||
/// Write out a vector of lines indented with tabs.
|
|
||||||
///
|
|
||||||
/// \param os The output stream to write to.
|
|
||||||
/// \param lines The lines of text to write.
|
|
||||||
/// \param tabStop The number of tabs to indent.
|
|
||||||
/// \param indentFirst Whether the first line should be indented.
|
|
||||||
void WriteTabIndented(std::ostream &os, std::vector<std::string> lines,
|
|
||||||
int tabStop, bool indentFirst);
|
|
||||||
|
|
||||||
/// Wrap a line, then output it to a stream.
|
|
||||||
///
|
|
||||||
/// \param os The output stream to write to.
|
|
||||||
/// \param line The line to wrap and output.
|
|
||||||
/// \param maxLength The maximum length of each section of text.
|
|
||||||
/// \param tabStop The number of tabs to indent.
|
|
||||||
/// \param indentFirst Whether the first line should be indented.
|
|
||||||
void WriteTabIndented(std::ostream &os, std::string line, size_t maxLength,
|
|
||||||
int tabStop, bool indentFirst);
|
|
||||||
|
|
||||||
|
|
||||||
/// Write a string vector to the output stream in the same format as
|
/// Return a string represention of a string vector in the form [size]{"foo", "bar", ...}.
|
||||||
/// VectorToString.
|
|
||||||
std::ostream &VectorToString(std::ostream &os, const std::vector<std::string> &svec);
|
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);
|
std::string VectorToString(const std::vector<std::string> &svec);
|
||||||
|
|
||||||
|
|
||||||
} // namespace string
|
} // namespace S
|
||||||
|
} // namespace U
|
||||||
} // namespace scsl
|
} // namespace scsl
|
||||||
|
|
||||||
|
|
|
@ -1,37 +1,10 @@
|
||||||
///
|
|
||||||
/// \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 <cstring>
|
||||||
|
#include "TLV.h"
|
||||||
#include <scsl/TLV.h>
|
|
||||||
|
|
||||||
|
|
||||||
using namespace scsl;
|
using namespace scsl;
|
||||||
|
|
||||||
|
|
||||||
/// REC_SIZE calculates the total length of a TLV record, including the
|
#define REC_SIZE(x) ((std::size_t)x.Len + 2)
|
||||||
/// two byte header.
|
|
||||||
#define REC_SIZE(x) ((std::size_t)x.Len + 2)
|
|
||||||
|
|
||||||
|
|
||||||
namespace scsl {
|
namespace scsl {
|
||||||
|
@ -101,10 +74,6 @@ SetRecord(Record &rec, uint8_t tag, uint8_t len, const char *val)
|
||||||
void
|
void
|
||||||
ReadFromMemory(Record &rec, uint8_t *cursor)
|
ReadFromMemory(Record &rec, uint8_t *cursor)
|
||||||
{
|
{
|
||||||
assert(cursor != nullptr);
|
|
||||||
if (cursor == nullptr) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
rec.Tag = cursor[0];
|
rec.Tag = cursor[0];
|
||||||
rec.Len = cursor[1];
|
rec.Len = cursor[1];
|
||||||
memcpy(rec.Val, cursor + 2, rec.Len);
|
memcpy(rec.Val, cursor + 2, rec.Len);
|
||||||
|
@ -124,10 +93,6 @@ FindTag(Arena &arena, uint8_t *cursor, Record &rec)
|
||||||
cursor = SkipRecord(rec, cursor);
|
cursor = SkipRecord(rec, cursor);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!arena.CursorInArena(cursor)) {
|
|
||||||
cursor = nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
return cursor;
|
return cursor;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -135,20 +100,13 @@ FindTag(Arena &arena, uint8_t *cursor, Record &rec)
|
||||||
uint8_t *
|
uint8_t *
|
||||||
LocateTag(Arena &arena, uint8_t *cursor, Record &rec)
|
LocateTag(Arena &arena, uint8_t *cursor, Record &rec)
|
||||||
{
|
{
|
||||||
uint8_t tag = TAG_EMPTY;
|
uint8_t tag, len;
|
||||||
uint8_t len;
|
|
||||||
|
|
||||||
if (cursor == nullptr) {
|
if (cursor == nullptr) {
|
||||||
cursor = arena.Start();
|
cursor = arena.Start();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!arena.CursorInArena(cursor)) {
|
while ((tag = cursor[0]) != rec.Tag) {
|
||||||
cursor = arena.Start();
|
|
||||||
}
|
|
||||||
|
|
||||||
while (arena.CursorInArena(cursor) &&
|
|
||||||
((tag = cursor[0]) != rec.Tag)) {
|
|
||||||
assert(arena.CursorInArena(cursor));
|
|
||||||
len = cursor[1];
|
len = cursor[1];
|
||||||
if (!spaceAvailable(arena, cursor, len)) {
|
if (!spaceAvailable(arena, cursor, len)) {
|
||||||
return nullptr;
|
return nullptr;
|
||||||
|
@ -157,10 +115,6 @@ LocateTag(Arena &arena, uint8_t *cursor, Record &rec)
|
||||||
cursor += 2;
|
cursor += 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!arena.CursorInArena(cursor)) {
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (tag != rec.Tag) {
|
if (tag != rec.Tag) {
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
@ -200,7 +154,7 @@ DeleteRecord(Arena &arena, uint8_t *cursor)
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
uint8_t len = cursor[1] + 2;
|
uint8_t len = cursor[1] + 2;
|
||||||
uint8_t *stop = arena.Start() + arena.Size();
|
uint8_t *stop = arena.Start() + arena.Size();
|
||||||
|
|
||||||
stop -= len;
|
stop -= len;
|
|
@ -1,5 +1,5 @@
|
||||||
///
|
///
|
||||||
/// \file include/scsl/TLV.h
|
/// \file TLV.h
|
||||||
/// \author K. Isom <kyle@imap.cc>
|
/// \author K. Isom <kyle@imap.cc>
|
||||||
/// \date 2023-10-06
|
/// \date 2023-10-06
|
||||||
/// \brief TLV.h implements basic tag-length-value records.
|
/// \brief TLV.h implements basic tag-length-value records.
|
||||||
|
@ -14,18 +14,15 @@
|
||||||
#ifndef KIMODEM_TLV_H
|
#ifndef KIMODEM_TLV_H
|
||||||
#define KIMODEM_TLV_H
|
#define KIMODEM_TLV_H
|
||||||
|
|
||||||
#include <array>
|
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
|
#include <array>
|
||||||
|
|
||||||
#include "Arena.h"
|
#include "Arena.h"
|
||||||
|
|
||||||
|
|
||||||
namespace scsl {
|
namespace scsl {
|
||||||
|
|
||||||
/// \brief Tag-length-value record tooling
|
|
||||||
namespace TLV {
|
namespace TLV {
|
||||||
|
|
||||||
|
|
||||||
#ifndef TLV_MAX_LEN
|
#ifndef TLV_MAX_LEN
|
||||||
static constexpr size_t TLV_MAX_LEN = 253;
|
static constexpr size_t TLV_MAX_LEN = 253;
|
||||||
#endif
|
#endif
|
||||||
|
@ -33,7 +30,7 @@ static constexpr size_t TLV_MAX_LEN = 253;
|
||||||
static constexpr uint8_t TAG_EMPTY = 0;
|
static constexpr uint8_t TAG_EMPTY = 0;
|
||||||
|
|
||||||
|
|
||||||
/// \brief Tag-length-value record with single byte tags and lengths.
|
/// Record describes a tag-length-value record.
|
||||||
///
|
///
|
||||||
/// TLV records occupy a fixed size in memory, which can be controlled with the
|
/// 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.
|
/// TLV_MAX_LEN define. If this isn't defined, it defaults to a size of 253.
|
||||||
|
@ -48,7 +45,7 @@ struct Record {
|
||||||
uint8_t Val[TLV_MAX_LEN];
|
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.
|
/// to in the arena.
|
||||||
///
|
///
|
||||||
/// \param arena The backing memory store.
|
/// \param arena The backing memory store.
|
||||||
|
@ -88,7 +85,7 @@ void DeleteRecord(Arena &arena, uint8_t *cursor);
|
||||||
///
|
///
|
||||||
/// \param arena The backing memory for the TLV store.
|
/// \param arena The backing memory for the TLV store.
|
||||||
/// \param cursor A pointer to memory inside the arena; if it's NULL, the
|
/// \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.
|
/// \param rec The record to be filled.
|
||||||
/// \return If the tag is found, a cursor pointing to the next record is
|
/// \return If the tag is found, a cursor pointing to the next record is
|
||||||
/// returned; otherwise nullptr is returned.
|
/// returned; otherwise nullptr is returned.
|
||||||
|
@ -99,7 +96,7 @@ uint8_t *FindTag(Arena &arena, uint8_t *cursor, Record &rec);
|
||||||
///
|
///
|
||||||
/// \param arena The backing memory for the TLV store.
|
/// \param arena The backing memory for the TLV store.
|
||||||
/// \param cursor A pointer to memory inside the arena; if it's NULL, the
|
/// \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.
|
/// \param rec The record to be filled.
|
||||||
/// \return If the tag is found, a cursor pointing to the record is
|
/// \return If the tag is found, a cursor pointing to the record is
|
||||||
/// returned; otherwise nullptr is returned.
|
/// returned; otherwise nullptr is returned.
|
||||||
|
@ -112,7 +109,7 @@ uint8_t *LocateTag(Arena &arena, uint8_t *cursor, Record &rec);
|
||||||
///
|
///
|
||||||
/// \param arena The backing memory for the TLV store.
|
/// \param arena The backing memory for the TLV store.
|
||||||
/// \param cursor A pointer to memory inside the arena; if it's NULL, the
|
/// \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
|
/// \return If the arena has space available, a cursor pointing the start
|
||||||
/// of empty space; otherwise, nullptr is returned.
|
/// of empty space; otherwise, nullptr is returned.
|
||||||
uint8_t *FindEmpty(Arena &arena, uint8_t *cursor);
|
uint8_t *FindEmpty(Arena &arena, uint8_t *cursor);
|
|
@ -0,0 +1,52 @@
|
||||||
|
//
|
||||||
|
// 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
|
|
@ -0,0 +1,43 @@
|
||||||
|
///
|
||||||
|
/// \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
|
|
@ -0,0 +1,59 @@
|
||||||
|
#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;
|
||||||
|
}
|
|
@ -0,0 +1,108 @@
|
||||||
|
#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;
|
||||||
|
}
|
|
@ -0,0 +1,59 @@
|
||||||
|
//
|
||||||
|
// 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;
|
||||||
|
}
|
|
@ -1,128 +0,0 @@
|
||||||
///
|
|
||||||
/// \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
|
|
|
@ -1,41 +0,0 @@
|
||||||
///
|
|
||||||
/// \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
|
|
|
@ -1,214 +0,0 @@
|
||||||
///
|
|
||||||
/// \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
|
|
|
@ -1,43 +0,0 @@
|
||||||
///
|
|
||||||
/// \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
|
|
|
@ -1,164 +0,0 @@
|
||||||
///
|
|
||||||
/// \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
|
|
|
@ -1,113 +0,0 @@
|
||||||
///
|
|
||||||
/// \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
|
|
|
@ -1,577 +0,0 @@
|
||||||
///
|
|
||||||
/// \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
|
|
|
@ -1,505 +0,0 @@
|
||||||
///
|
|
||||||
/// \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
|
|
|
@ -1,34 +0,0 @@
|
||||||
///
|
|
||||||
/// \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
|
|
|
@ -1,352 +0,0 @@
|
||||||
///
|
|
||||||
/// \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
|
|
|
@ -1,246 +0,0 @@
|
||||||
///
|
|
||||||
/// \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
|
|
|
@ -1,60 +0,0 @@
|
||||||
///
|
|
||||||
/// \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
|
|
|
@ -1,54 +0,0 @@
|
||||||
///
|
|
||||||
/// \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
|
|
|
@ -1,70 +0,0 @@
|
||||||
///
|
|
||||||
/// \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
|
|
|
@ -1,104 +0,0 @@
|
||||||
///
|
|
||||||
/// \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
|
|
|
@ -1,127 +0,0 @@
|
||||||
///
|
|
||||||
/// \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
|
|
|
@ -1,37 +0,0 @@
|
||||||
///
|
|
||||||
/// \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
|
|
|
@ -1,76 +1,56 @@
|
||||||
///
|
//
|
||||||
/// \file phonebook.cc
|
// Created by kyle on 2023-10-10.
|
||||||
/// \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 <iostream>
|
||||||
#include <string>
|
#include <string>
|
||||||
using namespace std;
|
using namespace std;
|
||||||
|
|
||||||
#include <scsl/Arena.h>
|
#include "Arena.h"
|
||||||
#include <scsl/Commander.h>
|
#include "Commander.h"
|
||||||
#include <scsl/Dictionary.h>
|
#include "Dictionary.h"
|
||||||
#include <scsl/Flags.h>
|
|
||||||
|
|
||||||
|
|
||||||
using namespace scsl;
|
using namespace scsl;
|
||||||
|
|
||||||
static const char *defaultPhonebook = "pb.dat";
|
static const char *defaultPhonebook = "pb.dat";
|
||||||
static std::string pbFile(defaultPhonebook);
|
static char *pbFile = (char *)defaultPhonebook;
|
||||||
static Arena arena;
|
static Arena arena;
|
||||||
static Dictionary pb(arena);
|
static Dictionary pb(arena);
|
||||||
|
|
||||||
|
|
||||||
static bool
|
static bool
|
||||||
listFiles(std::vector<std::string> argv)
|
listFiles(int argc, char **argv)
|
||||||
{
|
{
|
||||||
(void) argv; // provided for interface compatibility.
|
(void)argc; (void)argv;
|
||||||
cout << "[+] keys in '" << pbFile << "':\n";
|
cout << "[+] keys in '" << pbFile << "':\n";
|
||||||
cout << pb;
|
cout << pb;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
static bool
|
static bool
|
||||||
newPhonebook(std::vector<std::string> argv)
|
newPhonebook(int argc, char **argv)
|
||||||
{
|
{
|
||||||
auto size = std::stoul(argv[0]);
|
(void)argc;
|
||||||
|
|
||||||
|
auto size = std::stoul(string(argv[0]));
|
||||||
cout << "[+] create new " << size << "B phonebook '" << pbFile << "'\n";
|
cout << "[+] create new " << size << "B phonebook '" << pbFile << "'\n";
|
||||||
|
|
||||||
return arena.Create(pbFile.c_str(), size) == 0;
|
return arena.Create(pbFile, size) == 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
static bool
|
static bool
|
||||||
delKey(std::vector<std::string> argv)
|
delKey(int argc, char **argv)
|
||||||
{
|
{
|
||||||
|
(void)argc;
|
||||||
string key = argv[0];
|
string key = argv[0];
|
||||||
|
|
||||||
cout << "[+] deleting key '" << key << "'\n";
|
cout << "[+] deleting key '" << key << "'\n";
|
||||||
return pb.Delete(key.c_str(), key.size());
|
return pb.Delete(key.c_str(), key.size());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
static bool
|
static bool
|
||||||
hasKey(std::vector<std::string> argv)
|
hasKey(int argc, char **argv)
|
||||||
{
|
{
|
||||||
|
(void)argc;
|
||||||
string key = argv[0];
|
string key = argv[0];
|
||||||
|
|
||||||
cout << "[+] looking up '" << key << "': ";
|
cout << "[+] looking up '" << key << "': ";
|
||||||
|
@ -80,13 +60,13 @@ hasKey(std::vector<std::string> argv)
|
||||||
}
|
}
|
||||||
|
|
||||||
cout << "not found\n";
|
cout << "not found\n";
|
||||||
return false;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
static bool
|
static bool
|
||||||
getKey(std::vector<std::string> argv)
|
getKey(int argc, char **argv)
|
||||||
{
|
{
|
||||||
|
(void)argc;
|
||||||
TLV::Record rec;
|
TLV::Record rec;
|
||||||
auto key = string(argv[0]);
|
auto key = string(argv[0]);
|
||||||
|
|
||||||
|
@ -102,8 +82,9 @@ getKey(std::vector<std::string> argv)
|
||||||
|
|
||||||
|
|
||||||
static bool
|
static bool
|
||||||
putKey(std::vector<std::string> argv)
|
putKey(int argc, char **argv)
|
||||||
{
|
{
|
||||||
|
(void)argc;
|
||||||
auto key = string(argv[0]);
|
auto key = string(argv[0]);
|
||||||
auto val = string(argv[1]);
|
auto val = string(argv[1]);
|
||||||
|
|
||||||
|
@ -117,7 +98,6 @@ putKey(std::vector<std::string> argv)
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
static void
|
static void
|
||||||
usage(ostream &os, int exc)
|
usage(ostream &os, int exc)
|
||||||
{
|
{
|
||||||
|
@ -138,36 +118,28 @@ usage(ostream &os, int exc)
|
||||||
int
|
int
|
||||||
main(int argc, char *argv[])
|
main(int argc, char *argv[])
|
||||||
{
|
{
|
||||||
int retc = 1;
|
int optind = 1;
|
||||||
bool help = false;
|
|
||||||
std::string fileName(pbFile);
|
|
||||||
|
|
||||||
auto *flags = new scsl::Flags("phonebook",
|
for (optind = 1; optind < argc; optind++) {
|
||||||
"A tool for interacting with Arena-backed dictionary files.");
|
auto arg = string(argv[optind]);
|
||||||
flags->Register("-f", pbFile, "path to a phonebook file");
|
if (arg[0] != '-') break;
|
||||||
flags->Register("-h", false, "print a help message");
|
if (arg == "-h") usage(cout, 0);
|
||||||
|
if (arg == "-f") {
|
||||||
|
pbFile = argv[optind+1];
|
||||||
|
optind++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
auto parsed = flags->Parse(argc, argv);
|
usage(cerr, 1);
|
||||||
if (parsed != scsl::Flags::ParseStatus::OK) {
|
|
||||||
std::cerr << "Failed to parse flags: "
|
|
||||||
<< scsl::Flags::ParseStatusToString(parsed) << "\n";
|
|
||||||
exit(1);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
flags->GetString("-f", fileName);
|
if (argc <= 1) {
|
||||||
flags->GetBool("-h", help);
|
usage(cout, 0);
|
||||||
|
|
||||||
pbFile = fileName;
|
|
||||||
|
|
||||||
if (help) {
|
|
||||||
usage(std::cout, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (flags->NumArgs() == 0) {
|
|
||||||
usage(std::cerr, 1);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
auto command = string(argv[optind++]);
|
||||||
Commander commander;
|
Commander commander;
|
||||||
|
|
||||||
commander.Register(Subcommand("list", 0, listFiles));
|
commander.Register(Subcommand("list", 0, listFiles));
|
||||||
commander.Register(Subcommand("new", 1, newPhonebook));
|
commander.Register(Subcommand("new", 1, newPhonebook));
|
||||||
commander.Register(Subcommand("del", 1, delKey));
|
commander.Register(Subcommand("del", 1, delKey));
|
||||||
|
@ -175,24 +147,18 @@ main(int argc, char *argv[])
|
||||||
commander.Register(Subcommand("get", 1, getKey));
|
commander.Register(Subcommand("get", 1, getKey));
|
||||||
commander.Register(Subcommand("put", 2, putKey));
|
commander.Register(Subcommand("put", 2, putKey));
|
||||||
|
|
||||||
auto command = flags->Arg(0);
|
|
||||||
if (command != "new") {
|
if (command != "new") {
|
||||||
cout << "[+] loading phonebook from " << pbFile << "\n";
|
cout << "[+] loading phonebook from " << pbFile << "\n";
|
||||||
if (arena.Open(pbFile.c_str()) != 0) {
|
if (arena.Open(pbFile) != 0) {
|
||||||
cerr << "Failed to open " << pbFile << "\n";
|
cerr << "Failed to open " << pbFile << "\n";
|
||||||
exit(1);
|
exit(1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
auto args = flags->Args();
|
auto result = commander.Run(command, argc-optind, argv+optind);
|
||||||
args.erase(args.begin());
|
|
||||||
auto result = commander.Run(command, args);
|
|
||||||
delete flags;
|
|
||||||
|
|
||||||
switch (result) {
|
switch (result) {
|
||||||
case Subcommand::Status::OK:
|
case Subcommand::Status::OK:
|
||||||
std::cout << "[+] OK\n";
|
std::cout << "[+] OK\n";
|
||||||
retc = 0;
|
|
||||||
break;
|
break;
|
||||||
case Subcommand::Status::NotEnoughArgs:
|
case Subcommand::Status::NotEnoughArgs:
|
||||||
usage(cerr, 1);
|
usage(cerr, 1);
|
||||||
|
@ -207,6 +173,4 @@ main(int argc, char *argv[])
|
||||||
default:
|
default:
|
||||||
abort();
|
abort();
|
||||||
}
|
}
|
||||||
|
|
||||||
return retc;
|
|
||||||
}
|
}
|
|
@ -1,13 +1,10 @@
|
||||||
///
|
///
|
||||||
/// \file scsl.h
|
/// \file scsl.h
|
||||||
/// \author kyle (kyle@imap.cc)
|
/// \author kyle
|
||||||
/// \date 2023-10-10
|
/// \created 2023-10-10
|
||||||
/// \brief scsl is my collection of C++ data structures and code.
|
/// \brief scsl is my collection of C++ data structures and code.
|
||||||
///
|
///
|
||||||
/// scsl.h is a utility header that includes all of SCSL.
|
|
||||||
///
|
|
||||||
/// \section COPYRIGHT
|
/// \section COPYRIGHT
|
||||||
///
|
|
||||||
/// Copyright 2023 K. Isom <kyle@imap.cc>
|
/// Copyright 2023 K. Isom <kyle@imap.cc>
|
||||||
///
|
///
|
||||||
/// Permission to use, copy, modify, and/or distribute this software for
|
/// Permission to use, copy, modify, and/or distribute this software for
|
||||||
|
@ -28,15 +25,12 @@
|
||||||
#define SCSL_SCSL_H
|
#define SCSL_SCSL_H
|
||||||
|
|
||||||
|
|
||||||
#include <scsl/Arena.h>
|
#include <klib/Arena.h>
|
||||||
#include <scsl/Buffer.h>
|
#include <klib/Buffer.h>
|
||||||
#include <scsl/Commander.h>
|
#include <klib/Dictionary.h>
|
||||||
#include <scsl/Dictionary.h>
|
#include <klib/Exceptions.h>
|
||||||
#include <scsl/Exceptions.h>
|
#include <klib/TLV.h>
|
||||||
#include <scsl/Flags.h>
|
#include <klib/Test.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.
|
/// scsl is the top-level namespace containing all the code in this library.
|
||||||
|
@ -55,7 +49,7 @@ namespace scsl {
|
||||||
///
|
///
|
||||||
/// On the one hand, I was building a wireless modem for some Z80 computers I
|
/// 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
|
/// 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
|
/// well as short names to host:port descriptors. I had a limited amount of of
|
||||||
/// persistent NVRAM storage and no SD card or other removeable media, so
|
/// persistent NVRAM storage and no SD card or other removeable media, so
|
||||||
/// typical desktop-oriented serialization mechanisms weren't going to really
|
/// typical desktop-oriented serialization mechanisms weren't going to really
|
||||||
/// work well. Furthermore, when working with microcontrollers, I prefer not to
|
/// work well. Furthermore, when working with microcontrollers, I prefer not to
|
||||||
|
@ -72,12 +66,6 @@ namespace scsl {
|
||||||
/// working on a graphical editor. For this, I needed some data structures to
|
/// working on a graphical editor. For this, I needed some data structures to
|
||||||
/// manage memory in the editor. Thus, Buffer was born.
|
/// 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.
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
|
@ -1,3 +1,2 @@
|
||||||
set(SCSL_INCLUDE_DIRS include/scsl)
|
set(SCSL_INCLUDE_DIRS include/scsl)
|
||||||
set(SCSL_LIBRARIES libscsl.a)
|
set(SCSL_LIBRARIES libscsl.a)
|
||||||
|
|
||||||
|
|
|
@ -1,230 +0,0 @@
|
||||||
///
|
|
||||||
/// \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
187
src/scmp/Math.cc
|
@ -1,187 +0,0 @@
|
||||||
///
|
|
||||||
/// \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
|
|
||||||
|
|
|
@ -1,63 +0,0 @@
|
||||||
///
|
|
||||||
/// \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
|
|
|
@ -1,91 +0,0 @@
|
||||||
#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
290
src/sl/Arena.cc
|
@ -1,290 +0,0 @@
|
||||||
///
|
|
||||||
/// \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
|
|
|
@ -1,78 +0,0 @@
|
||||||
///
|
|
||||||
/// \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
|
|
|
@ -1,74 +0,0 @@
|
||||||
#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]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,246 +0,0 @@
|
||||||
///
|
|
||||||
/// \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
|
|
|
@ -1,232 +0,0 @@
|
||||||
///
|
|
||||||
/// \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
|
|
|
@ -1,70 +0,0 @@
|
||||||
///
|
|
||||||
/// \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
|
|
|
@ -1,39 +0,0 @@
|
||||||
///
|
|
||||||
/// \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
|
|
|
@ -1,137 +0,0 @@
|
||||||
///
|
|
||||||
/// \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
|
|
|
@ -1,149 +0,0 @@
|
||||||
///
|
|
||||||
/// \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
|
|
|
@ -0,0 +1,119 @@
|
||||||
|
///
|
||||||
|
/// \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
163
test/buffer.cc
|
@ -1,163 +0,0 @@
|
||||||
///
|
|
||||||
/// \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;
|
|
||||||
}
|
|
|
@ -1,81 +0,0 @@
|
||||||
///
|
|
||||||
/// \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
295
test/coord2d.cc
|
@ -1,295 +0,0 @@
|
||||||
///
|
|
||||||
/// \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;
|
|
||||||
}
|
|
|
@ -1,142 +0,0 @@
|
||||||
///
|
|
||||||
/// \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;
|
|
||||||
}
|
|
|
@ -1,84 +0,0 @@
|
||||||
///
|
|
||||||
/// \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
279
test/madgwick.cc
|
@ -1,279 +0,0 @@
|
||||||
#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
186
test/math.cc
|
@ -1,186 +0,0 @@
|
||||||
///
|
|
||||||
/// \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;
|
|
||||||
}
|
|
|
@ -1,123 +0,0 @@
|
||||||
#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;
|
|
||||||
}
|
|
|
@ -1,493 +0,0 @@
|
||||||
#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;
|
|
||||||
}
|
|
|
@ -1,89 +0,0 @@
|
||||||
///
|
|
||||||
/// \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;
|
|
||||||
}
|
|
|
@ -1,182 +0,0 @@
|
||||||
///
|
|
||||||
/// \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;
|
|
||||||
}
|
|
172
test/tlv.cc
172
test/tlv.cc
|
@ -1,172 +0,0 @@
|
||||||
///
|
|
||||||
/// \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
514
test/vector.cc
|
@ -1,514 +0,0 @@
|
||||||
//
|
|
||||||
// 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;
|
|
||||||
}
|
|
|
@ -3,7 +3,7 @@
|
||||||
|
|
||||||
|
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <scsl/TLV.h>
|
#include "TLV.h"
|
||||||
|
|
||||||
|
|
||||||
#define ARENA_SIZE 128
|
#define ARENA_SIZE 128
|
|
@ -0,0 +1,121 @@
|
||||||
|
#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
446
trunk
|
@ -1,446 +0,0 @@
|
||||||
#!/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" "$@"
|
|
Loading…
Reference in New Issue