diff --git a/core/include/join/view.hpp b/core/include/join/view.hpp new file mode 100644 index 00000000..a32e4c22 --- /dev/null +++ b/core/include/join/view.hpp @@ -0,0 +1,181 @@ +/** + * MIT License + * + * Copyright (c) 2026 Mathieu Rabine + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef JOIN_CORE_VIEW_HPP +#define JOIN_CORE_VIEW_HPP + +// C++. +#include +#include +#include +#include +#include +#include + +// C. +#include +#include +#include + +namespace join +{ + /** + * @brief non-owning contiguous sequence of elements. + */ + template + class __attribute__ ((packed)) View + { + public: + /** + * @brief create an empty view. + */ + constexpr View () noexcept = default; + + /** + * @brief create a view. + * @param data first element. + * @param size number of elements, must fit in 32 bits. + */ + constexpr View (Type* data, size_t size) noexcept + : _data (data) + , _size (static_cast (size)) + { + assert (size <= std::numeric_limits::max ()); + } + + /** + * @brief get the first element. + * @return pointer to the first element. + */ + constexpr Type* data () const noexcept + { + return _data; + } + + /** + * @brief get the number of elements. + * @return number of elements. + */ + constexpr size_t size () const noexcept + { + return _size; + } + + /** + * @brief check if the view is empty. + * @return true if empty. + */ + constexpr bool empty () const noexcept + { + return _size == 0; + } + + /** + * @brief get an iterator to the first element. + * @return iterator to the first element. + */ + constexpr Type* begin () const noexcept + { + return _data; + } + + /** + * @brief get an iterator past the last element. + * @return iterator past the last element. + */ + constexpr Type* end () const noexcept + { + return _data + _size; + } + + /** + * @brief access an element. + * @param pos element position. + * @return element. + */ + constexpr Type& operator[] (size_t pos) const noexcept + { + return _data[pos]; + } + + private: + /// first element. + Type* _data = nullptr; + + /// number of elements. + uint32_t _size = 0; + }; + + /** + * @brief compare views. + * @param lhs view to compare. + * @param rhs view to compare to. + * @return true if equal. + */ + template + constexpr bool operator== (const View& lhs, const View& rhs) + { + return std::equal (lhs.begin (), lhs.end (), rhs.begin (), rhs.end ()); + } + + /** + * @brief three-way compare views. + * @param lhs view to compare. + * @param rhs view to compare to. + * @return ordering of lhs relative to rhs. + */ + template + constexpr std::partial_ordering operator<=> (const View& lhs, const View& rhs) + { + return std::lexicographical_compare_three_way (lhs.begin (), lhs.end (), rhs.begin (), rhs.end ()); + } + + /** + * @brief compare character views. + * @param lhs view to compare. + * @param rhs view to compare to. + * @return true if equal. + */ + template + requires std::same_as, char> + constexpr bool operator== (const View& lhs, const View& rhs) + { + return std::string_view (lhs.data (), lhs.size ()) == std::string_view (rhs.data (), rhs.size ()); + } + + /** + * @brief three-way compare character views. + * @param lhs view to compare. + * @param rhs view to compare to. + * @return ordering of lhs relative to rhs. + */ + template + requires std::same_as, char> + constexpr std::partial_ordering operator<=> (const View& lhs, const View& rhs) + { + return std::string_view (lhs.data (), lhs.size ()) <=> std::string_view (rhs.data (), rhs.size ()); + } +} + +#endif diff --git a/core/tests/CMakeLists.txt b/core/tests/CMakeLists.txt index af20af66..dab05f2e 100644 --- a/core/tests/CMakeLists.txt +++ b/core/tests/CMakeLists.txt @@ -27,6 +27,11 @@ target_link_libraries(variant.gtest ${JOIN_CORE} GTest::gtest_main) add_test(NAME variant.gtest COMMAND variant.gtest) install(TARGETS variant.gtest RUNTIME DESTINATION ${CMAKE_INSTALL_DATADIR}/${PROJECT_NAME}/test) +add_executable(view.gtest view_test.cpp) +target_link_libraries(view.gtest ${JOIN_CORE} GTest::gtest_main) +add_test(NAME view.gtest COMMAND view.gtest) +install(TARGETS view.gtest RUNTIME DESTINATION ${CMAKE_INSTALL_DATADIR}/${PROJECT_NAME}/test) + add_executable(backoff.gtest backoff_test.cpp) target_link_libraries(backoff.gtest ${JOIN_CORE} GTest::gtest_main) add_test(NAME backoff.gtest COMMAND backoff.gtest) diff --git a/core/tests/view_test.cpp b/core/tests/view_test.cpp new file mode 100644 index 00000000..5a5686f4 --- /dev/null +++ b/core/tests/view_test.cpp @@ -0,0 +1,196 @@ +/** + * MIT License + * + * Copyright (c) 2026 Mathieu Rabine + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +// libjoin. +#include + +// Libraries. +#include + +// C++. +#include +#include +#include + +using join::View; + +/** + * @brief test layout. + */ +TEST (View, layout) +{ + EXPECT_EQ (sizeof (View), sizeof (int*) + sizeof (uint32_t)); + EXPECT_TRUE ((std::is_trivially_copyable_v>)); +} + +/** + * @brief test create. + */ +TEST (View, create) +{ + static constexpr int values[] = {1, 2, 3}; + static_assert (View ().empty ()); + static_assert (View (values, 3)[2] == 3); + static_assert (View (values, 3) == View (values, 3)); + static_assert ((View (values, 2) <=> View (values, 3)) < 0); + + View view (values, 3); + EXPECT_EQ (view.data (), values); + EXPECT_EQ (view.size (), 3); +} + +/** + * @brief test data. + */ +TEST (View, data) +{ + int values[] = {1, 2, 3}; + EXPECT_EQ (View ().data (), nullptr); + EXPECT_EQ (View (values, 3).data (), values); +} + +/** + * @brief test size. + */ +TEST (View, size) +{ + int values[] = {1, 2, 3}; + EXPECT_EQ (View ().size (), 0); + EXPECT_EQ (View (values, 2).size (), 2); +} + +/** + * @brief test empty. + */ +TEST (View, empty) +{ + int values[] = {1, 2, 3}; + EXPECT_TRUE (View ().empty ()); + EXPECT_TRUE (View (values, 0).empty ()); + EXPECT_FALSE (View (values, 3).empty ()); +} + +/** + * @brief test begin. + */ +TEST (View, begin) +{ + int values[] = {1, 2, 3}; + View view (values, 3); + EXPECT_EQ (view.begin (), values); + EXPECT_EQ (*view.begin (), 1); +} + +/** + * @brief test end. + */ +TEST (View, end) +{ + int values[] = {1, 2, 3}; + View view (values, 3); + EXPECT_EQ (view.end (), values + 3); + + int sum = 0; + for (int value : view) + { + sum += value; + } + EXPECT_EQ (sum, 6); +} + +/** + * @brief test operator[]. + */ +TEST (View, at) +{ + int values[] = {1, 2, 3}; + View view (values, 3); + EXPECT_EQ (view[0], 1); + EXPECT_EQ (view[2], 3); + view[1] = 5; + EXPECT_EQ (values[1], 5); +} + +/** + * @brief test operator==. + */ +TEST (View, equal) +{ + int lhs[] = {1, 2, 3}; + int rhs[] = {1, 2, 3}; + EXPECT_TRUE (View (lhs, 3) == View (rhs, 3)); + EXPECT_FALSE (View (lhs, 3) == View (rhs, 2)); + rhs[2] = 4; + EXPECT_FALSE (View (lhs, 3) == View (rhs, 3)); + EXPECT_TRUE (View () == View ()); + EXPECT_TRUE (View () == View (lhs, 0)); + + double nan[] = {std::nan ("")}; + EXPECT_FALSE (View (nan, 1) == View (nan, 1)); + + EXPECT_TRUE (View () == View ("", 0)); + EXPECT_TRUE (View ("abc", 3) == View ("abcd", 3)); + EXPECT_FALSE (View ("abc", 3) == View ("abd", 3)); + EXPECT_FALSE (View ("abc", 3) == View ("ab", 2)); + + char text[] = "abc"; + EXPECT_TRUE (View (text, 3) == View (text, 3)); + EXPECT_FALSE (View (text, 3) == View (text, 2)); +} + +/** + * @brief test operator<=>. + */ +TEST (View, compare) +{ + int lhs[] = {1, 2, 3}; + int rhs[] = {1, 2, 4}; + EXPECT_EQ (View (lhs, 3) <=> View (rhs, 3), std::partial_ordering::less); + EXPECT_EQ (View (rhs, 3) <=> View (lhs, 3), std::partial_ordering::greater); + EXPECT_EQ (View (lhs, 2) <=> View (rhs, 2), std::partial_ordering::equivalent); + EXPECT_EQ (View (lhs, 2) <=> View (lhs, 3), std::partial_ordering::less); + + double nan[] = {std::nan ("")}; + EXPECT_EQ (View (nan, 1) <=> View (nan, 1), std::partial_ordering::unordered); + + EXPECT_EQ (View ("abc", 3) <=> View ("abd", 3), std::partial_ordering::less); + EXPECT_EQ (View ("abd", 3) <=> View ("abc", 3), std::partial_ordering::greater); + EXPECT_EQ (View ("abc", 3) <=> View ("abcd", 3), std::partial_ordering::equivalent); + EXPECT_EQ (View ("ab", 2) <=> View ("abc", 3), std::partial_ordering::less); + EXPECT_EQ (View ("\xC3", 1) <=> View ("a", 1), std::partial_ordering::greater); + + char high[] = "\xC3"; + char low[] = "a"; + EXPECT_EQ (View (high, 1) <=> View (low, 1), std::partial_ordering::greater); + EXPECT_EQ (View (low, 1) <=> View (high, 1), std::partial_ordering::less); +} + +/** + * @brief main function. + */ +int main (int argc, char** argv) +{ + testing::InitGoogleTest (&argc, argv); + return RUN_ALL_TESTS (); +}