diff --git a/doc/src/string_helper.dox b/doc/src/string_helper.dox index f7e26bb..eb74a27 100644 --- a/doc/src/string_helper.dox +++ b/doc/src/string_helper.dox @@ -71,7 +71,7 @@ You can simply return \c false to terminate join process. The argument you assigned to argument will not be taken into join process when you return false. Then, you can pass the created #JoinDataProvider object to #Join function. -And specify decilmer at the same time. +And specify delimiter at the same time. Then you can get the final joined string. There is an example: @@ -88,7 +88,7 @@ auto joined_string = YYCC::StringHelper::Join( ++iter; return true; }, - decilmer + delimiter ); \endcode @@ -105,7 +105,7 @@ Otherwise this overload will throw template error. std::vector data { YYCC_U8(""), YYCC_U8("1"), YYCC_U8("2"), YYCC_U8("") }; -auto joined_string = YYCC::StringHelper::Join(data.begin(), data.end(), decilmer); +auto joined_string = YYCC::StringHelper::Join(data.begin(), data.end(), delimiter); \endcode \section string_helper__lower_upper Lower Upper @@ -134,14 +134,14 @@ std::vector SplitView(const yycc_u8string_view&, const yycc_ \endcode All these overloads take a string view as the first argument representing the string need to be split. -The second argument is a string view representing the decilmer for splitting. +The second argument is a string view representing the delimiter for splitting. The only difference between these 2 split function are overt according to their names. The first split function will return a list of copied string as its split result. The second split function will return a list of string view as its split result, and it will keep valid as long as the life time of your given string view argument. It also means that the last overload will cost less memory if you don't need the copy of original string. -If the source string (the string need to be split) is empty, or the decilmer is empty, +If the source string (the string need to be split) is empty, or the delimiter is empty, the result will only has 1 item and this item is source string itself. There is no way that these methods return an empty list, except the code is buggy. diff --git a/src/YYCCLegacy/StringHelper.cpp b/src/YYCCLegacy/StringHelper.cpp index d0dfe0c..d529d33 100644 --- a/src/YYCCLegacy/StringHelper.cpp +++ b/src/YYCCLegacy/StringHelper.cpp @@ -110,18 +110,18 @@ namespace YYCC::StringHelper { #pragma region Join - yycc_u8string Join(JoinDataProvider fct_data, const yycc_u8string_view& decilmer) { + yycc_u8string Join(JoinDataProvider fct_data, const yycc_u8string_view& delimiter) { yycc_u8string ret; bool is_first = true; yycc_u8string_view element; // fetch element while (fct_data(element)) { - // insert decilmer + // insert delimiter if (is_first) is_first = false; else { - // append decilmer. - ret.append(decilmer); + // append delimiter. + ret.append(delimiter); } // insert element if it is not empty @@ -175,9 +175,9 @@ namespace YYCC::StringHelper { #pragma region Split - std::vector Split(const yycc_u8string_view& strl, const yycc_u8string_view& _decilmer) { + std::vector Split(const yycc_u8string_view& strl, const yycc_u8string_view& _delimiter) { // call split view - auto view_result = SplitView(strl, _decilmer); + auto view_result = SplitView(strl, _delimiter); // copy string view result to string std::vector elems; @@ -189,7 +189,7 @@ namespace YYCC::StringHelper { return elems; } - std::vector SplitView(const yycc_u8string_view& strl, const yycc_u8string_view& _decilmer) { + std::vector SplitView(const yycc_u8string_view& strl, const yycc_u8string_view& _delimiter) { // Reference: // https://stackoverflow.com/questions/14265581/parse-split-a-string-in-c-using-string-delimiter-standard-c @@ -197,18 +197,18 @@ namespace YYCC::StringHelper { std::vector elems; // if string need to be splitted is empty, return original string (empty string). - // if decilmer is empty, return original string. - yycc_u8string decilmer(_decilmer); - if (strl.empty() || decilmer.empty()) { + // if delimiter is empty, return original string. + yycc_u8string delimiter(_delimiter); + if (strl.empty() || delimiter.empty()) { elems.emplace_back(strl); return elems; } // start spliting std::size_t previous = 0, current; - while ((current = strl.find(decilmer.c_str(), previous)) != yycc_u8string::npos) { + while ((current = strl.find(delimiter.c_str(), previous)) != yycc_u8string::npos) { elems.emplace_back(strl.substr(previous, current - previous)); - previous = current + decilmer.size(); + previous = current + delimiter.size(); } // try insert last part but prevent possible out of range exception if (previous <= strl.size()) { diff --git a/src/YYCCLegacy/StringHelper.hpp b/src/YYCCLegacy/StringHelper.hpp index e6834c0..084a34c 100644 --- a/src/YYCCLegacy/StringHelper.hpp +++ b/src/YYCCLegacy/StringHelper.hpp @@ -82,10 +82,10 @@ namespace YYCC::StringHelper { * You can use this universal join function for any custom container by * using C++ lambda syntax to create a code block adapted to this function pointer. * @param[in] fct_data The function pointer in JoinDataProvider type prividing the data to be joined. - * @param[in] decilmer The decilmer used for joining. + * @param[in] delimiter The delimiter used for joining. * @return The result string of joining. */ - yycc_u8string Join(JoinDataProvider fct_data, const yycc_u8string_view& decilmer); + yycc_u8string Join(JoinDataProvider fct_data, const yycc_u8string_view& delimiter); /** * @brief Specialized join function for standard library container. * @tparam InputIt @@ -93,11 +93,11 @@ namespace YYCC::StringHelper { * It also can be dereferenced and then implicitly converted to yycc_u8string_view. * @param[in] first The beginning of the range of elements to join. * @param[in] last The terminal of the range of elements to join (exclusive). - * @param[in] decilmer The decilmer used for joining. + * @param[in] delimiter The delimiter used for joining. * @return The result string of joining. */ template - yycc_u8string Join(InputIt first, InputIt last, const yycc_u8string_view& decilmer) { + yycc_u8string Join(InputIt first, InputIt last, const yycc_u8string_view& delimiter) { return Join([&first, &last](yycc_u8string_view& view) -> bool { // if we reach tail, return false to stop join process if (first == last) return false; @@ -105,7 +105,7 @@ namespace YYCC::StringHelper { view = *first; ++first; return true; - }, decilmer); + }, delimiter); } /** @@ -132,28 +132,28 @@ namespace YYCC::StringHelper { yycc_u8string Upper(const yycc_u8string_view& strl); /** - * @brief Split given string with specified decilmer. + * @brief Split given string with specified delimiter. * @param[in] strl The string need to be splitting. - * @param[in] _decilmer The decilmer for splitting. + * @param[in] _delimiter The delimiter for splitting. * @return * The split result. * \par - * If given string or decilmer are empty, + * If given string or delimiter are empty, * the result container will only contain 1 entry which is equal to given string. */ - std::vector Split(const yycc_u8string_view& strl, const yycc_u8string_view& _decilmer); + std::vector Split(const yycc_u8string_view& strl, const yycc_u8string_view& _delimiter); /** - * @brief Split given string with specified decilmer as string view. + * @brief Split given string with specified delimiter as string view. * @param[in] strl The string need to be splitting. - * @param[in] _decilmer The decilmer for splitting. + * @param[in] _delimiter The delimiter for splitting. * @return * The split result with string view format. * This will not produce any copy of original string. * \par - * If given string or decilmer are empty, + * If given string or delimiter are empty, * the result container will only contain 1 entry which is equal to given string. * @see Split(const yycc_u8string_view&, const yycc_char8_t*) */ - std::vector SplitView(const yycc_u8string_view& strl, const yycc_u8string_view& _decilmer); + std::vector SplitView(const yycc_u8string_view& strl, const yycc_u8string_view& _delimiter); } diff --git a/src/yycc/string/op.cpp b/src/yycc/string/op.cpp index f5920ba..6400589 100644 --- a/src/yycc/string/op.cpp +++ b/src/yycc/string/op.cpp @@ -114,18 +114,18 @@ namespace yycc::string::op { #pragma region Join - NS_YYCC_STRING::u8string join(JoinDataProvider fct_data, const NS_YYCC_STRING::u8string_view& decilmer) { + NS_YYCC_STRING::u8string join(JoinDataProvider fct_data, const NS_YYCC_STRING::u8string_view& delimiter) { NS_YYCC_STRING::u8string ret; bool is_first = true; NS_YYCC_STRING::u8string_view element; // fetch element while (fct_data(element)) { - // insert decilmer + // insert delimiter if (is_first) is_first = false; else { - // append decilmer. - ret.append(decilmer); + // append delimiter. + ret.append(delimiter); } // insert element if it is not empty @@ -179,9 +179,37 @@ namespace yycc::string::op { #pragma region Split - std::vector split(const NS_YYCC_STRING::u8string_view& strl, const NS_YYCC_STRING::u8string_view& _decilmer) { + std::vector split(const NS_YYCC_STRING::u8string_view& strl, const NS_YYCC_STRING::u8string_view& _delimiter) { + // Reference: + // https://stackoverflow.com/questions/14265581/parse-split-a-string-in-c-using-string-delimiter-standard-c + + // prepare return value + std::vector elems; + + // if string need to be splitted is empty, return original string (empty string). + // if delimiter is empty, return original string. + NS_YYCC_STRING::u8string delimiter(_delimiter); + if (strl.empty() || delimiter.empty()) { + elems.emplace_back(strl); + return elems; + } + + // start spliting + std::size_t previous = 0, current; + while ((current = strl.find(delimiter.c_str(), previous)) != NS_YYCC_STRING::u8string::npos) { + elems.emplace_back(strl.substr(previous, current - previous)); + previous = current + delimiter.size(); + } + // try insert last part but prevent possible out of range exception + if (previous <= strl.size()) { + elems.emplace_back(strl.substr(previous)); + } + return elems; + } + + std::vector split_owned(const NS_YYCC_STRING::u8string_view& strl, const NS_YYCC_STRING::u8string_view& _delimiter) { // call split view - auto view_result = split_view(strl, _decilmer); + auto view_result = split(strl, _delimiter); // copy string view result to string std::vector elems; @@ -193,34 +221,6 @@ namespace yycc::string::op { return elems; } - std::vector split_view(const NS_YYCC_STRING::u8string_view& strl, const NS_YYCC_STRING::u8string_view& _decilmer) { - // Reference: - // https://stackoverflow.com/questions/14265581/parse-split-a-string-in-c-using-string-delimiter-standard-c - - // prepare return value - std::vector elems; - - // if string need to be splitted is empty, return original string (empty string). - // if decilmer is empty, return original string. - NS_YYCC_STRING::u8string decilmer(_decilmer); - if (strl.empty() || decilmer.empty()) { - elems.emplace_back(strl); - return elems; - } - - // start spliting - std::size_t previous = 0, current; - while ((current = strl.find(decilmer.c_str(), previous)) != NS_YYCC_STRING::u8string::npos) { - elems.emplace_back(strl.substr(previous, current - previous)); - previous = current + decilmer.size(); - } - // try insert last part but prevent possible out of range exception - if (previous <= strl.size()) { - elems.emplace_back(strl.substr(previous)); - } - return elems; - } - #pragma endregion } diff --git a/src/yycc/string/op.hpp b/src/yycc/string/op.hpp index 52e5a9f..8bd74e7 100644 --- a/src/yycc/string/op.hpp +++ b/src/yycc/string/op.hpp @@ -77,10 +77,10 @@ namespace yycc::string::op { * You can use this universal join function for any custom container by * using C++ lambda syntax to create a code block adapted to this function pointer. * @param[in] fct_data The function pointer in JoinDataProvider type prividing the data to be joined. - * @param[in] decilmer The decilmer used for joining. + * @param[in] delimiter The delimiter used for joining. * @return The result string of joining. */ - NS_YYCC_STRING::u8string join(JoinDataProvider fct_data, const NS_YYCC_STRING::u8string_view& decilmer); + NS_YYCC_STRING::u8string join(JoinDataProvider fct_data, const NS_YYCC_STRING::u8string_view& delimiter); /** * @brief Specialized join function for standard library container. * @tparam InputIt @@ -88,11 +88,11 @@ namespace yycc::string::op { * It also can be dereferenced and then implicitly converted to NS_YYCC_STRING::u8string_view. * @param[in] first The beginning of the range of elements to join. * @param[in] last The terminal of the range of elements to join (exclusive). - * @param[in] decilmer The decilmer used for joining. + * @param[in] delimiter The delimiter used for joining. * @return The result string of joining. */ template - NS_YYCC_STRING::u8string join(InputIt first, InputIt last, const NS_YYCC_STRING::u8string_view& decilmer) { + NS_YYCC_STRING::u8string join(InputIt first, InputIt last, const NS_YYCC_STRING::u8string_view& delimiter) { return join([&first, &last](NS_YYCC_STRING::u8string_view& view) -> bool { // if we reach tail, return false to stop join process if (first == last) return false; @@ -100,7 +100,7 @@ namespace yycc::string::op { view = *first; ++first; return true; - }, decilmer); + }, delimiter); } /** @@ -126,30 +126,31 @@ namespace yycc::string::op { */ NS_YYCC_STRING::u8string upper(const NS_YYCC_STRING::u8string_view& strl); + /** + * @brief Split given string with specified delimiter as string view. + * @param[in] strl The string need to be splitting. + * @param[in] _delimiter The delimiter for splitting. + * @return + * The split result with string view format. + * This will not produce any copy of original string. + * \par + * If given string or delimiter are empty, + * the result container will only contain 1 entry which is equal to given string. + * @see Split(const NS_YYCC_STRING::u8string_view&, const NS_YYCC_STRING::u8char*) + */ + std::vector split(const NS_YYCC_STRING::u8string_view& strl, const NS_YYCC_STRING::u8string_view& _delimiter); /** - * @brief Split given string with specified decilmer. + * @brief Split given string with specified delimiter. * @param[in] strl The string need to be splitting. - * @param[in] _decilmer The decilmer for splitting. + * @param[in] _delimiter The delimiter for splitting. * @return * The split result. * \par - * If given string or decilmer are empty, + * If given string or delimiter are empty, * the result container will only contain 1 entry which is equal to given string. */ - std::vector split(const NS_YYCC_STRING::u8string_view& strl, const NS_YYCC_STRING::u8string_view& _decilmer); - /** - * @brief Split given string with specified decilmer as string view. - * @param[in] strl The string need to be splitting. - * @param[in] _decilmer The decilmer for splitting. - * @return - * The split result with string view format. - * This will not produce any copy of original string. - * \par - * If given string or decilmer are empty, - * the result container will only contain 1 entry which is equal to given string. - * @see Split(const NS_YYCC_STRING::u8string_view&, const NS_YYCC_STRING::u8char*) - */ - std::vector split_view(const NS_YYCC_STRING::u8string_view& strl, const NS_YYCC_STRING::u8string_view& _decilmer); + std::vector split_owned(const NS_YYCC_STRING::u8string_view& strl, const NS_YYCC_STRING::u8string_view& _delimiter); + // undefined lazy_split(const NS_YYCC_STRING::u8string_view& strl, const NS_YYCC_STRING::u8string_view& _delimiter); } diff --git a/testbench/main_legacy.cpp b/testbench/main_legacy.cpp index 3041158..19fe7de 100644 --- a/testbench/main_legacy.cpp +++ b/testbench/main_legacy.cpp @@ -224,10 +224,10 @@ namespace YYCCTestbench { Assert(test_split[1] == YYCC_U8("1"), YYCC_U8("YYCC::StringHelper::Split")); Assert(test_split[2] == YYCC_U8("2"), YYCC_U8("YYCC::StringHelper::Split")); Assert(test_split[3] == YYCC_U8(""), YYCC_U8("YYCC::StringHelper::Split")); - test_split = YYCC::StringHelper::Split(YYCC_U8("test"), YYCC_U8("-")); // no matched decilmer + test_split = YYCC::StringHelper::Split(YYCC_U8("test"), YYCC_U8("-")); // no matched delimiter Assert(test_split.size() == 1u, YYCC_U8("YYCC::StringHelper::Split")); Assert(test_split[0] == YYCC_U8("test"), YYCC_U8("YYCC::StringHelper::Split")); - test_split = YYCC::StringHelper::Split(YYCC_U8("test"), YYCC::yycc_u8string_view()); // empty decilmer + test_split = YYCC::StringHelper::Split(YYCC_U8("test"), YYCC::yycc_u8string_view()); // empty delimiter Assert(test_split.size() == 1u, YYCC_U8("YYCC::StringHelper::Split")); Assert(test_split[0] == YYCC_U8("test"), YYCC_U8("YYCC::StringHelper::Split")); test_split = YYCC::StringHelper::Split(YYCC::yycc_u8string_view(), YYCC_U8("")); // empty source string @@ -413,12 +413,12 @@ namespace YYCCTestbench { YYCC::yycc_u8string test_slashed_path(YYCC::StdPatch::ToUTF8Path(test_path)); #if YYCC_OS == YYCC_OS_WINDOWS - std::wstring wdecilmer(1u, std::filesystem::path::preferred_separator); - YYCC::yycc_u8string decilmer(YYCC::EncodingHelper::WcharToUTF8(wdecilmer)); + std::wstring wdelimiter(1u, std::filesystem::path::preferred_separator); + YYCC::yycc_u8string delimiter(YYCC::EncodingHelper::WcharToUTF8(wdelimiter)); #else - YYCC::yycc_u8string decilmer(1u, std::filesystem::path::preferred_separator); + YYCC::yycc_u8string delimiter(1u, std::filesystem::path::preferred_separator); #endif - YYCC::yycc_u8string test_joined_path(YYCC::StringHelper::Join(c_UTF8TestStrTable.begin(), c_UTF8TestStrTable.end(), decilmer)); + YYCC::yycc_u8string test_joined_path(YYCC::StringHelper::Join(c_UTF8TestStrTable.begin(), c_UTF8TestStrTable.end(), delimiter)); Assert(test_slashed_path == test_joined_path, YYCC_U8("YYCC::StdPatch::ToStdPath, YYCC::StdPatch::ToUTF8Path")); diff --git a/testbench/yycc/string/op.cpp b/testbench/yycc/string/op.cpp index 54656ed..ae23a75 100644 --- a/testbench/yycc/string/op.cpp +++ b/testbench/yycc/string/op.cpp @@ -3,6 +3,8 @@ #include #include +#include + #define OP ::yycc::string::op namespace yycctest::string::op { @@ -13,7 +15,36 @@ namespace yycctest::string::op { } TEST(StringOp, Replace) { - + // Normal case + { + auto rv = OP::replace(YYCC_U8("aabbcc"), YYCC_U8("bb"), YYCC_U8("dd")); + EXPECT_EQ(rv, YYCC_U8("aaddcc")); + } + // No matched expected string + { + auto rv = OP::replace(YYCC_U8("aabbcc"), YYCC_U8("zz"), YYCC_U8("yy")); + EXPECT_EQ(rv, YYCC_U8("aabbcc")); + } + // Empty expected string + { + auto rv = OP::replace(YYCC_U8("aabbcc"), u8string_view(), YYCC_U8("zz")); + EXPECT_EQ(rv, YYCC_U8("aabbcc")); + } + // Empty replace string + { + auto rv = OP::replace(YYCC_U8("aaaabbaa"), YYCC_U8("aa"), YYCC_U8("")); + EXPECT_EQ(rv, YYCC_U8("bb")); + } + // Nested replacing + { + auto rv = OP::replace(YYCC_U8("aaxcc"), YYCC_U8("x"), YYCC_U8("yx")); + EXPECT_EQ(rv, YYCC_U8("aayxcc")); + } + // Empty source string + { + auto rv = OP::replace(u8string_view(), YYCC_U8(""), YYCC_U8("xy")); + EXPECT_EQ(rv, YYCC_U8("")); + } } TEST(StringOp, Lower) { @@ -27,11 +58,39 @@ namespace yycctest::string::op { } TEST(StringOp, Join) { - + std::vector datas{YYCC_U8(""), YYCC_U8("1"), YYCC_U8("2"), YYCC_U8("")}; + auto rv = OP::join(datas.begin(), datas.end(), YYCC_U8(", ")); + EXPECT_EQ(rv, YYCC_U8(", 1, 2, ")); } TEST(StringOp, Split) { - + // Normal + { + auto rv = OP::split(YYCC_U8(", 1, 2, "), YYCC_U8(", ")); + ASSERT_EQ(rv.size(), 4u); + EXPECT_EQ(rv[0], YYCC_U8("")); + EXPECT_EQ(rv[1], YYCC_U8("1")); + EXPECT_EQ(rv[2], YYCC_U8("2")); + EXPECT_EQ(rv[3], YYCC_U8("")); + } + // No matched delimiter + { + auto rv = OP::split(YYCC_U8("test"), YYCC_U8("-")); + ASSERT_EQ(rv.size(), 1u); + EXPECT_EQ(rv[0], YYCC_U8("test")); + } + // Empty delimiter + { + auto rv = OP::split(YYCC_U8("test"), u8string_view()); + ASSERT_EQ(rv.size(), 1u); + EXPECT_EQ(rv[0], YYCC_U8("test")); + } + // Empty source string + { + auto rv = OP::split(u8string_view(), YYCC_U8("")); + ASSERT_EQ(rv.size(), 1u); + EXPECT_TRUE(rv[0].empty()); + } } -} +} // namespace yycctest::string::op diff --git a/testbench/yycc/string/reinterpret.cpp b/testbench/yycc/string/reinterpret.cpp index a4570e6..946c58b 100644 --- a/testbench/yycc/string/reinterpret.cpp +++ b/testbench/yycc/string/reinterpret.cpp @@ -1,6 +1,60 @@ +#include #include #include +#include + +#include + +#define REINTERPRET ::yycc::string::reinterpret +#define CONST_VOID_PTR(p) reinterpret_cast(p) +#define VOID_PTR(p) reinterpret_cast(p) namespace yycctest::string::reinterpret { -} + static u8string PROBE(YYCC_U8("Test")); + + TEST(StringReinterpret, ConstPointer) { + const auto* src = PROBE.data(); + const auto* dst = REINTERPRET::as_ordinary(src); + const auto* new_src = REINTERPRET::as_utf8(dst); + + // Pointer should point to the same address after casting. + EXPECT_EQ(CONST_VOID_PTR(src), CONST_VOID_PTR(dst)); + EXPECT_EQ(CONST_VOID_PTR(src), CONST_VOID_PTR(new_src)); + } + + TEST(StringReinterpret, Pointer) { + auto* src = PROBE.data(); + auto* dst = REINTERPRET::as_ordinary(src); + auto* new_src = REINTERPRET::as_utf8(dst); + + // Pointer should point to the same address after casting. + EXPECT_EQ(VOID_PTR(src), VOID_PTR(dst)); + EXPECT_EQ(VOID_PTR(src), VOID_PTR(new_src)); + } + + TEST(StringReinterpret, String) { + auto src = u8string(PROBE); + auto dst = REINTERPRET::as_ordinary(src); + auto new_src = REINTERPRET::as_utf8(dst); + + // Check memory length and data. + ASSERT_EQ(src.length(), dst.length()); + EXPECT_TRUE(std::memcmp(src.data(), dst.data(), src.length()) == 0); + ASSERT_EQ(src.length(), new_src.length()); + EXPECT_TRUE(std::memcmp(src.data(), new_src.data(), src.length()) == 0); + } + + TEST(StringReinterpret, StringView) { + auto src = u8string_view(PROBE); + auto dst = REINTERPRET::as_ordinary_view(src); + auto new_src = REINTERPRET::as_utf8_view(dst); + + // Check memory length and data. + ASSERT_EQ(src.length(), dst.length()); + EXPECT_TRUE(std::memcmp(src.data(), dst.data(), src.length()) == 0); + ASSERT_EQ(src.length(), new_src.length()); + EXPECT_TRUE(std::memcmp(src.data(), new_src.data(), src.length()) == 0); + } + +} // namespace yycctest::string::reinterpret