diff options
Diffstat (limited to 'comp6771/2')
| -rw-r--r-- | comp6771/2/.clang-format | 103 | ||||
| -rw-r--r-- | comp6771/2/.gitignore | 16 | ||||
| -rw-r--r-- | comp6771/2/CMakeLists.txt | 71 | ||||
| -rw-r--r-- | comp6771/2/src/filtered_string_view.cpp | 277 | ||||
| -rw-r--r-- | comp6771/2/src/filtered_string_view.h | 104 | ||||
| -rw-r--r-- | comp6771/2/src/filtered_string_view.test.cpp | 559 |
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"); +} |
