Compare commits
50 Commits
Author | SHA1 | Date |
---|---|---|
|
6266148eed | |
|
af61d914f0 | |
|
f99d5a8356 | |
|
94672bba98 | |
|
33675c18ec | |
|
f76d524999 | |
|
1bc8a9ad82 | |
|
9a83cf6c08 | |
|
28238ba041 | |
|
044afb9a60 | |
|
8ba8dee78d | |
|
7f0a814b3f | |
|
8868fe40a1 | |
|
c5308dedba | |
|
9a8dc08a4f | |
|
9faed6a95b | |
|
168ee430f4 | |
|
f393f8614f | |
|
8b63985ac9 | |
|
1420ff343d | |
|
10888b4872 | |
|
0bf4dd54f3 | |
|
aee337f2e9 | |
|
4e83da345f | |
|
540a0d5b74 | |
|
2d7a8d5c1d | |
|
0c7fa41cc8 | |
|
6a421d6adf | |
|
4b1007123a | |
|
68ed5e0aca | |
|
2fceae91fd | |
|
1c2f9af9d9 | |
|
e923335396 | |
|
fa8a89625b | |
|
b49caa3ec9 | |
|
4eb4008130 | |
|
2a23d2e204 | |
|
a5bb31943c | |
|
f896a108dd | |
|
9494871145 | |
|
1c6de07b7a | |
|
eaea8c2ab0 | |
|
6dab443789 | |
|
0a661a7d70 | |
|
b1bbaebdac | |
|
a9991f241a | |
|
49eea73449 | |
|
36fe049485 | |
|
8d02d078e7 | |
|
5f3dc6e9f6 |
|
@ -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:
|
||||
|
|
|
@ -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
|
16
.clang-tidy
16
.clang-tidy
|
@ -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
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
.trunk
|
||||
.vc
|
||||
.vscode
|
||||
|
||||
|
@ -10,6 +9,7 @@ build
|
|||
core
|
||||
core.*
|
||||
cmake-build-*
|
||||
compile_commands.json
|
||||
|
||||
bufferTest
|
||||
dictionaryTest
|
||||
|
|
|
@ -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" />
|
||||
|
|
|
@ -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" />
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
*out
|
||||
*logs
|
||||
*actions
|
||||
*notifications
|
||||
*tools
|
||||
plugins
|
||||
user_trunk.yaml
|
||||
user.yaml
|
|
@ -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
|
118
CMakeLists.txt
118
CMakeLists.txt
|
@ -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)
|
||||
|
||||
|
|
|
@ -1,5 +1,9 @@
|
|||
# scsl : The Shimmering Clarity Standard C++ Library
|
||||
|
||||
[](https://dl.circleci.com/status-badge/redirect/gh/shimmering-clarity/scsl/tree/master)
|
||||
|
||||
[](https://scan.coverity.com/projects/shimmering-clarity-scsl)
|
||||
|
||||
scsl is a collection of software I found myself needing to use repeatedly.
|
||||
|
||||
Full [Doxygen documentation](https://docs.shimmering-clarity.net/scsl/)
|
||||
|
|
|
@ -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;
|
||||
}
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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;
|
||||
}
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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.
|
|
@ -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.
|
|
@ -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;
|
||||
};
|
|
@ -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
|
|
@ -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;
|
|
@ -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
|
|
@ -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
|
||||
|
||||
|
|
@ -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);
|
|
@ -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.
|
||||
|
||||
|
||||
}
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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;
|
||||
}
|
|
@ -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
|
|
@ -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
|
||||
|
|
@ -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
|
|
@ -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
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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,
|
|
@ -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));
|
||||
}
|
||||
|
||||
|
|
@ -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);
|
||||
}
|
|
@ -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
|
|
@ -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]);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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
|
|
@ -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
|
|
@ -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;
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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();
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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);
|
||||
|
|
@ -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;
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -3,7 +3,7 @@
|
|||
|
||||
|
||||
#include <string.h>
|
||||
#include "TLV.h"
|
||||
#include <scsl/TLV.h>
|
||||
|
||||
|
||||
#define ARENA_SIZE 128
|
|
@ -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;
|
||||
}
|
|
@ -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;
|
||||
}
|
124
tlvTest.cc
124
tlvTest.cc
|
@ -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;
|
||||
}
|
|
@ -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" "$@"
|
Loading…
Reference in New Issue