aboutsummaryrefslogtreecommitdiff
path: root/comp6771/2
diff options
context:
space:
mode:
authorNicolas James <Eele1Ephe7uZahRie@tutanota.com>2025-02-13 18:00:17 +1100
committerNicolas James <Eele1Ephe7uZahRie@tutanota.com>2025-02-13 18:00:17 +1100
commit98cef5e9a772602d42acfcf233838c760424db9a (patch)
tree5277fa1d7cc0a69a0f166fcbf10fd320f345f049 /comp6771/2
initial commit
Diffstat (limited to 'comp6771/2')
-rw-r--r--comp6771/2/.clang-format103
-rw-r--r--comp6771/2/.gitignore16
-rw-r--r--comp6771/2/CMakeLists.txt71
-rw-r--r--comp6771/2/src/filtered_string_view.cpp277
-rw-r--r--comp6771/2/src/filtered_string_view.h104
-rw-r--r--comp6771/2/src/filtered_string_view.test.cpp559
6 files changed, 1130 insertions, 0 deletions
diff --git a/comp6771/2/.clang-format b/comp6771/2/.clang-format
new file mode 100644
index 0000000..d1555d3
--- /dev/null
+++ b/comp6771/2/.clang-format
@@ -0,0 +1,103 @@
+Language: Cpp
+Standard: Cpp11
+AccessModifierOffset: -3
+AlignAfterOpenBracket: Align
+AlignConsecutiveAssignments: false
+AlignConsecutiveDeclarations: false
+AlignConsecutiveMacros: false
+AlignEscapedNewlines: Right
+AlignOperands: true
+AlignTrailingComments: false
+AllowAllArgumentsOnNextLine: false
+AllowAllConstructorInitializersOnNextLine: false
+AllowAllParametersOfDeclarationOnNextLine: false
+AllowShortBlocksOnASingleLine: false
+AllowShortCaseLabelsOnASingleLine: true
+AllowShortFunctionsOnASingleLine: Empty
+AllowShortIfStatementsOnASingleLine: false
+AllowShortLambdasOnASingleLine: All
+AllowShortLoopsOnASingleLine: false
+AlwaysBreakAfterReturnType: None
+AlwaysBreakBeforeMultilineStrings: false
+AlwaysBreakTemplateDeclarations: Yes
+BinPackArguments: false
+BinPackParameters: false
+BreakBeforeBinaryOperators: NonAssignment
+BreakBeforeBraces: Custom
+BraceWrapping:
+ AfterCaseLabel: false
+ AfterClass: false
+ AfterControlStatement: MultiLine
+ AfterEnum: false
+ AfterFunction: false
+ AfterNamespace: false
+ AfterStruct: false
+ AfterUnion: false
+ AfterExternBlock: false
+ BeforeCatch: false
+ BeforeElse: true
+ BeforeLambdaBody: false
+ IndentBraces: false
+ SplitEmptyFunction: false
+ SplitEmptyRecord: false
+ SplitEmptyNamespace: false
+BreakBeforeTernaryOperators: true
+BreakConstructorInitializers: BeforeComma
+BreakInheritanceList: BeforeComma
+BreakStringLiterals: true
+ColumnLimit: 120
+CompactNamespaces: true
+ConstructorInitializerAllOnOneLineOrOnePerLine: false
+ConstructorInitializerIndentWidth: 0
+ContinuationIndentWidth: 4
+Cpp11BracedListStyle: true
+FixNamespaceComments: true
+IncludeBlocks: Preserve
+IncludeCategories:
+ - Regex: "^<[[:alnum:].]+>"
+ Priority: 2
+ - Regex: '^"[[:alnum:].]"'
+ Priority: 1
+IndentCaseBlocks: false
+IndentCaseLabels: false
+IndentGotoLabels: false
+IndentPPDirectives: AfterHash
+IndentWidth: 4
+IndentWrappedFunctionNames: false
+KeepEmptyLinesAtTheStartOfBlocks: false
+MaxEmptyLinesToKeep: 1
+NamespaceIndentation: All
+PenaltyBreakAssignment: 10
+PenaltyBreakBeforeFirstCallParameter: 10
+PenaltyBreakComment: 10
+PenaltyBreakFirstLessLess: 10
+PenaltyBreakString: 10
+PenaltyBreakTemplateDeclaration: 10
+PenaltyExcessCharacter: 10
+PenaltyReturnTypeOnItsOwnLine: 10
+PointerAlignment: Left
+ReflowComments: true
+SortIncludes: true
+SortUsingDeclarations: true
+SpaceAfterCStyleCast: false
+SpaceAfterLogicalNot: false
+SpaceAfterTemplateKeyword: false
+SpaceBeforeAssignmentOperators: true
+SpaceBeforeCpp11BracedList: false
+SpaceBeforeCtorInitializerColon: true
+SpaceBeforeInheritanceColon: true
+SpaceBeforeParens: ControlStatements
+SpaceBeforeRangeBasedForLoopColon: true
+SpaceBeforeSquareBrackets: false
+SpaceInEmptyBlock: false
+SpaceInEmptyParentheses: false
+SpacesBeforeTrailingComments: 1
+SpacesInAngles: false
+SpacesInCStyleCastParentheses: false
+SpacesInConditionalStatement: false
+SpacesInContainerLiterals: false
+SpacesInParentheses: false
+SpacesInSquareBrackets: false
+TabWidth: 4
+UseCRLF: false
+UseTab: ForIndentation \ No newline at end of file
diff --git a/comp6771/2/.gitignore b/comp6771/2/.gitignore
new file mode 100644
index 0000000..61b8ef7
--- /dev/null
+++ b/comp6771/2/.gitignore
@@ -0,0 +1,16 @@
+build/
+build_release/
+Testing/
+.cache/
+.clangd/
+.vs/
+.DS_Store
+CMakeFiles
+CMakeCache.txt
+CTestTestfile.cmake
+Makefile
+cmake_install.cmake
+compile_commands.json
+filtered_string_view_test
+libcatch2_main.a
+libfiltered_string_view.a
diff --git a/comp6771/2/CMakeLists.txt b/comp6771/2/CMakeLists.txt
new file mode 100644
index 0000000..aab06d3
--- /dev/null
+++ b/comp6771/2/CMakeLists.txt
@@ -0,0 +1,71 @@
+# ------------------------------------------------------------ #
+# -------------- DO NOT TOUCH BELOW THIS LINE ---------------- #
+# ------------------------------------------------------------ #
+
+# Make sure checks are configured
+execute_process(COMMAND bash util/setup.sh)
+
+# this must be the first line of a CMake script.
+# sets the lowerbound on what CMake version can be used.
+cmake_minimum_required(VERSION 3.0...3.5)
+
+# the name of this CMake project and what language it uses
+# we could list more languages if we were using more.
+project(COMP6771_LAB_001 LANGUAGES CXX)
+
+# we use C++20
+set(CMAKE_CXX_STANDARD 20)
+set(CMAKE_CXX_STANDARD_REQUIRED YES)
+set(CMAKE_CXX_EXTENSIONS NO)
+
+# this is helpful for editors like VS Code
+set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
+
+# helpful compiler flags for gcc/clang
+# the descriptions for these flags can be found on the GNU Compiler Collection's webpage.
+add_compile_options(
+ -Wall
+ -Wextra
+ -Werror
+ -pedantic-errors
+ -Wconversion
+ -Wsign-conversion
+ -Wdouble-promotion
+ -Wcast-align
+ -Wformat=2
+ -Wuninitialized
+ -Wnull-dereference
+ -Wnon-virtual-dtor
+ -Woverloaded-virtual
+ -Wdeprecated-copy-dtor
+ -Wold-style-cast
+ -Wzero-as-null-pointer-constant
+ -Wsuggest-override
+ -fstack-protector-strong
+)
+
+# debug builds should be compiled with sanitizers
+# sanitizers are small libraries that check things like buffer overrun with minimal runtime overhead.
+set(CMAKE_CXX_FLAGS_DEBUG_INIT "-fsanitize=address,undefined")
+set(CMAKE_CXX_FLAGS_RELWITHDEBINFO_INIT "-fsanitize=address,undefined")
+set(CMAKE_CXX_EXE_LINKER_FLAGS_DEBUG_INIT "-fsanitize=address,undefined")
+set(CMAKE_CXX_EXE_LINKER_FLAGS_RELWITHDEBINFO_INIT "-fsanitize=address,undefined")
+
+
+# add the testing library Catch2
+enable_testing()
+add_library(catch2_main lib/catch2_main.cpp)
+target_include_directories(catch2_main PUBLIC lib)
+# link the library so that other programs can get it
+link_libraries(catch2_main)
+
+# ------------------------------------------------------------ #
+# -------------- DO NOT MODIFY ABOVE THIS LINE --------------- #
+# ------------------------------------------------------------ #
+
+add_library(filtered_string_view src/filtered_string_view.h src/filtered_string_view.cpp)
+link_libraries(filtered_string_view)
+
+add_executable(filtered_string_view_test src/filtered_string_view.test.cpp)
+add_test(filtered_string_view_test filtered_string_view_test)
+
diff --git a/comp6771/2/src/filtered_string_view.cpp b/comp6771/2/src/filtered_string_view.cpp
new file mode 100644
index 0000000..dcf9d8d
--- /dev/null
+++ b/comp6771/2/src/filtered_string_view.cpp
@@ -0,0 +1,277 @@
+#include "./filtered_string_view.h"
+
+namespace fsv {
+ // Iterator implementation.
+ auto filtered_string_view::begin() const noexcept -> iterator {
+ // Our iterator might have to be incremented so that it's pointing to
+ // the first valid.
+ auto it = iterator{this->string, this->length, this->pred, this->string};
+ if (not std::invoke(this->pred, *it)) {
+ ++it;
+ }
+ return it;
+ }
+
+ auto filtered_string_view::end() const noexcept -> iterator {
+ return iterator{this->string, this->length, this->pred, this->string + this->length};
+ }
+
+ auto filtered_string_view::cbegin() const noexcept -> const_iterator {
+ return std::begin(*this);
+ }
+
+ auto filtered_string_view::cend() const noexcept -> const_iterator {
+ return std::end(*this);
+ }
+
+ auto filtered_string_view::rbegin() const noexcept -> reverse_iterator {
+ return std::reverse_iterator<iterator>(std::end(*this));
+ }
+
+ auto filtered_string_view::rend() const noexcept -> reverse_iterator {
+ return std::reverse_iterator<iterator>(std::begin(*this));
+ }
+
+ auto filtered_string_view::crbegin() const noexcept -> const_reverse_iterator {
+ return std::rbegin(*this);
+ }
+
+ auto filtered_string_view::crend() const noexcept -> const_reverse_iterator {
+ return std::rend(*this);
+ }
+
+ filtered_string_view::iterator::iterator(const value_type* const& str,
+ const std::size_t& len,
+ const filter& pred,
+ const value_type* const& i) noexcept
+ : string(str)
+ , length(&len)
+ , predicate(&pred)
+ , it(i) {}
+
+ auto filtered_string_view::iterator::operator*() const noexcept -> reference {
+ return *this->it;
+ }
+ auto filtered_string_view::iterator::operator->() const noexcept -> reference {
+ return this->operator*();
+ }
+
+ auto filtered_string_view::iterator::operator++() noexcept -> iterator& {
+ do {
+ ++this->it;
+ if (not std::invoke(*this->predicate, *this->it)) {
+ continue;
+ }
+ break;
+ } while (this->it != this->string + *this->length);
+
+ return *this;
+ }
+ auto filtered_string_view::iterator::operator++(int) noexcept -> iterator {
+ iterator ret = *this;
+ ++(*this);
+ return ret;
+ }
+ auto filtered_string_view::iterator::operator--() noexcept -> iterator& {
+ do {
+ --this->it;
+ if (not std::invoke(*this->predicate, *this->it)) {
+ continue;
+ }
+ break;
+ } while (this->it != this->string);
+
+ return *this;
+ }
+ auto filtered_string_view::iterator::operator--(int) noexcept -> iterator {
+ iterator ret = *this;
+ --(*this);
+ return ret;
+ }
+
+ auto operator==(const filtered_string_view::iterator& a, const filtered_string_view::iterator& b) noexcept -> bool {
+ return a.it == b.it;
+ }
+
+ auto operator!=(const filtered_string_view::iterator& a, const filtered_string_view::iterator& b) noexcept -> bool {
+ return not(a == b);
+ }
+
+ // Class methods implementation.
+ filtered_string_view::filtered_string_view() noexcept
+ : string(nullptr)
+ , length(0)
+ , pred(default_predicate) {}
+
+ filtered_string_view::filtered_string_view(const std::string& str) noexcept
+ : string(std::data(str))
+ , length(std::size(str))
+ , pred(default_predicate) {}
+
+ filtered_string_view::filtered_string_view(const std::string& str, filter pred) noexcept
+ : string(std::data(str))
+ , length(std::size(str))
+ , pred(pred) {}
+
+ filtered_string_view::filtered_string_view(const char* str) noexcept
+ : string(str)
+ , length(std::strlen(str))
+ , pred(default_predicate) {}
+
+ filtered_string_view::filtered_string_view(const char* str, filter pred) noexcept
+ : string(str)
+ , length(std::strlen(str))
+ , pred(pred) {}
+
+ filtered_string_view::filtered_string_view(const filtered_string_view& other) noexcept
+ : string(other.string)
+ , length(other.length)
+ , pred(other.pred) {}
+
+ filtered_string_view::filtered_string_view(filtered_string_view&& other) noexcept
+ : string(std::exchange(other.string, nullptr))
+ , length(std::exchange(other.length, 0))
+ , pred(std::exchange(other.pred, default_predicate)) {}
+
+ auto filtered_string_view::operator=(const filtered_string_view& other) noexcept -> filtered_string_view& {
+ if (&other != this) { // self-copied object should remain unchanged.
+ this->string = other.string;
+ this->length = other.length;
+ this->pred = other.pred;
+ }
+ return *this;
+ }
+
+ auto filtered_string_view::operator=(filtered_string_view&& other) noexcept -> filtered_string_view& {
+ if (&other != this) { // self-copied object should remain unchanged.
+ this->string = std::exchange(other.string, nullptr);
+ this->length = std::exchange(other.length, 0);
+ this->pred = std::exchange(other.pred, default_predicate);
+ }
+ return *this;
+ }
+
+ auto filtered_string_view::operator[](const std::size_t& n) const noexcept -> const char& {
+ return *std::next(std::begin(*this), n);
+ }
+ auto filtered_string_view::operator[](const int& n) const noexcept -> const char& {
+ return (*this)[static_cast<std::size_t>(n)];
+ }
+
+ filtered_string_view::operator std::string() const {
+ auto result = std::string{};
+ std::copy(std::begin(*this), std::end(*this), std::back_inserter(result));
+ return result;
+ }
+
+ auto filtered_string_view::at(const int index) const -> const char& {
+ const auto throw_index = [&]() {
+ throw std::domain_error("filtered_string_view::at(" + std::to_string(index) + "): invalid index");
+ };
+
+ if (index < 0) {
+ throw_index();
+ }
+
+ auto it = std::begin(*this);
+ for (auto i = 0; i < index; ++i) {
+ ++it;
+ if (it == std::end(*this)) {
+ throw_index();
+ }
+ }
+ return *it;
+ }
+
+ auto filtered_string_view::size() const noexcept -> std::size_t {
+ return std::distance(std::begin(*this), std::end(*this));
+ }
+
+ auto filtered_string_view::empty() const noexcept -> bool {
+ return std::size(*this) == 0;
+ }
+
+ auto filtered_string_view::data() const noexcept -> const char* {
+ return this->string;
+ }
+
+ auto filtered_string_view::predicate() const noexcept -> const filter& {
+ return this->pred;
+ }
+
+ auto operator==(const filtered_string_view& a, const filtered_string_view& b) -> bool {
+ return std::string{a} == std::string{b};
+ }
+
+ auto operator<=>(const filtered_string_view& a, const filtered_string_view& b) -> std::strong_ordering {
+ return std::string{a} <=> std::string{b};
+ }
+
+ auto operator<<(std::ostream& os, const filtered_string_view& fsv) -> std::ostream& {
+ os << std::string{fsv};
+ return os;
+ }
+
+ auto filtered_string_view::default_predicate(const char&) noexcept -> bool {
+ return true;
+ }
+
+ auto compose(const filtered_string_view& fsv, const std::vector<filter>& filts) -> filtered_string_view {
+ return filtered_string_view{std::data(fsv), [&](const auto& c) {
+ return std::all_of(std::begin(filts), std::end(filts), [&](const auto& func) {
+ return std::invoke(func, c);
+ });
+ }};
+ }
+
+ auto split(const filtered_string_view& fsv, const filtered_string_view& tok) -> std::vector<filtered_string_view> {
+ std::vector<filtered_string_view> result{};
+
+ const auto origin = std::string{fsv};
+ const auto target = std::string{tok};
+ const auto skip = std::size(target);
+
+ for (auto a = 0ul, b = origin.find(target, a); a != std::string::npos; a = b + skip, b = origin.find(target, a))
+ {
+ if (b == std::string::npos) {
+ result.push_back(fsv::substr(fsv, static_cast<int>(a)));
+ break;
+ }
+
+ if (a == b) {
+ result.push_back("");
+ continue;
+ }
+
+ result.push_back(fsv::substr(fsv, static_cast<int>(a), static_cast<int>(b - a)));
+ }
+
+ return result;
+ }
+
+ auto substr(const filtered_string_view& fsv, const int pos, const int count) -> filtered_string_view {
+ const auto predicate = fsv.predicate();
+
+ const auto rcount = count <= 0 ? static_cast<int>(std::size(fsv)) - pos : count;
+
+ if (rcount == 0 or std::begin(fsv) == std::end(fsv)) {
+ return filtered_string_view{};
+ }
+
+ const auto start =
+ std::next(std::begin(fsv), static_cast<fsv::filtered_string_view::iterator::difference_type>(pos));
+
+ if (start == std::end(fsv)) {
+ return filtered_string_view{};
+ }
+
+ const auto end = std::next(start, static_cast<fsv::filtered_string_view::iterator::difference_type>(rcount - 1));
+
+ return filtered_string_view{std::data(fsv), [=](const auto& c) {
+ if (not std::invoke(predicate, c)) {
+ return false;
+ }
+ return &c >= &*start and &c <= &*end;
+ }};
+ } // namespace fsv
+} // namespace fsv
diff --git a/comp6771/2/src/filtered_string_view.h b/comp6771/2/src/filtered_string_view.h
new file mode 100644
index 0000000..9142f84
--- /dev/null
+++ b/comp6771/2/src/filtered_string_view.h
@@ -0,0 +1,104 @@
+#ifndef COMP6771_ASS2_FSV_H
+#define COMP6771_ASS2_FSV_H
+
+#include <algorithm>
+#include <compare>
+#include <cstring>
+#include <functional>
+#include <iterator>
+#include <string>
+#include <utility>
+#include <vector>
+
+namespace fsv {
+ using filter = std::function<bool(const char&)>;
+
+ class filtered_string_view {
+ private: // Private data members.
+ const char* string;
+ std::size_t length;
+ filter pred;
+
+ public: // Iterator definition.
+ class iterator {
+ public:
+ using difference_type = std::size_t;
+ using value_type = char;
+ using pointer = void;
+ using reference = const char&;
+ using iterator_category = std::bidirectional_iterator_tag;
+
+ private:
+ const value_type* const string = nullptr;
+ const std::size_t* const length = nullptr;
+ const filter* const predicate = nullptr;
+ const value_type* it = nullptr;
+
+ public:
+ iterator() noexcept = default;
+ iterator(const value_type* const& str,
+ const std::size_t& len,
+ const filter& pred,
+ const value_type* const& it) noexcept;
+
+ auto operator*() const noexcept -> reference;
+ auto operator->() const noexcept -> reference;
+ auto operator++() noexcept -> iterator&;
+ auto operator++(int) noexcept -> iterator;
+ auto operator--() noexcept -> iterator&;
+ auto operator--(int) noexcept -> iterator;
+
+ friend auto operator==(const iterator& a, const iterator& b) noexcept -> bool;
+ friend auto operator!=(const iterator& a, const iterator& b) noexcept -> bool;
+ };
+ using const_iterator = iterator;
+ using reverse_iterator = std::reverse_iterator<iterator>;
+ using const_reverse_iterator = reverse_iterator;
+
+ iterator begin() const noexcept;
+ iterator end() const noexcept;
+ const_iterator cbegin() const noexcept;
+ const_iterator cend() const noexcept;
+ reverse_iterator rbegin() const noexcept;
+ reverse_iterator rend() const noexcept;
+ const_reverse_iterator crbegin() const noexcept;
+ const_reverse_iterator crend() const noexcept;
+
+ public: // Public functions.
+ filtered_string_view() noexcept;
+ filtered_string_view(const std::string& str) noexcept;
+ explicit filtered_string_view(const std::string& str, filter pred) noexcept;
+ filtered_string_view(const char* str) noexcept;
+ explicit filtered_string_view(const char* str, filter pred) noexcept;
+ filtered_string_view(const filtered_string_view& other) noexcept;
+ filtered_string_view(filtered_string_view&& other) noexcept;
+ ~filtered_string_view() noexcept = default;
+
+ auto operator=(const filtered_string_view& other) noexcept -> filtered_string_view&;
+ auto operator=(filtered_string_view&& other) noexcept -> filtered_string_view&;
+
+ auto operator[](const std::size_t& n) const noexcept -> const char&;
+ auto operator[](const int& n) const noexcept -> const char&;
+
+ auto at(const int index) const -> const char&;
+ auto size() const noexcept -> std::size_t;
+ auto empty() const noexcept -> bool;
+ auto data() const noexcept -> const char*;
+ auto predicate() const noexcept -> const filter&;
+
+ friend auto operator==(const filtered_string_view& a, const filtered_string_view& b) -> bool;
+ friend auto operator<=>(const filtered_string_view& a, const filtered_string_view& b) -> std::strong_ordering;
+ friend auto operator<<(std::ostream& os, const filtered_string_view& fsv) -> std::ostream&;
+
+ explicit operator std::string() const;
+
+ static auto default_predicate(const char&) noexcept -> bool;
+ };
+
+ auto compose(const filtered_string_view& fsv, const std::vector<filter>& filts) -> filtered_string_view;
+ auto split(const filtered_string_view& fsv, const filtered_string_view& tok) -> std::vector<filtered_string_view>;
+ auto substr(const filtered_string_view& fsv, const int pos = 0, const int count = 0) -> filtered_string_view;
+
+} // namespace fsv
+
+#endif // COMP6771_ASS2_FSV_H
diff --git a/comp6771/2/src/filtered_string_view.test.cpp b/comp6771/2/src/filtered_string_view.test.cpp
new file mode 100644
index 0000000..163c9ab
--- /dev/null
+++ b/comp6771/2/src/filtered_string_view.test.cpp
@@ -0,0 +1,559 @@
+#include "./filtered_string_view.h"
+
+#include <catch2/catch.hpp>
+
+// Specification: 2.3 - Static Data Members
+// Purpose: Ensure that the default predicate function returns true for any
+// character passed in.
+// Expected: The function should always return true for all possible arguments.
+// Note: Taken directly from the specification, modified for catch2.
+TEST_CASE("default predicate function always returns true") {
+ const auto min = std::numeric_limits<char>::min();
+ const auto max = std::numeric_limits<char>::max();
+ for (char c = min; c != max; c++) {
+ REQUIRE(fsv::filtered_string_view::default_predicate(c));
+ }
+}
+
+// Specification: 2.4.1 - Default Constructor
+// Purpose: Determine that the default constructor results in the equivalent
+// of an empty string.
+// Expected: The view should compare equal to an empty string with a zero
+// length. Data should be set to nullptr.
+// Note: We cannot test where the underlying predicate points to directly as
+// this would make the test brittle by introducing implementation details.
+// We may only test this implicitly via string comparisons.
+TEST_CASE("default constructed is equivalent to an empty string") {
+ const auto view = fsv::filtered_string_view{};
+ REQUIRE(std::string{""} == view);
+ REQUIRE(std::size(view) == 0);
+ REQUIRE(std::data(view) == nullptr);
+}
+
+// Specification: 2.4.2 - Implicit String Constructor
+// Purpose: Ensure the implicit string constructor does not modify the string it
+// originally refers to or filter the data. It has not been provided
+// with an alternative predicate, so it should perform no filtering.
+// Expected: The view should be equivalent to the parent string. Data should
+// point directly to the parent string. The length should be equal to the parent
+// string.
+TEST_CASE("implicit string constructor is equivalent to parent string") {
+ const auto parent = std::string{"parent string"};
+ const auto view = fsv::filtered_string_view{parent};
+
+ REQUIRE(parent == view);
+ REQUIRE(std::data(parent) == std::data(view));
+ REQUIRE(std::size(parent) == std::size(view));
+};
+
+// Specification: 2.4.3 - String Constructor with Predicate
+// Purpose: The string constructor with a supplied predicate should filter the
+// original string by the rules provided by the predicate.
+// Expected: The view should NOT be equivalent to the parent string. The lengths
+// should not be identical. This is provided that the predicate
+// filters part of the original string.
+TEST_CASE("string constructor with predicate filters parent string") {
+ const auto parent = std::string{"parent string"};
+ const auto view = fsv::filtered_string_view{parent, [](const char& c) { return c != 's'; }};
+ REQUIRE(view == std::string{"parent tring"});
+ REQUIRE(std::data(parent) == std::data(view));
+ REQUIRE(std::size(parent) != std::size(view));
+};
+
+// Specification: 2.4.4 - Implicit Null-Terminated String Constructor
+// Purpose: The implicit null terminated string constructor should perform
+// identically to the standard string constructor.
+// Expected: The view should be equivalent to the parent string in data, length
+// and equality.
+TEST_CASE("implicit null terminated string does not filter parent string") {
+ const auto parent = std::string{"parent string"};
+ const auto view = fsv::filtered_string_view{parent.c_str()};
+ REQUIRE(view == parent);
+ REQUIRE(std::data(parent) == std::data(view));
+ REQUIRE(std::size(parent) == std::size(view));
+};
+
+// Specification: 2.4.5 - Null-Terminated String with Predicate Constructor
+// Purpose: The null terminated string with predicate constructor should operate
+// identically to the standard string constructor with a predicate.
+// Given a predicate, it should filter the string via that rule.
+// Expected: The view should not be equivalent to the parent string. Data should
+// remain the same, while size should be different.
+TEST_CASE("null-terminated string with predicate constructor filters the parent string") {
+ const auto parent = std::string{"parent string"};
+ const auto view = fsv::filtered_string_view{parent.c_str(), [](const char& c) { return c != 's'; }};
+ REQUIRE(view == std::string{"parent tring"});
+ REQUIRE(std::data(parent) == std::data(view));
+ REQUIRE(std::size(parent) != std::size(view));
+}
+
+// Specification: 2.4.6 - Copy and Move Constructors
+// Purpose: Copy constructors function as expected. In addition, the copy
+// constructor has an equality constraint.
+// Expected: The newly constructed object must compare equal to the copied
+// object.
+// Note: This test was taken from the specification, modified for catch2. We
+// split the test into two for readability (the move constructor is
+// separate), and include additional length and non std::data equality
+// tests.
+TEST_CASE("copy constructors compare equal") {
+ const auto parent = fsv::filtered_string_view{"parent string"};
+ const auto copy = parent;
+
+ REQUIRE(std::data(copy) == std::data(parent));
+ REQUIRE(copy == parent);
+ REQUIRE(std::size(copy) == std::size(parent));
+}
+
+// Specification: 2.4.6 - Copy and Move Constructors
+// Purpose: Copy and move constructors function as expected. In addition, the
+// copy and move constructors have a number of constraints supplied by
+// the specification.
+// Expected: The move constructor should default construct the moved-from object,
+// while the new object should be identical to the original object.
+TEST_CASE("move constructor is identical to moved from object before move") {
+ const auto parent = fsv::filtered_string_view{"parent string"};
+ auto copy = parent;
+
+ const auto move = std::move(copy);
+ REQUIRE(std::data(copy) == nullptr);
+ REQUIRE(copy == fsv::filtered_string_view{});
+}
+
+// Specification: 2.5.2 - Copy Assignment
+// Purpose: Copy assignment should copy at least the length, data and predicate
+// of other such that the they compare equal via operator==.
+// Expected: The view targeted by the copy assignment should compare equal to
+// the parent view.
+// Notes: Copied from the specification, modified for catch2.
+TEST_CASE("copy assignment passes equality comparison to parent object") {
+ const auto parent = fsv::filtered_string_view{"42 bro", [](const char& c) { return c == '4' || c == '2'; }};
+ auto copy = fsv::filtered_string_view{};
+ copy = parent;
+ REQUIRE(copy == parent);
+}
+
+// Specification: 2.5.2 - Copy Assignment
+// Purpose: Copy assignment should not modify itself when self copied.
+// Expected: Copy remains identical despite a self-copy.
+TEST_CASE("copy assignment should not change object if self copied") {
+ auto parent = fsv::filtered_string_view{"42 bro", [](const char& c) { return c == '4' || c == '2'; }};
+ const auto copy = parent; // We copy here so we keep the previous information.
+ parent = parent;
+ REQUIRE(copy == parent);
+}
+
+// Specification 2.5.3 - Move Assignment
+// Purpose: A moved from object should be left in a valid state equal to a
+// default constructed view. In addition, the move should move the
+// contents of the old view to the new view.
+// Expected: The moved from object is default constructed, while the new object
+// should be identical to the parent view.
+TEST_CASE("move assignment should transfer contents and default construct moved") {
+ auto parent = fsv::filtered_string_view{"'89 baby", [](const char& c) { return c == '8' || c == '9'; }};
+
+ // Copy parent before the move so we can check the move consists of this.
+ const auto copy = parent;
+
+ auto move = fsv::filtered_string_view{};
+ move = std::move(parent);
+ REQUIRE(std::data(move) == std::data(copy));
+ REQUIRE(move == copy);
+ REQUIRE(std::size(move) == std::size(copy));
+
+ // Ensure default construction.
+ REQUIRE(std::size(parent) == 0);
+ REQUIRE(std::data(parent) == nullptr);
+}
+
+// Specification 2.5.3 - Move Assignment
+// Purpose: Move assignment should not modify itself when self moved.
+// Expected: Move remains identical despite a self-move.
+TEST_CASE("move assignment should not change object if self copied") {
+ auto parent = fsv::filtered_string_view{"42 bro", [](const char& c) { return c == '4' || c == '2'; }};
+ const auto copy = parent; // We copy here so we keep the previous information.
+ parent = std::move(parent);
+ REQUIRE(copy == parent);
+}
+
+// Specification 2.5.4 - Subscript
+// Purpose: Test if we can read the string given a specific subscript. We supply
+// a predicate which filters some data so we can read into a view.
+// Expected: The subscript is equal to the expected characters, filtering out
+// those defined by the predicate.
+TEST_CASE("subscript should be affected by view predicate") {
+ const auto parent = fsv::filtered_string_view("123", [](const char& c) { return c != '2'; });
+ REQUIRE(parent[0] == '1');
+ REQUIRE(parent[1] == '3');
+}
+
+// Specification 2.5.5 - String Type Conversion
+// Purpose: We should be able to convert a view back into a string. Filtered out
+// data should be removed, as if we were looking at the view.
+// Expected: The view converted into a string should return a string affected by
+// the predicate.
+TEST_CASE("string conversion should be affected by view predicate") {
+ const auto parent = fsv::filtered_string_view{"123", [](const char& c) { return c != '2'; }};
+ REQUIRE(static_cast<std::string>(parent) == "13");
+}
+
+// Specification 2.5.5 - String Type Conversion
+// Purpose: A string conversion should be a copy of the underlying data.
+// Expected: The data pointer to the view should be different to the new string.
+TEST_CASE("string conversion should return a new string") {
+ const auto parent = fsv::filtered_string_view{"123", [](const char& c) { return c != '2'; }};
+ const auto s = static_cast<std::string>(parent);
+ REQUIRE(std::data(s) != std::data(parent));
+}
+
+// Specification: 2.6.1 - At
+// Purpose: At performs bounds checking of a filtered string. We ensure at performs
+// identically to subscript operators, unless we read out of bounds, in
+// which case it throws.
+// Expected: The view should return expected at results, similar to subscript.
+TEST_CASE("at should be affected by view predicate") {
+ const auto parent = fsv::filtered_string_view{"123", [](const char& c) { return c != '2'; }};
+ REQUIRE(parent.at(0) == '1');
+ REQUIRE(parent.at(1) == '3');
+}
+
+// Specification: 2.6.1 - At
+// Purpose: At should throw on an out of bounds read, negative or positive, with
+// a specific message and std::domain_error.
+// Expected: The view should throw if we read outside of size().
+TEST_CASE("at should throw on out of bounds reads") {
+ const auto parent = fsv::filtered_string_view{"123", [](const char& c) { return c != '2'; }};
+ REQUIRE_THROWS_AS(parent.at(-1), std::domain_error);
+ REQUIRE_THROWS_WITH(parent.at(-1), "filtered_string_view::at(-1): invalid index");
+ REQUIRE_THROWS_WITH(parent.at(2), "filtered_string_view::at(2): invalid index");
+ REQUIRE_THROWS_AS(parent.at(2), std::domain_error);
+}
+
+// Specification: 2.6.2 - Size
+// Purpose: Size should be equal to the size of the parent string with no pred.
+// Expected: The string size should be equal to the view size with no predicate.
+TEST_CASE("size should be equal to the parent string with no predicate") {
+ const auto parent = fsv::filtered_string_view{"Maltese"};
+ REQUIRE(std::size(parent) == 7);
+}
+
+// Specification: 2.6.2 - Size
+// Purpose: Size should be equal to the size of the filtered string.
+// Expected: The string size should be equal to the amount of times the predicate
+// returned true for that parent string.
+TEST_CASE("size should be affected by view predicate") {
+ const auto parent = fsv::filtered_string_view{"Maltese", [](const char& c) { return c == 'a' or c == 'e'; }};
+ REQUIRE(std::size(parent) == 3);
+}
+
+// Specification: 2.6.3 - Empty
+// Purpose: Empty should return true when the size of the view is zero.
+// Expected: An empty view should return true. A non-empty view should return
+// false.
+TEST_CASE("empty should return true when size is zero, and false when nonzero") {
+ REQUIRE(fsv::filtered_string_view{}.empty() == true);
+ REQUIRE(fsv::filtered_string_view{"1"}.empty() == false);
+}
+
+// Specification: 2.6.3 - Empty
+// Purpose: Empty should return true when the view with predicate results in an
+// empty string.
+// Expected: The 'viewed' string with a false predicate should always return true.
+TEST_CASE("empty should return true when predicate filters all characters") {
+ const auto parent = fsv::filtered_string_view{"non_empty", [](const char&) { return false; }};
+ REQUIRE(parent.size() == 0);
+ REQUIRE(parent.empty() == true);
+}
+
+// Specification: 2.6.3 - Data
+// Purpose: Data should return the owned string, without an affected filter.
+// Expected: The data of a string view should not be affected by the predicate,
+// so should be equal to the parent string despite the predicate.
+TEST_CASE("data should not be affected by the predicate") {
+ const auto parent = std::string{"chars"};
+ const auto view = fsv::filtered_string_view{parent, [](const char&) { return false; }};
+ REQUIRE(std::string{std::data(view)} == parent);
+}
+
+// Specification: 2.6.3 - Predicate
+// Purpose: The predicate method should return the predicate passed in any
+// constructor.
+// Expected: The effects of a function should passed to the view should be made
+// when calling the function provided by the predicate method.
+TEST_CASE("predicate returns the same function during construction") {
+ int count = 0;
+ const auto view = fsv::filtered_string_view{"", [&](const char&) {
+ ++count;
+ return true;
+ }};
+
+ // We don't know how many times predicate calls the original function, so we
+ // store the result of count after the constructor.
+ const auto before_call = count;
+
+ view.predicate()(char{});
+ REQUIRE(before_call + 1 == count);
+}
+
+// Specification: 2.7.1 - Equality Comparison
+// Purpose: Ensure that the predicate affects the result of the equality
+// comparison.
+// Expected: Equality should be true when the predicate results in two
+// functionally identical views of strings regardless of its data.
+TEST_CASE("equality comparison is true when affected by predicate") {
+ const auto view1 = fsv::filtered_string_view{"bca", [](const char& c) { return c == 'a'; }};
+ const auto view2 = fsv::filtered_string_view{"abc", [](const char& c) { return c == 'a'; }};
+ REQUIRE(view1 == view2);
+}
+
+// Specification: 2.7.1 - Equality Comparison
+// Purpose: Ensure that the predicate affects the result of the equality
+// comparison.
+// Expected: Equality should be false when the predicate differs on the same
+// string and produces different results through the view.
+TEST_CASE("equality comparison is false when affected by predicate") {
+ const auto parent = std::string{"abc"};
+ const auto view1 = fsv::filtered_string_view{parent, [](const char& c) { return c == 'a'; }};
+ const auto view2 = fsv::filtered_string_view{parent, [](const char& c) { return c == 'b'; }};
+ REQUIRE(not(view1 == view2));
+}
+
+// Specification: 2.7.1 - Equality Comparison
+// Purpose: Ensure that inequality functions as expected.
+// Expected: Equality should be the inverse of inequality.
+TEST_CASE("inequality is the inverse of equality") {
+ const auto view1 = fsv::filtered_string_view{"bca", [](const char& c) { return c == 'a'; }};
+ const auto view2 = fsv::filtered_string_view{"abc", [](const char& c) { return c == 'a'; }};
+ REQUIRE(view1 == view2);
+ REQUIRE(not(view1 != view2));
+}
+
+// Specification: 2.7.2 - Relational Comparison
+// Purpose: Ensure that relational comparisons functions as expected.
+// Expected: Relational comparisons should be logically consistent.
+// Note: Test copied from the specification, modified for catch2.
+TEST_CASE("relational comparison is logically consistent") {
+ const auto lo = fsv::filtered_string_view{"aaa"};
+ const auto hi = fsv::filtered_string_view{"zzz"};
+
+ REQUIRE(lo < hi);
+ REQUIRE(lo <= hi);
+ REQUIRE(not(lo > hi));
+ REQUIRE(not(lo >= hi));
+ REQUIRE(lo <=> hi == std::strong_ordering::less);
+}
+
+// Specification: 2.7.2 - Relational Comparison
+// Purpose: Ensure that relational comparisons functions are affected by the
+// predicate.
+// Expected: Relational comparisons should use the result of the view, not the
+// source string.
+TEST_CASE("relational comparison is affected by predicate") {
+ const auto lo = fsv::filtered_string_view{"za", [](const auto& c) { return c != 'z'; }};
+ const auto hi = fsv::filtered_string_view{"az", [](const auto& c) { return c != 'a'; }};
+
+ REQUIRE(lo < hi);
+ REQUIRE(lo <= hi);
+ REQUIRE(not(lo > hi));
+ REQUIRE(not(lo >= hi));
+ REQUIRE(lo <=> hi == std::strong_ordering::less);
+}
+
+// Specification: 2.7.3 - Output Stream
+// Purpose: Ensure the filtered string outputs to a stream and is filtered by
+// the predicate.
+// Expected: The output stream filters the underlying data.
+TEST_CASE("output stream is affected by predicate") {
+ const auto view = fsv::filtered_string_view{"output!", [](const auto& c) { return c != 't'; }};
+
+ auto stream = std::stringstream{};
+ stream << view;
+
+ REQUIRE(std::string{stream.str()} == "oupu!");
+}
+
+// Specification: 2.8.1 - Compose
+// Purpose: Ensure that compose results in the logical AND expression equivalent
+// defined in the specification.
+// Expected: The result of multiple predicates are applied and only those which
+// return true for all provided predicates are viewed in the
+// underlying filter.
+// Notes: Taken from the specification, modified for catch2.
+TEST_CASE("compose functions as logical AND expression for predicates") {
+ const auto best_languages = fsv::filtered_string_view{"c / c++"};
+ const auto vf = std::vector<fsv::filter>{[](const char& c) { return c == 'c' || c == '+' || c == '/'; },
+ [](const char& c) { return c > ' '; },
+ [](const char&) { return true; }};
+
+ const auto sv = fsv::compose(best_languages, vf);
+ REQUIRE(std::string{sv} == "c/c++");
+}
+
+// Specification 2.8.2 - Split
+// Purpose: Ensure that split conforms to the specification (similar to
+// python3's string split).
+// Expected: The delimiter should not appear in any of the split views.
+// Note: Test from specification, modified for catch2.
+TEST_CASE("split does not include delimiters") {
+ const auto view = fsv::filtered_string_view{"0xDEADBEEF / 0xdeadbeef"};
+ const auto token = fsv::filtered_string_view{" / "};
+ const auto result = fsv::split(view, token);
+ REQUIRE(result == std::vector<fsv::filtered_string_view>{"0xDEADBEEF", "0xdeadbeef"});
+}
+
+// Specification 2.8.2 - Split
+// Purpose: Ensure that split conforms to the specification (similar to
+// python3's string split).
+// Expected: The delimiter should produce empty strings when no characters appear
+// before or after it.
+// Note: Test from specification, modified for catch2.
+TEST_CASE("split produces empty strings when around lone delimiters") {
+ const auto view = fsv::filtered_string_view{"xax"};
+ const auto token = fsv::filtered_string_view{"x"};
+ const auto result = fsv::split(view, token);
+
+ REQUIRE(result == std::vector<fsv::filtered_string_view>{"", "a", ""});
+}
+
+// Specification 2.8.2 - Split
+// Purpose: Ensure that split conforms to the specification (similar to
+// python3's string split).
+// Expected: The delimiter should produce empty strings when only delimiters
+// are present.
+// Note: Test from specification, modified for catch2.
+TEST_CASE("split produces multiple empty strings around only delimiters") {
+ const auto view = fsv::filtered_string_view{"xx"};
+ const auto token = fsv::filtered_string_view{"x"};
+ const auto result = fsv::split(view, token);
+
+ REQUIRE(result == std::vector<fsv::filtered_string_view>{"", "", ""});
+}
+
+// Specification 2.8.2 - Split
+// Purpose: Ensure that split conforms to the specification (IDENTICAL to
+// python3's string split). We test multiple edge cases here, with
+// multiple delimiters on the outside and inside to ensure we conform.
+// Expected: The output should match the python3 equivalent split function.
+TEST_CASE("split functions as expected multiple delimiters") {
+ const auto view = fsv::filtered_string_view{"a a Complicated Split Many Tokens among this stringa haha"};
+ const auto token = fsv::filtered_string_view{"a"};
+ const auto result = fsv::split(view, token);
+
+ const auto expected = std::vector<fsv::filtered_string_view>{"",
+ " ",
+ " Complic",
+ "ted Split M",
+ "ny Tokens ",
+ "mong this string",
+ " h",
+ "h",
+ ""};
+ REQUIRE(result == expected);
+}
+
+// Specification 2.8.3 - Substr
+// Purpose: Ensure that substr conforms to the specification.
+// Expected: The substring should split the view as if an offeset was provided
+// to the original string.
+// Note: Test from specification, modified for catch2.
+TEST_CASE("substr splits at correct offset") {
+ const auto view = fsv::filtered_string_view{"Siberian Husky"};
+ REQUIRE(fsv::substr(view, 9) == "Husky");
+}
+
+// Specification 2.8.3 - Substr
+// Purpose: Ensure that substr conforms to the specification.
+// Expected: The substring should split the view as if an offeset was provided
+// to the original string.
+// Note: Test from specification, modified for catch2.
+TEST_CASE("substr respects view predicate") {
+ const auto view =
+ fsv::filtered_string_view{"Sled Dog", [](const auto& c) { return std::isupper(static_cast<unsigned char>(c)); }};
+ REQUIRE(fsv::substr(view, 0, 2) == "SD");
+}
+
+// Specification 2.8.3 - Substr
+// Purpose: Ensure that substr conforms to the specification.
+// Expected: The substring should produce an empty string when provided with
+// zero as a length argument.
+TEST_CASE("substr produces an empty string with zero length") {
+ const auto view = fsv::filtered_string_view{"length zero", [](const auto&) { return false; }};
+ REQUIRE(fsv::substr(view, 0) == "");
+}
+
+// Specification: 2.9 - Iterator
+// Purpose: Ensure the iterator functions correctly on an empty view.
+// Expected: A std::copy of the empty view should result in an empty string.
+TEST_CASE("forward iterators function on empty views") {
+ const auto view = fsv::filtered_string_view{};
+ auto str = std::string{};
+ std::copy(std::begin(view), std::end(view), std::back_inserter(str));
+ REQUIRE(str == "");
+}
+
+// Specification: 2.9 - Iterator
+// Purpose: Ensure we have access to a valid iterator which conforms to the
+// standard. Cend and Cbegin should also be available and identical to
+// the standard non-const iterator.
+// Expected: A std::copy of the view should remain equal to the underlying
+// string, given the predicate does not modify its output.
+TEST_CASE("forward iterators function as expected") {
+ const auto view = fsv::filtered_string_view{"iterator"};
+
+ auto str = std::string{};
+ std::copy(std::begin(view), std::end(view), std::back_inserter(str));
+ REQUIRE(str == "iterator");
+
+ str.clear();
+ std::copy(std::cbegin(view), std::cend(view), std::back_inserter(str));
+ REQUIRE(str == "iterator");
+}
+
+// Specification: 2.9 - Iterator
+// Purpose: Ensure we have access to a valid iterator which conforms to the
+// standard. Cend and cbegin should also be available.
+// Expected: A std::copy should copy the underlying string filter.
+TEST_CASE("forward iterator is affected by predicate") {
+ const auto view = fsv::filtered_string_view{"iterator", [](const auto& c) { return c != 'r'; }};
+
+ auto str = std::string{};
+ std::copy(std::begin(view), std::end(view), std::back_inserter(str));
+ REQUIRE(str == "iteato");
+
+ str.clear();
+ std::copy(std::cbegin(view), std::cend(view), std::back_inserter(str));
+ REQUIRE(str == "iteato");
+}
+
+// Specification: 2.10 - Range
+// Purpose: Ensure our iterator functions as a bidirectional range.
+// Expected: A std::copy of a backwards iterator produces the view in reverse
+// of the begin iterators.
+TEST_CASE("bidirectional iterators function as expected") {
+ const auto view = fsv::filtered_string_view{"iterator"};
+
+ auto str = std::string{};
+ std::copy(std::rbegin(view), std::rend(view), std::back_inserter(str));
+ REQUIRE(str == "rotareti");
+
+ str.clear();
+ std::copy(std::crbegin(view), std::crend(view), std::back_inserter(str));
+ REQUIRE(str == "rotareti");
+}
+
+// Specification: 2.10 - Range
+// Purpose: Ensure our iterator functions as a bidirectional range with respect
+// to the provided predicate.
+// Expected: A std::copy of a backwards iterator produces the view in reverse
+// of the begin iterators, including the affects of the predicate.
+TEST_CASE("bidirectional iterators are affected by predicate.") {
+ const auto view = fsv::filtered_string_view{"iterator", [](const auto& c) { return c != 'r'; }};
+
+ auto str = std::string{};
+ std::copy(std::rbegin(view), std::rend(view), std::back_inserter(str));
+ REQUIRE(str == "otaeti");
+
+ str.clear();
+ std::copy(std::crbegin(view), std::crend(view), std::back_inserter(str));
+ REQUIRE(str == "otaeti");
+}