Compare commits

...

60 Commits

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

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

19
.circleci/config.yml Normal file
View File

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

67
.clang-format Normal file
View File

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

View File

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

2
.gitignore vendored
View File

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

View File

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

View File

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

8
.trunk/.gitignore vendored Normal file
View File

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

27
.trunk/trunk.yaml Normal file
View File

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

View File

@ -1,11 +1,12 @@
cmake_minimum_required(VERSION 3.22)
project(scsl LANGUAGES CXX
VERSION 0.2.3
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)

61
README.md Normal file
View File

@ -0,0 +1,61 @@
# scsl : The Shimmering Clarity Standard C++ Library
[![CircleCI](https://dl.circleci.com/status-badge/img/gh/shimmering-clarity/scsl/tree/master.svg?style=svg)](https://dl.circleci.com/status-badge/redirect/gh/shimmering-clarity/scsl/tree/master)
[![image](https://scan.coverity.com/projects/29251/badge.svg)](https://scan.coverity.com/projects/shimmering-clarity-scsl)
scsl is a collection of software I found myself needing to use repeatedly.
Full [Doxygen documentation](https://docs.shimmering-clarity.net/scsl/)
is available.
## Introduction
This is a collection of C++ code that I find useful in building things.
It arose from two main use cases.
### The modem
On the one hand, I was building a wireless modem for some Z80 computers I
have. I needed to be able to store a phonebook of SSIDs and WPA keys, as
well as short names to host:port descriptors. I had a limited amount of
persistent NVRAM storage and no SD card or other removeable media, so
typical desktop-oriented serialization mechanisms weren't going to really
work well. Furthermore, when working with microcontrollers, I prefer not to
dynamically allocate memory as much as possible. This led to building out
Arena, TLV::Record to store the records, and finally Dictionary to make use
of both of them.
Closely related to this, I've been working on building an ARM-based handheld
computer, for which I would also need a memory arena.
### The text editors
Some time ago, I wrote a console text editor of my own; then later, started
working on a graphical editor. For this, I needed some data structures to
manage memory in the editor. Thus, Buffer was born.
### Finally
I'd been writing Go professionally for a while, but C was my first love. I
recently started a job that is mostly in C++, and the best way for me to
learn is to build a bunch of stuff with it. So, I took a bunch of micro-
controller stuff I'd been writing and started building out some other stuff.
## License
Copyright 2023 K. Isom <kyle@imap.cc>
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.

View File

@ -1,27 +0,0 @@
scsl : The Shimmering Clarity Standard C++ Library
==================================================
scsl is a collection of software I found myself needing to use repeatedly.
Full `Doxygen documentation`_ is available.
.. _Doxygen documentation: https://docs.shimmering-clarity.net/scsl/
License
-------
Copyright 2023 K. Isom <kyle@imap.cc>
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.

View File

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

View File

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

View File

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

View File

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

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

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

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

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

View File

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

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

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

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

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

View File

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

View File

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

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

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

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

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

View File

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

View File

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

View File

@ -1,5 +1,5 @@
///
/// \file Commander.h
/// \file include/scsl/Commander.h
/// \author K. Isom <kyle@imap.cc>
/// \date 2023-10-10
/// \brief Subprogram tooling.
@ -28,6 +28,7 @@
/// PERFORMANCE OF THIS SOFTWARE.
///
#include <cstdint>
#include <functional>
#include <map>
#include <string>
@ -43,15 +44,17 @@ namespace scsl {
/// CommanderFunc describes a function that can be run in Commander.
///
/// It expects an argument count and a list of arguments.
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 {
public:
/// Status describes the results of running a Subcommand.
enum class Status : uint8_t {
enum class Status : int8_t {
/// The subcommand executed correctly.
OK = 0,
/// Not enough arguments were supplied to the subcommand.
@ -69,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.
@ -78,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:
@ -114,7 +118,7 @@ public:
bool Register(Subcommand scmd);
/// Try to run a subcommand registered with this Commander.
Subcommand::Status Run(std::string command, int argc, char **argv);
Subcommand::Status Run(std::string command, std::vector<std::string> args);
private:
std::map<std::string, Subcommand *> cmap;
};

View File

@ -1,5 +1,5 @@
///
/// \file Dictionary.h
/// \file include/scsl/Dictionary.h
/// \author kyle (kyle@imap.cc)
/// \date 2023-10-12
/// \brief Key-value store built on top of Arena and TLV.
@ -39,17 +39,7 @@ static constexpr uint8_t DICTIONARY_TAG_VAL = 2;
namespace scsl {
/*
* A Dictionary is a collection of key-value pairs, similar to how
* a dictionary is a mapping of names to definitions.
*/
/// Dictionary implements a key-value store on top of Arena and TLV::Record.
///
/// phonebook of SSIDs and WPA keys on a microcontroller. This phonebook had to
/// be stored in persistent NVRAM storage, preëmpting the use of std::map or
/// similar. The hardware in use was also not conducive to more expensive
/// options. It was originally named Phonebook until it was adapted to a more
/// general-purpose data structure.
/// \brief Key-value store on top of Arena and TLV::Record.
///
/// Keys and vales are stored as sequential pairs of TLV records; they are
/// expected to contain string values but this isn't necessarily the case. The

View File

@ -1,5 +1,5 @@
///
/// \file Flag.h
/// \file include/scsl/Flags.h
/// \author K. Isom <kyle@imap.cc>
/// \date 2023-10-12
/// \brief Flag declares a command-line flag parser.
@ -55,7 +55,7 @@ typedef union {
} FlagValue;
/// Flag describes an individual command-line flag.
/// \brief Individual command-line flag
typedef struct {
FlagType Type; ///< The type of the value in the flag.
bool WasSet; ///< The flag was set on the command-line.
@ -64,7 +64,7 @@ typedef struct {
FlagValue Value; ///< The flag's value.
} Flag;
/// NewFlag is a helper function for constructing a new flag.
/// \brief NewFlag is a helper function for constructing a new flag.
///
/// \param fName The name of the flag.
/// \param fType The type of the flag.
@ -72,7 +72,7 @@ typedef struct {
/// \return A pointer to a flag.
Flag *NewFlag(std::string fName, FlagType fType, std::string fDescription);
/// Flags provides a basic facility for processing command line flags.
/// \brief Basic facility for processing command line flags.
///
/// Any remaining arguments after the args are added to the parser as
/// arguments that can be accessed with NumArgs, Args, and Arg.
@ -92,41 +92,43 @@ Flag *NewFlag(std::string fName, FlagType fType, std::string fDescription);
///
/// A short example program is below:
///
/// ```c++
/// int
/// main(int argc, char *argv[])
/// {
/// std::string server = "service.example.com";
/// unsigned int port = 1234;
///
/// std::string server = "service.example.com";
/// unsigned int port = 1234;
///
/// auto flags = new scsl::Flags("example-client",
/// "This interacts with the example.com service.");
/// "This interacts with the example.com service.");
/// flags->Register("-p", port, "server port");
/// flags->Register("-s", server, "hostname to connect to");
///
///
/// auto status = flags->Parse(argc, argv);
/// if (status != ParseStatus::OK) {
/// std::cerr << "failed to parse flags: "
/// << scsl::Flags::ParseStatusToString(status)
/// << "\n";
/// << scsl::Flags::ParseStatusToString(status)
/// << "\n";
/// exit(1);
/// }
///
///
/// auto wasSet = flags->GetString("-s", server);
/// if (wasSet) {
/// std::cout << "hostname override: " << server << "\n";
/// }
///
/// if (wasSet) {
/// std::cout << "hostname override: " << server << "\n";
/// }
///
/// wasSet = flags->GetUnsignedInteger("-p", port);
/// if (wasSet) {
/// std::cout << "port override: " << port << "\n";
/// }
///
/// if (wasSet) {
/// std::cout << "port override: " << port << "\n";
/// }
///
/// std::cout << "connecting to " << server << ":" << port << "\n";
/// for (size_t i = 0; i < flags.NumArgs(); i++) {
/// std::cout << "\tExecuting command " << flags.Arg(i) << "\n";
/// }
/// return 0;
/// return 0;
/// }
/// ```
///
class Flags {
public:
@ -243,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();
@ -280,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);
@ -327,7 +340,7 @@ public:
private:
ParseStatus parseArg(int argc, char **argv, int &index);
Flag *checkGetArg(std::string fName, FlagType eType);
Flag *checkGetArg(std::string& fName, FlagType eType);
std::string name;
std::string description;

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

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

View File

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

View File

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

View File

@ -33,7 +33,7 @@
#include <scsl/Commander.h>
#include <scsl/Dictionary.h>
#include <scsl/Exceptions.h>
#include <scsl/Flag.h>
#include <scsl/Flags.h>
#include <scsl/StringUtil.h>
#include <scsl/TLV.h>
#include <scsl/Test.h>
@ -55,7 +55,7 @@ namespace scsl {
///
/// On the one hand, I was building a wireless modem for some Z80 computers I
/// have. I needed to be able to store a phonebook of SSIDs and WPA keys, as
/// well as short names to host:port descriptors. I had a limited amount of of
/// well as short names to host:port descriptors. I had a limited amount of
/// persistent NVRAM storage and no SD card or other removeable media, so
/// typical desktop-oriented serialization mechanisms weren't going to really
/// work well. Furthermore, when working with microcontrollers, I prefer not to
@ -72,6 +72,12 @@ namespace scsl {
/// working on a graphical editor. For this, I needed some data structures to
/// manage memory in the editor. Thus, Buffer was born.
///
/// \subsection finally Finally
///
/// I'd been writing Go professionally for a while, but C was my first love. I
/// recently started a job that is mostly in C++, and the best way for me to
/// learn is to build a bunch of stuff with it. So, I took a bunch of micro-
/// controller stuff I'd been writing and started building out some other stuff.
}

View File

@ -1,5 +1,5 @@
///
/// \file Test.h
/// \file Assert.h
/// \author K. Isom <kyle@imap.cc>
/// \date 2023-10-09
/// \brief Tooling to assist in building test programs..
@ -27,23 +27,23 @@
#include <string>
namespace scsl {
namespace sctest {
/// TestAssert is a variant on the assert macro. This variant is intended to be
/// Assert is a variant on the assert macro. This variant is intended to be
/// a drop-in replacement for the cassert macro: even in release mode, the tests
/// should still run.
///
/// If NDEBUG is set, TestAssert will throw an exception if condition is false.
/// If NDEBUG is set, Assert will throw an exception if condition is false.
/// Otherwise, it calls assert after printing the message.
///
/// \param condition If true, TestAssert throws an exception.
void TestAssert(bool condition);
/// \param condition If true, Assert throws an exception.
void Assert(bool condition);
/// TestAssert is a variant on the assert macro.
/// Assert is a variant on the assert macro.
///
/// If NDEBUG is set, TestAssert will throw an exception if condition is false.
/// If NDEBUG is set, Assert will throw an exception if condition is false.
/// Otherwise, it calls assert after printing the message.
///
/// In addition to NDEBUG, SCSL_NOEXCEPT will suppress assertions.
@ -52,7 +52,7 @@ void TestAssert(bool condition);
///
/// \param condition The condition to assert.
/// \param message The message that should be displayed if condition is false.
void TestAssert(bool condition, std::string message);
void Assert(bool condition, std::string message);
} // namespace scsl

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

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

View File

@ -1,5 +1,5 @@
///
/// \file Exceptions.h
/// \file include/sctest/Exceptions.h
/// \author K. Isom <kyle@imap.cc>
/// \date 2023-10-10
/// \brief Custom exceptions for use in SCSL used in writing test programs.
@ -19,6 +19,7 @@
/// TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
/// PERFORMANCE OF THIS SOFTWARE.
///
#ifndef SCSL_EXCEPTIONS_H
#define SCSL_EXCEPTIONS_H
@ -27,10 +28,10 @@
#include <string>
namespace scsl {
namespace sctest {
/// NotImplemented is an exception reserved for unsupported platforms.
/// \brief Exception reserved for unsupported platforms.
///
/// It is used to mark functionality included for compatibility, and useful for
/// debugging.
@ -63,7 +64,7 @@ private:
};
} // namespace scsl
} // namespace sctest
#endif //SCSL_EXCEPTIONS_H
#endif // SCSL_EXCEPTIONS_H

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

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

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

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

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

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

View File

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

View File

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

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

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

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

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

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

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

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

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

View File

@ -20,23 +20,18 @@
/// PERFORMANCE OF THIS SOFTWARE.
///
#include <cstdio>
#include <cstdlib>
#include <cstring>
#if defined(__posix__) || defined(__linux__) || defined(__APPLE__)
#include <sys/mman.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#define PROT_RW (PROT_WRITE|PROT_READ)
#endif
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <ios>
#include "Arena.h"
#include <scsl/Arena.h>
#define PROT_RW (PROT_WRITE|PROT_READ)
namespace scsl {
@ -74,16 +69,12 @@ Arena::SetAlloc(size_t allocSize)
this->arenaType = ArenaType::Alloc;
this->size = allocSize;
this->store = new uint8_t[allocSize];
if (this->store == nullptr) {
return -1;
}
this->Clear();
return 0;
}
#if defined(__posix__) || defined(__linux__) || defined(__APPLE__)
int
Arena::MemoryMap(int memFileDes, size_t memSize)
{
@ -93,9 +84,9 @@ Arena::MemoryMap(int memFileDes, size_t memSize)
this->arenaType = ArenaType::MemoryMapped;
this->size = memSize;
this->store = (uint8_t *) mmap(NULL, memSize, PROT_RW, MAP_SHARED,
memFileDes, 0);
if ((void *) this->store == MAP_FAILED) {
this->store = static_cast<uint8_t *>(mmap(nullptr, memSize, PROT_RW, MAP_SHARED,
memFileDes, 0));
if (static_cast<void *>(this->store) == MAP_FAILED) {
return -1;
}
this->fd = memFileDes;
@ -112,119 +103,51 @@ Arena::Open(const char *path)
this->Destroy();
}
if (stat(path, &st) != 0) {
return -1;
}
this->fd = open(path, O_RDWR);
if (this->fd == -1) {
return -1;
}
return this->MemoryMap(this->fd, (size_t) st.st_size);
if (stat(path, &st) != 0) {
return -1;
}
return this->MemoryMap(this->fd, static_cast<size_t>(st.st_size));
}
int
Arena::Create(const char *path, size_t fileSize)
{
FILE *fHandle = nullptr;
int newFileDes = 0;
int ret = -1;
if (this->size > 0) {
this->Destroy();
}
fHandle = fopen(path, "w");
if (fHandle == nullptr) {
return -1;
}
newFileDes = fileno(fHandle);
if (ftruncate(newFileDes, fileSize) == -1) {
return -1;
}
close(newFileDes);
return this->Open(path);
}
#elif defined(__WIN64__) || defined(__WIN32__) || defined(WIN32)
int
Arena::Open(const char *path)
{
HANDLE fHandle;
DWORD fRead = 0;
size_t fSize;
size_t fRemaining;
auto *cursor = this->store;
OVERLAPPED overlap = {0};
fHandle = CreateFileA(
(LPSTR) path,
GENERIC_READ,
(FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE),
NULL,
OPEN_ALWAYS,
FILE_ATTRIBUTE_NORMAL,
NULL);
if (fHandle == INVALID_HANDLE_VALUE) {
return Windows::DisplayWinError("CreateFileA", NULL);
}
if (SetFilePointer(fHandle, 0, 0, FILE_BEGIN) != 0) {
return Windows::DisplayWinError("SetFilePointer", fHandle);
}
if (GetFileSizeEx(fHandle, reinterpret_cast<PLARGE_INTEGER>(&fSize)) !=
TRUE) {
return Windows::DisplayWinError("GetFileSizeEx", fHandle);
}
this->SetAlloc(fSize);
cursor = this->NewCursor();
this->store[0] = 1;
fRemaining = fSize;
while (fRemaining != 0) {
overlap.Offset = (fSize - fRemaining);
if (ReadFile(fHandle, cursor, fSize - 1,
&fRead,
&overlap) != TRUE) {
auto errorCode = GetLastError();
if (errorCode != ERROR_HANDLE_EOF) {
this->Destroy();
return Windows::DisplayWinError("ReadFile", fHandle);
}
break;
auto *fHandle = fopen(path, "w");
if (fHandle != nullptr) {
auto newFileDes = fileno(fHandle);
if (ftruncate(newFileDes, static_cast<off_t>(fileSize)) == 0) {
ret = this->Open(path);
}
cursor += fRead;
fRemaining -= fRead;
close(newFileDes);
fclose(fHandle);
}
CloseHandle(fHandle);
return 0;
return ret;
}
int
Arena::Create(const char *path, size_t fileSize)
{
auto errorCode = Windows::CreateFixedSizeFile(path, fileSize);
if (errorCode != 0) {
return errorCode;
}
return this->Open(path);
}
#endif
bool
Arena::CursorInArena(const uint8_t *cursor)
{
if (cursor == nullptr) {
return false;
}
if (cursor < this->store) {
return false;
}
@ -261,28 +184,26 @@ Arena::Destroy()
}
switch (this->arenaType) {
case ArenaType::Static:
break;
case ArenaType::Alloc:
delete[] this->store;
break;
#if defined(__posix__) || defined(__linux__) || defined(__APPLE__)
case ArenaType::MemoryMapped:
if (munmap(this->store, this->size) == -1) {
abort();
return;
}
if (close(this->fd) == -1) {
abort();
}
this->fd = 0;
break;
#endif
default:
#if defined(NDEBUG)
case ArenaType::Static:
break;
case ArenaType::Alloc:
delete[] this->store;
break;
case ArenaType::MemoryMapped:
if (munmap(this->store, this->size) == -1) {
abort();
return;
}
if (close(this->fd) == -1) {
abort();
}
this->fd = 0;
break;
default:
#if defined(NDEBUG)
return;
#else
abort();
#endif
@ -292,16 +213,15 @@ Arena::Destroy()
this->arenaType = ArenaType::Uninit;
this->size = 0;
this->store = nullptr;
return;
}
std::ostream &
operator<<(std::ostream &os, Arena &arena)
{
auto cursor = arena.Start();
auto *cursor = arena.Start();
char cursorString[33] = {0};
snprintf(cursorString, 32, "%#016llx",
(long long unsigned int) cursor);
(long long unsigned int)(cursor));
os << "Arena<";
switch (arena.Type()) {
@ -314,16 +234,14 @@ operator<<(std::ostream &os, Arena &arena)
case ArenaType::Alloc:
os << "allocated";
break;
#if defined(__posix__) || defined(__linux__) || defined(__APPLE__)
case ArenaType::MemoryMapped:
os << "mmap/file";
break;
#endif
case ArenaType::MemoryMapped:
os << "mmap/file";
break;
default:
os << "unknown (this is a bug)";
}
os << ">@0x";
os << std::hex << (uintptr_t) &arena;
os << std::hex << static_cast<void *>(&arena);
os << std::dec;
os << ",store<" << arena.Size() << "B>@";
os << std::hex << cursorString;
@ -336,15 +254,10 @@ operator<<(std::ostream &os, Arena &arena)
int
Arena::Write(const char *path)
{
FILE *arenaFile = nullptr;
int retc = -1;
#if defined(__posix__) || defined(__linux__) || defined(__APPLE__)
arenaFile = fopen(path, "w");
FILE *arenaFile = fopen(path, "w");
if (arenaFile == nullptr) {
#else
if (fopen_s(&arenaFile, path, "w") != 0) {
#endif
return -1;
}

View File

@ -22,10 +22,12 @@
#include <cassert>
#include <cstring>
#include <iomanip>
#include <ios>
#include <iostream>
#include "Buffer.h"
#include <scsl/Buffer.h>
namespace scsl {
@ -81,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)
{
@ -98,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());
}
@ -107,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;
@ -139,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());
}
@ -190,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);
}
@ -217,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()
{
@ -305,7 +383,11 @@ Buffer::shiftRight(size_t offset, size_t delta)
resized = true;
}
if (this->length == 0) return 0;
if (this->length < offset) {
for (size_t i = this->length; i < offset; i++) {
this->contents[i] = ' ';
}
}
memmove(this->contents + (offset + delta), this->contents + offset,

View File

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

View File

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

View File

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

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

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

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

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

View File

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

View File

@ -23,14 +23,15 @@
#include <cassert>
#include <cstring>
#include "TLV.h"
#include <scsl/TLV.h>
using namespace scsl;
/// REC_SIZE calculates the total length of a TLV record, including the
/// two byte header.
#define REC_SIZE(x) ((std::size_t)x.Len + 2)
#define REC_SIZE(x) ((std::size_t)x.Len + 2)
namespace scsl {
@ -100,6 +101,10 @@ SetRecord(Record &rec, uint8_t tag, uint8_t len, const char *val)
void
ReadFromMemory(Record &rec, uint8_t *cursor)
{
assert(cursor != nullptr);
if (cursor == nullptr) {
return;
}
rec.Tag = cursor[0];
rec.Len = cursor[1];
memcpy(rec.Val, cursor + 2, rec.Len);
@ -116,10 +121,13 @@ FindTag(Arena &arena, uint8_t *cursor, Record &rec)
{
cursor = LocateTag(arena, cursor, rec);
if (rec.Tag != TAG_EMPTY) {
std::cout << "skipping record\n";
cursor = SkipRecord(rec, cursor);
}
if (!arena.CursorInArena(cursor)) {
cursor = nullptr;
}
return cursor;
}
@ -127,30 +135,32 @@ 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) {
std::cout << "move cursor to arena start\n";
cursor = arena.Start();
}
while ((tag = cursor[0]) != rec.Tag) {
if (!arena.CursorInArena(cursor)) {
cursor = arena.Start();
}
while (arena.CursorInArena(cursor) &&
((tag = cursor[0]) != rec.Tag)) {
assert(arena.CursorInArena(cursor));
std::cout << "cursor is in arena\n";
len = cursor[1];
std::cout << "record length" << len << "\n";
if (!spaceAvailable(arena, cursor, len)) {
std::cout << "no space available\n";
return nullptr;
}
cursor += len;
cursor += 2;
}
if (!arena.CursorInArena(cursor)) {
return nullptr;
}
if (tag != rec.Tag) {
return nullptr;
}
@ -190,7 +200,7 @@ DeleteRecord(Arena &arena, uint8_t *cursor)
return;
}
uint8_t len = cursor[1] + 2;
uint8_t len = cursor[1] + 2;
uint8_t *stop = arena.Start() + arena.Size();
stop -= len;

View File

@ -1,8 +1,8 @@
///
/// \file Test.cc
/// \file src/sctest/Assert.cc
/// \author K. Isom <kyle@imap.cc>
/// \date 2023-10-09
/// \brief Tooling to assist in building test programs..
/// \brief Assertion tooling useful in building test programs.
///
/// Copyright 2023 K. Isom <kyle@imap.cc>
///
@ -20,21 +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) {
@ -46,7 +47,7 @@ TestAssert(bool condition, std::string message)
void
TestAssert(bool condition)
Assert(bool condition)
{
#if defined(NDEBUG)
if (condition) {
@ -57,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
@ -66,4 +67,4 @@ TestAssert(bool condition)
}
} // namespace scsl
} // namespace sctest

View File

@ -1,5 +1,5 @@
///
/// \file Exceptions.cc
/// \file src/test/Exceptions.cc
/// \author K. Isom <kyle@imap.cc>
/// \date 2023-10-10
/// \brief Custom exceptions used in writing test programs.
@ -20,15 +20,14 @@
/// 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 *
AssertionFailed::what() const throw()
@ -37,6 +36,4 @@ AssertionFailed::what() const throw()
}
}
} // namespace sctest

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

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

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

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

View File

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

163
test/buffer.cc Normal file
View File

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

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

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

295
test/coord2d.cc Executable file
View File

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

142
test/dictionary.cc Normal file
View File

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

View File

@ -1,11 +1,34 @@
//
// Created by kyle on 10/15/23.
//
///
/// \file test/flags.cc
/// \author K.Isom <kyle@imap.cc>
/// \date 2017-06-05
/// \brief Demonstration of the scsl::Flags class.
///
/// \note This isn't a test program, and won't be run as part of a
/// normal ctest run. It's meant to show how flags work in
/// practice.
///
/// \section COPYRIGHT
///
/// Copyright 2017 K. Isom <kyle@imap.cc>
///
/// Permission to use, copy, modify, and/or distribute this software for
/// any purpose with or without fee is hereby granted, provided that
/// the above copyright notice and this permission notice appear in all /// copies.
///
/// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
/// WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
/// WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
/// AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
/// DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA
/// OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
/// TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
/// PERFORMANCE OF THIS SOFTWARE.
///
#include <iostream>
#include "Flag.h"
#include "Test.h"
#include <scsl/Flags.h>
#include <sctest/Assert.h>
using namespace scsl;
@ -26,7 +49,7 @@ main(int argc, char *argv[])
flags->Register("-u", (unsigned int)42, "test unsigned integer with a long description line. This should trigger multiline text-wrapping.");
flags->Register("-i", -42, "test integer");
flags->Register("-size", FlagType::SizeT, "test size_t");
TestAssert(flags->Size() == 5, "flags weren't registered");
sctest::Assert(flags->Size() == 5, "flags weren't registered");
auto status = flags->Parse(argc, argv);

279
test/madgwick.cc Normal file
View File

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

186
test/math.cc Normal file
View File

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

123
test/orientation.cc Normal file
View File

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

493
test/quaternion.cc Normal file
View File

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

89
test/simple_suite_example.cc Executable file
View File

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

182
test/stringutil.cc Normal file
View File

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

View File

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

172
test/tlv.cc Normal file
View File

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

514
test/vector.cc Normal file
View File

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

View File

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

446
trunk Executable file
View File

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