/src/abseil-cpp/absl/strings/match.cc
Line | Count | Source |
1 | | // Copyright 2017 The Abseil Authors. |
2 | | // |
3 | | // Licensed under the Apache License, Version 2.0 (the "License"); |
4 | | // you may not use this file except in compliance with the License. |
5 | | // You may obtain a copy of the License at |
6 | | // |
7 | | // https://www.apache.org/licenses/LICENSE-2.0 |
8 | | // |
9 | | // Unless required by applicable law or agreed to in writing, software |
10 | | // distributed under the License is distributed on an "AS IS" BASIS, |
11 | | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
12 | | // See the License for the specific language governing permissions and |
13 | | // limitations under the License. |
14 | | |
15 | | #include "absl/strings/match.h" |
16 | | |
17 | | #include <algorithm> |
18 | | #include <cstddef> |
19 | | #include <cstdint> |
20 | | |
21 | | #include "absl/base/attributes.h" |
22 | | #include "absl/base/config.h" |
23 | | #include "absl/base/internal/endian.h" |
24 | | #include "absl/base/optimization.h" |
25 | | #include "absl/numeric/bits.h" |
26 | | #include "absl/strings/ascii.h" |
27 | | #include "absl/strings/internal/memutil.h" |
28 | | #include "absl/strings/string_view.h" |
29 | | |
30 | | namespace absl { |
31 | | ABSL_NAMESPACE_BEGIN |
32 | | |
33 | | bool EqualsIgnoreCase(absl::string_view piece1, |
34 | 0 | absl::string_view piece2) noexcept { |
35 | 0 | return (piece1.size() == piece2.size() && |
36 | 0 | 0 == absl::strings_internal::memcasecmp(piece1.data(), piece2.data(), |
37 | 0 | piece1.size())); |
38 | | // memcasecmp uses absl::ascii_tolower(). |
39 | 0 | } |
40 | | |
41 | | namespace { |
42 | | |
43 | | // For larger haystacks (n >= 256), Case-Insensitive Boyer-Moore-Horspool |
44 | | // provides sub-linear O(N / M) average-case performance, although it still has |
45 | | // O(N * M) theoretical worst-case scaling. |
46 | | // Scans the haystack from left to right using a search window of size `m`. |
47 | | // For each window offset, Horspool inspects the rightmost character of the |
48 | | // window (`haystack[pos + m - 1]`) first, enabling multi-byte shifts when |
49 | | // mismatches occur and reducing average search time to O(N / M). |
50 | | ABSL_ATTRIBUTE_NOINLINE bool StrContainsIgnoreCaseBMH( |
51 | 0 | absl::string_view haystack, absl::string_view needle) noexcept { |
52 | 0 | const size_t n = haystack.size(); |
53 | 0 | const size_t m = needle.size(); |
54 | | |
55 | | // Step 1: Initialize the 256-entry shift table. |
56 | | // Unknown characters default to full window shift of `m` bytes. |
57 | 0 | size_t shift[256]; |
58 | 0 | for (size_t i = 0; i < 256; ++i) { |
59 | 0 | shift[i] = m; |
60 | 0 | } |
61 | | |
62 | | // Populate shift distances for needle[0..m-2]. Dual assignment for |
63 | | // ascii_tolower and ascii_toupper stores distance from rightmost occurrence |
64 | | // to end of needle. |
65 | 0 | for (size_t i = 0; i < m - 1; ++i) { |
66 | 0 | const unsigned char c = static_cast<unsigned char>(needle[i]); |
67 | 0 | shift[static_cast<unsigned char>(absl::ascii_tolower(c))] = m - 1 - i; |
68 | 0 | shift[static_cast<unsigned char>(absl::ascii_toupper(c))] = m - 1 - i; |
69 | 0 | } |
70 | | |
71 | | // Step 2: Search loop across candidate window offsets. |
72 | 0 | size_t pos = 0; |
73 | 0 | while (pos <= n - m) { |
74 | 0 | const unsigned char last_hay = |
75 | 0 | static_cast<unsigned char>(haystack[pos + m - 1]); |
76 | 0 | const unsigned char last_needle = static_cast<unsigned char>(needle[m - 1]); |
77 | | |
78 | | // Check 1: Inspect right-most character of window first. |
79 | 0 | if (last_hay == last_needle || |
80 | 0 | absl::ascii_tolower(last_hay) == absl::ascii_tolower(last_needle)) { |
81 | | // Check 2: Pre-filter on first character of window. |
82 | 0 | const unsigned char first_hay = static_cast<unsigned char>(haystack[pos]); |
83 | 0 | const unsigned char first_needle = static_cast<unsigned char>(needle[0]); |
84 | 0 | if (first_hay == first_needle || |
85 | 0 | absl::ascii_tolower(first_hay) == absl::ascii_tolower(first_needle)) { |
86 | | // Check 3: Compare interior (m - 2) bytes. |
87 | 0 | if (EqualsIgnoreCase(haystack.substr(pos + 1, m - 2), |
88 | 0 | needle.substr(1, m - 2))) { |
89 | 0 | return true; |
90 | 0 | } |
91 | 0 | } |
92 | 0 | } |
93 | | |
94 | | // Advance window by shift distance determined by right-most haystack byte. |
95 | 0 | pos += shift[last_hay]; |
96 | 0 | } |
97 | 0 | return false; |
98 | 0 | } |
99 | | |
100 | | } // namespace |
101 | | |
102 | | bool StrContainsIgnoreCase(absl::string_view haystack, |
103 | 0 | absl::string_view needle) noexcept { |
104 | 0 | const size_t n = haystack.size(); |
105 | 0 | const size_t m = needle.size(); |
106 | 0 | if (m == 0) return true; |
107 | 0 | if (n < m) return false; |
108 | 0 | if (m == 1) return StrContainsIgnoreCase(haystack, needle[0]); |
109 | | |
110 | | // For short haystacks (n < 256) or small needles (m == 2), avoid the |
111 | | // initialization overhead of a 256-entry shift table. Instead, use a fast |
112 | | // first-and-last character prefilter before inspecting interior bytes. |
113 | 0 | if (n < 256 || m == 2) { |
114 | 0 | const char first_needle = |
115 | 0 | absl::ascii_tolower(static_cast<unsigned char>(needle[0])); |
116 | 0 | const char last_needle = |
117 | 0 | absl::ascii_tolower(static_cast<unsigned char>(needle[m - 1])); |
118 | |
|
119 | 0 | for (size_t pos = 0; pos <= n - m; ++pos) { |
120 | 0 | const unsigned char first_hay = static_cast<unsigned char>(haystack[pos]); |
121 | 0 | if (absl::ascii_tolower(first_hay) != first_needle) continue; |
122 | | |
123 | 0 | const unsigned char last_hay = |
124 | 0 | static_cast<unsigned char>(haystack[pos + m - 1]); |
125 | 0 | if (absl::ascii_tolower(last_hay) != last_needle) continue; |
126 | | |
127 | 0 | if (m == 2 || EqualsIgnoreCase(haystack.substr(pos + 1, m - 2), |
128 | 0 | needle.substr(1, m - 2))) { |
129 | 0 | return true; |
130 | 0 | } |
131 | 0 | } |
132 | 0 | return false; |
133 | 0 | } |
134 | | |
135 | 0 | return StrContainsIgnoreCaseBMH(haystack, needle); |
136 | 0 | } |
137 | | |
138 | | bool StrContainsIgnoreCase(absl::string_view haystack, |
139 | 0 | char needle) noexcept { |
140 | 0 | char upper_needle = absl::ascii_toupper(static_cast<unsigned char>(needle)); |
141 | 0 | char lower_needle = absl::ascii_tolower(static_cast<unsigned char>(needle)); |
142 | 0 | if (upper_needle == lower_needle) { |
143 | 0 | return StrContains(haystack, needle); |
144 | 0 | } |
145 | 0 | if (haystack.size() < 64) { |
146 | 0 | for (char c : haystack) { |
147 | 0 | if (c == lower_needle || c == upper_needle) return true; |
148 | 0 | } |
149 | 0 | return false; |
150 | 0 | } |
151 | 0 | const char both_cstr[3] = {lower_needle, upper_needle, '\0'}; |
152 | 0 | return haystack.find_first_of(both_cstr) != absl::string_view::npos; |
153 | 0 | } |
154 | | |
155 | | bool StartsWithIgnoreCase(absl::string_view text, |
156 | 0 | absl::string_view prefix) noexcept { |
157 | 0 | return (text.size() >= prefix.size()) && |
158 | 0 | EqualsIgnoreCase(text.substr(0, prefix.size()), prefix); |
159 | 0 | } |
160 | | |
161 | | bool EndsWithIgnoreCase(absl::string_view text, |
162 | 0 | absl::string_view suffix) noexcept { |
163 | 0 | return (text.size() >= suffix.size()) && |
164 | 0 | EqualsIgnoreCase(text.substr(text.size() - suffix.size()), suffix); |
165 | 0 | } |
166 | | |
167 | | absl::string_view FindLongestCommonPrefix(absl::string_view a, |
168 | 0 | absl::string_view b) { |
169 | 0 | const absl::string_view::size_type limit = std::min(a.size(), b.size()); |
170 | 0 | const char* const pa = a.data(); |
171 | 0 | const char* const pb = b.data(); |
172 | 0 | absl::string_view::size_type count = (unsigned) 0; |
173 | |
|
174 | 0 | if (ABSL_PREDICT_FALSE(limit < 8)) { |
175 | 0 | while (ABSL_PREDICT_TRUE(count + 2 <= limit)) { |
176 | 0 | uint16_t xor_bytes = absl::little_endian::Load16(pa + count) ^ |
177 | 0 | absl::little_endian::Load16(pb + count); |
178 | 0 | if (ABSL_PREDICT_FALSE(xor_bytes != 0)) { |
179 | 0 | if (ABSL_PREDICT_TRUE((xor_bytes & 0xff) == 0)) ++count; |
180 | 0 | return absl::string_view(pa, count); |
181 | 0 | } |
182 | 0 | count += 2; |
183 | 0 | } |
184 | 0 | if (ABSL_PREDICT_TRUE(count != limit)) { |
185 | 0 | if (ABSL_PREDICT_TRUE(pa[count] == pb[count])) ++count; |
186 | 0 | } |
187 | 0 | return absl::string_view(pa, count); |
188 | 0 | } |
189 | | |
190 | 0 | do { |
191 | 0 | uint64_t xor_bytes = absl::little_endian::Load64(pa + count) ^ |
192 | 0 | absl::little_endian::Load64(pb + count); |
193 | 0 | if (ABSL_PREDICT_FALSE(xor_bytes != 0)) { |
194 | 0 | count += static_cast<uint64_t>(absl::countr_zero(xor_bytes) >> 3); |
195 | 0 | return absl::string_view(pa, count); |
196 | 0 | } |
197 | 0 | count += 8; |
198 | 0 | } while (ABSL_PREDICT_TRUE(count + 8 < limit)); |
199 | | |
200 | 0 | count = limit - 8; |
201 | 0 | uint64_t xor_bytes = absl::little_endian::Load64(pa + count) ^ |
202 | 0 | absl::little_endian::Load64(pb + count); |
203 | 0 | if (ABSL_PREDICT_TRUE(xor_bytes != 0)) { |
204 | 0 | count += static_cast<uint64_t>(absl::countr_zero(xor_bytes) >> 3); |
205 | 0 | return absl::string_view(pa, count); |
206 | 0 | } |
207 | 0 | return absl::string_view(pa, limit); |
208 | 0 | } |
209 | | |
210 | | absl::string_view FindLongestCommonSuffix(absl::string_view a, |
211 | 0 | absl::string_view b) { |
212 | 0 | const absl::string_view::size_type limit = std::min(a.size(), b.size()); |
213 | 0 | if (limit == 0) return absl::string_view(); |
214 | | |
215 | 0 | const char* pa = a.data() + a.size() - 1; |
216 | 0 | const char* pb = b.data() + b.size() - 1; |
217 | 0 | absl::string_view::size_type count = (unsigned) 0; |
218 | 0 | while (count < limit && *pa == *pb) { |
219 | 0 | --pa; |
220 | 0 | --pb; |
221 | 0 | ++count; |
222 | 0 | } |
223 | |
|
224 | 0 | return absl::string_view(++pa, count); |
225 | 0 | } |
226 | | |
227 | | ABSL_NAMESPACE_END |
228 | | } // namespace absl |