Compare commits

...

50 Commits

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

This error had been hidden due to building with NDEBUG, which ifdefs out
the self-test. The code was probably changed during the refactoring
process.
2023-10-21 19:53:22 -07:00
Kyle Isom f393f8614f Adjust phonebook help usage.
- If a user requests help via the -h flag, the program shouldn't exit
  with an error code, and it should dump information to stdout, not
  stderr.
2023-10-21 19:49:36 -07:00
Kyle Isom 8b63985ac9 Fix flags usage and make Commander Flags capable.
- Programs should exit on Flags parse error.
- Commander now accepts a string vector for interop with Flags.
2023-10-21 19:45:07 -07:00
Kyle Isom 1420ff343d tests: simple test example should be a standalone demo. 2023-10-21 17:45:33 -07:00
Kyle Isom 10888b4872 tests: all tests now use SimpleSuite.
- Also continuing doc updates.
2023-10-21 17:40:01 -07:00
Kyle Isom 0bf4dd54f3 Remove unused Debug header. 2023-10-21 12:34:38 -07:00
Kyle Isom aee337f2e9 clang-tidy fixes, documentation, refactoring. 2023-10-21 02:07:59 -07:00
Kyle Isom 4e83da345f Attempt to fix CircleCI trunk config, round 3. 2023-10-20 21:39:23 -07:00
Kyle Isom 540a0d5b74 Attempt to fix CircleCI trunk config, round 2. 2023-10-20 21:21:39 -07:00
Kyle Isom 2d7a8d5c1d Attempt to fix CircleCI trunk config. 2023-10-20 21:20:40 -07:00
Kyle Isom 0c7fa41cc8 Document and refactor geom code, round 2.
- Doxygenate headers.
- Rename to bring methods and functions in line with everything else.
2023-10-20 21:17:18 -07:00
Kyle Isom 6a421d6adf Document and refactor geom code.
- Doxygenate headers.
- Rename to bring methods and functions in line with everything else.
2023-10-20 20:45:39 -07:00
Kyle Isom 4b1007123a Test suite cleanups, convert Coord2D to Vector<T, 2>.
- The standard SimpleSuite setup now include flags to suppress printing
  the report in addition to silencing the test runs. This is useful in
  automated testing.
- Point2D and Polar2D in Coord2D have been converted from custom types
  to Vector<int, 2> and Vector<double, 2>, respectively.
2023-10-20 19:05:55 -07:00
Kyle Isom 68ed5e0aca Minor code cleanups. 2023-10-20 16:13:49 -07:00
Kyle Isom 2fceae91fd geom: add missing include. 2023-10-20 03:14:12 -07:00
Kyle Isom 1c2f9af9d9 ci: update script name.
This is a change in the upstream dev container.
2023-10-20 03:08:08 -07:00
Kyle Isom e923335396 Alpine image can't run trunk. 2023-10-20 03:06:04 -07:00
Kyle Isom fa8a89625b add trunk config to repo 2023-10-20 03:05:12 -07:00
Kyle Isom b49caa3ec9 Cleaning up and documenting scmp code. 2023-10-20 02:59:36 -07:00
Kyle Isom 4eb4008130 Further code cleanups and documentation.
- Coverity defects.
- Documentation.
2023-10-20 01:31:06 -07:00
Kyle Isom 2a23d2e204 Fix CircleCI build, more coverity fixes. 2023-10-19 22:57:55 -07:00
Kyle Isom a5bb31943c Add trunk to repo, import clang-format. 2023-10-19 22:04:15 -07:00
Kyle Isom f896a108dd Coverity fixes.
Primarily using std::move in more places.
2023-10-19 21:43:38 -07:00
Kyle Isom 9494871145 README.md: use the correct badge for CircleCI. 2023-10-19 21:19:53 -07:00
Kyle Isom 1c6de07b7a test/coord2d: add missing <array> include. 2023-10-19 21:18:08 -07:00
Kyle Isom eaea8c2ab0 Buffer: add explicit/defensive coding checks. 2023-10-19 21:00:35 -07:00
Kyle Isom 6dab443789 Dictionary: add defensive coding checks. 2023-10-19 21:00:21 -07:00
Kyle Isom 0a661a7d70 Close file handle when done.
This also brings the arena to a single-return standard.
2023-10-19 20:55:31 -07:00
Kyle Isom b1bbaebdac Slow working on bringing the old code up to standard.
- Documentation updates - most of the old files use non-Doxygen or
  no/minimal header comments.
- Rework SimpleSuite to be more useful.
- Coverity-surfaced fixes.
2023-10-19 20:32:46 -07:00
Kyle Isom a9991f241a Add badges to README. 2023-10-19 11:16:13 -07:00
Kyle Isom 49eea73449 Arena: remove resource leak. 2023-10-19 10:59:43 -07:00
Kyle Isom 36fe049485 Continuing refactor work. 2023-10-19 00:58:40 -07:00
Kyle Isom 8d02d078e7 refactor namespaces 2023-10-18 23:57:50 -07:00
Kyle Isom 5f3dc6e9f6 Restructure project, start importing sc3 code. 2023-10-18 23:44:05 -07:00
73 changed files with 7444 additions and 916 deletions

View File

@ -1,25 +1,18 @@
# Use the latest 2.1 version of CircleCI pipeline process engine.
# See: https://circleci.com/docs/configuration-reference
version: 2.1
# Define a job to be invoked later in a workflow.
# See: https://circleci.com/docs/configuration-reference/#jobs
jobs:
ctest:
# Specify the execution environment. You can specify an image from Docker Hub or use one of our convenience images from CircleCI's Developer Hub.
# See: https://circleci.com/docs/configuration-reference/#executor-job
docker:
- image: git.wntrmute.dev/sc/dev:alpine
# Add steps to the job
# See: https://circleci.com/docs/configuration-reference/#steps
steps:
- checkout
- run:
name: Setup cmake build
command: setup-cmake.sh
command: cmake-build-and-test.sh
- run:
name: Valgrind checks.
command: cmake-run-valgrind.sh
# Orchestrate jobs using workflows
# See: https://circleci.com/docs/configuration-reference/#workflows
workflows:
ctest:
jobs:

67
.clang-format Normal file
View File

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

View File

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

2
.gitignore vendored
View File

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

View File

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

View File

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

8
.trunk/.gitignore vendored Normal file
View File

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

27
.trunk/trunk.yaml Normal file
View File

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

View File

@ -1,11 +1,12 @@
cmake_minimum_required(VERSION 3.22)
project(scsl LANGUAGES CXX
VERSION 0.2.5
VERSION 1.1.4
DESCRIPTION "Shimmering Clarity Standard Library")
set(CMAKE_CXX_STANDARD 14)
set(CMAKE_VERBOSE_MAKEFILES TRUE)
set(VERBOSE YES)
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
# compile options:
# -Wall Default to all errors.
@ -24,37 +25,66 @@ add_compile_options(
"-g"
"$<$<CONFIG:RELEASE>:-O2>"
)
#add_link_options("-fsanitize=address")
if ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang")
add_compile_options("-stdlib=libc++")
else ()
# nothing special for gcc at the moment
# nothing special for gcc At the moment
endif ()
add_compile_definitions(SCSL_DESKTOP_BUILD)
add_compile_definitions(SCSL_VERSION=${PROJECT_VERSION})
set(HEADER_FILES scsl.h
Arena.h
Buffer.h
Commander.h
Dictionary.h
Exceptions.h
Flag.h
StringUtil.h
TLV.h
Test.h
set(HEADER_FILES
include/scsl/scsl.h
include/scsl/Arena.h
include/scsl/Buffer.h
include/scsl/Commander.h
include/scsl/Dictionary.h
include/scsl/Flags.h
include/scsl/SimpleConfig.h
include/scsl/StringUtil.h
include/scsl/TLV.h
include/scmp/estimation.h
include/scmp/geom.h
include/scmp/scmp.h
include/scmp/Math.h
include/scmp/geom/Coord2D.h
include/scmp/geom/Orientation.h
include/scmp/geom/Quaternion.h
include/scmp/geom/Vector.h
include/scmp/estimation/Madgwick.h
include/sctest/sctest.h
include/sctest/Assert.h
include/sctest/Checks.h
include/sctest/Exceptions.h
include/sctest/Report.h
include/sctest/SimpleSuite.h
)
include_directories(include)
set(SOURCE_FILES
Arena.cc
Buffer.cc
Commander.cc
Dictionary.cc
Exceptions.cc
Flag.cc
StringUtil.cc
TLV.cc
Test.cc
src/sl/Arena.cc
src/sl/Buffer.cc
src/sl/Commander.cc
src/sl/Dictionary.cc
src/test/Exceptions.cc
src/sl/Flags.cc
src/sl/SimpleConfig.cc
src/sl/StringUtil.cc
src/sl/TLV.cc
src/scmp/Math.cc
src/scmp/Coord2D.cc
src/scmp/Orientation.cc
src/scmp/Quaternion.cc
src/test/Assert.cc
src/test/Report.cc
src/test/SimpleSuite.cc
)
if (APPLE)
@ -67,31 +97,43 @@ add_library(scsl
${SOURCE_FILES} ${HEADER_FILES})
endif()
add_executable(phonebook phonebook.cc)
add_executable(phonebook src/bin/phonebook.cc)
target_link_libraries(phonebook scsl)
include(CTest)
enable_testing()
add_executable(buffer_test bufferTest.cc)
target_link_libraries(buffer_test scsl)
add_test(bufferTest buffer_test)
set(TEST_SOURCES)
macro(generate_test name)
add_executable(test_${name} test/${name}.cc ${TEST_SOURCES} ${ARGN})
target_link_libraries(test_${name} ${PROJECT_NAME})
target_include_directories(test_${name} PRIVATE test)
add_test(test_${name} test_${name})
endmacro()
add_executable(tlv_test tlvTest.cc)
target_link_libraries(tlv_test scsl)
add_test(tlvTest tlv_test)
# core standard library
generate_test(buffer)
generate_test(tlv)
generate_test(dictionary)
generate_test(stringutil)
add_executable(dictionary_test dictionaryTest.cc)
target_link_libraries(dictionary_test scsl)
add_test(dictionaryTest dictionary_test)
# math and physics
generate_test(coord2d)
generate_test(madgwick)
generate_test(math)
generate_test(orientation)
generate_test(quaternion)
generate_test(vector)
add_executable(flag_test flagTest.cc)
target_link_libraries(flag_test scsl)
add_test(flagTest flag_test)
# test tooling
add_executable(flags-demo test/flags.cc)
target_link_libraries(flags-demo ${PROJECT_NAME})
add_executable(stringutil_test stringutil_test.cc)
target_link_libraries(stringutil_test scsl)
add_test(stringutilTest stringutil_test)
add_executable(simple-test-demo test/simple_suite_example.cc)
target_link_libraries(simple-test-demo ${PROJECT_NAME})
add_executable(config-explorer test/config-explorer.cc)
target_link_libraries(config-explorer ${PROJECT_NAME})
include(CMakePackageConfigHelpers)
write_basic_package_version_file(
@ -112,7 +154,7 @@ add_custom_target(deploy-docs
configure_file(scsl.pc.in scsl.pc @ONLY)
install(TARGETS scsl LIBRARY DESTINATION lib)
install(TARGETS phonebook RUNTIME DESTINATION bin)
install(FILES ${HEADER_FILES} DESTINATION include/scsl)
install(DIRECTORY include/ DESTINATION include)
install(FILES scslConfig.cmake DESTINATION share/scsl/cmake)
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/scsl.pc DESTINATION lib/pkgconfig)

View File

@ -1,5 +1,9 @@
# scsl : The Shimmering Clarity Standard C++ Library
[![CircleCI](https://dl.circleci.com/status-badge/img/gh/shimmering-clarity/scsl/tree/master.svg?style=svg)](https://dl.circleci.com/status-badge/redirect/gh/shimmering-clarity/scsl/tree/master)
[![image](https://scan.coverity.com/projects/29251/badge.svg)](https://scan.coverity.com/projects/shimmering-clarity-scsl)
scsl is a collection of software I found myself needing to use repeatedly.
Full [Doxygen documentation](https://docs.shimmering-clarity.net/scsl/)

View File

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

View File

@ -8,9 +8,9 @@ set(DOXYGEN_EXTRACT_ALL YES)
message(STATUS "Doxygen found, building docs.")
doxygen_add_docs(scsl_docs
${HEADER_FILES} ${SOURCE_FILES}
${HEADER_FILES}
ALL
USE_STAMP_FILE)
add_dependencies(scsl scsl_docs)
install(DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/html DESTINATION share/doc/scsl)
install(DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/man DESTINATION share)

View File

@ -20,9 +20,9 @@ set(CPACK_DEBIAN_PACKAGE_GENERATE_SHLIBS ON)
set(CPACK_DEBIAN_FILE_NAME DEB-DEFAULT)
if(LINUX)
set(CPACK_GENERATOR "DEB;STGZ;TGZ")
set(CPACK_GENERATOR "DEB;RPM;STGZ;TGZ")
elseif(APPLE)
set(CPACK_GENERATOR "productbuild")
set(CPACK_GENERATOR "STGZ;TGZ")
elseif(MSVC OR MSYS OR MINGW)
set(CPACK_GENERATOR "NSIS;ZIP")
else()

View File

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

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

@ -0,0 +1,128 @@
///
/// \file include/scmp/Math.h
/// \author K. Isom <kyle@imap.cc>
/// \date 2017-06-05
/// \brief Common math functions.
///
/// Arena defines a memory management backend for pre-allocating memory.
///
/// Copyright 2023 K. Isom <kyle@imap.cc>
///
/// Permission to use, copy, modify, and/or distribute this software for
/// any purpose with or without fee is hereby granted, provided that
/// the above copyright notice and this permission notice appear in all /// copies.
///
/// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
/// WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
/// WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
/// AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
/// DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA
/// OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
/// TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
/// PERFORMANCE OF THIS SOFTWARE.
///
#ifndef SCSL_SCMP_MATH_H
#define SCSL_SCMP_MATH_H
#include <cmath>
#include <vector>
namespace scmp {
/// MAX_RADIAN is a precomputed 2 * M_PI.
constexpr double MAX_RADIAN = 2 * M_PI;
constexpr double MIN_RADIAN = -2 * M_PI;
constexpr double PI_D = 3.141592653589793;
/// Roll m die of n sides, returning a vector of the dice.
std::vector<int> Die(int m, int n);
/// Roll m die of n sides, returning the total of the die.
int DieTotal(int m, int n);
/// Roll m die of n sides, and take the total of the top k die.
int BestDie(int k, int m, int n);
/// \brief Convert radians to degrees.
///
/// \param rads the Angle in radians
/// \return the Angle in degrees.
float RadiansToDegreesF(float rads);
/// \brief Convert radians to degrees.
///
/// \param rads the Angle in radians
/// \return the Angle in degrees.
double RadiansToDegreesD(double rads);
/// \brief Convert degrees to radians.
///
/// \param degrees the Angle in degrees
/// \return the Angle in radians.
float DegreesToRadiansF(float degrees);
/// \brief Convert degrees to radians.
///
/// \param degrees the Angle in degrees
/// \return the Angle in radians.
double DegreesToRadiansD(double degrees);
/// \brief RotateRadians rotates theta0 by theta1 radians, wrapping
/// the result to MIN_RADIAN <= result <= MAX_RADIAN.
///
/// \param theta0
/// \param theta1
/// \return
double RotateRadians(double theta0, double theta1);
/// \brief Get the default epsilon value.
///
/// \param epsilon The variable to store the epsilon value in.
void DefaultEpsilon(double &epsilon);
/// \brief Get the default epsilon value.
///
/// \param epsilon The variable to store the epsilon value in.
void DefaultEpsilon(float &epsilon);
/// \brief Get the default epsilon for integer types.
///
/// \param epsilon The variable to store the epsilon value in.
void DefaultEpsilon(int& epsilon);
/// \brief Return whether the two values of type T are equal to within
/// some tolerance.
///
/// \tparam T The type of value
/// \param a A value of type T used as the left-hand side of an
/// equality check.
/// \param b A value of type T used as the right-hand side of an
/// equality check.
/// \param epsilon The tolerance value.
/// \return Whether the two values are "close enough" to be considered
/// equal.
template <typename T>
static T
WithinTolerance(T a, T b, T epsilon)
{
return std::abs(a - b) <= epsilon;
}
/// \brief Integer square-root.
///
/// \param n A max-value integer whose square root should be returned.
/// \return The square root of $n$.
size_t ISqrt(size_t n);
} // namespace scmp
#endif //SCSL_SCMP_MATH_H

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

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

View File

@ -0,0 +1,214 @@
///
/// \file include/scmp/estimation/Madgwick.h
/// \author K. Isom <kyle@imap.cc>
/// \date 2019-08-06
/// \brief Implementation of a Madgwick estimation.
///
/// See https://courses.cs.washington.edu/courses/cse466/14au/labs/l4/madgwick_internal_report.pdf.
///
/// \section COPYRIGHT
///
/// Copyright 2019 K. Isom <kyle@imap.cc>
///
/// Permission to use, copy, modify, and/or distribute this software for
/// any purpose with or without fee is hereby granted, provided that
/// the above copyright notice and this permission notice appear in all /// copies.
///
/// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
/// WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
/// WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
/// AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
/// DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA
/// OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
/// TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
/// PERFORMANCE OF THIS SOFTWARE.
///
#ifndef SCMP_FILTER_MADGWICK_H
#define SCMP_FILTER_MADGWICK_H
#include <scmp/geom/Vector.h>
#include <scmp/geom/Quaternion.h>
/// scmp contains the chimmering clarity math and physics code.
namespace scmp {
namespace estimation {
/// \brief Madgwick implements an efficient Orientation estimation for
/// Intertial Measurement Units (IMUs).
///
/// Madgwick is a novel Orientation estimation applicable to IMUs
/// consisting of tri-Axis gyroscopes and accelerometers, and MARG
/// sensor arrays that also include tri-Axis magnetometers. The MARG
/// implementation incorporates magnetic distortionand gyroscope bias
/// drift compensation.
///
/// It is described in the paper [An efficient Orientation estimation for inertial and inertial/magnetic sensor arrays](http://x-io.co.uk/res/doc/madgwick_internal_report.pdf).
///
/// \tparam T A floating point type.
template <typename T>
class Madgwick {
public:
/// \brief The Madgwick estimation is initialised with an identity MakeQuaternion.
Madgwick() : deltaT(0.0), previousSensorFrame(), sensorFrame()
{};
/// \brief The Madgwick estimation is initialised with a sensor frame.
///
/// \param sf A sensor frame; if zero, the sensor frame will be
/// initialised as an identity MakeQuaternion.
Madgwick(scmp::geom::Vector<T, 3> sf) : deltaT(0.0), previousSensorFrame()
{
if (!sf.IsZero()) {
sensorFrame = scmp::geom::MakeQuaternion<T>(sf, 0.0);
}
}
/// \brief Initialise the estimation with a sensor frame MakeQuaternion.
///
/// \param sf A MakeQuaternion representing the current Orientation.
Madgwick(scmp::geom::Quaternion<T> sf) :
deltaT(0.0), previousSensorFrame(), sensorFrame(sf)
{};
/// \brief Return the current orientation as measured by the
/// estimation.
///
/// \return The current sensor frame.
scmp::geom::Quaternion<T>
Orientation() const
{
return this->sensorFrame;
}
/// \brief Return the estimation's rate of angular change from a
/// sensor frame.
///
/// Return the rate of change of the Orientation of the earth
/// frame with respect to the sensor frame.
///
/// \param gyro A three-dimensional vector containing gyro
/// readings as w_x, w_y, w_z.
/// \return A MakeQuaternion representing the rate of angular
/// change.
scmp::geom::Quaternion<T>
AngularRate(const scmp::geom::Vector<T, 3> &gyro) const
{
return (this->sensorFrame * 0.5) * scmp::geom::Quaternion<T>(gyro, 0.0);
}
/// \brief Update the sensor frame to a new frame.
///
/// \param sf The new sensor frame replacing the previous one.
/// \param delta The time delta since the last update.
void
UpdateFrame(const scmp::geom::Quaternion<T> &sf, T delta)
{
this->previousSensorFrame = this->sensorFrame;
this->sensorFrame = sf;
this->deltaT = delta;
}
/// \brief Update the sensor frame to a new frame.
///
/// \warning The estimation's default Δt must be set before
/// calling this.
///
/// \param sf The new sensor frame replacing the previous one.
void
UpdateFrame(const scmp::geom::Quaternion<T> &sf)
{
this->UpdateFrame(sf, this->deltaT);
}
/// \brief Update the sensor frame with a gyroscope reading.
///
/// This method will assert that the Δt value is not zero
/// within a 100μs tolerance. This assert is compiled out with
/// the compile flag NDEBUG, but may be useful to catch
/// possible errors.
///
/// \param gyro A three-dimensional vector containing gyro
/// readings as w_x, w_y, w_z.
/// \param delta The time step between readings. It must not
/// be zero.
void
UpdateAngularOrientation(const scmp::geom::Vector<T, 3> &gyro, T delta)
{
// Ensure the delta isn't zero within a 100 μs
// tolerance.
if (scmp::WithinTolerance<T>(delta, 0.0, 0.00001)) {
return;
}
scmp::geom::Quaternion<T> q = this->AngularRate(gyro) * delta;
this->UpdateFrame(this->sensorFrame + q, delta);
}
/// \brief Update the sensor frame with a gyroscope reading.
///
/// If no Δt is provided, the estimation's default is used.
///
/// \warning The default Δt must be explicitly set using DeltaT
/// before calling this.
///
/// \param gyro A three-dimensional vector containing gyro
/// readings as w_x, w_y, w_z.
void
UpdateAngularOrientation(const scmp::geom::Vector<T, 3> &gyro)
{
this->UpdateAngularOrientation(gyro, this->deltaT);
}
/// \brief Retrieve a vector of the Euler angles in ZYX
/// Orientation.
///
/// \return A vector of Euler angles as <ψ, θ, ϕ>.
scmp::geom::Vector<T, 3>
Euler()
{
return this->sensorFrame.Euler();
}
/// \brief Set the default Δt.
///
/// \note This must be explicitly called before calling any
/// method which uses the estimation's internal Δt.
///
/// \param newDeltaT The time delta to use when no time delta
/// is provided.
void
DeltaT(T newDeltaT)
{
this->deltaT = newDeltaT;
}
/// \brief Retrieve the estimation's current ΔT.
///
/// \return The current value the estimation will default to using
/// if no time delta is provided.
T DeltaT() { return this->deltaT; }
private:
T deltaT;
scmp::geom::Quaternion<T> previousSensorFrame;
scmp::geom::Quaternion<T> sensorFrame;
};
/// \brief Madgwickd is a shorthand alias for a Madgwick<double>.
using Madgwickd = Madgwick<double>;
/// \brief Madgwickf is a shorthand alias for a Madgwick<float>.
using Madgwickf = Madgwick<float>;
} // namespace estimation
} // namespace scmp
#endif // SCMP_FILTER_MADGWICK_H

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

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

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

@ -0,0 +1,164 @@
///
/// \file include/scmp/geom/Coord2D.h
/// \author K. Isom <kyle@imap.cc>
/// \date 2017-06-05
/// \brief 2D point and polar coordinate systems.
///
/// \section COPYRIGHT
///
/// Copyright 2017 K. Isom <kyle@imap.cc>
///
/// Permission to use, copy, modify, and/or distribute this software for
/// any purpose with or without fee is hereby granted, provided that
/// the above copyright notice and this permission notice appear in all /// copies.
///
/// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
/// WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
/// WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
/// AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
/// DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA
/// OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
/// TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
/// PERFORMANCE OF THIS SOFTWARE.
///
#ifndef SCMATH_GEOM_COORD2D_H
#define SCMATH_GEOM_COORD2D_H
#include <cmath>
#include <ostream>
#include <vector>
#include <scmp/geom/Vector.h>
namespace scmp {
namespace geom {
class Point2D;
class Polar2D;
/// \brief Point2D is a cartesian (X,Y) pairing.
class Point2D : public Vector<int, 2> {
public:
/// \brief A Point2D defaults to (0,0).
Point2D();
/// \brief Initialize a Point2D At (_x, _y).
Point2D(int _x, int _y);
/// \brief Initialize a Point2D from a Polar2D coordinate.
Point2D(const Polar2D &pol);
/// \brief Return the X component of the point.
int X() const;
/// \brief Set the X component of the point.
void X(int _x);
/// \brief Return the Y component of the point.
int Y() const;
/// Set the Y component of the point.
void Y(int _y);
/// \brief ToString returns a string in the format (x,y).
std::string ToString();
/// \brief ToPolar converts the Point2D to a polar coordinate
/// in-place.
void ToPolar(Polar2D &);
/// \brief Rotate rotates the point by theta radians.
///
/// \param rotated Stores the rotated point.
/// \param theta The Angle (in radians) to Rotate the point.
void Rotate(Point2D& rotated, double theta);
/// \brief Rotate this point around a series of vertices.
///
/// \param vertices A series of vertices to Rotate this point around.
/// \param theta The Angle to Rotate by.
/// \return A series of rotated points.
std::vector<Point2D> Rotate(std::vector<Polar2D> vertices, double theta);
/// \brief Translate adds this point to the first argument,
/// storing the result in the second argument.
///
/// \param other The point to translate by.
/// \param translated The point to store the translation in.
void Translate(const Point2D &other, Point2D &translated);
/// \brief Distance returns the distance from this point to another.
int Distance(const Point2D &other) const;
friend std::ostream &operator<<(std::ostream &outs, const Point2D &pt);
};
/// \brief Polar2D is a pairing of a radius r and angle θ from some
/// reference point; in this library, it is assumed to be the
/// Cartesian origin (0, 0).
class Polar2D : public Vector<double, 2> {
public:
/// A Polar2D can be initialised as a zeroised polar coordinate, by specifying
/// the radius and Angle directly, or via conversion from a Point2D.
/// \brief Construct a zero polar coordinate.
Polar2D();
/// \brief Construct a polar coordinate from a radius and
/// angle.
///
/// \param _r A radius
/// \param _theta An angle
Polar2D(double _r, double _theta);
/// \brief Construct a polar coordinate from a point.
///
/// This construct uses the origin (0,0) as the reference point.
///
/// \param point A 2D Cartesian point.
Polar2D(const Point2D& point);
/// \brief Return the radius component of this coordinate.
double R() const;
/// \brief Set the radius component of this coordinate.
void R(const double _r);
/// \brief Return the angle component of this coordinate.
double Theta() const;
/// \brief Set the angle component of this coordinate.
void Theta(const double _theta);
/// \brief Return the coordinate in string form.
std::string ToString();
/// \brief Construct a Point2D representing this Polar2D.
void ToPoint(Point2D &point);
/// \brief Rotate polar coordinate by some angle.
///
/// \param rotated The rotated Polar2D will be stored in this
/// coordinate.
/// \param delta The angle to rotate by.
void Rotate(Polar2D &rotated, double delta);
/// \brief Rotate this polar coordinate around a 2D point.
///
/// \param other The reference point.
/// \param result The point where the result will stored.
/// \param delta The angle to rotate by.
void RotateAround(const Point2D &other, Point2D &result, double delta);
friend std::ostream &operator<<(std::ostream &, const Polar2D &);
};
} // end namespace geom
} // end namespace math
#endif

View File

@ -0,0 +1,113 @@
///
/// \file include/scmp/geom/Orientation.h
/// \author K. Isom <kyle@imap.cc>
/// \date 2017-06-05
/// \brief Orientation of vectors w.r.t. a reference plane, assumed to
/// be the Earth.
///
/// Copyright 2023 K. Isom <kyle@imap.cc>
///
/// Permission to use, copy, modify, and/or distribute this software for
/// any purpose with or without fee is hereby granted, provided that
/// the above copyright notice and this permission notice appear in all /// copies.
///
/// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
/// WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
/// WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
/// AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
/// DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA
/// OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
/// TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
/// PERFORMANCE OF THIS SOFTWARE.
///
#include <cstdint>
#include <scmp/geom/Vector.h>
#ifndef SCMATH_GEOM_ORIENTATION_H
#define SCMATH_GEOM_ORIENTATION_H
namespace scmp {
namespace geom {
/// \defgroup basis Basis vector indices.
/// The following constants are provided as a convenience for indexing two-
/// and three-dimensional vectors.
/// \ingroup basis
/// \brief Convenience constant for the x index.
constexpr uint8_t BasisX = 0;
/// \ingroup basis
/// \brief Convenience constant for the y index.
constexpr uint8_t BasisY = 1;
/// \ingroup basis
/// \brief Convenience constant for the z index.
constexpr uint8_t BasisZ = 2;
/// \brief Basis2D provides basis vectors for Vector2ds.
static const Vector2D Basis2D[] = {
Vector2D{1, 0},
Vector2D{0, 1},
};
/// \brief Basis2D provides basis vectors for Vector2fs.
static const Vector2F Basis2F[] = {
Vector2F{1, 0},
Vector2F{0, 1},
};
/// \brief Basis2D provides basis vectors for Vector3ds.
static const Vector3D Basis3D[] = {
Vector3D{1, 0, 0},
Vector3D{0, 1, 0},
Vector3D{0, 0, 1},
};
/// \brief Basis2D provides basis vectors for Vector3fs.
static const Vector3F Basis3F[] = {
Vector3F{1, 0, 0},
Vector3F{0, 1, 0},
Vector3F{0, 0, 1},
};
/// \brief Compass heading for a Vector2F.
///
/// \param vec A vector Orientation.
/// \return The compass heading of the vector in radians.
float Heading2F(Vector2F vec);
/// \brief Compass heading for a Vector2D.
///
/// \param vec A vector Orientation.
/// \return The compass heading of the vector in radians.
double Heading2D(Vector2D vec);
/// \brief Compass heading for a Vector2F.
///
/// \param vec A vector Orientation.
/// \return The compass heading of the vector in radians.
float Heading3F(Vector3F vec);
/// Heading3D returns a compass heading for a Vector2F.
///
/// \param vec A vector Orientation.
/// \return The compass heading of the vector in radians.
double Heading3D(Vector3D vec);
} // namespace geom
} // namespace scmp
#endif // SCMATH_GEOM_ORIENTATION_H

View File

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

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

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

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

@ -0,0 +1,34 @@
///
/// \file include/scmp/scmp.h
/// \author K. Isom <kyle@imap.cc>
/// \date 2023-10-19
/// \brief Aggregated scmp include header.
///
/// Copyright 2023 K. Isom <kyle@imap.cc>
///
/// Permission to use, copy, modify, and/or distribute this software for
/// any purpose with or without fee is hereby granted, provided that
/// the above copyright notice and this permission notice appear in all /// copies.
///
/// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
/// WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
/// WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
/// AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
/// DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA
/// OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
/// TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
/// PERFORMANCE OF THIS SOFTWARE.
///
#ifndef SCSL_SCMP_H
#define SCSL_SCMP_H
/// \brief Shimmering Clarity Math & Physics toolkit.
///
/// The Shimmering Clarity contains code related to math and physics,
/// particularly as relevant to game programming and robotics.
namespace scmp {}
#endif //SCSL_SCMP_H

View File

@ -21,12 +21,6 @@
/// TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
/// PERFORMANCE OF THIS SOFTWARE.
///
/// \section PLATFORM SUPPORT
///
/// Arena will build on the major platforms, but memory-mapped files are only
/// supported on Unix-like systems. File I/O on Windows, for example, reads the
/// file into an allocated arena. See Arena::Open for more details.
#ifndef KIMODEM_ARENA_H
#define KIMODEM_ARENA_H
@ -37,7 +31,7 @@
#include <iostream>
#include <sys/stat.h>
#include "Exceptions.h"
#include "sctest/Exceptions.h"
#if defined(__WIN64__) || defined(__WIN32__) || defined(WIN32)
@ -67,7 +61,7 @@ enum class ArenaType
};
/// Arena is the class that implements a memory arena.
/// \brief Fixed, pre-allocated memory.
///
/// The Arena uses the concept of a cursor to point to memory in the arena. The
/// #Start and #End methods return pointers to the start and end of the
@ -76,7 +70,7 @@ enum class ArenaType
/// The arena should be initialized with one of the Set methods (SetStatic,
/// SetAlloc) or one of the file-based options (Create, Open, MemoryMap). At
/// this point, no further memory management should be done until the end of the
/// arena's life, at which point Destroy should be called.
/// arena's life, At which point Destroy should be called.
class Arena {
public:
/// An Arena is initialized with no backing memory.

View File

@ -1,5 +1,5 @@
///
/// \file Buffer.h
/// \file include/scsl/Buffer.h
/// \author K. Isom <kyle@imap.cc>
/// \date 2023-10-09
/// \brief Buffer implements basic line buffers.
@ -33,7 +33,7 @@
namespace scsl {
/// Buffer is a basic line buffer.
/// \brief Basic line buffer.
///
/// The buffer manages its own internal memory, growing and shrinking
/// as needed. Its capacity is separate from its length; the optimal
@ -47,76 +47,89 @@ namespace scsl {
/// memory if possible, but only if #AutoTrimIsEnabled (it is by default).
class Buffer {
public:
/// A Buffer can be constructed empty, with no memory allocated (yet).
/// \brief Construct an empty buffer with no memory allocated.
Buffer();
/// A Buffer can be constructed with an explicit capacity.
/// \buffer Constructor with explicit memory capacity.
///
/// \param initialCapacity The initial allocation size for the buffer.
/// \param initialCapacity The initial allocation size for the
/// buffer.
explicit Buffer(size_t initialCapacity);
/// A Buffer can be initialized with a starting C-style string.
/// \brief Construct with a C-style string.
explicit Buffer(const char *s);
/// A Buffer can be initialized with a starting string.
explicit Buffer(const std::string s);
/// \buffer Construct with an initial string.
explicit Buffer(const std::string& s);
~Buffer()
{ this->Reclaim(); }
~Buffer();
/// Contents returns the Buffer's contents.
uint8_t *Contents() const
{ return this->contents; }
/// \brief Retrieve the buffer's contents.
uint8_t *Contents() const;
/// Length returns the length of the data currently stored in the
/// buffer.
size_t Length() const
{ return this->length; };
std::string ToString() const;
/// Capacity returns the amount of memory allocated to the Buffer.
size_t Capacity() const
{ return this->capacity; }
/// \brief The length of data stored in the buffer.
///
/// \return The number of bytes stored in the Buffer.
size_t Length() const;
/// Append copies in a C-style string to the end of the buffer.
/// \brief Return the amount of memory allocated for the
/// Buffer.
size_t Capacity() const;
/// \brief Append a C-style string to the end of the buffer.
///
/// \param s The string to append.
/// \return True if the Buffer was resized.
bool Append(const char *s);
/// Append copies in a string to the end of the buffer.
/// Append Append a string to the end of the buffer.
///
/// \param s The string to append.
/// \return True if the Buffer was resized.
bool Append(const std::string s);
bool Append(const std::string &s);
/// Append copies in a byte buffer to the end of the buffer.
/// \brief Append a byte buffer to the end of the buffer.
///
/// \param data The byte buffer to insert.
/// \param datalen The length of the byte buffer.
/// \return True if the Buffer was resized.
bool Append(const uint8_t *data, const size_t datalen);
/// Append copies a single character to the end of the buffer.
/// \brief Append a single character to the end of the buffer.
///
/// \param c The character to append.
/// \return True if the Buffer was resized.
bool Append(const uint8_t c);
/// Insert copies a C-style string into the buffer at index.
/// \brief Insert a C-style string into the buffer at index.
///
/// \note As this is intended for use in text editing, an
/// insert into a buffer after the length will insert
/// spaces before the content.
///
/// \param index The index to insert the string at.
/// \param s The string to insert.
/// \return True if the Buffer was resized.
bool Insert(const size_t index, const char *s);
/// Insert copies a string into the buffer at index.
/// \brief Insert a string into the buffer at index.
///
/// \note As this is intended for use in text editing, an
/// insert into a buffer after the length will insert
/// spaces before the content.
///
/// \param index The index the string should be inserted at.
/// \param s The string to insert.
/// \return True if the Buffer was resized.
bool Insert(const size_t index, const std::string s);
bool Insert(const size_t index, const std::string &s);
/// Insert copies a uint8_t buffer into the buffer at index.
/// \brief Insert a uint8_t buffer into the buffer at index.
///
/// \note As this is intended for use in text editing, an
/// insert into a buffer after the length will insert
/// spaces before the content.
///
/// \param index The index to insert the buffer at.
/// \param data The buffer to insert.
@ -125,21 +138,25 @@ public:
bool
Insert(const size_t index, const uint8_t *data, const size_t datalen);
/// Insert copies a character into the buffer at index.
/// \brief Insert a character into the buffer at index.
///
/// \note As this is intended for use in text editing, an
/// insert into a buffer after the length will insert
/// spaces before the content.
///
/// \param index The index to insert the character at.
/// \param c The character to insert.
/// \return True if the Buffer was resized.
bool Insert(const size_t index, const uint8_t c);
/// Remove removes `count` bytes from the buffer at `index`.
/// \brief Remove `count` bytes from the buffer at `index`.
///
/// \param index The starting index to remove bytes from.
/// \param count The number of bytes to remove.
/// \return True if the Buffer was resized.
bool Remove(const size_t index, const size_t count);
/// Remove removes a single byte from the buffer.
/// \brief Remove removes a single byte from the buffer.
///
/// \param index The index pointing to the byte to be removed.
/// \return True if the Buffer was resized.
@ -147,7 +164,7 @@ public:
/* memory management */
/// Resize changes the capacity of the buffer to `newCapacity`.
/// \brief Changes the capacity of the buffer to `newCapacity`.
///
/// If newCapacity is less than the length of the Buffer, it
/// will remove enough bytes from the end to make this happen.
@ -155,27 +172,23 @@ public:
/// \param newCapacity The new capacity for the Buffer.
void Resize(size_t newCapacity);
/// Trim will resize the Buffer to an appropriate size based on
/// its length.
/// \brief Resize the Buffer capacity based on its length.
///
/// \return The new capacity of the Buffer.
size_t Trim();
/// DisableAutoTrim prevents the #Buffer from automatically
/// trimming memory after a call to #Remove.
void DisableAutoTrim()
{ this->autoTrim = false; }
void DisableAutoTrim();
/// EnableAutoTrim enables automatically trimming memory after
/// calls to #Remove.
void EnableAutoTrim()
{ this->autoTrim = true; }
void EnableAutoTrim();
/// AutoTrimIsEnabled returns true if autotrim is enabled.
///
/// \return #Remove will call Trim.
bool AutoTrimIsEnabled()
{ return this->autoTrim; }
bool AutoTrimIsEnabled();
/// Clear removes the data stored in the buffer. It will not
/// call #Trim; the capacity of the buffer will not be altered.

View File

@ -1,5 +1,5 @@
///
/// \file Commander.h
/// \file include/scsl/Commander.h
/// \author K. Isom <kyle@imap.cc>
/// \date 2023-10-10
/// \brief Subprogram tooling.
@ -44,9 +44,11 @@ namespace scsl {
/// CommanderFunc describes a function that can be run in Commander.
///
/// It expects an argument count and a list of arguments.
using CommanderFunc = std::function<bool (int, char **)>;
using CommanderFunc = std::function<bool (std::vector<std::string>)>;
/// \brief Subcommands used by Commander.
///
/// Subcommands are the individual commands for the program. A Subcommand
/// will check that it has enough arguments before running its function.
class Subcommand {
@ -70,8 +72,8 @@ public:
/// \param name The subcommand name; this is the name that will select this command.
/// \param argc The minimum number of arguments required by this subcommand.
/// \param func A valid CommanderFunc.
Subcommand(std::string name, int argc, CommanderFunc func)
: fn(func), args(argc), command(name)
Subcommand(std::string name, size_t argc, CommanderFunc func)
: fn(func), requiredArgs(argc), command(name)
{}
/// Name returns the name of this subcommand.
@ -79,16 +81,17 @@ public:
/// Run attempts to run the CommanderFunc for this subcommand.
///
/// \param argc The number of arguments supplied.
/// \param argv The argument list.
/// \param args The argument list.
/// \return A Status type indicating the status of running the command.
Status Run(int argc, char **argv);
Status Run(std::vector<std::string> args);
private:
CommanderFunc fn;
int args;
size_t requiredArgs;
std::string command;
};
/// \brief Subcommander manager for programs.
///
/// Commander collects subcommands and can run the apppropriate one.
///
/// For example:
@ -115,7 +118,7 @@ public:
bool Register(Subcommand scmd);
/// Try to run a subcommand registered with this Commander.
Subcommand::Status Run(std::string command, int argc, char **argv);
Subcommand::Status Run(std::string command, std::vector<std::string> args);
private:
std::map<std::string, Subcommand *> cmap;
};

View File

@ -1,5 +1,5 @@
///
/// \file Dictionary.h
/// \file include/scsl/Dictionary.h
/// \author kyle (kyle@imap.cc)
/// \date 2023-10-12
/// \brief Key-value store built on top of Arena and TLV.
@ -39,11 +39,7 @@ static constexpr uint8_t DICTIONARY_TAG_VAL = 2;
namespace scsl {
/*
* A Dictionary is a collection of key-value pairs, similar to how
* a dictionary is a mapping of names to definitions.
*/
/// Dictionary implements a key-value store on top of Arena and TLV::Record.
/// \brief Key-value store on top of Arena and TLV::Record.
///
/// Keys and vales are stored as sequential pairs of TLV records; they are
/// expected to contain string values but this isn't necessarily the case. The

View File

@ -1,5 +1,5 @@
///
/// \file Flag.h
/// \file include/scsl/Flags.h
/// \author K. Isom <kyle@imap.cc>
/// \date 2023-10-12
/// \brief Flag declares a command-line flag parser.
@ -55,7 +55,7 @@ typedef union {
} FlagValue;
/// Flag describes an individual command-line flag.
/// \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.
@ -64,7 +64,7 @@ typedef struct {
FlagValue Value; ///< The flag's value.
} Flag;
/// NewFlag is a helper function for constructing a new 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.
@ -72,7 +72,7 @@ typedef struct {
/// \return A pointer to a flag.
Flag *NewFlag(std::string fName, FlagType fType, std::string fDescription);
/// Flags provides a basic facility for processing command line flags.
/// \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.
@ -245,6 +245,17 @@ public:
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();
@ -282,7 +293,7 @@ public:
/// Return a particular argument.
///
/// \param index The argument number to extract.
/// \return The argument at index i. If the index is greater than
/// \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);
@ -329,7 +340,7 @@ public:
private:
ParseStatus parseArg(int argc, char **argv, int &index);
Flag *checkGetArg(std::string fName, FlagType eType);
Flag *checkGetArg(std::string& fName, FlagType eType);
std::string name;
std::string description;

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

@ -0,0 +1,246 @@
///
/// \file include/scsl/SimpleConfig.h
/// \author K. Isom <kyle@imap.cc>
/// \date 2023-10-21
/// \brief Simple project configuration.
///
/// This is an implementation of a simple global configuration system
/// for projects based on a Go version I've used successfully in
/// several projects.
///
/// Copyright 2023 K. Isom <kyle@imap.cc>
///
/// Permission to use, copy, modify, and/or distribute this software for
/// any purpose with or without fee is hereby granted, provided that
/// the above copyright notice and this permission notice appear in all /// copies.
///
/// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
/// WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
/// WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
/// AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
/// DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA
/// OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
/// TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
/// PERFORMANCE OF THIS SOFTWARE.
///
#ifndef SCSL_SIMPLECONFIG_H
#define SCSL_SIMPLECONFIG_H
#include <map>
#include <string>
#include <vector>
namespace scsl {
/// \brief SimpleConfig is a basic configuration for projects.
///
/// SimpleConfig is a basic key-value system. It can optionally load
/// key-value pairs from a file, which should consist of key = value
/// lines. Comments may be added by starting the line with a '#'; these
/// lines will be skipped. Comments may have leading whitespace. Any
/// empty lines or lines consisting solely of whitespace will also be
/// skipped.
///
/// When values are retrieved by one of the variants on Get, they are
/// looked up in the following order, assuming `key` and an optional
/// `prefix` set on the config:
///
/// 1. Any cached key-value pairs. Loading a file caches the key-value
/// pairs in the file. The file is not used again, unless another
/// call to Load is made. If the cache has a name for `key`, it will
/// be returned.
/// 2. The value is looked up from the environment. An optional prefix
/// can be set; if so, if there is an environment named
/// `{prefix}{key}`, the value will be cached and it will be
/// returned.
/// 3. If a default value has been provided, it will be cached and
/// returned.
/// 4. If none of the previous steps has provided a value, an empty
/// string will be returned.
///
/// In Go projects, I've used the global config to great success.
/// However, callers may set up an explicit configuration instance.
class SimpleConfig {
public:
#if defined(SCSL_DESKTOP_BUILD)
/// \brief Load key-value pairs from a file.
///
/// \note This operates on the global config.
///
/// \param path The path to a config file.
/// \return 0 if the file was loaded successfully.
static int LoadGlobal(const char *path);
/// \brief Load key-value pairs from a file.
///
/// \note This operates on the global config.
///
/// \param path The path to a config file.
/// \return 0 if the file was loaded successfully.
static int LoadGlobal(std::string &path);
/// \brief Set the prefix in use by the config.
///
/// \note This operates on the global config.
///
/// \param prefix The prefix to prepend to the key when looking
/// up values from the environment.
static void SetPrefixGlobal(const std::string &prefix);
/// \brief Return the keys cached in the config.
///
/// Note that this won't returned any non-cached environment
/// values.
///
/// \note This operates on the global config.
///
/// \return A list of keys stored under the config.
static std::vector<std::string> KeyListGlobal();
/// \brief Get the value stored for the key from the config.
///
/// \note This operates on the global config.
///
/// \param key The key to look up. See the class documentation
/// for information on how this is used.
/// \return The value stored under the key, or an empty string.
static std::string GetGlobal(std::string &key);
/// \brief Get the value stored for the key from the config.
///
/// \note This operates on the global config.
///
/// \param key The key to look up. See the class documentation
/// for information on how this is used.
/// \return The value stored under the key, or an empty string.
static std::string GetGlobal(const char *key);
/// \brief Get the value stored for the key from the config.
///
/// \note This operates on the global config.
///
/// \param key The key to look up. See the class documentation
/// for information on how this is used.
/// \param defaultValue A default value to cache and use if no
/// value is stored under the key.
/// \return The value stored under the key, or the default
/// value.
static std::string GetGlobal(const char *key, const std::string &defaultValue);
/// \brief Get the value stored for the key from the config.
///
/// \note This operates on the global config.
///
/// \param key The key to look up. See the class documentation
/// for information on how this is used.
/// \param defaultValue A default value to cache and use if no
/// value is stored under the key.
/// \return The value stored under the key, or the default
/// value.
static std::string GetGlobal(std::string &key, const std::string &defaultValue);
/// \brief Set a configuration value. This will override any
/// value set.
static void PutGlobal(std::string &key, const std::string &value);
/// \brief Set a configuration value. This will override any
/// value set.
static void PutGlobal(const char *key, const std::string &value);
#endif
/// \brief The constructor doesn't need any initialisation.
SimpleConfig();
/// \brief The constructor can explicitly set the environment
/// prefix.
explicit SimpleConfig(std::string &prefix);
/// \brief Load key-value pairs from a file.
///
/// \param path The path to a config file.
/// \return 0 if the file was loaded successfully.
int Load(const char *path);
/// \brief Load key-value pairs from a file.
///
/// \param path The path to a config file.
/// \return 0 if the file was loaded successfully.
int Load(std::string& path);
/// \brief Set the prefix in use by the config.
///
/// \param prefix The prefix to prepend to the key when looking
/// up values from the environment.
void SetPrefix(const std::string &prefix);
/// \brief Return the keys cached in the config.
///
/// Note that this won't returned any non-cached environment
/// values.
///
/// \return A list of keys stored under the config.
std::vector<std::string> KeyList();
/// \brief Get the value stored for the key from the config.
///
/// \param key The key to look up. See the class documentation
/// for information on how this is used.
/// \return The value stored under the key, or an empty string.
std::string Get(std::string &key);
/// \brief Get the value stored for the key from the config.
///
/// \param key The key to look up. See the class documentation
/// for information on how this is used.
/// \return The value stored under the key, or an empty string.
std::string Get(const char *key);
/// \brief Get the value stored for the key from the config.
///
/// \param key The key to look up. See the class documentation
/// for information on how this is used.
/// \param defaultValue A default value to cache and use if no
/// value is stored under the key.
/// \return The value stored under the key, or the default
/// value.
std::string Get(std::string &key, std::string defaultValue);
/// \brief Get the value stored for the key from the config.
///
/// \param key The key to look up. See the class documentation
/// for information on how this is used.
/// \param defaultValue A default value to cache and use if no
/// value is stored under the key.
/// \return The value stored under the key, or the default
/// value.
std::string Get(const char *key, std::string defaultValue);
/// \brief Set a configuration value. This will override any
/// value set.
///
/// \param key The key to look up. See the class documentation
/// for information on how this is used.
/// \param value The value to set.
void Put(std::string &key, const std::string value);
/// \brief Set a configuration value. This will override any
/// value set.
///
/// \param key The key to look up. See the class documentation
/// for information on how this is used.
/// \param value The value to set.
void Put(const char *key, const std::string value);
private:
std::string envPrefix;
std::map<std::string, std::string> vars;
};
} // namespace scsl
#endif //SCSL_SIMPLECONFIG_H

View File

@ -1,5 +1,5 @@
///
/// \file StringUtil.h
/// \file include/scsl/StringUtil.h
/// \author K. Isom <kyle@imap.cc>
/// \date 2023-10-14
/// \brief Utilities for working with strings.
@ -32,34 +32,31 @@
namespace scsl {
/// namespace U contains utilities.
namespace U {
/// namespace S contains string-related functions.
namespace S {
/// String-related utility functions.
namespace scstring {
/// Remove any whitespace at the beginning of the string. The string
/// Remove any whitespace At the beginning of the string. The string
/// is modified in-place.
void TrimLeadingWhitespace(std::string &s);
/// Remove any whitespace at the end of the string. The string is
/// Remove any whitespace At the end of the string. The string is
/// modified in-place.
void TrimTrailingWhitespace(std::string &s);
/// Remove any whitespace at the beginning and end of the string. The
/// Remove any whitespace At the beginning and end of the string. The
/// string is modified in-place.
void TrimWhitespace(std::string &s);
/// Remove any whitespace at the beginning of the string. The original
/// Remove any whitespace At the beginning of the string. The original
/// string isn't modified, and a copy is returned.
std::string TrimLeadingWhitespaceDup(std::string s);
/// Remove any whitespace at the end of the string. The original string
/// Remove any whitespace At the end of the string. The original string
/// isn't modified, and a copy is returned.
std::string TrimTrailingWhitespaceDup(std::string s);
/// Remove any whitespace at the beginning and end of the string. The
/// Remove any whitespace At the beginning and end of the string. The
/// original string isn't modified, and a copy is returned.
std::string TrimWhitespaceDup(std::string s);
@ -89,12 +86,12 @@ std::vector<std::string> SplitKeyValuePair(std::string line, char delimiter);
/// \param maxCount The maximum number of parts to split. If 0, there is no
/// limit to the number of parts.
/// \return A vector containing all the parts of the string.
std::vector<std::string> SplitN(std::string, std::string delimiter, size_t maxCount=0);
std::vector<std::string> SplitN(std::string s, std::string delimiter, size_t maxCount=0);
/// WrapText is a very simple wrapping function that breaks the line into
/// lines of at most lineLength characters. It does this by breaking the
/// 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);
std::vector<std::string> WrapText(std::string& line, size_t lineLength);
/// Write out a vector of lines indented with tabs.
///
@ -125,8 +122,7 @@ std::ostream &VectorToString(std::ostream &os, const std::vector<std::string> &s
std::string VectorToString(const std::vector<std::string> &svec);
} // namespace S
} // namespace U
} // namespace string
} // namespace scsl

View File

@ -1,5 +1,5 @@
///
/// \file TLV.h
/// \file include/scsl/TLV.h
/// \author K. Isom <kyle@imap.cc>
/// \date 2023-10-06
/// \brief TLV.h implements basic tag-length-value records.
@ -21,6 +21,8 @@
namespace scsl {
/// \brief Tag-length-value record tooling
namespace TLV {
@ -31,7 +33,7 @@ static constexpr size_t TLV_MAX_LEN = 253;
static constexpr uint8_t TAG_EMPTY = 0;
/// Record describes a tag-length-value record.
/// \brief Tag-length-value record with single byte tags and lengths.
///
/// TLV records occupy a fixed size in memory, which can be controlled with the
/// TLV_MAX_LEN define. If this isn't defined, it defaults to a size of 253.
@ -46,7 +48,7 @@ struct Record {
uint8_t Val[TLV_MAX_LEN];
};
/// WriteToMemory writes the TLV record into the arena at the location pointed
/// WriteToMemory writes the TLV record into the arena At the location pointed
/// to in the arena.
///
/// \param arena The backing memory store.
@ -86,7 +88,7 @@ void DeleteRecord(Arena &arena, uint8_t *cursor);
///
/// \param arena The backing memory for the TLV store.
/// \param cursor A pointer to memory inside the arena; if it's NULL, the
/// search starts at the beginning of the arena.
/// search starts At the beginning of the arena.
/// \param rec The record to be filled.
/// \return If the tag is found, a cursor pointing to the next record is
/// returned; otherwise nullptr is returned.
@ -97,7 +99,7 @@ uint8_t *FindTag(Arena &arena, uint8_t *cursor, Record &rec);
///
/// \param arena The backing memory for the TLV store.
/// \param cursor A pointer to memory inside the arena; if it's NULL, the
/// search starts at the beginning of the arena.
/// search starts At the beginning of the arena.
/// \param rec The record to be filled.
/// \return If the tag is found, a cursor pointing to the record is
/// returned; otherwise nullptr is returned.
@ -110,7 +112,7 @@ uint8_t *LocateTag(Arena &arena, uint8_t *cursor, Record &rec);
///
/// \param arena The backing memory for the TLV store.
/// \param cursor A pointer to memory inside the arena; if it's NULL, the
/// search starts at the beginning of the arena.
/// search starts At the beginning of the arena.
/// \return If the arena has space available, a cursor pointing the start
/// of empty space; otherwise, nullptr is returned.
uint8_t *FindEmpty(Arena &arena, uint8_t *cursor);

View File

@ -33,7 +33,7 @@
#include <scsl/Commander.h>
#include <scsl/Dictionary.h>
#include <scsl/Exceptions.h>
#include <scsl/Flag.h>
#include <scsl/Flags.h>
#include <scsl/StringUtil.h>
#include <scsl/TLV.h>
#include <scsl/Test.h>
@ -72,6 +72,12 @@ namespace scsl {
/// working on a graphical editor. For this, I needed some data structures to
/// manage memory in the editor. Thus, Buffer was born.
///
/// \subsection finally Finally
///
/// I'd been writing Go professionally for a while, but C was my first love. I
/// recently started a job that is mostly in C++, and the best way for me to
/// learn is to build a bunch of stuff with it. So, I took a bunch of micro-
/// controller stuff I'd been writing and started building out some other stuff.
}

View File

@ -1,5 +1,5 @@
///
/// \file Test.h
/// \file Assert.h
/// \author K. Isom <kyle@imap.cc>
/// \date 2023-10-09
/// \brief Tooling to assist in building test programs..
@ -27,23 +27,23 @@
#include <string>
namespace scsl {
namespace sctest {
/// TestAssert is a variant on the assert macro. This variant is intended to be
/// 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, TestAssert will throw an exception if condition is false.
/// 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, TestAssert throws an exception.
void TestAssert(bool condition);
/// \param condition If true, Assert throws an exception.
void Assert(bool condition);
/// TestAssert is a variant on the assert macro.
/// Assert is a variant on the assert macro.
///
/// If NDEBUG is set, TestAssert will throw an exception if condition is false.
/// 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.
@ -52,7 +52,7 @@ void TestAssert(bool condition);
///
/// \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);
void Assert(bool condition, std::string message);
} // namespace scsl

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

@ -0,0 +1,54 @@
///
/// \file Checks.h
/// \author K. Isom <kyle@imap.cc>
/// \date 2017-06-05
/// \brief Provides a number of utility macros for testing.
///
/// Copyright 2017 K. Isom <kyle@imap.cc>
///
/// Permission to use, copy, modify, and/or distribute this software for
/// any purpose with or without fee is hereby granted, provided that
/// the above copyright notice and this permission notice appear in all /// copies.
///
/// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
/// WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
/// WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
/// AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
/// DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA
/// OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
/// TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
/// PERFORMANCE OF THIS SOFTWARE.
///
#ifndef SCTEST_CHECKS_H
#define SCTEST_CHECKS_H
#include <scmp/Math.h>
namespace sctest {
// The following checks are designed as shortcuts that return false on
// if some condition isn't met.
#define SCTEST_CHECK(x) if (!(x)) { return false; }
#define SCTEST_CHECK_FALSE(x) if ((x)) { return false; }
#define SCTEST_CHECK_EQ(x, y) if ((x) != (y)) { return false; }
#define SCTEST_CHECK_NE(x, y) if ((x) == (y)) { return false; }
#define SCTEST_CHECK_GEQ(x, y) if ((x) < (y)) { return false; }
#define SCTEST_CHECK_LEQ(x, y) if ((x) > (y)) { return false; }
#define SCTEST_CHECK_FEQ(x, y) { float eps; scmp::DefaultEpsilon(eps); if (!scmp::WithinTolerance((x), (y), eps)) { return false; }}
#define SCTEST_CHECK_DEQ(x, y) { double eps; scmp::DefaultEpsilon(eps); if (!scmp::WithinTolerance((x), (y), eps)) { return false; }}
#define SCTEST_CHECK_FEQ_EPS(x, y, eps) { if (!scmp::WithinTolerance<float>((x), (y), (eps))) { return false; }}
#define SCTEST_CHECK_FNE_EPS(x, y, eps) { if (scmp::WithinTolerance<float>((x), (y), (eps))) { return false; }}
#define SCTEST_CHECK_DEQ_EPS(x, y, eps) { if (!scmp::WithinTolerance<double>((x), (y), (eps))) { return false; }}
#define SCTEST_CHECK_DNE_EPS(x, y, eps) { if (scmp::WithinTolerance<double>((x), (y), (eps))) { return false; }}
} // namespace sctest
#endif

View File

@ -1,5 +1,5 @@
///
/// \file Exceptions.h
/// \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.
@ -19,6 +19,7 @@
/// TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
/// PERFORMANCE OF THIS SOFTWARE.
///
#ifndef SCSL_EXCEPTIONS_H
#define SCSL_EXCEPTIONS_H
@ -27,10 +28,10 @@
#include <string>
namespace scsl {
namespace sctest {
/// NotImplemented is an exception reserved for unsupported platforms.
/// \brief Exception reserved for unsupported platforms.
///
/// It is used to mark functionality included for compatibility, and useful for
/// debugging.
@ -63,7 +64,7 @@ private:
};
} // namespace scsl
} // namespace sctest
#endif //SCSL_EXCEPTIONS_H
#endif // SCSL_EXCEPTIONS_H

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

@ -0,0 +1,104 @@
///
/// \file include/sctest/Report.h
/// \author K. Isom <kyle@imap.cc>
/// \date 2017-06-05
/// \brief Unit test reporting class.
///
/// Copyright 2017 K. Isom <kyle@imap.cc>
///
/// Permission to use, copy, modify, and/or distribute this software for
/// any purpose with or without fee is hereby granted, provided that
/// the above copyright notice and this permission notice appear in all /// copies.
///
/// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
/// WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
/// WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
/// AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
/// DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA
/// OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
/// TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
/// PERFORMANCE OF THIS SOFTWARE.
///
#ifndef SCTEST_REPORT_H
#define SCTEST_REPORT_H
#include <chrono>
namespace sctest {
/// \brief A Report holds test run results.
///
/// This is designed to work with SimpleSuite, but might be useful
/// for other things.
class Report {
public:
/// \brief Construct a new Report, zeroed out.
Report();
/// \brief Failing returns the count of failed tests.
///
/// \details If a test is run and expected to pass, but fails,
/// it is marked as failed. If a test is expected to
/// fail, but passes, it is marked as failed.
///
/// \return The number of tests that failed.
size_t Failing() const;
/// \brief The number of tests that have passed successfully.
size_t Passing() const;
/// \brief The number of tests registered.
size_t Total() const;
/// \brief Report a test as having failed.
void Failed();
/// \brief Report a test as having passed.
void Passed();
/// \brief Register more tests in the report.
///
/// This is used to track the total number of tests in the
/// report.
void AddTest(size_t testCount = 0);
/// \brief Reset the internal state.
///
/// All fields in the Report will be zeroed out.
///
/// \param testCount
void Reset(size_t testCount = 0);
/// \brief Mark the start of test runs.
///
/// This is used for tracking how long the tests took to complete.
void Start();
/// \brief Mark the end of test runs.
///
/// This is used for tracking how long the tests took to complete.
void End();
/// \brief Retrieve how long the tests took to run.
///
/// This only makes sense to run after called to Start and End.
///
/// \return The number of milliseconds that have elapsed.
std::chrono::duration<double, std::milli>
Elapsed() const;
private:
size_t failing;
size_t passed;
size_t total;
std::chrono::time_point<std::chrono::steady_clock> start;
std::chrono::time_point<std::chrono::steady_clock> end;
};
std::ostream &operator<<(std::ostream &os, const Report &report);
} // end namespace sctest
#endif

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

@ -0,0 +1,127 @@
///
/// \file include/sctest/SimpleSuite.h
/// \author K. Isom <kyle@imap.cc>
/// \date 2017-06-05
/// \brief Defines a simple unit testing framework.
///
/// Copyright 2017 K. Isom <kyle@imap.cc>
///
/// Permission to use, copy, modify, and/or distribute this software for
/// any purpose with or without fee is hereby granted, provided that
/// the above copyright notice and this permission notice appear in all /// copies.
///
/// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
/// WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
/// WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
/// AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
/// DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA
/// OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
/// TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
/// PERFORMANCE OF THIS SOFTWARE.
///
#ifndef SCTEST_SIMPLESUITE_H
#define SCTEST_SIMPLESUITE_H
#include <functional>
#include <string>
#include <vector>
#include <sctest/Report.h>
namespace sctest {
/// \brief UnitTest describes a single unit test.
///
/// It is a predicate: did the test pass?
struct UnitTest {
/// What name should be shown when running tests?
std::string name;
/// This is the test function to be run.
std::function<bool()> test;
/// This is the value the test returns if it passes.
bool expect;
};
/// \brief SimpleSuite is a test-running harness for simple tests.
///
/// A simple test is defined as a test that takes no arguments and
/// returns a boolean status where true indicates the test has passed.
class SimpleSuite {
public:
SimpleSuite();
/// \brief Silence suppresses output.
void Silence();
/// \brief Define a suite setup function.
///
/// If present, this setup function is called At the start of
/// the Run method, before tests are run. It should be a
/// predicate: if it returns false, tests automatically fail.
void Setup(std::function<bool(void)> setupFn) { fnSetup = setupFn; }
/// \brief Define a teardown function.
///
/// If present, this teardown function is called At the end of
/// the Run method, after all tests have run.
void Teardown(std::function<bool(void)> teardownFn) { fnTeardown = teardownFn; }
/// \brief Register a new simple test.
///
/// \param label The text that will identify test when
/// running.
/// \param test This test should return true if the test has
/// passed.
void AddTest(std::string label, std::function<bool(void)> test);
/// \brief Register a test that is expected to return false.
///
/// \param label The text that will identify test when
/// running.
/// \param test This test should return false if the test has
/// passed.
void AddFailingTest(std::string label, std::function<bool(void)> test);
/// \brief Run all the registered tests.
///
/// \return True if all tests have passed.
bool Run();
/// Reporting methods.
/// \brief Reset clears the report statistics.
///
/// Reset will preserve the setup and teardown functions, just
/// resetting the suite's internal state.
void Reset();
/// \brief Returns true if Run has been called.
bool HasRun() const;
/// \brief Retrieve the test run results.
///
/// The results will only be valid if Run has been called.
Report GetReport();
private:
bool quiet;
std::function<bool(void)> fnSetup, fnTeardown;
std::vector<UnitTest> tests;
// Report functions.
Report report;
bool hasRun; // Have the tests been run yet?
bool hasPassed;
};
std::ostream& operator<<(std::ostream& os, SimpleSuite &suite);
} // namespace sctest
#endif

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

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

View File

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

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

@ -0,0 +1,230 @@
///
/// \file include/scmp/geom/Coord2D.h
/// \author K. Isom <kyle@imap.cc>
/// \date 2017-06-05
/// \brief 2D point and polar coordinate systems.
///
/// Copyright 2023 K. Isom <kyle@imap.cc>
///
/// Permission to use, copy, modify, and/or distribute this software for
/// any purpose with or without fee is hereby granted, provided that
/// the above copyright notice and this permission notice appear in all /// copies.
///
/// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
/// WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
/// WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
/// AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
/// DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA
/// OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
/// TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
/// PERFORMANCE OF THIS SOFTWARE.
///
#include <cmath>
#include <iostream>
#include <vector>
#include <scmp/Math.h>
#include <scmp/geom/Coord2D.h>
#include <scmp/geom/Orientation.h>
#include <scmp/geom/Vector.h>
namespace scmp {
namespace geom {
//
// Point2D
Point2D::Point2D() : Vector<int, 2>{0, 0}
{
};
Point2D::Point2D(int _x, int _y) : Vector<int, 2>{_x, _y}
{}
Point2D::Point2D(const Polar2D &pol)
: Vector<int, 2>{static_cast<int>(std::rint(std::cos(pol.Theta()) * pol.R())),
static_cast<int>(std::rint(std::sin(pol.Theta()) * pol.R()))}
{}
int
Point2D::X() const
{
return this->At(BasisX);
}
void
Point2D::X(int _x)
{
this->Set(BasisX, _x);
}
int
Point2D::Y() const
{
return this->At(BasisY);
}
void
Point2D::Y(int _y)
{
this->Set(BasisY, _y);
}
std::ostream &
operator<<(std::ostream &outs, const Point2D &pt)
{
outs << "(" << std::to_string(pt.X()) << ", " << std::to_string(pt.Y()) << ")";
return outs;
}
std::string
Point2D::ToString()
{
return "(" + std::to_string(this->X()) + ", " + std::to_string(this->Y()) + ")";
}
void
Point2D::ToPolar(Polar2D &pol)
{
pol.R(std::sqrt(this->X() * this->X() + this->Y() * this->Y()));
pol.Theta(std::atan2(this->Y(), this->X()));
}
void
Point2D::Rotate(Point2D &pt, double theta)
{
Polar2D pol(*this);
pol.Rotate(pol, theta);
pol.ToPoint(pt);
}
void
Point2D::Translate(const Point2D &origin, Point2D &translated)
{
translated.X(origin.X() + this->X());
translated.Y(origin.Y() + this->Y());
}
std::vector<Point2D>
Point2D::Rotate(std::vector<Polar2D> vertices, double theta)
{
std::vector<Point2D> rotated;
for (auto &v: vertices) {
Point2D p;
v.RotateAround(*this, p, theta);
rotated.push_back(p);
}
return rotated;
}
int
Point2D::Distance(const Point2D& other) const
{
auto dx = other.X() - this->X();
auto dy = other.Y() - this->Y();
return static_cast<int>(std::rint(std::sqrt(dx * dx + dy * dy)));
}
// Polar2D
Polar2D::Polar2D() : Vector<double, 2>{0.0, 0.0} {};
Polar2D::Polar2D(double _r, double _theta) : Vector<double, 2>{_r, _theta}
{}
Polar2D::Polar2D(const Point2D &point)
: Vector<double, 2>{std::sqrt((point.X() * point.X()) + (point.Y() * point.Y())),
std::atan2(point.Y(), point.X())}
{}
double
Polar2D::R() const
{
return this->At(0);
}
void
Polar2D::R(const double _r)
{
this->Set(0, _r);
}
double
Polar2D::Theta() const
{
return this->At(1);
}
void
Polar2D::Theta(const double _theta)
{
this->Set(1, _theta);
}
void
Polar2D::ToPoint(Point2D &point)
{
point.Y(static_cast<int>(std::rint(std::sin(this->Theta()) * this->R())));
point.X(static_cast<int>(std::rint(std::cos(this->Theta()) * this->R())));
}
std::string
Polar2D::ToString()
{
return "(" + std::to_string(this->R()) +
", " + std::to_string(this->Theta()) + ")";
}
void
Polar2D::Rotate(Polar2D &rotated, double delta)
{
rotated.R(this->R());
rotated.Theta(RotateRadians(this->Theta(), delta));
}
void
Polar2D::RotateAround(const Point2D &origin, Point2D &point, double delta)
{
Polar2D rot;
this->Rotate(rot, delta);
rot.ToPoint(point);
point.Translate(origin, point);
}
std::ostream &
operator<<(std::ostream &outs, const Polar2D &pol)
{
outs << "(" << pol.R() << ", " << pol.Theta() << ")";
return outs;
}
} // end namespace geom
} // end namespace scmp

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

@ -0,0 +1,187 @@
///
/// \file Math.cc
/// \author K. Isom <kyle@imap.cc>
/// \date 2020-02-26
/// \brief Mathematical convience functions.
///
/// Copyright 2020 K. Isom <kyle@imap.cc>
///
/// Permission to use, copy, modify, and/or distribute this software for
/// any purpose with or without fee is hereby granted, provided that
/// the above copyright notice and this permission notice appear in all /// copies.
///
/// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
/// WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
/// WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
/// AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
/// DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA
/// OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
/// TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
/// PERFORMANCE OF THIS SOFTWARE.
///
#include <algorithm>
#include <functional>
#include <numeric>
#include <random>
#include <vector>
#include <scmp/Math.h>
namespace scmp {
std::vector<int>
Die(int m, int n)
{
std::uniform_int_distribution<> die(1, n);
std::random_device rd;
std::vector<int> dice;
int i = 0;
for (i = 0; i < m; i++) {
dice.push_back(die(rd));
}
return dice;
}
int
BestDie(int k, int m, int n)
{
auto dice = Die(m, n);
if (k < m) {
std::sort(dice.begin(), dice.end(), std::greater<>());
dice.resize(static_cast<size_t>(k));
}
return std::accumulate(dice.begin(), dice.end(), 0);
}
int
DieTotal(int m, int n)
{
std::uniform_int_distribution<> die(1, n);
std::random_device rd;
int i = 0, total = 0;
for (i = 0; i < m; i++) {
total += die(rd);
}
return total;
}
float
RadiansToDegreesF(float rads)
{
return rads * (180.0 / M_PI);
}
double
RadiansToDegreesD(double rads)
{
return rads * (180.0 / M_PI);
}
float
DegreesToRadiansF(float degrees)
{
return degrees * M_PI / 180.0;
}
double
DegreesToRadiansD(double degrees)
{
return degrees * M_PI / 180.0;
}
double
RotateRadians(double theta0, double theta1)
{
auto dtheta = theta0 + theta1;
if (dtheta > M_PI) {
dtheta -= MAX_RADIAN;
} else if (dtheta < -M_PI) {
dtheta += MAX_RADIAN;
}
if ((dtheta < -M_PI) || (dtheta > M_PI)) {
return RotateRadians(dtheta, 0);
}
return dtheta;
}
static constexpr double Epsilon_double = 0.0001;
static constexpr float Epsilon_float = 0.0001;
void
DefaultEpsilon(double& epsilon)
{
epsilon = Epsilon_double;
}
void
DefaultEpsilon(float& epsilon)
{
epsilon = Epsilon_float;
}
void
DefaultEpsilon(int& epsilon)
{
epsilon = 0;
}
size_t
ISqrt(size_t n)
{
if (n < 2) {
return n;
}
size_t start = 0;
size_t end = n / 2;
size_t result = 0;
while (start <= end) {
auto middle = (start + end) >> 1;
result = middle * middle;
if (result == n) {
return middle;
}
if (result < n) {
start = middle + 1;
result = middle;
} else {
end = middle - 1;
result = middle;
}
}
return result;
}
} // namespace scmp

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

@ -0,0 +1,63 @@
///
/// \file src/scmp/geom/Orientation.cc
/// \author K. Isom <kyle@imap.cc>
/// \date 2017-06-05
/// \brief Orientation of vectors w.r.t. a reference plane, assumed to
/// be the Earth.
///
/// Copyright 2017 K. Isom <kyle@imap.cc>
///
/// Permission to use, copy, modify, and/or distribute this software for
/// any purpose with or without fee is hereby granted, provided that
/// the above copyright notice and this permission notice appear in all /// copies.
///
/// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
/// WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
/// WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
/// AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
/// DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA
/// OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
/// TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
/// PERFORMANCE OF THIS SOFTWARE.
///
#include <scmp/geom/Vector.h>
#include <scmp/geom/Orientation.h>
namespace scmp {
namespace geom {
float
Heading2F(Vector2F vec)
{
return vec.Angle(Basis2F[BasisX]);
}
float
Heading3F(Vector3F vec)
{
const Vector2F vec2f {vec.At(BasisX), vec.At(BasisY)};
return Heading2F(vec2f);
}
double
Heading2D(Vector2D vec)
{
return vec.Angle(Basis2D[BasisX]);
}
double
Heading3D(Vector3D vec)
{
const Vector2D vec2d {vec.At(BasisX), vec.At(BasisY)};
return Heading2D(vec2d);
}
} // namespace geom
} // namespace scmp

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

@ -0,0 +1,91 @@
#include <iostream>
#include <scmp/geom/Quaternion.h>
namespace scmp {
namespace geom {
Quaternionf
MakeQuaternion(Vector3F axis, float angle)
{
return Quaternionf(axis.UnitVector() * std::sin(angle / 2.0),
std::cos(angle / 2.0));
}
Quaterniond
MakeQuaternion(Vector3D axis, double angle)
{
return Quaterniond(axis.UnitVector() * std::sin(angle / 2.0),
std::cos(angle / 2.0));
}
Quaternionf
FloatQuaternionFromEuler(Vector3F euler)
{
float x, y, z, w;
euler = euler / 2.0;
float cos_yaw = std::cos(euler[0]);
float cos_pitch = std::cos(euler[1]);
float cos_roll = std::cos(euler[2]);
float sin_yaw = std::sin(euler[0]);
float sin_pitch = std::sin(euler[1]);
float sin_roll = std::sin(euler[2]);
x = (sin_yaw * cos_pitch * cos_roll) + (cos_yaw * sin_pitch * sin_roll);
y = (sin_yaw * cos_pitch * sin_roll) - (cos_yaw * sin_pitch * cos_roll);
z = (cos_yaw * cos_pitch * sin_roll) + (sin_yaw * sin_pitch * cos_roll);
w = (cos_yaw * cos_pitch * cos_roll) - (sin_yaw * sin_pitch * sin_roll);
return Quaternionf(Vector4F{w, x, y, z});
}
Quaterniond
DoubleQuaternionFromEuler(Vector3D euler)
{
double x, y, z, w;
euler = euler / 2.0;
double cos_yaw = std::cos(euler[0]);
double cos_pitch = std::cos(euler[1]);
double cos_roll = std::cos(euler[2]);
double sin_yaw = std::sin(euler[0]);
double sin_pitch = std::sin(euler[1]);
double sin_roll = std::sin(euler[2]);
x = (sin_yaw * cos_pitch * cos_roll) + (cos_yaw * sin_pitch * sin_roll);
y = (sin_yaw * cos_pitch * sin_roll) - (cos_yaw * sin_pitch * cos_roll);
z = (cos_yaw * cos_pitch * sin_roll) + (sin_yaw * sin_pitch * cos_roll);
w = (cos_yaw * cos_pitch * cos_roll) - (sin_yaw * sin_pitch * sin_roll);
return Quaterniond(Vector4D{w, x, y, z});
}
void
QuaternionSelfTest()
{
#ifndef NDEBUG
Vector3F v {1.0, 0.0, 0.0};
Vector3F yAxis {0.0, 1.0, 0.0};
float angle = M_PI / 2;
Quaternionf p = MakeQuaternion(yAxis, angle);
Quaternionf q;
Vector3F vr {0.0, 0.0, 1.0};
p.SetEpsilon(0.0001);
assert(p.IsUnitQuaternion());
assert(p.Rotate(v) == vr);
assert(p * q == p);
#endif
}
} // namespace geom
} // namespace scmp

View File

@ -20,23 +20,18 @@
/// PERFORMANCE OF THIS SOFTWARE.
///
#include <cstdio>
#include <cstdlib>
#include <cstring>
#if defined(__posix__) || defined(__linux__) || defined(__APPLE__)
#include <sys/mman.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#define PROT_RW (PROT_WRITE|PROT_READ)
#endif
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <ios>
#include "Arena.h"
#include <scsl/Arena.h>
#define PROT_RW (PROT_WRITE|PROT_READ)
namespace scsl {
@ -74,16 +69,12 @@ Arena::SetAlloc(size_t allocSize)
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)
{
@ -93,9 +84,9 @@ Arena::MemoryMap(int memFileDes, size_t memSize)
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) {
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;
@ -112,119 +103,51 @@ Arena::Open(const char *path)
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);
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)
{
FILE *fHandle = nullptr;
int newFileDes = 0;
int ret = -1;
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;
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);
}
cursor += fRead;
fRemaining -= fRead;
close(newFileDes);
fclose(fHandle);
}
CloseHandle(fHandle);
return 0;
return ret;
}
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 == nullptr) {
return false;
}
if (cursor < this->store) {
return false;
}
@ -261,28 +184,26 @@ Arena::Destroy()
}
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)
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
@ -292,16 +213,15 @@ Arena::Destroy()
this->arenaType = ArenaType::Uninit;
this->size = 0;
this->store = nullptr;
return;
}
std::ostream &
operator<<(std::ostream &os, Arena &arena)
{
auto cursor = arena.Start();
auto *cursor = arena.Start();
char cursorString[33] = {0};
snprintf(cursorString, 32, "%#016llx",
(long long unsigned int) cursor);
(long long unsigned int)(cursor));
os << "Arena<";
switch (arena.Type()) {
@ -314,16 +234,14 @@ operator<<(std::ostream &os, Arena &arena)
case ArenaType::Alloc:
os << "allocated";
break;
#if defined(__posix__) || defined(__linux__) || defined(__APPLE__)
case ArenaType::MemoryMapped:
os << "mmap/file";
break;
#endif
case ArenaType::MemoryMapped:
os << "mmap/file";
break;
default:
os << "unknown (this is a bug)";
}
os << ">@0x";
os << std::hex << (uintptr_t) &arena;
os << std::hex << static_cast<void *>(&arena);
os << std::dec;
os << ",store<" << arena.Size() << "B>@";
os << std::hex << cursorString;
@ -336,15 +254,10 @@ operator<<(std::ostream &os, Arena &arena)
int
Arena::Write(const char *path)
{
FILE *arenaFile = nullptr;
int retc = -1;
#if defined(__posix__) || defined(__linux__) || defined(__APPLE__)
arenaFile = fopen(path, "w");
FILE *arenaFile = fopen(path, "w");
if (arenaFile == nullptr) {
#else
if (fopen_s(&arenaFile, path, "w") != 0) {
#endif
return -1;
}

View File

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

View File

@ -22,22 +22,23 @@
#include <iostream>
#include "Commander.h"
#include <scsl/Commander.h>
namespace scsl {
Subcommand::Status
Subcommand::Run(int argc, char **argv)
Subcommand::Run(std::vector<std::string> args)
{
if (argc < this->args) {
auto argc = args.size();
if (argc < this->requiredArgs) {
std::cerr << "[!] " << this->command << " expects ";
std::cerr << this->args << " args, but was given ";
std::cerr << this->requiredArgs << " args, but was given ";
std::cerr << argc << " args.\n";
return Subcommand::Status::NotEnoughArgs;
}
if (this->fn(argc, argv)) {
if (this->fn(args)) {
return Subcommand::Status::OK;
}
@ -45,7 +46,6 @@ Subcommand::Run(int argc, char **argv)
}
Commander::Commander()
: cmap()
{
this->cmap.clear();
}
@ -64,14 +64,14 @@ Commander::Register(Subcommand scmd)
Subcommand::Status
Commander::Run(std::string command, int argc, char **argv)
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(argc, argv);
return scmd->Run(std::move(args));
}

View File

@ -24,7 +24,7 @@
#include <cstdlib>
#include <cstring>
#include "Dictionary.h"
#include <scsl/Dictionary.h>
#if defined(SCSL_DESKTOP_BUILD)
#include <iostream>
@ -158,6 +158,10 @@ operator<<(std::ostream &os, const Dictionary &dictionary)
uint8_t *cursor = (dictionary.arena).Start();
TLV::Record rec;
if (cursor == nullptr) {
return os;
}
TLV::ReadFromMemory(rec, cursor);
if (rec.Tag == TLV::TAG_EMPTY) {
os << "\t(NONE)" << std::endl;
@ -168,7 +172,11 @@ operator<<(std::ostream &os, const Dictionary &dictionary)
os << "\t" << rec.Val << "->";
cursor = TLV::SkipRecord(rec, cursor);
TLV::ReadFromMemory(rec, cursor);
os << rec.Val << std::endl;
if (cursor == nullptr) {
os << "*** CORRUPT DICTIONARY ***\n";
break;
}
os << rec.Val << "\n";
cursor = TLV::SkipRecord(rec, cursor);
TLV::ReadFromMemory(rec, cursor);
}

View File

@ -1,5 +1,5 @@
///
/// \file Flag.cc
/// \file src/sl/Flags.cc
/// \author K. Isom <kyle@imap.cc>
/// \date 2023-10-12
/// \brief Flag defines a command-line flag parser.
@ -20,20 +20,21 @@
/// PERFORMANCE OF THIS SOFTWARE.
///
#include <cassert>
#include <iostream>
#include <regex>
#include <vector>
#include <utility>
#include "Flag.h"
#include "StringUtil.h"
#include <scsl/Flags.h>
#include <scsl/StringUtil.h>
#include <vector>
namespace scsl {
static const std::regex isFlag("^--?[a-zA-Z0-9][a-zA-Z0-9-_]*$",
std::regex_constants::nosubs);
std::regex_constants::nosubs | std::regex_constants::optimize);
std::string
Flags::ParseStatusToString(ParseStatus status)
@ -56,12 +57,12 @@ Flags::ParseStatusToString(ParseStatus status)
Flag *
NewFlag(std::string fName, FlagType fType, std::string fDescription)
{
auto flag = new Flag;
auto *flag = new Flag;
flag->Type = fType;
flag->WasSet = false;
flag->Name = fName;
flag->Description = fDescription;
flag->Name = std::move(fName);
flag->Description = std::move(fDescription);
flag->Value = FlagValue{};
return flag;
@ -69,20 +70,19 @@ NewFlag(std::string fName, FlagType fType, std::string fDescription)
Flags::Flags(std::string fName)
: name(fName), description("")
{
}
: name(std::move(fName))
{}
Flags::Flags(std::string fName, std::string fDescription)
: name(fName), description(fDescription)
: name(std::move(fName)), description(std::move(fDescription))
{
}
Flags::~Flags()
{
for (auto flag : this->flags) {
for (auto &flag: this->flags) {
if (flag.second->Type == FlagType::String) {
delete flag.second->Value.s;
}
@ -94,7 +94,7 @@ Flags::~Flags()
bool
Flags::Register(std::string fName, FlagType fType, std::string fDescription)
{
if (!std::regex_search(fName, isFlag) || fName == "-h") {
if (!std::regex_search(fName, isFlag)) {
return false;
}
@ -102,7 +102,8 @@ Flags::Register(std::string fName, FlagType fType, std::string fDescription)
return false;
}
auto flag = NewFlag(fName, fType, fDescription);
auto *flag = NewFlag(fName, fType, std::move(fDescription));
assert(flag != nullptr);
this->flags[fName] = flag;
return true;
}
@ -111,7 +112,7 @@ Flags::Register(std::string fName, FlagType fType, std::string fDescription)
bool
Flags::Register(std::string fName, bool defaultValue, std::string fDescription)
{
if (!this->Register(fName, FlagType::Boolean, fDescription)) {
if (!this->Register(fName, FlagType::Boolean, std::move(fDescription))) {
return false;
}
@ -123,7 +124,7 @@ Flags::Register(std::string fName, bool defaultValue, std::string fDescription)
bool
Flags::Register(std::string fName, int defaultValue, std::string fDescription)
{
if (!this->Register(fName, FlagType::Integer, fDescription)) {
if (!this->Register(fName, FlagType::Integer, std::move(fDescription))) {
return false;
}
@ -135,9 +136,11 @@ Flags::Register(std::string fName, int defaultValue, std::string fDescription)
bool
Flags::Register(std::string fName, unsigned int defaultValue, std::string fDescription)
{
if (!this->Register(fName, FlagType::UnsignedInteger, fDescription)) {
if (!this->Register(fName, FlagType::UnsignedInteger, std::move(fDescription))) {
return false;
}
assert(this->flags.count(fName) != 0);
assert(this->flags[fName] != nullptr);
this->flags[fName]->Value.u = defaultValue;
return true;
@ -147,7 +150,7 @@ Flags::Register(std::string fName, unsigned int defaultValue, std::string fDescr
bool
Flags::Register(std::string fName, size_t defaultValue, std::string fDescription)
{
if (!this->Register(fName, FlagType::SizeT, fDescription)) {
if (!this->Register(fName, FlagType::SizeT, std::move(fDescription))) {
return false;
}
@ -159,7 +162,19 @@ Flags::Register(std::string fName, size_t defaultValue, std::string fDescription
bool
Flags::Register(std::string fName, std::string defaultValue, std::string fDescription)
{
if (!this->Register(fName, FlagType::String, fDescription)) {
if (!this->Register(fName, FlagType::String, std::move(fDescription))) {
return false;
}
this->flags[fName]->Value.s = new std::string(std::move(defaultValue));
return true;
}
bool
Flags::Register(std::string fName, const char *defaultValue, std::string fDescription)
{
if (!this->Register(fName, FlagType::String, std::move(fDescription))) {
return false;
}
@ -189,7 +204,7 @@ Flags::Lookup(std::string fName)
bool
Flags::ValueOf(std::string fName, FlagValue &value)
{
if (this->flags.count(fName)) {
if (this->flags.count(fName) != 0U) {
return false;
}
@ -202,13 +217,13 @@ Flags::ParseStatus
Flags::parseArg(int argc, char **argv, int &index)
{
std::string arg(argv[index]);
U::S::TrimWhitespace(arg);
scstring::TrimWhitespace(arg);
index++;
if (!std::regex_search(arg, isFlag)) {
return ParseStatus::EndOfFlags;
}
index++;
if (this->flags.count(arg) == 0) {
if (arg == "-h" || arg == "--help") {
Usage(std::cout, 0);
@ -216,7 +231,7 @@ Flags::parseArg(int argc, char **argv, int &index)
return ParseStatus::NotRegistered;
}
auto flag = flags[arg];
auto *flag = flags[arg];
if ((flag->Type != FlagType::Boolean) && index == argc) {
return ParseStatus::NotEnoughArgs;
}
@ -228,11 +243,11 @@ Flags::parseArg(int argc, char **argv, int &index)
return ParseStatus::OK;
case FlagType::Integer:
flag->WasSet = true;
flag->Value.i = std::stoi(argv[++index], 0, 0);
flag->Value.i = std::stoi(argv[++index], nullptr, 0);
return ParseStatus::OK;
case FlagType::UnsignedInteger:
flag->WasSet = true;
flag->Value.u = static_cast<unsigned int>(std::stoi(argv[index++], 0, 0));
flag->Value.u = static_cast<unsigned int>(std::stoi(argv[index++], nullptr, 0));
return ParseStatus::OK;
case FlagType::String:
flag->WasSet = true;
@ -270,7 +285,7 @@ Flags::Parse(int argc, char **argv, bool skipFirst)
case ParseStatus::EndOfFlags:
while (index < argc) {
this->args.push_back(std::string(argv[index]));
this->args.emplace_back(argv[index]);
index++;
}
continue;
@ -285,7 +300,6 @@ Flags::Parse(int argc, char **argv, bool skipFirst)
throw std::runtime_error("unhandled parse state");
#endif
}
}
return ParseStatus::OK;
@ -298,26 +312,44 @@ Flags::Usage(std::ostream &os, int exitCode)
os << this->name << ":\t";
auto indent = this->name.size() + 7;
U::S::WriteTabIndented(os, description, 72 - indent, indent / 8, false);
scstring::WriteTabIndented(os, this->description, 72 - indent,
indent / 8, false);
os << "\n\n";
for (const auto &pair : this->flags) {
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";
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";
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";
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";
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
@ -332,8 +364,8 @@ Flags::Usage(std::ostream &os, int exitCode)
os << argLine;
indent = argLine.size();
U::S::WriteTabIndented(os, pair.second->Description,
72-indent, (indent/8)+2, false);
scstring::WriteTabIndented(os, argDesc, 72 - indent,
(indent / 8) + 2, false);
}
os << "\n";
@ -367,13 +399,13 @@ Flags::Arg(size_t i)
Flag *
Flags::checkGetArg(std::string fName, FlagType eType)
Flags::checkGetArg(std::string &fName, FlagType eType)
{
if (this->flags[fName] == 0) {
if (this->flags[fName] == nullptr) {
return nullptr;
}
auto flag = this->flags[fName];
auto *flag = this->flags[fName];
if (flag == nullptr) {
return nullptr;
}
@ -389,7 +421,7 @@ Flags::checkGetArg(std::string fName, FlagType eType)
bool
Flags::GetBool(std::string fName, bool &flagValue)
{
auto flag = this->checkGetArg(fName, FlagType::Boolean);
auto *flag = this->checkGetArg(fName, FlagType::Boolean);
flagValue = flag->Value.b;
return flag->WasSet;
@ -399,7 +431,7 @@ Flags::GetBool(std::string fName, bool &flagValue)
bool
Flags::GetInteger(std::string fName, int &flagValue)
{
auto flag = this->checkGetArg(fName, FlagType::Integer);
auto *flag = this->checkGetArg(fName, FlagType::Integer);
flagValue = flag->Value.i;
return flag->WasSet;
@ -409,7 +441,7 @@ Flags::GetInteger(std::string fName, int &flagValue)
bool
Flags::GetUnsignedInteger(std::string fName, unsigned int &flagValue)
{
auto flag = this->checkGetArg(fName, FlagType::UnsignedInteger);
auto *flag = this->checkGetArg(fName, FlagType::UnsignedInteger);
flagValue = flag->Value.u;
return flag->WasSet;
@ -419,7 +451,7 @@ Flags::GetUnsignedInteger(std::string fName, unsigned int &flagValue)
bool
Flags::GetSizeT(std::string fName, std::size_t &flagValue)
{
auto flag = this->checkGetArg(fName, FlagType::SizeT);
auto *flag = this->checkGetArg(fName, FlagType::SizeT);
flagValue = flag->Value.size;
return flag->WasSet;
@ -429,7 +461,7 @@ Flags::GetSizeT(std::string fName, std::size_t &flagValue)
bool
Flags::GetString(std::string fName, std::string &flagValue)
{
auto flag = this->checkGetArg(fName, FlagType::String);
auto *flag = this->checkGetArg(fName, FlagType::String);
if (flag->Value.s == nullptr) {
return false;
@ -440,5 +472,4 @@ Flags::GetString(std::string fName, std::string &flagValue)
}
} // namespace scsl
}// namespace scsl

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

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

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

@ -0,0 +1,246 @@
///
/// \file src/sl/SimpleConfig.cc
/// \author K. Isom <kyle@imap.cc>
/// \date 2023-10-21
/// \brief Simple project configuration.
///
/// This is an implementation of a simple global configuration system
/// for projects based on a Go version I've used successfully in
/// several projects.
///
/// Copyright 2023 K. Isom <kyle@imap.cc>
///
/// Permission to use, copy, modify, and/or distribute this software for
/// any purpose with or without fee is hereby granted, provided that
/// the above copyright notice and this permission notice appear in all /// copies.
///
/// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
/// WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
/// WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
/// AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
/// DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA
/// OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
/// TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
/// PERFORMANCE OF THIS SOFTWARE.
///
#include <cstdlib>
#include <fstream>
#include <regex>
#include <scsl/SimpleConfig.h>
#include <scsl/StringUtil.h>
namespace scsl {
#if defined(SCSL_DESKTOP_BUILD)
static SimpleConfig globalConfig;
#endif
static constexpr auto regexOpts = std::regex_constants::nosubs |
std::regex_constants::optimize |
std::regex_constants::ECMAScript;
static const std::regex commentLine("^\\s*#.*$", regexOpts);
static const std::regex keyValueLine("^\\s*\\w+\\s*=\\s*\\w+", regexOpts);
int
SimpleConfig::LoadGlobal(const char *path)
{
return globalConfig.Load(path);
}
int
SimpleConfig::LoadGlobal(std::string &path)
{
return globalConfig.Load(path);
}
void
SimpleConfig::SetPrefixGlobal(const std::string &prefix)
{
globalConfig.SetPrefix(prefix);
}
std::vector<std::string>
SimpleConfig::KeyListGlobal()
{
return globalConfig.KeyList();
}
std::string
SimpleConfig::GetGlobal(std::string &key)
{
return globalConfig.Get(key);
}
std::string
SimpleConfig::GetGlobal(const char *key)
{
return globalConfig.Get(key);
}
std::string
SimpleConfig::GetGlobal(std::string &key, const std::string &defaultValue)
{
return globalConfig.Get(key, defaultValue);
}
std::string
SimpleConfig::GetGlobal(const char *key, const std::string &defaultValue)
{
return globalConfig.Get(key, defaultValue);
}
void
SimpleConfig::PutGlobal(std::string &key, const std::string &value)
{
return globalConfig.Put(key, value);
}
void
SimpleConfig::PutGlobal(const char *key, const std::string &value)
{
return globalConfig.Put(key, value);
}
SimpleConfig::SimpleConfig()
{
}
SimpleConfig::SimpleConfig(std::string &prefix)
: envPrefix(prefix)
{
}
int
SimpleConfig::Load(const char *path)
{
auto spath = std::string(path);
return this->Load(spath);
}
int
SimpleConfig::Load(std::string &path)
{
std::ifstream configFile(path);
std::string line;
while (std::getline(configFile, line)) {
scstring::TrimWhitespace(line);
if (line.size() == 0) {
continue;
}
if (std::regex_search(line, commentLine)) {
return -1;
}
if (std::regex_search(line, keyValueLine)) {
auto pair = scstring::SplitKeyValuePair(line, "=");
if (pair.size() < 2) {
return -1;
}
this->vars[pair[0]] = pair[1];
}
}
return 0;
}
void
SimpleConfig::SetPrefix(const std::string &prefix)
{
this->envPrefix = std::move(prefix);
}
std::string
SimpleConfig::Get(std::string &key)
{
return this->Get(key, "");
}
std::string
SimpleConfig::Get(const char *key)
{
auto keyStr = std::string(key);
return this->Get(keyStr, "");
}
std::string
SimpleConfig::Get(std::string &key, std::string defaultValue)
{
if (this->vars.count(key)) {
return this->vars[key];
}
auto envKey = this->envPrefix + key;
const char *envValue = getenv(envKey.c_str());
if (envValue != nullptr) {
this->vars[key] = std::string(envValue);
return this->Get(key);
}
this->vars[key] = std::move(defaultValue);
return this->Get(key);
}
std::string
SimpleConfig::Get(const char *key, std::string defaultValue)
{
auto keyStr = std::string(key);
return this->Get(keyStr, std::move(defaultValue));
}
void
SimpleConfig::Put(std::string &key, const std::string value)
{
this->vars[key] = std::move(value);
}
void
SimpleConfig::Put(const char *key, const std::string value)
{
auto keyStr = std::string(key);
this->vars[std::move(keyStr)] = std::move(value);
}
std::vector<std::string>
SimpleConfig::KeyList()
{
std::vector<std::string> keyList;
std::transform(this->vars.begin(), this->vars.end(), std::back_inserter(keyList),
[](std::pair<std::string, std::string> pair){return pair.first;});
return keyList;
}
} // namespace SimpleConfig

View File

@ -24,25 +24,23 @@
#include <iostream>
#include <sstream>
#include "StringUtil.h"
#include <scsl/StringUtil.h>
namespace scsl {
/// namespace U contains utilities.
namespace U {
/// namespace S contains string-related functions.
namespace S {
namespace scstring {
std::vector<std::string>
SplitKeyValuePair(std::string line, std::string delimiter)
{
auto pair = SplitN(line, delimiter, 2);
auto pair = SplitN(std::move(line), std::move(delimiter), 2);
if (pair.size() == 0) {
if (pair.empty()) {
return {"", ""};
} else if (pair.size() == 1) {
}
if (pair.size() == 1) {
return {pair[0], ""};
}
@ -62,7 +60,7 @@ SplitKeyValuePair(std::string line, char delimiter)
std::string sDelim;
sDelim.push_back(delimiter);
return SplitKeyValuePair(line, sDelim);
return SplitKeyValuePair(std::move(line), sDelim);
}
@ -72,7 +70,7 @@ TrimLeadingWhitespace(std::string &s)
s.erase(s.begin(),
std::find_if(s.begin(), s.end(),
[](unsigned char ch) {
return !std::isspace(ch);
return std::isspace(ch) == 0;
}));
}
@ -81,7 +79,7 @@ void
TrimTrailingWhitespace(std::string &s)
{
s.erase(std::find_if(s.rbegin(), s.rend(), [](unsigned char ch) {
return !std::isspace(ch);
return std::isspace(ch) == 0;
}).base(), s.end());
}
@ -125,9 +123,9 @@ SplitN(std::string s, std::string delim, size_t maxCount)
size_t ss = 0;
size_t se = 0;
for (ss = 0; s.size() != 0 && ss < s.size(); ss++) {
for (ss = 0; !s.empty() && ss < s.size(); ss++) {
se = s.find(delim, ss);
if ((maxCount > 0) && (parts.size() == maxCount - 1)) {
if ((maxCount > 0) && (parts.size() == (maxCount - 1))) {
se = s.size();
} else if (se == std::string::npos) {
se = s.size();
@ -143,7 +141,7 @@ SplitN(std::string s, std::string delim, size_t maxCount)
std::vector<std::string>
WrapText(std::string line, size_t lineLength)
WrapText(std::string& line, size_t lineLength)
{
std::vector<std::string> wrapped;
auto parts = SplitN(line, " ", 0);
@ -154,8 +152,8 @@ WrapText(std::string line, size_t lineLength)
}
std::string wLine;
for (auto word: parts) {
if (word.size() == 0) {
for (auto &word: parts) {
if (word.empty()) {
continue;
}
@ -164,13 +162,13 @@ WrapText(std::string line, size_t lineLength)
wLine.clear();
}
if (wLine.size() > 0) {
if (!wLine.empty()) {
wLine += " ";
}
wLine += word;
}
if (wLine.size() > 0) {
if (!wLine.empty()) {
wrapped.push_back(wLine);
}
@ -182,7 +180,7 @@ void
WriteTabIndented(std::ostream &os, std::vector<std::string> lines,
int tabStop, bool indentFirst)
{
std::string indent(tabStop, '\t');
std::string const indent(tabStop, '\t');
for (size_t i = 0; i < lines.size(); i++) {
if (i > 0 || indentFirst) {
@ -198,7 +196,7 @@ WriteTabIndented(std::ostream &os, std::string line, size_t maxLength,
int tabStop, bool indentFirst)
{
auto lines = WrapText(line, maxLength);
WriteTabIndented(os, lines, tabStop, indentFirst);
WriteTabIndented(os, std::move(lines), tabStop, indentFirst);
}
@ -230,6 +228,5 @@ VectorToString(const std::vector<std::string> &svec)
}
} // namespace S
} // namespace U
} // namespace string
} // namespace scsl

View File

@ -23,14 +23,15 @@
#include <cassert>
#include <cstring>
#include "TLV.h"
#include <scsl/TLV.h>
using namespace scsl;
/// REC_SIZE calculates the total length of a TLV record, including the
/// two byte header.
#define REC_SIZE(x) ((std::size_t)x.Len + 2)
#define REC_SIZE(x) ((std::size_t)x.Len + 2)
namespace scsl {
@ -100,6 +101,10 @@ SetRecord(Record &rec, uint8_t tag, uint8_t len, const char *val)
void
ReadFromMemory(Record &rec, uint8_t *cursor)
{
assert(cursor != nullptr);
if (cursor == nullptr) {
return;
}
rec.Tag = cursor[0];
rec.Len = cursor[1];
memcpy(rec.Val, cursor + 2, rec.Len);
@ -117,9 +122,10 @@ FindTag(Arena &arena, uint8_t *cursor, Record &rec)
cursor = LocateTag(arena, cursor, rec);
if (rec.Tag != TAG_EMPTY) {
cursor = SkipRecord(rec, cursor);
if (!arena.CursorInArena(cursor)) {
cursor = nullptr;
}
}
if (!arena.CursorInArena(cursor)) {
cursor = nullptr;
}
return cursor;
@ -129,18 +135,19 @@ FindTag(Arena &arena, uint8_t *cursor, Record &rec)
uint8_t *
LocateTag(Arena &arena, uint8_t *cursor, Record &rec)
{
uint8_t tag, len;
if (!arena.CursorInArena(cursor)) {
cursor = nullptr;
}
uint8_t tag = TAG_EMPTY;
uint8_t len;
if (cursor == nullptr) {
cursor = arena.Start();
}
while (((tag = cursor[0]) != rec.Tag) &&
(arena.CursorInArena(cursor))) {
if (!arena.CursorInArena(cursor)) {
cursor = arena.Start();
}
while (arena.CursorInArena(cursor) &&
((tag = cursor[0]) != rec.Tag)) {
assert(arena.CursorInArena(cursor));
len = cursor[1];
if (!spaceAvailable(arena, cursor, len)) {
@ -193,7 +200,7 @@ DeleteRecord(Arena &arena, uint8_t *cursor)
return;
}
uint8_t len = cursor[1] + 2;
uint8_t len = cursor[1] + 2;
uint8_t *stop = arena.Start() + arena.Size();
stop -= len;

View File

@ -1,8 +1,8 @@
///
/// \file Test.cc
/// \file src/sctest/Assert.cc
/// \author K. Isom <kyle@imap.cc>
/// \date 2023-10-09
/// \brief Tooling to assist in building test programs..
/// \brief Assertion tooling useful in building test programs.
///
/// Copyright 2023 K. Isom <kyle@imap.cc>
///
@ -20,22 +20,22 @@
/// PERFORMANCE OF THIS SOFTWARE.
///
#include "Exceptions.h"
#include "Test.h"
#include "sctest/Exceptions.h"
#include <sctest/Assert.h>
#include <cassert>
#include <iostream>
#include <sstream>
namespace scsl {
namespace sctest {
void
TestAssert(bool condition, std::string message)
Assert(bool condition, std::string message)
{
#if defined(NDEBUG) || defined(SCSL_NOEXCEPT)
if (!condition) {
throw AssertionFailed(message);
throw AssertionFailed(std::move(message));
}
#else
if (!condition) {
@ -47,7 +47,7 @@ TestAssert(bool condition, std::string message)
void
TestAssert(bool condition)
Assert(bool condition)
{
#if defined(NDEBUG)
if (condition) {
@ -58,7 +58,7 @@ TestAssert(bool condition)
#else
std::stringstream msg;
msg << "assertion failed at " << __FILE__ << ":" << __LINE__;
msg << "assertion failed At " << __FILE__ << ":" << __LINE__;
throw AssertionFailed(msg.str());
#endif
#else
@ -67,4 +67,4 @@ TestAssert(bool condition)
}
} // namespace scsl
} // namespace sctest

View File

@ -1,5 +1,5 @@
///
/// \file Exceptions.cc
/// \file src/test/Exceptions.cc
/// \author K. Isom <kyle@imap.cc>
/// \date 2023-10-10
/// \brief Custom exceptions used in writing test programs.
@ -20,13 +20,13 @@
/// PERFORMANCE OF THIS SOFTWARE.
///
#include "Exceptions.h"
#include <sctest/Exceptions.h>
namespace scsl {
namespace sctest {
AssertionFailed::AssertionFailed(std::string message) : msg(message) {}
AssertionFailed::AssertionFailed(std::string message) : msg(std::move(message)) {}
const char *
@ -36,4 +36,4 @@ AssertionFailed::what() const throw()
}
}
} // namespace sctest

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

@ -0,0 +1,137 @@
///
/// \file src/test/Report.cpp
/// \author Kyle Isom
/// \date 2017-06-07
///
/// \brief Defines a Report structure that contains information about
/// the results of unit tests.
///
/// Copyright 2017 K. Isom <kyle@imap.cc>
///
/// Permission to use, copy, modify, and/or distribute this software for
/// any purpose with or without fee is hereby granted, provided that
/// the above copyright notice and this permission notice appear in all /// copies.
///
/// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
/// WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
/// WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
/// AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
/// DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA
/// OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
/// TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
/// PERFORMANCE OF THIS SOFTWARE.
#include <chrono>
#include <iomanip>
#include <ostream>
#include <sctest/Report.h>
namespace sctest {
Report::Report()
{
this->Reset(0);
}
size_t
Report::Failing() const
{
return this->failing;
}
size_t
Report::Passing() const
{
return this->passed;
}
size_t
Report::Total() const
{
return this->total;
}
void
Report::Failed()
{
this->failing++;
}
void
Report::Passed()
{
this->passed++;
}
void
Report::AddTest(size_t testCount)
{
this->total += testCount;
}
void
Report::Reset(size_t testCount)
{
auto now = std::chrono::steady_clock::now();
this->total = testCount;
this->passed = 0;
this->failing = 0;
this->Start();
this->end = now;
}
void
Report::Start()
{
this->start = std::chrono::steady_clock::now();
}
void
Report::End()
{
this->end = std::chrono::steady_clock::now();
}
std::chrono::duration<double, std::milli>
Report::Elapsed() const
{
return this->end - this->start;
}
std::ostream&
operator<<(std::ostream &os, const Report &report)
{
auto elapsed = report.Elapsed();
os << report.Passing() << "/"
<< report.Total() << " tests passed in "
<< std::setw(3) << elapsed.count() << "ms";
auto failed = report.Failing();
if (failed > 0) {
os << " (" << failed << " tests failed)";
}
return os;
}
} // end namespace sctest

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

@ -0,0 +1,149 @@
///
/// \file SimpleSuite.cc
/// \author K. Isom <kyle@imap.cc>
/// \date 2017-06-05
/// \brief Defines a simple unit testing framework.
///
/// Copyright 2017 K. Isom <kyle@imap.cc>
///
/// Permission to use, copy, modify, and/or distribute this software for
/// any purpose with or without fee is hereby granted, provided that
/// the above copyright notice and this permission notice appear in all /// copies.
///
/// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
/// WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
/// WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
/// AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
/// DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA
/// OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
/// TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
/// PERFORMANCE OF THIS SOFTWARE.
///
#include <iostream>
#include <sctest/SimpleSuite.h>
namespace sctest {
#define unless(cond) if (!(cond))
static bool
stub()
{ return true; }
SimpleSuite::SimpleSuite()
: quiet(false), fnSetup(stub), fnTeardown(stub), tests(),
report(), hasRun(false)
{
this->Reset();
}
void
SimpleSuite::Silence()
{
// Silence will fall.
quiet = true;
}
void
SimpleSuite::AddTest(std::string name, std::function<bool()> test)
{
const UnitTest test_case = {std::move(name), std::move(test), true};
tests.push_back(test_case);
}
void
SimpleSuite::AddFailingTest(std::string name, std::function<bool()> test)
{
const UnitTest test_case = {std::move(name), std::move(test), false};
tests.push_back(test_case);
}
bool
SimpleSuite::Run()
{
report.Reset(this->tests.size());
unless(quiet) { std::cout << "Setting up the tests.\n"; }
unless(fnSetup()) { return false; }
this->hasRun = true;
this->hasPassed = true;
for (size_t i = 0; i < this->report.Total() && this->hasPassed; i++) {
const UnitTest testCase = this->tests.at(i);
unless(quiet) {
std::cout << "[" << i + 1 << "/"
<< this -> report.Total()
<< "] Running test "
<< testCase.name << ": ";
}
this->hasPassed = (testCase.test() == testCase.expect);
if (this->hasPassed) {
report.Passed();
} else {
report.Failed();
}
if (quiet) { continue; }
if (this->hasPassed) {
std::cout << "[PASS]";
} else {
std::cout << "[FAIL]";
}
std::cout << "\n";
}
unless(quiet) { std::cout << "Tearing down the tests.\n"; }
unless(fnTeardown()) { return false; }
report.End();
return this->hasPassed;
}
void
SimpleSuite::Reset()
{
this->report.Reset(0);
this->hasRun = false;
this->hasPassed = false;
}
bool
SimpleSuite::HasRun() const
{
return this->hasRun;
}
Report
SimpleSuite::GetReport()
{
return report;
}
std::ostream &
operator<<(std::ostream &os, SimpleSuite &suite)
{
if (suite.HasRun()) {
os << "OK: " << suite.GetReport();
} else {
os << "Test suite hasn't run.";
}
return os;
}
} // end namespace sctest

View File

@ -1,152 +0,0 @@
///
/// \file 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 <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";
}
static void
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 = U::S::WrapText(testLine, 16);
TestAssert(wrapped.size() == expected.size(),
U::S::VectorToString(wrapped) + " != " + U::S::VectorToString(expected));
for (size_t i = 0; i < wrapped.size(); i++) {
TestAssert(wrapped[i] == expected[i],
"\"" + wrapped[i] + "\" != \"" + expected[i] + "\"");
}
U::S::WriteTabIndented(std::cout, wrapped, 4, true);
}
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"});
TestWrapping();
}

163
test/buffer.cc Normal file
View File

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

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

@ -0,0 +1,81 @@
///
/// \file test/config-explorer.cc
/// \author K. Isom <kyle@imap.cc>
/// \date 2023-10-21
/// \brief Commandline tools for interacting with simple configurations.
///
/// Copyright 2023 K. Isom <kyle@imap.cc>
///
/// Permission to use, copy, modify, and/or distribute this software for
/// any purpose with or without fee is hereby granted, provided that
/// the above copyright notice and this permission notice appear in all /// copies.
///
/// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
/// WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
/// WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
/// AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
/// DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA
/// OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
/// TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
/// PERFORMANCE OF THIS SOFTWARE.
///
#include <iostream>
#include <string>
#include <scsl/Flags.h>
#include <scsl/SimpleConfig.h>
int
main(int argc, char *argv[])
{
int retc = 1;
bool listKeys;
std::string fileName;
std::string prefix;
std::string defaultValue;
auto *flags = new scsl::Flags("config-explorer",
"interact with a simple configuration system");
flags->Register("-d", "", "set a default value");
flags->Register("-f", scsl::FlagType::String, "path to a configuration file");
flags->Register("-l", false, "list cached keys at the end");
flags->Register("-p", "CX_",
"prefix for configuration environment variables");
auto parsed = flags->Parse(argc, argv);
if (parsed != scsl::Flags::ParseStatus::OK) {
std::cerr << "Failed to parse flags: "
<< scsl::Flags::ParseStatusToString(parsed) << "\n";
exit(1);
}
flags->GetString("-d", defaultValue);
flags->GetString("-f", fileName);
flags->GetBool("-l", listKeys);
flags->GetString("-p", prefix);
scsl::SimpleConfig::SetPrefixGlobal(prefix);
if (!fileName.empty()) {
if (scsl::SimpleConfig::LoadGlobal(fileName) != 0) {
std::cerr << "[!] failed to load " << fileName << "\n";
return retc;
}
}
for (auto &key : flags->Args()) {
auto val = scsl::SimpleConfig::GetGlobal(key, defaultValue);
std::cout << key << ": " << val << "\n";
}
if (listKeys) {
std::cout << "[+] cached keys\n";
for (auto &key : scsl::SimpleConfig::KeyListGlobal()) {
std::cout << "\t- " << key << "\n";
}
}
delete flags;
return retc;
}

295
test/coord2d.cc Executable file
View File

@ -0,0 +1,295 @@
///
/// \file test/coord2d.cc
/// \author K.Isom <kyle@imap.cc>
/// \date 2017-06-05
/// \brief Unit tests on 2D geometry code.
///
/// \section COPYRIGHT
///
/// Copyright 2017 K. Isom <kyle@imap.cc>
///
/// Permission to use, copy, modify, and/or distribute this software for
/// any purpose with or without fee is hereby granted, provided that
/// the above copyright notice and this permission notice appear in all /// copies.
///
/// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
/// WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
/// WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
/// AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
/// DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA
/// OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
/// TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
/// PERFORMANCE OF THIS SOFTWARE.
///
#include <array>
#include <iostream>
#include <vector>
#include <scsl/Flags.h>
#include <scmp/Math.h>
#include <scmp/geom/Coord2D.h>
#include <sctest/Checks.h>
#include <sctest/SimpleSuite.h>
using namespace scmp::geom;
using namespace sctest;
namespace {
#define CHECK_ROTATE(theta, expected) if (!scmp::WithinTolerance(scmp::RotateRadians((double)theta, 0), (double)expected, (double)0.0001)) { \
std::cerr << "Expected " << theta << " to wrap to " << expected << "\n"; \
std::cerr << " have " << scmp::RotateRadians(theta, 0) << "\n"; \
return false; \
}
bool
geomValidateAngularRotation()
{
CHECK_ROTATE(0, 0);
CHECK_ROTATE(M_PI/4, M_PI/4);
CHECK_ROTATE(M_PI/2, M_PI/2);
CHECK_ROTATE(3 * M_PI / 4, 3 * M_PI / 4);
CHECK_ROTATE(M_PI, M_PI);
CHECK_ROTATE(5 * M_PI / 4, -3 * M_PI / 4);
CHECK_ROTATE(3 * M_PI / 2, -(M_PI / 2));
CHECK_ROTATE(7 * M_PI / 4, -(M_PI / 4));
CHECK_ROTATE(4 * M_PI, 0)
return true;
}
bool
geomConversionIdentities()
{
const std::array<Point2D,4> points = {
Point2D(1, 0),
Point2D(0, 1),
Point2D(-1, 0),
Point2D(0, -1)
};
const std::array<Polar2D,4> polars = {
Polar2D(1, 0),
Polar2D(1, scmp::DegreesToRadiansD(90)),
Polar2D(1, scmp::DegreesToRadiansD(180)),
Polar2D(1, scmp::DegreesToRadiansD(-90)),
};
for (auto i = 0; i < 4; i++) {
const Polar2D pol(points.at(i));
if (pol != polars.at(i)) {
std::cerr << "! measured value outside tolerance ("
<< i << ")\n";
std::cerr << " " << points.at(i) << "" << pol
<< "" << polars.at(i) << "\n";
return false;
}
const Point2D point(pol);
SCTEST_CHECK(point == points.at(i));
}
Point2D point(3, 5);
Point2D point2;
Polar2D polar;
Polar2D polar2;
point.ToPolar(polar);
polar.ToPoint(point2);
SCTEST_CHECK_EQ(point, point2);
point2.ToPolar(polar2);
SCTEST_CHECK_EQ(polar, polar2);
return true;
}
bool
geomVerifyBasicProperties()
{
const Point2D pt1(1, 1);
const Point2D pt2(2, 2);
const Point2D pt3(3, 3);
SCTEST_CHECK((pt1 + pt2) == pt3);
SCTEST_CHECK((pt3 - pt2) == pt1);
// commutative
SCTEST_CHECK((pt1 + pt2) == (pt2 + pt1));
SCTEST_CHECK((pt1 + pt3) == (pt3 + pt1));
SCTEST_CHECK((pt2 + pt3) == (pt3 + pt2));
// associative
SCTEST_CHECK(((pt1 + pt2) + pt3) == (pt1 + (pt2 + pt3)));
// transitive
const Point2D pt4(1, 1);
const Point2D pt5(1, 1);
SCTEST_CHECK(pt1 == pt4);
SCTEST_CHECK(pt4 == pt5);
SCTEST_CHECK(pt1 == pt5);
// scaling
const Point2D pt6(2, 3);
const Point2D pt7(8, 12);
SCTEST_CHECK((pt6 * 4) == pt7);
return true;
}
bool
geomComparePoint2D()
{
const Point2D pt1(1, 1);
const Point2D pt2(1, 1);
const Point2D pt3(0, 1);
SCTEST_CHECK(pt1 == pt2);
SCTEST_CHECK_FALSE(pt2 == pt3);
return true;
}
bool
geomRotatePoint2D()
{
std::array<Point2D, 4> vertices = {
Point2D(1, 0), // θ = 0
Point2D(0, 1), // θ = π/2
Point2D(-1, 0), // θ = π
Point2D(0, -1) // θ = 3π/2
};
for (auto i = 0; i < 4; i++) {
auto first = i % 4;
auto expected = (i + 1) % 4;
Point2D vertex;
vertices.at(first).Rotate(vertex, 1.5708);
if (vertex != vertices.at(expected)) {
std::cerr << "expected: " << expected << "\n";
std::cerr << " have: " << vertex << "\n";
return false;
}
}
return true;
}
bool
geomRotatePointsAboutOrigin()
{
Point2D origin(3, 3);
double theta = 0;
const std::vector<Polar2D> vertices {
Polar2D(2, 0),
Polar2D(1.41421, 2.35619),
Polar2D(1.41421, -2.35619)
};
// expected coordinates with no rotation
std::vector<Point2D> rotated0 {
Point2D(5, 3),
Point2D(2, 4),
Point2D(2, 2)
};
auto rotated = origin.Rotate(vertices, theta);
for (auto i = 0; i < 3; i++) {
SCTEST_CHECK(rotated.at(i) == rotated0.at(i));
}
// expected after 90° rotation
theta = scmp::DegreesToRadiansD(90);
std::vector<Point2D> rotated90 {
Point2D(3, 5),
Point2D(2, 2),
Point2D(4, 2)
};
rotated = origin.Rotate(vertices, theta);
for (auto i = 0; i < 3; i++) {
SCTEST_CHECK(rotated.at(i) == rotated90.at(i));
}
// expected after 180° rotation
theta = scmp::DegreesToRadiansD(180);
std::vector<Point2D> rotated180 {
Point2D(1, 3),
Point2D(4, 2),
Point2D(4, 4)
};
rotated = origin.Rotate(vertices, theta);
for (auto i = 0; i < 3; i++) {
SCTEST_CHECK(rotated.at(i) == rotated180.at(i));
}
return true;
}
bool
pointDistances()
{
const Point2D origin;
const Point2D y2(0, 2);
const Point2D x2(2, 0);
const int dist2 = 2;
const int dist10 = 10;
const Point2D deg45{8, 6};
SCTEST_CHECK_EQ(y2.Distance(origin), dist2);
SCTEST_CHECK_EQ(x2.Distance(origin), dist2);
SCTEST_CHECK_EQ(deg45.Distance(origin), dist10);
return true;
};
} // anonymous namespace
int
main(int argc, char *argv[])
{
auto noReport = false;
auto quiet = false;
auto flags = new scsl::Flags("test_orientation",
"This test validates various orientation-related components in scmp.");
flags->Register("-n", false, "don't print the report");
flags->Register("-q", false, "suppress test output");
auto parsed = flags->Parse(argc, argv);
if (parsed != scsl::Flags::ParseStatus::OK) {
std::cerr << "Failed to parse flags: "
<< scsl::Flags::ParseStatusToString(parsed) << "\n";
exit(1);
}
SimpleSuite suite;
flags->GetBool("-n", noReport);
flags->GetBool("-q", quiet);
if (quiet) {
suite.Silence();
}
suite.AddTest("geomValidateAngularRotation", geomValidateAngularRotation);
suite.AddTest("geomConversionIdentities", geomConversionIdentities);
suite.AddTest("geomVerifyBasicProperties", geomVerifyBasicProperties);
suite.AddTest("geomComparePoint2D", geomComparePoint2D);
suite.AddTest("geomRotatePoint2D", geomRotatePoint2D);
suite.AddTest("geomRotatePointsAboutOrigin", geomRotatePointsAboutOrigin);
suite.AddTest("pointDistances", pointDistances);
delete flags;
auto result = suite.Run();
if (!noReport) { std::cout << suite.GetReport() << "\n"; }
return result ? 0 : 1;
}

142
test/dictionary.cc Normal file
View File

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

View File

@ -1,11 +1,34 @@
//
// Created by kyle on 10/15/23.
//
///
/// \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 "Flag.h"
#include "Test.h"
#include <scsl/Flags.h>
#include <sctest/Assert.h>
using namespace scsl;
@ -26,7 +49,7 @@ main(int argc, char *argv[])
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");
TestAssert(flags->Size() == 5, "flags weren't registered");
sctest::Assert(flags->Size() == 5, "flags weren't registered");
auto status = flags->Parse(argc, argv);

279
test/madgwick.cc Normal file
View File

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

186
test/math.cc Normal file
View File

@ -0,0 +1,186 @@
///
/// \file test/math.cc
/// \author K. Isom <kyle@imap.cc>
/// \date 2023-10-20
/// \brief Unit tests for math functions.
///
/// Arena defines a memory management backend for pre-allocating memory.
///
/// Copyright 2023 K. Isom <kyle@imap.cc>
///
/// Permission to use, copy, modify, and/or distribute this software for
/// any purpose with or without fee is hereby granted, provided that
/// the above copyright notice and this permission notice appear in all /// copies.
///
/// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
/// WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
/// WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
/// AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
/// DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA
/// OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
/// TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
/// PERFORMANCE OF THIS SOFTWARE.
///
#include <iostream>
#include <scsl/Flags.h>
#include <scmp/Math.h>
#include <sctest/Checks.h>
#include <sctest/SimpleSuite.h>
namespace {
bool
BestDie()
{
// Theoretically, this could fail. The odds of that happening
// with 100 die and a proper RNG is 0.99999998792533,
// practically 100%. At 1000 die, it's virtually guaranteed.
auto n = scmp::BestDie(1000, 6, 1);
SCTEST_CHECK_EQ(n, 6);
return true;
}
bool
DieTotal()
{
auto n = scmp::DieTotal(100, 6);
SCTEST_CHECK_GEQ(n, 100);
SCTEST_CHECK_LEQ(n, 600);
return true;
}
bool
WithinToleranceFloat()
{
float x = 0.1235;
float y = 0.1236;
float eps = 0.0;
float expected = 0.1234;
scmp::DefaultEpsilon(eps);
SCTEST_CHECK_FEQ_EPS(x, expected, eps);
SCTEST_CHECK_FNE_EPS(y, expected, eps);
return true;
}
bool
WithinToleranceDouble()
{
double x = 0.12348;
double y = 0.1236;
double eps = 0.0;
double expected = 0.12345;
scmp::DefaultEpsilon(eps);
SCTEST_CHECK_DEQ_EPS(x, expected, eps);
SCTEST_CHECK_DNE_EPS(y, expected, eps);
return true;
}
bool
RotateRadians()
{
double theta0 = 0.0;
double theta1 = scmp::PI_D;
auto rotated = scmp::RotateRadians(theta0, theta1);
SCTEST_CHECK_DEQ(rotated, theta1);
rotated = scmp::RotateRadians(rotated, theta1);
SCTEST_CHECK_DEQ(rotated, theta0);
theta1 = scmp::PI_D * 3 / 2;
rotated = scmp::RotateRadians(theta0, theta1);
SCTEST_CHECK_DEQ(rotated, -scmp::PI_D / 2);
rotated = scmp::RotateRadians(rotated, theta1);
SCTEST_CHECK_DEQ(rotated, scmp::PI_D);
return true;
}
bool
IntegerSquareRoot()
{
static std::vector<size_t> ns{
// standard integer roots
4, 9, 16, 25, 36, 49, 64, 81, 100, 121, 144,
// a few float cases
42, 90, 92
};
static std::vector<size_t> expected{
// standard integer roots
2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12,
// a few float cases
6, 9, 10
};
SCTEST_CHECK_EQ(ns.size(), expected.size());
for (size_t i = 0; i < ns.size(); i++) {
auto root = scmp::ISqrt(ns.at(i));
SCTEST_CHECK_EQ(root, expected.at(i));
}
return true;
}
} // anonymous namespace
int
main(int argc, char *argv[])
{
auto noReport = false;
auto quiet = false;
auto flags = new scsl::Flags("test_orientation",
"This test validates various orientation-related components in scmp.");
flags->Register("-n", false, "don't print the report");
flags->Register("-q", false, "suppress test output");
auto parsed = flags->Parse(argc, argv);
if (parsed != scsl::Flags::ParseStatus::OK) {
std::cerr << "Failed to parse flags: "
<< scsl::Flags::ParseStatusToString(parsed) << "\n";
exit(1);
}
sctest::SimpleSuite suite;
flags->GetBool("-n", noReport);
flags->GetBool("-q", quiet);
if (quiet) {
suite.Silence();
}
suite.AddTest("BestDie", BestDie);
suite.AddTest("DieTotal", DieTotal);
suite.AddTest("WithinToleranceFloat", WithinToleranceFloat);
suite.AddTest("WithinToleranceDouble", WithinToleranceDouble);
suite.AddTest("RotateRadians", RotateRadians);
suite.AddTest("IntegerSquareRoot", IntegerSquareRoot);
delete flags;
auto result = suite.Run();
if (!noReport) { std::cout << suite.GetReport() << "\n"; }
return result ? 0 : 1;
}

123
test/orientation.cc Normal file
View File

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

493
test/quaternion.cc Normal file
View File

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

89
test/simple_suite_example.cc Executable file
View File

@ -0,0 +1,89 @@
///
/// \file test/simple_suite_example.cc
/// \author K. Isom <kyle@imap.cc>
/// \date 2017-06-05
///
/// simple_suite_example demonstrates the usage of the SimpleSuite test class
/// and serves to unit test the unit tester (qui custodiet ipsos custodes)?
///
///
/// Copyright 2017 K. Isom <kyle@imap.cc>
///
/// Permission to use, copy, modify, and/or distribute this software for
/// any purpose with or without fee is hereby granted, provided that the
/// above copyright notice and this permission notice appear in all copies.
///
/// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
/// WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
/// WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR
/// BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES
/// OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
/// WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
/// ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
/// SOFTWARE.
///
#include <iostream>
#include <scsl/Flags.h>
#include <sctest/SimpleSuite.h>
static bool
prepareTests()
{
std::cout << "time passes...\n";
std::cout << "tests are ready.\n";
return true;
}
static bool
destroyTests()
{
std::cout << "time passes...\n" ;
std::cout << "tests have been destroyed.\n";
return true;
}
static bool addOne() { return 1 + 1 == 2; }
static bool four() { return 2 + 2 == 4; }
static bool nope() { return 2 + 2 == 5; }
int
main(int argc, char *argv[])
{
auto noReport = false;
auto quiet = false;
auto flags = new scsl::Flags("test_orientation",
"This test validates various orientation-related components in scmp.");
flags->Register("-n", false, "don't print the report");
flags->Register("-q", false, "suppress test output");
auto parsed = flags->Parse(argc, argv);
if (parsed != scsl::Flags::ParseStatus::OK) {
std::cerr << "Failed to parse flags: "
<< scsl::Flags::ParseStatusToString(parsed) << "\n";
exit(1);
}
sctest::SimpleSuite suite;
flags->GetBool("-n", noReport);
flags->GetBool("-q", quiet);
if (quiet) {
suite.Silence();
}
suite.Setup(prepareTests);
suite.Teardown(destroyTests);
suite.AddTest("1 + 1", addOne);
suite.AddTest("fourness", four);
suite.AddFailingTest("self-evident truth", nope);
delete flags;
auto result = suite.Run();
if (!noReport) { std::cout << suite.GetReport() << "\n"; }
return result ? 0 : 1;
}

182
test/stringutil.cc Normal file
View File

@ -0,0 +1,182 @@
///
/// \file test/stringutil_test.cc
/// \author kyle
/// \date 10/14/23
/// \brief Ensure the stringutil functions work.
///
/// Copyright 2023 K. Isom <kyle@imap.cc>
///
/// Permission to use, copy, modify, and/or distribute this software for
/// any purpose with or without fee is hereby granted, provided that the
/// above copyright notice and this permission notice appear in all copies.
///
/// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
/// WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
/// WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR
/// BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES
/// OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
/// WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
/// ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
/// SOFTWARE.
///
#include <functional>
#include <iostream>
#include <scsl/Flags.h>
#include <scsl/StringUtil.h>
#include <sctest/Assert.h>
#include <sctest/SimpleSuite.h>
using namespace scsl;
namespace {
void
TestTrimming(std::string line, std::string lExpected, std::string rExpected, std::string expected)
{
std::string result;
std::string message;
result = scstring::TrimLeadingWhitespaceDup(line);
message = "TrimLeadingDup(\"" + line + "\"): '" + result + "'";
sctest::Assert(result == lExpected, message);
result = scstring::TrimTrailingWhitespaceDup(line);
message = "TrimTrailingDup(\"" + line + "\"): '" + result + "'";
sctest::Assert(result == rExpected, message);
result = scstring::TrimWhitespaceDup(line);
message = "TrimDup(\"" + line + "\"): '" + result + "'";
sctest::Assert(result == expected, message);
result = line;
scstring::TrimLeadingWhitespace(result);
message = "TrimLeadingDup(\"" + line + "\"): '" + result + "'";
sctest::Assert(result == lExpected, message);
result = line;
scstring::TrimTrailingWhitespace(result);
message = "TrimTrailingDup(\"" + line + "\"): '" + result + "'";
sctest::Assert(result == rExpected, message);
result = line;
scstring::TrimWhitespace(result);
message = "TrimDup(\"" + line + "\"): '" + result + "'";
sctest::Assert(result == expected, message);
}
std::function<bool()>
TestSplit(std::string line, std::string delim, size_t maxCount, std::vector<std::string> expected)
{
return [line, delim, maxCount, expected]() {
return scstring::SplitN(line, delim, maxCount) == expected;
};
}
bool
TestSplitChar()
{
auto expected = std::vector<std::string>{"hello", "world"};
const auto *inputLine = "hello=world\n";
auto actual = scstring::SplitKeyValuePair(inputLine, '=');
return actual == expected;
}
bool
TestWrapping()
{
std::string testLine = "A much longer line, something that can be tested with WrapText. ";
testLine += "Does it handle puncuation? I hope so.";
std::vector<std::string> expected{
"A much longer",
"line, something",
"that can be",
"tested with",
"WrapText. Does",
"it handle",
"puncuation? I",
"hope so.",
};
auto wrapped = scstring::WrapText(testLine, 16);
if (wrapped.size() != expected.size()) {
std::cerr << scstring::VectorToString(wrapped)
<< " != "
<< scstring::VectorToString(expected)
<< "\n";
}
for (size_t i = 0; i < wrapped.size(); i++) {
if (wrapped[i] == expected[i]) {
continue;
}
std::cerr << "[" << i << "] \"" << wrapped[i] << "\" != \""
<< expected[i] << "\"\n";
return false;
}
// scstring::WriteTabIndented(std::cout, wrapped, 4, true);
return true;
}
} // anonymous namespace
int
main(int argc, char *argv[])
{
auto noReport = false;
auto quiet = false;
auto *flags = new scsl::Flags("test_orientation",
"This test validates various orientation-related components in scmp.");
flags->Register("-n", false, "don't print the report");
flags->Register("-q", false, "suppress test output");
auto parsed = flags->Parse(argc, argv);
if (parsed != scsl::Flags::ParseStatus::OK) {
std::cerr << "Failed to parse flags: "
<< scsl::Flags::ParseStatusToString(parsed) << "\n";
}
sctest::SimpleSuite suite;
flags->GetBool("-n", noReport);
flags->GetBool("-q", quiet);
if (quiet) {
suite.Silence();
}
TestTrimming(" foo\t ", "foo\t ", " foo", "foo");
TestTrimming(" foo\tbar ", "foo\tbar ", " foo\tbar", "foo\tbar");
suite.AddTest("SplitN(0)", TestSplit("abc:def:ghij:klm", ":", 0,
std::vector<std::string>{"abc", "def", "ghij", "klm"}));
suite.AddTest("SplitN(3)", TestSplit("abc:def:ghij:klm", ":", 3,
std::vector<std::string>{"abc", "def", "ghij:klm"}));
suite.AddTest("SplitN(2)", TestSplit("abc:def:ghij:klm", ":", 2,
std::vector<std::string>{"abc", "def:ghij:klm"}));
suite.AddTest("SplitN(1)", TestSplit("abc:def:ghij:klm", ":", 1,
std::vector<std::string>{"abc:def:ghij:klm"}));
suite.AddTest("SplitN(0) with empty element",
TestSplit("abc::def:ghi", ":", 0,
std::vector<std::string>{"abc", "", "def", "ghi"}));
suite.AddTest("TestSplitKV(char)", TestSplitChar);
suite.AddTest("TextWrapping", TestWrapping);
delete flags;
auto result = suite.Run();
if (!noReport) { std::cout << suite.GetReport() << "\n"; }
return result ? 0 : 1;
}

View File

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

172
test/tlv.cc Normal file
View File

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

514
test/vector.cc Normal file
View File

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

View File

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

446
trunk Executable file
View File

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