//===----------------------------------------------------------------------===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception // //===----------------------------------------------------------------------===// // UNSUPPORTED: c++03, c++11, c++14, c++17 // template // class std::ranges::lazy_split_view; // // These test check the output `lazy_split_view` produces for a variety of inputs, including many corner cases, with no // restrictions on which member functions can be called. #include #include #include #include #include #include #include #include #include #include #include "types.h" // Basic utility to convert a range to a string-like type. This handles ranges // that do not contain character types and can work with non-contiguous inputs. template class BasicSmallString { std::vector buffer_{}; public: constexpr BasicSmallString(std::basic_string_view v) requires (std::same_as || #ifndef TEST_HAS_NO_WIDE_CHARACTERS std::same_as || #endif std::same_as || std::same_as || std::same_as) : buffer_(v.begin(), v.end()) {} template constexpr BasicSmallString(I b, const S& e) { for (; b != e; ++b) { buffer_.push_back(*b); } } template constexpr BasicSmallString(R&& from) : BasicSmallString(from.begin(), from.end()) {} friend constexpr bool operator==(const BasicSmallString& lhs, const BasicSmallString& rhs) { return lhs.buffer_ == rhs.buffer_; } }; template constexpr bool is_equal(View& view, const Expected& expected) { using Char = std::ranges::range_value_t>; using Str = BasicSmallString; auto actual_it = view.begin(); auto expected_it = expected.begin(); for (; actual_it != view.end() && expected_it != expected.end(); ++actual_it, ++expected_it) { if (Str(*actual_it) != Str(*expected_it)) return false; } return actual_it == view.end() && expected_it == expected.end(); } template constexpr bool test_function_call(T&& input, Separator&& separator, std::array expected) { std::ranges::lazy_split_view v(input, separator); return is_equal(v, expected); } template constexpr bool test_with_piping(T&& input, Separator&& separator, std::array expected) { auto expected_it = expected.begin(); for (auto e : input | std::ranges::views::lazy_split(separator)) { if (expected_it == expected.end()) return false; if (!std::ranges::equal(e, *expected_it)) return false; ++expected_it; } return expected_it == expected.end(); } constexpr bool test_l_r_values() { using namespace std::string_view_literals; // Both lvalues and rvalues can be used as input. { // Lvalues. { auto input = "abc"sv; auto sep = " "sv; [[maybe_unused]] std::ranges::lazy_split_view v(input, sep); } // Const lvalues. { const auto input = "abc"sv; const auto sep = " "sv; [[maybe_unused]] std::ranges::lazy_split_view v(input, sep); } // Rvalues. { auto input = "abc"sv; auto sep = " "sv; [[maybe_unused]] std::ranges::lazy_split_view v(std::move(input), std::move(sep)); } // Const rvalues. { const auto input = "abc"sv; const auto sep = " "sv; [[maybe_unused]] std::ranges::lazy_split_view v(std::move(input), std::move(sep)); } } return true; } constexpr bool test_string_literal_separator() { using namespace std::string_view_literals; // Splitting works as expected when the separator is a single character literal. { std::ranges::lazy_split_view v("abc def"sv, ' '); assert(is_equal(v, std::array{"abc"sv, "def"sv})); } // Counterintuitively, a seemingly equivalent separator expressed as a string literal doesn't match anything. This is // because of the implicit terminating null in the literal. { std::ranges::lazy_split_view v("abc def"sv, " "); assert(is_equal(v, std::array{"abc def"sv})); } // To illustrate the previous point further, the separator is actually a two-character string literal: `{' ', '\0'}`. // Should the input string contain that two-character sequence, the separator would match. { std::ranges::lazy_split_view v("abc \0def"sv, " "); assert(is_equal(v, std::array{"abc"sv, "def"sv})); } return true; } // Make sure that a string literal and a `string_view` produce the same results (which isn't always the case, see // below). template constexpr std::string_view sv(T&& str) { return std::string_view(str); }; template constexpr void test_one(T&& input, Separator&& separator, std::array expected) { assert(test_function_call(input, separator, expected)); assert(test_with_piping(input, separator, expected)); // In addition to the `(ForwardView, ForwardView)` case, test the `(ForwardView, tiny-range)` and `(InputView, // tiny-range)` cases (all of which have unique code paths). if constexpr (std::is_same_v, char>) { assert(test_function_call(CopyableView(input), ForwardTinyView(separator), expected)); assert(test_with_piping(CopyableView(input), ForwardTinyView(separator), expected)); assert(test_function_call(InputView(input), ForwardTinyView(separator), expected)); assert(test_with_piping(InputView(input), ForwardTinyView(separator), expected)); } } constexpr bool test_string_literals() { // These tests show characteristic examples of how using string literals with `lazy_split_view` produces unexpected // results due to the implicit terminating null that is treated as part of the range. using namespace std::string_view_literals; char short_sep = ' '; auto long_sep = "12"sv; // When splitting a string literal, only the last segment will be null-terminated (getting the terminating null from // the original range). { std::array expected = {"abc"sv, std::string_view("def", sizeof("def"))}; assert(test_function_call("abc def", short_sep, expected)); assert(test_with_piping("abc def", short_sep, expected)); assert(test_function_call("abc12def", long_sep, expected)); assert(test_with_piping("abc12def", long_sep, expected)); } // Empty string. { // Because an empty string literal contains an implicit terminating null, the output will contain one segment. std::array expected = {std::string_view("", 1)}; assert(test_function_call("", short_sep, expected)); assert(test_with_piping("", short_sep, expected)); assert(test_function_call("", long_sep, expected)); assert(test_with_piping("", long_sep, expected)); } // Terminating null in the separator -- the character literal `' '` and the seemingly equivalent string literal `" "` // are treated differently due to the presence of an implicit `\0` in the latter. { const char input[] = "abc def"; std::array expected_unsplit = {std::string_view(input, sizeof(input))}; std::array expected_split = {"abc"sv, std::string_view("def", sizeof("def"))}; assert(test_function_call(input, " ", expected_unsplit)); assert(test_function_call("abc \0def", " ", expected_split)); // Note: string literals don't work with piping because arrays decay to pointers, and pointers don't model `range`. } // Empty separator. { auto empty_sep = ""sv; std::array expected = {"a"sv, "b"sv, "c"sv, "\0"sv}; assert(test_function_call("abc", empty_sep, expected)); assert(test_with_piping("abc", empty_sep, expected)); } return true; } bool test_nontrivial_characters() { // Try a deliberately heavyweight "character" type to see if it triggers any corner cases. using Map = std::map; using Vec = std::vector; Map sep = {{"yyy", 999}}; Map m1 = { {"a", 1}, {"bc", 2}, }; Map m2 = { {"def", 3}, }; Map m3 = { {"g", 4}, {"hijk", 5}, }; Vec expected1 = {m1, m2}; Vec expected2 = {m3}; std::ranges::lazy_split_view v(Vec{m1, m2, sep, m3}, sep); // Segment 1: {m1, m2} auto outer = v.begin(); assert(outer != v.end()); auto inner = (*outer).begin(); assert(*inner++ == m1); assert(*inner++ == m2); assert(inner == (*outer).end()); // Segment 2: {m3} ++outer; assert(outer != v.end()); inner = (*outer).begin(); assert(*inner++ == m3); assert(inner == (*outer).end()); ++outer; assert(outer == v.end()); return true; } constexpr bool main_test() { using namespace std::string_view_literals; char short_sep = ' '; auto long_sep = "12"sv; // One separator. { std::array expected = {"abc"sv, "def"sv}; test_one("abc def"sv, short_sep, expected); test_one("abc12def"sv, long_sep, expected); } // Several separators in a row. { std::array expected = {"abc"sv, ""sv, ""sv, ""sv, "def"sv}; test_one("abc def"sv, short_sep, expected); test_one("abc12121212def"sv, long_sep, expected); } // Trailing separator. { std::array expected = {"abc"sv, "def"sv, ""sv}; test_one("abc def "sv, short_sep, expected); test_one("abc12def12"sv, long_sep, expected); } // Leading separator. { std::array expected = {""sv, "abc"sv, "def"sv}; test_one(" abc def"sv, short_sep, expected); test_one("12abc12def"sv, long_sep, expected); } // No separator. { std::array expected = {"abc"sv}; test_one("abc"sv, short_sep, expected); test_one("abc"sv, long_sep, expected); } // Input consisting of a single separator. { std::array expected = {""sv, ""sv}; test_one(" "sv, short_sep, expected); test_one("12"sv, long_sep, expected); } // Input consisting of only separators. { std::array expected = {""sv, ""sv, ""sv, ""sv}; test_one(" "sv, short_sep, expected); test_one("121212"sv, long_sep, expected); } // The separator and the string use the same character only. { auto overlapping_sep = "aaa"sv; std::array expected = {""sv, "aa"sv}; test_one("aaaaa"sv, overlapping_sep, expected); } // Many redundant separators. { std::array expected = {""sv, ""sv, "abc"sv, ""sv, ""sv, "def"sv, ""sv, ""sv}; test_one(" abc def "sv, short_sep, expected); test_one("1212abc121212def1212"sv, long_sep, expected); } // Separators after every character. { std::array expected = {""sv, "a"sv, "b"sv, "c"sv, ""sv}; test_one(" a b c "sv, short_sep, expected); test_one("12a12b12c12"sv, long_sep, expected); } // Overlap between the separator and the string (see https://wg21.link/lwg3505). { auto overlapping_sep = "ab"sv; std::array expected = {"a"sv, "aa"sv, ""sv, "b"sv}; test_one("aabaaababb"sv, overlapping_sep, expected); } // Empty input. { std::array expected = {}; test_one(""sv, short_sep, expected); test_one(""sv, long_sep, expected); } // Empty separator. { auto empty_sep = ""sv; std::array expected = {"a"sv, "b"sv, "c"sv}; test_one("abc"sv, empty_sep, expected); test_one("abc"sv, empty_sep, expected); } // Terminating null as a separator. { std::array expected = {"abc"sv, "def"sv}; test_one("abc\0def"sv, '\0', expected); test_one("abc\0\0def"sv, "\0\0"sv, expected); } // Different character types. { // `char`. test_function_call("abc def", ' ', std::array{"abc", "def"}); #ifndef TEST_HAS_NO_WIDE_CHARACTERS // `wchar_t`. test_function_call(L"abc def", L' ', std::array{L"abc", L"def"}); #endif // `char8_t`. test_function_call(u8"abc def", u8' ', std::array{u8"abc", u8"def"}); // `char16_t`. test_function_call(u"abc def", u' ', std::array{u"abc", u"def"}); // `char32_t`. test_function_call(U"abc def", U' ', std::array{U"abc", U"def"}); } // Non-character input. { std::array expected = {std::array{1, 2, 3}, std::array{4, 5, 6}}; test_one(std::array{1, 2, 3, 0, 4, 5, 6}, 0, expected); test_one(std::array{1, 2, 3, 0, 0, 0, 4, 5, 6}, std::array{0, 0, 0}, expected); } return true; } int main(int, char**) { main_test(); static_assert(main_test()); test_string_literals(); static_assert(test_string_literals()); test_l_r_values(); static_assert(test_l_r_values()); test_string_literal_separator(); static_assert(test_string_literal_separator()); // Note: map is not `constexpr`, so this test is runtime-only. test_nontrivial_characters(); return 0; }