diff --git a/libc/src/__support/CPP/string_view.h b/libc/src/__support/CPP/string_view.h index b6e9a2d0e3843..6c17e9b3dbe0c 100644 --- a/libc/src/__support/CPP/string_view.h +++ b/libc/src/__support/CPP/string_view.h @@ -33,41 +33,30 @@ class string_view { return 0; } - static constexpr size_t length(const char *Str) { - for (const char *End = Str;; ++End) - if (*End == '\0') - return End - Str; - } - - bool equals(string_view Other) const { - return (Len == Other.Len && - compareMemory(Data, Other.Data, Other.Len) == 0); - } - public: - using value_type = char; - using size_type = size_t; - using difference_type = ptrdiff_t; - using pointer = char *; - using const_pointer = const char *; - using reference = char &; - using const_reference = const char &; - using const_iterator = char *; - using iterator = const_iterator; - // special value equal to the maximum value representable by the type // size_type. - inline static constexpr size_t npos = -1; + static constexpr size_t npos = -1; constexpr string_view() : Data(nullptr), Len(0) {} // Assumes Str is a null-terminated string. The length of the string does // not include the terminating null character. - // Preconditions: [Str, Str + ​length(Str)) is a valid range. - constexpr string_view(const char *Str) : Data(Str), Len(length(Str)) {} + explicit constexpr string_view(const char *Str) : Data(Str), Len(0) { + if (Str == nullptr) + return; + for (const char *D = Data; *D != '\0'; ++D, ++Len) + ; + if (Len == 0) + Data = nullptr; + } - // Preconditions: [Str, Str + N) is a valid range. - constexpr string_view(const char *Str, size_t N) : Data(Str), Len(N) {} + explicit constexpr string_view(const char *Str, size_t N) + : Data(N ? Str : nullptr), Len(Str == nullptr ? 0 : N) {} + + // Ctor for raw literal. + template + constexpr string_view(const char (&Str)[N]) : string_view(Str, N - 1) {} constexpr const char *data() const { return Data; } @@ -101,14 +90,16 @@ class string_view { return Len < Other.Len ? -1 : 1; } + // An equivalent method is not available in std::string_view. + bool equals(string_view Other) const { + return (Len == Other.Len && + compareMemory(Data, Other.Data, Other.Len) == 0); + } + inline bool operator==(string_view Other) const { return equals(Other); } inline bool operator!=(string_view Other) const { return !(*this == Other); } - inline bool operator<(string_view Other) const { - return compare(Other) == -1; - } - inline bool operator<=(string_view Other) const { - return compare(Other) != 1; - } + inline bool operator<(string_view Other) const { return compare(Other) == -1; } + inline bool operator<=(string_view Other) const { return compare(Other) != 1; } inline bool operator>(string_view Other) const { return compare(Other) == 1; } inline bool operator>=(string_view Other) const { return compare(Other) != -1; @@ -125,6 +116,16 @@ class string_view { // The behavior is undefined if n > size(). void remove_suffix(size_t N) { Len -= N; } + // An equivalent method is not available in std::string_view. + string_view trim(const char C) const { + string_view Copy = *this; + while (Copy.starts_with(C)) + Copy = Copy.drop_front(); + while (Copy.ends_with(C)) + Copy = Copy.drop_back(); + return Copy; + } + // Check if this string starts with the given Prefix. bool starts_with(string_view Prefix) const { return Len >= Prefix.Len && @@ -161,27 +162,129 @@ class string_view { return string_view(Data + Start, min(N, Len - Start)); } + // Search for the first character matching the character + // + // Returns The index of the first character satisfying the character starting + // from From, or npos if not found. + size_t find_first_of(const char c, size_t From = 0) const noexcept { + string_view S = drop_front(From); + while (!S.empty()) { + if (S.front() == c) + return size() - S.size(); + S = S.drop_front(); + } + return npos; + } + + // Search for the last character matching the character + // + // Return the index of the last character equal to the |c| before End. + size_t find_last_of(const char c, size_t End = npos) const { + End = End > size() ? size() : End + 1; + string_view S = drop_back(size() - End); + while (!S.empty()) { + if (S.back() == c) + return S.size() - 1; + S = S.drop_back(); + } + return npos; + } + + // Search for the first character satisfying the predicate Function + // + // Returns The index of the first character satisfying Function starting from + // From, or npos if not found. + template size_t find_if(F Function, size_t From = 0) const { + string_view S = drop_front(From); + while (!S.empty()) { + if (Function(S.front())) + return size() - S.size(); + S = S.drop_front(); + } + return npos; + } + + // Search for the first character not satisfying the predicate Function + // Returns The index of the first character not satisfying Function starting + // from From, or npos if not found. + template size_t find_if_not(F Function, size_t From = 0) const { + return find_if([Function](char c) { return !Function(c); }, From); + } + // front - Get the first character in the string. char front() const { return Data[0]; } // back - Get the last character in the string. char back() const { return Data[Len - 1]; } - // Finds the first occurence of c in this view, starting at position From. - size_t find_first_of(const char c, size_t From = 0) const { - for (size_t Pos = From; Pos < size(); ++Pos) - if ((*this)[Pos] == c) - return Pos; - return npos; + // Return a string_view equal to 'this' but with the first N elements + // dropped. + string_view drop_front(size_t N = 1) const { return substr(N); } + + // Return a string_view equal to 'this' but with the last N elements + // dropped. + string_view drop_back(size_t N = 1) const { return substr(0, size() - N); } + + // Return a string_view equal to 'this' but with only the first N + // elements remaining. If N is greater than the length of the + // string, the entire string is returned. + string_view take_front(size_t N = 1) const { + if (N >= size()) + return *this; + return drop_back(size() - N); } - // Finds the last occurence of c in this view, ending at position End. - size_t find_last_of(const char c, size_t End = npos) const { - End = End > size() ? size() : End + 1; - for (; End > 0; --End) - if ((*this)[End - 1] == c) - return End - 1; - return npos; + // Return a string_view equal to 'this' but with only the last N + // elements remaining. If N is greater than the length of the + // string, the entire string is returned. + string_view take_back(size_t N = 1) const { + if (N >= size()) + return *this; + return drop_front(size() - N); + } + + // Return the longest prefix of 'this' such that every character + // in the prefix satisfies the given predicate. + template string_view take_while(F Function) const { + return substr(0, find_if_not(Function)); + } + + // Return the longest prefix of 'this' such that no character in + // the prefix satisfies the given predicate. + template string_view take_until(F Function) const { + return substr(0, find_if(Function)); + } + + // Return a string_view equal to 'this', but with all characters satisfying + // the given predicate dropped from the beginning of the string. + template string_view drop_while(F Function) const { + return substr(find_if_not(Function)); + } + + // Return a string_view equal to 'this', but with all characters not + // satisfying the given predicate dropped from the beginning of the string. + template string_view drop_until(F Function) const { + return substr(find_if(Function)); + } + + // Returns true if this string_view has the given prefix and removes that + // prefix. + bool consume_front(string_view Prefix) { + if (!starts_with(Prefix)) + return false; + + *this = drop_front(Prefix.size()); + return true; + } + + // Returns true if this string_view has the given suffix and removes that + // suffix. + bool consume_back(string_view Suffix) { + if (!ends_with(Suffix)) + return false; + + *this = drop_back(Suffix.size()); + return true; } }; diff --git a/libc/src/stdio/printf_core/core_structs.h b/libc/src/stdio/printf_core/core_structs.h index 25e530337eec4..c229096039feb 100644 --- a/libc/src/stdio/printf_core/core_structs.h +++ b/libc/src/stdio/printf_core/core_structs.h @@ -58,8 +58,8 @@ struct FormatSection { if (has_conv != other.has_conv) return false; - if (cpp::string_view(raw_string, raw_len) != - cpp::string_view(other.raw_string, other.raw_len)) + if (!cpp::string_view(raw_string, raw_len) + .equals(cpp::string_view(other.raw_string, other.raw_len))) return false; if (has_conv) { diff --git a/libc/test/src/__support/CPP/stringview_test.cpp b/libc/test/src/__support/CPP/stringview_test.cpp index bd6b614218bb4..87853d8b1cfc7 100644 --- a/libc/test/src/__support/CPP/stringview_test.cpp +++ b/libc/test/src/__support/CPP/stringview_test.cpp @@ -1,5 +1,4 @@ -//===-- Unittests for string_view -//------------------------------------------===// +//===-- Unittests for string_view ------------------------------------------===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. @@ -19,11 +18,19 @@ TEST(LlvmLibcStringViewTest, InitializeCheck) { v = string_view(""); ASSERT_EQ(v.size(), size_t(0)); - ASSERT_TRUE(v.data() != nullptr); + ASSERT_TRUE(v.data() == nullptr); + + v = string_view(nullptr); + ASSERT_EQ(v.size(), size_t(0)); + ASSERT_TRUE(v.data() == nullptr); + + v = string_view(nullptr, 10); + ASSERT_EQ(v.size(), size_t(0)); + ASSERT_TRUE(v.data() == nullptr); v = string_view("abc", 0); ASSERT_EQ(v.size(), size_t(0)); - ASSERT_TRUE(v.data() != nullptr); + ASSERT_TRUE(v.data() == nullptr); v = string_view("123456789"); ASSERT_EQ(v.size(), size_t(9)); @@ -31,13 +38,13 @@ TEST(LlvmLibcStringViewTest, InitializeCheck) { TEST(LlvmLibcStringViewTest, Equals) { string_view v("abc"); - ASSERT_EQ(v, string_view("abc")); - ASSERT_NE(v, string_view()); - ASSERT_NE(v, string_view("")); - ASSERT_NE(v, string_view("123")); - ASSERT_NE(v, string_view("abd")); - ASSERT_NE(v, string_view("aaa")); - ASSERT_NE(v, string_view("abcde")); + ASSERT_TRUE(v.equals(string_view("abc"))); + ASSERT_FALSE(v.equals(string_view())); + ASSERT_FALSE(v.equals(string_view(""))); + ASSERT_FALSE(v.equals(string_view("123"))); + ASSERT_FALSE(v.equals(string_view("abd"))); + ASSERT_FALSE(v.equals(string_view("aaa"))); + ASSERT_FALSE(v.equals(string_view("abcde"))); } TEST(LlvmLibcStringViewTest, startsWith) { @@ -74,12 +81,12 @@ TEST(LlvmLibcStringViewTest, RemovePrefix) { string_view a("123456789"); a.remove_prefix(0); ASSERT_EQ(a.size(), size_t(9)); - ASSERT_TRUE(a == "123456789"); + ASSERT_TRUE(a.equals(string_view("123456789"))); string_view b("123456789"); b.remove_prefix(4); ASSERT_EQ(b.size(), size_t(5)); - ASSERT_TRUE(b == "56789"); + ASSERT_TRUE(b.equals(string_view("56789"))); string_view c("123456789"); c.remove_prefix(9); @@ -90,18 +97,59 @@ TEST(LlvmLibcStringViewTest, RemoveSuffix) { string_view a("123456789"); a.remove_suffix(0); ASSERT_EQ(a.size(), size_t(9)); - ASSERT_TRUE(a == "123456789"); + ASSERT_TRUE(a.equals(string_view("123456789"))); string_view b("123456789"); b.remove_suffix(4); ASSERT_EQ(b.size(), size_t(5)); - ASSERT_TRUE(b == "12345"); + ASSERT_TRUE(b.equals(string_view("12345"))); string_view c("123456789"); c.remove_suffix(9); ASSERT_EQ(c.size(), size_t(0)); } +TEST(LlvmLibcStringViewTest, TrimSingleChar) { + string_view v(" 123456789 "); + auto t = v.trim(' '); + ASSERT_EQ(t.size(), size_t(9)); + ASSERT_TRUE(t.equals(string_view("123456789"))); + + v = string_view("====12345=="); + t = v.trim(' '); + ASSERT_EQ(v.size(), size_t(11)); + ASSERT_TRUE(t.equals(string_view("====12345=="))); + + t = v.trim('='); + ASSERT_EQ(t.size(), size_t(5)); + ASSERT_TRUE(t.equals(string_view("12345"))); + + v = string_view("12345==="); + t = v.trim('='); + ASSERT_EQ(t.size(), size_t(5)); + ASSERT_TRUE(t.equals(string_view("12345"))); + + v = string_view("===========12345"); + t = v.trim('='); + ASSERT_EQ(t.size(), size_t(5)); + ASSERT_TRUE(t.equals(string_view("12345"))); + + v = string_view("============"); + t = v.trim('='); + ASSERT_EQ(t.size(), size_t(0)); + ASSERT_TRUE(t.data() == nullptr); + + v = string_view(); + t = v.trim(' '); + ASSERT_EQ(t.size(), size_t(0)); + ASSERT_TRUE(t.data() == nullptr); + + v = string_view(""); + t = v.trim(' '); + ASSERT_EQ(t.size(), size_t(0)); + ASSERT_TRUE(t.data() == nullptr); +} + TEST(LlvmLibcStringViewTest, Observer) { string_view ABC("abc"); ASSERT_EQ(ABC.size(), size_t(3)); @@ -112,6 +160,32 @@ TEST(LlvmLibcStringViewTest, Observer) { bool isDigit(char c) { return c >= '0' && c <= '9'; } +TEST(LlvmLibcStringViewTest, Transform) { + ASSERT_TRUE(string_view("123abc").drop_back(3).equals("123")); + ASSERT_TRUE(string_view("123abc").drop_front(3).equals("abc")); + ASSERT_TRUE(string_view("123abc").take_back(3).equals("abc")); + ASSERT_TRUE(string_view("123abc").take_front(3).equals("123")); + + ASSERT_TRUE(string_view("123abc").take_while(&isDigit).equals("123")); + ASSERT_TRUE(string_view("abc123").take_until(&isDigit).equals("abc")); + ASSERT_TRUE(string_view("123abc").drop_while(&isDigit).equals("abc")); + ASSERT_TRUE(string_view("abc123").drop_until(&isDigit).equals("123")); +} + +TEST(LlvmLibcStringViewTest, ConsumeFront) { + string_view Tmp("abc"); + ASSERT_FALSE(Tmp.consume_front("###")); + ASSERT_TRUE(Tmp.consume_front("ab")); + ASSERT_TRUE(Tmp.equals("c")); +} + +TEST(LlvmLibcStringViewTest, ConsumeBack) { + string_view Tmp("abc"); + ASSERT_FALSE(Tmp.consume_back("###")); + ASSERT_TRUE(Tmp.consume_back("bc")); + ASSERT_TRUE(Tmp.equals("a")); +} + TEST(LlvmLibcStringViewTest, FindFirstOf) { string_view Tmp("abca"); ASSERT_TRUE(Tmp.find_first_of('a') == 0); diff --git a/libc/test/src/dirent/dirent_test.cpp b/libc/test/src/dirent/dirent_test.cpp index 82fb09acedd56..5fb32052ce032 100644 --- a/libc/test/src/dirent/dirent_test.cpp +++ b/libc/test/src/dirent/dirent_test.cpp @@ -33,13 +33,13 @@ TEST(LlvmLibcDirentTest, SimpleOpenAndRead) { struct ::dirent *d = __llvm_libc::readdir(dir); if (d == nullptr) break; - if (string_view(&d->d_name[0]) == "file1.txt") + if (string_view(&d->d_name[0]).equals("file1.txt")) file1 = d; - if (string_view(&d->d_name[0]) == "file2.txt") + if (string_view(&d->d_name[0]).equals("file2.txt")) file2 = d; - if (string_view(&d->d_name[0]) == "dir1") + if (string_view(&d->d_name[0]).equals("dir1")) dir1 = d; - if (string_view(&d->d_name[0]) == "dir2") + if (string_view(&d->d_name[0]).equals("dir2")) dir2 = d; } diff --git a/libc/utils/MPFRWrapper/MPFRUtils.cpp b/libc/utils/MPFRWrapper/MPFRUtils.cpp index 54c0939929576..60651dacd7421 100644 --- a/libc/utils/MPFRWrapper/MPFRUtils.cpp +++ b/libc/utils/MPFRWrapper/MPFRUtils.cpp @@ -380,12 +380,7 @@ class MPFRNumber { char buffer[printBufSize]; mpfr_snprintf(buffer, printBufSize, "%100.50Rf", value); cpp::string_view view(buffer); - // Trim whitespaces - const char whitespace = ' '; - while (!view.empty() && view.front() == whitespace) - view.remove_prefix(1); - while (!view.empty() && view.back() == whitespace) - view.remove_suffix(1); + view = view.trim(' '); return std::string(view.data()); }