/src/CMake/Source/cmStringAlgorithms.cxx
Line | Count | Source |
1 | | /* Distributed under the OSI-approved BSD 3-Clause License. See accompanying |
2 | | file LICENSE.rst or https://cmake.org/licensing for details. */ |
3 | | #include "cmStringAlgorithms.h" |
4 | | |
5 | | #include <algorithm> |
6 | | #include <cerrno> |
7 | | #include <cstddef> // IWYU pragma: keep |
8 | | #include <cstdio> |
9 | | #include <cstdlib> |
10 | | #include <limits> |
11 | | |
12 | | #include "cmsys/String.h" |
13 | | |
14 | | bool cmStrCaseEq(cm::string_view s1, cm::string_view s2) |
15 | 17.1k | { |
16 | 17.1k | if (s1.size() != s2.size()) { |
17 | 16.9k | return false; |
18 | 16.9k | } |
19 | | |
20 | 274 | return std::equal(s1.begin(), s1.end(), s2.begin(), [](char a, char b) { |
21 | 274 | return cmsysString_tolower(a) == cmsysString_tolower(b); |
22 | 274 | }); |
23 | 17.1k | } |
24 | | |
25 | | std::string cmTrimWhitespace(cm::string_view str) |
26 | 1.50k | { |
27 | | // XXX(clang-tidy): This declaration and the next cannot be `const auto*` |
28 | | // because the qualification of `auto` is platform-dependent. |
29 | | // NOLINTNEXTLINE(readability-qualified-auto) |
30 | 1.50k | auto start = str.begin(); |
31 | 119k | while (start != str.end() && cmsysString_isspace(*start)) { |
32 | 118k | ++start; |
33 | 118k | } |
34 | 1.50k | if (start == str.end()) { |
35 | 30 | return std::string(); |
36 | 30 | } |
37 | | // NOLINTNEXTLINE(readability-qualified-auto) |
38 | 1.47k | auto stop = str.end() - 1; |
39 | 19.2k | while (cmsysString_isspace(*stop)) { |
40 | 17.7k | --stop; |
41 | 17.7k | } |
42 | 1.47k | return std::string(start, stop + 1); |
43 | 1.50k | } |
44 | | |
45 | | cm::string_view cmStripWhitespace(cm::string_view str) |
46 | 0 | { |
47 | 0 | std::string::size_type const l = str.size(); |
48 | |
|
49 | 0 | std::string::size_type s = 0; |
50 | 0 | while (s < l && cmsysString_isspace(str[s])) { |
51 | 0 | ++s; |
52 | 0 | } |
53 | 0 | if (s == l) { |
54 | 0 | return cm::string_view{}; |
55 | 0 | } |
56 | 0 | std::string::size_type e = l - 1; |
57 | 0 | while (cmsysString_isspace(str[e])) { |
58 | 0 | --e; |
59 | 0 | } |
60 | 0 | return str.substr(s, e + 1 - s); |
61 | 0 | } |
62 | | |
63 | | std::string cmRemoveQuotes(cm::string_view str) |
64 | 1.50k | { |
65 | | // We process only strings that have two quotes at least. |
66 | | // Also front() and back() are only defined behavior on non empty strings. |
67 | 1.50k | if (str.size() >= 2 && // |
68 | 1.45k | str.front() == '"' && // |
69 | 62 | str.back() == '"') { |
70 | | // Remove a quote from the front and back |
71 | 13 | str.remove_prefix(1); |
72 | 13 | str.remove_suffix(1); |
73 | 13 | } |
74 | 1.50k | return std::string(str); |
75 | 1.50k | } |
76 | | |
77 | | std::string cmEscapeQuotes(cm::string_view str) |
78 | 1.50k | { |
79 | 1.50k | std::string result; |
80 | 1.50k | result.reserve(str.size()); |
81 | 1.81M | for (char const ch : str) { |
82 | 1.81M | if (ch == '"') { |
83 | 36.2k | result += '\\'; |
84 | 36.2k | } |
85 | 1.81M | result += ch; |
86 | 1.81M | } |
87 | 1.50k | return result; |
88 | 1.50k | } |
89 | | |
90 | | std::size_t cmLevenshteinDistance(cm::string_view a, cm::string_view b) |
91 | 0 | { |
92 | 0 | if (a == b) { |
93 | 0 | return 0; |
94 | 0 | } |
95 | | |
96 | 0 | std::size_t const aSize = a.size(); |
97 | 0 | std::size_t const bSize = b.size(); |
98 | 0 | if (aSize == 0) { |
99 | 0 | return bSize; |
100 | 0 | } |
101 | 0 | if (bSize == 0) { |
102 | 0 | return aSize; |
103 | 0 | } |
104 | | |
105 | 0 | std::vector<std::size_t> previous(bSize + 1, 0); |
106 | 0 | std::vector<std::size_t> current(bSize + 1, 0); |
107 | |
|
108 | 0 | for (std::size_t j = 0; j <= bSize; ++j) { |
109 | 0 | previous[j] = j; |
110 | 0 | } |
111 | |
|
112 | 0 | for (std::size_t i = 1; i <= aSize; ++i) { |
113 | 0 | current[0] = i; |
114 | 0 | for (std::size_t j = 1; j <= bSize; ++j) { |
115 | 0 | std::size_t const substitutionCost = (a[i - 1] == b[j - 1]) ? 0 : 1; |
116 | 0 | current[j] = std::min({ previous[j] + 1, current[j - 1] + 1, |
117 | 0 | previous[j - 1] + substitutionCost }); |
118 | 0 | } |
119 | 0 | previous.swap(current); |
120 | 0 | } |
121 | |
|
122 | 0 | return previous[bSize]; |
123 | 0 | } |
124 | | |
125 | | std::string cmFindClosestString(cm::string_view input, |
126 | | std::vector<std::string> const& candidates) |
127 | 0 | { |
128 | 0 | if (candidates.empty()) { |
129 | 0 | return std::string(); |
130 | 0 | } |
131 | | |
132 | 0 | std::string best; |
133 | 0 | std::size_t bestDistance = std::numeric_limits<std::size_t>::max(); |
134 | 0 | for (std::string const& candidate : candidates) { |
135 | 0 | std::size_t const distance = cmLevenshteinDistance(input, candidate); |
136 | 0 | if (distance < bestDistance) { |
137 | 0 | bestDistance = distance; |
138 | 0 | best = candidate; |
139 | 0 | } |
140 | 0 | } |
141 | | |
142 | | // Scale the acceptable edit distance with the input length (roughly one |
143 | | // typo per 5 characters), but keep it within [kMinDistance, kMaxDistance] |
144 | | // so very short inputs still allow one edit and very long inputs don't |
145 | | // start accepting barely-related matches. |
146 | 0 | std::size_t const kMinDistance = 1; |
147 | 0 | std::size_t const kMaxDistance = 4; |
148 | 0 | std::size_t const maxDistance = |
149 | 0 | std::min(kMaxDistance, std::max(kMinDistance, input.size() / 5 + 1)); |
150 | 0 | if (bestDistance > maxDistance) { |
151 | 0 | return std::string(); |
152 | 0 | } |
153 | | |
154 | 0 | return best; |
155 | 0 | } |
156 | | |
157 | | namespace { |
158 | | template <std::size_t N, typename T> |
159 | | inline void MakeDigits(cm::string_view& view, char (&digits)[N], |
160 | | char const* pattern, T value) |
161 | 1.04M | { |
162 | 1.04M | int res = std::snprintf(digits, N, pattern, value); |
163 | 1.04M | if (res > 0 && res < static_cast<int>(N)) { |
164 | 1.04M | view = cm::string_view(digits, static_cast<std::size_t>(res)); |
165 | 1.04M | } |
166 | 1.04M | } cmStringAlgorithms.cxx:void (anonymous namespace)::MakeDigits<32ul, int>(std::__1::basic_string_view<char, std::__1::char_traits<char> >&, char (&) [32ul], char const*, int) Line | Count | Source | 161 | 998k | { | 162 | 998k | int res = std::snprintf(digits, N, pattern, value); | 163 | 998k | if (res > 0 && res < static_cast<int>(N)) { | 164 | 998k | view = cm::string_view(digits, static_cast<std::size_t>(res)); | 165 | 998k | } | 166 | 998k | } |
cmStringAlgorithms.cxx:void (anonymous namespace)::MakeDigits<32ul, unsigned int>(std::__1::basic_string_view<char, std::__1::char_traits<char> >&, char (&) [32ul], char const*, unsigned int) Line | Count | Source | 161 | 62 | { | 162 | 62 | int res = std::snprintf(digits, N, pattern, value); | 163 | 62 | if (res > 0 && res < static_cast<int>(N)) { | 164 | 62 | view = cm::string_view(digits, static_cast<std::size_t>(res)); | 165 | 62 | } | 166 | 62 | } |
cmStringAlgorithms.cxx:void (anonymous namespace)::MakeDigits<32ul, long>(std::__1::basic_string_view<char, std::__1::char_traits<char> >&, char (&) [32ul], char const*, long) Line | Count | Source | 161 | 15.3k | { | 162 | 15.3k | int res = std::snprintf(digits, N, pattern, value); | 163 | 15.3k | if (res > 0 && res < static_cast<int>(N)) { | 164 | 15.3k | view = cm::string_view(digits, static_cast<std::size_t>(res)); | 165 | 15.3k | } | 166 | 15.3k | } |
cmStringAlgorithms.cxx:void (anonymous namespace)::MakeDigits<32ul, unsigned long>(std::__1::basic_string_view<char, std::__1::char_traits<char> >&, char (&) [32ul], char const*, unsigned long) Line | Count | Source | 161 | 26.4k | { | 162 | 26.4k | int res = std::snprintf(digits, N, pattern, value); | 163 | 26.4k | if (res > 0 && res < static_cast<int>(N)) { | 164 | 26.4k | view = cm::string_view(digits, static_cast<std::size_t>(res)); | 165 | 26.4k | } | 166 | 26.4k | } |
Unexecuted instantiation: cmStringAlgorithms.cxx:void (anonymous namespace)::MakeDigits<32ul, long long>(std::__1::basic_string_view<char, std::__1::char_traits<char> >&, char (&) [32ul], char const*, long long) Unexecuted instantiation: cmStringAlgorithms.cxx:void (anonymous namespace)::MakeDigits<32ul, unsigned long long>(std::__1::basic_string_view<char, std::__1::char_traits<char> >&, char (&) [32ul], char const*, unsigned long long) Unexecuted instantiation: cmStringAlgorithms.cxx:void (anonymous namespace)::MakeDigits<32ul, double>(std::__1::basic_string_view<char, std::__1::char_traits<char> >&, char (&) [32ul], char const*, double) |
167 | | } // unnamed namespace |
168 | | |
169 | | cmAlphaNum::cmAlphaNum(int val) |
170 | 998k | { |
171 | 998k | MakeDigits(this->View_, this->Digits_, "%i", val); |
172 | 998k | } |
173 | | |
174 | | cmAlphaNum::cmAlphaNum(unsigned int val) |
175 | 62 | { |
176 | 62 | MakeDigits(this->View_, this->Digits_, "%u", val); |
177 | 62 | } |
178 | | |
179 | | cmAlphaNum::cmAlphaNum(long int val) |
180 | 15.3k | { |
181 | 15.3k | MakeDigits(this->View_, this->Digits_, "%li", val); |
182 | 15.3k | } |
183 | | |
184 | | cmAlphaNum::cmAlphaNum(unsigned long int val) |
185 | 26.4k | { |
186 | 26.4k | MakeDigits(this->View_, this->Digits_, "%lu", val); |
187 | 26.4k | } |
188 | | |
189 | | cmAlphaNum::cmAlphaNum(long long int val) |
190 | 0 | { |
191 | 0 | MakeDigits(this->View_, this->Digits_, "%lli", val); |
192 | 0 | } |
193 | | |
194 | | cmAlphaNum::cmAlphaNum(unsigned long long int val) |
195 | 0 | { |
196 | 0 | MakeDigits(this->View_, this->Digits_, "%llu", val); |
197 | 0 | } |
198 | | |
199 | | cmAlphaNum::cmAlphaNum(float val) |
200 | 0 | { |
201 | 0 | MakeDigits(this->View_, this->Digits_, "%g", static_cast<double>(val)); |
202 | 0 | } |
203 | | |
204 | | cmAlphaNum::cmAlphaNum(double val) |
205 | 0 | { |
206 | 0 | MakeDigits(this->View_, this->Digits_, "%g", val); |
207 | 0 | } |
208 | | |
209 | | std::string cmCatViews( |
210 | | std::initializer_list<std::pair<cm::string_view, std::string*>> views) |
211 | 7.16M | { |
212 | 7.16M | std::size_t totalSize = 0; |
213 | 7.16M | std::string* rvalueString = nullptr; |
214 | 7.16M | std::size_t rvalueStringLength = 0; |
215 | 7.16M | std::size_t rvalueStringOffset = 0; |
216 | 16.8M | for (auto const& view : views) { |
217 | | // Find the rvalue string with the largest capacity. |
218 | 16.8M | if (view.second && |
219 | 1.00M | (!rvalueString || |
220 | 900k | view.second->capacity() > rvalueString->capacity())) { |
221 | 900k | rvalueString = view.second; |
222 | 900k | rvalueStringLength = rvalueString->length(); |
223 | 900k | rvalueStringOffset = totalSize; |
224 | 900k | } |
225 | 16.8M | totalSize += view.first.size(); |
226 | 16.8M | } |
227 | | |
228 | 7.16M | std::string result; |
229 | 7.16M | std::string::size_type initialLen = 0; |
230 | 7.16M | if (rvalueString && rvalueString->capacity() >= totalSize) { |
231 | 214k | result = std::move(*rvalueString); |
232 | 6.95M | } else { |
233 | 6.95M | rvalueString = nullptr; |
234 | 6.95M | } |
235 | 7.16M | result.resize(totalSize); |
236 | 7.16M | if (rvalueString && rvalueStringOffset > 0) { |
237 | 73.0k | std::copy_backward(result.begin(), result.begin() + rvalueStringLength, |
238 | 73.0k | result.begin() + rvalueStringOffset + |
239 | 73.0k | rvalueStringLength); |
240 | 73.0k | } |
241 | 7.16M | std::string::iterator sit = result.begin() + initialLen; |
242 | 16.8M | for (auto const& view : views) { |
243 | 16.8M | if (rvalueString && view.second == rvalueString) { |
244 | 214k | sit += rvalueStringLength; |
245 | 16.6M | } else { |
246 | 16.6M | sit = std::copy_n(view.first.data(), view.first.size(), sit); |
247 | 16.6M | } |
248 | 16.8M | } |
249 | 7.16M | return result; |
250 | 7.16M | } |
251 | | |
252 | | bool cmStrToLong(char const* str, long* value) |
253 | 0 | { |
254 | 0 | errno = 0; |
255 | 0 | char* endp; |
256 | 0 | *value = strtol(str, &endp, 10); |
257 | 0 | return (*endp == '\0') && (endp != str) && (errno == 0); |
258 | 0 | } |
259 | | |
260 | | bool cmStrToLong(std::string const& str, long* value) |
261 | 0 | { |
262 | 0 | return cmStrToLong(str.c_str(), value); |
263 | 0 | } |
264 | | |
265 | | bool cmStrToULong(char const* str, unsigned long* value) |
266 | 0 | { |
267 | 0 | errno = 0; |
268 | 0 | char* endp; |
269 | 0 | while (cmsysString_isspace(*str)) { |
270 | 0 | ++str; |
271 | 0 | } |
272 | 0 | if (*str == '-') { |
273 | 0 | return false; |
274 | 0 | } |
275 | 0 | *value = strtoul(str, &endp, 10); |
276 | 0 | return (*endp == '\0') && (endp != str) && (errno == 0); |
277 | 0 | } |
278 | | |
279 | | bool cmStrToULong(std::string const& str, unsigned long* value) |
280 | 0 | { |
281 | 0 | return cmStrToULong(str.c_str(), value); |
282 | 0 | } |
283 | | |
284 | | bool cmStrToLongLong(char const* str, long long* value) |
285 | 0 | { |
286 | 0 | errno = 0; |
287 | 0 | char* endp; |
288 | 0 | *value = strtoll(str, &endp, 10); |
289 | 0 | return (*endp == '\0') && (endp != str) && (errno == 0); |
290 | 0 | } |
291 | | |
292 | | bool cmStrToLongLong(std::string const& str, long long* value) |
293 | 0 | { |
294 | 0 | return cmStrToLongLong(str.c_str(), value); |
295 | 0 | } |
296 | | |
297 | | bool cmStrToULongLong(char const* str, unsigned long long* value) |
298 | 0 | { |
299 | 0 | errno = 0; |
300 | 0 | char* endp; |
301 | 0 | while (cmsysString_isspace(*str)) { |
302 | 0 | ++str; |
303 | 0 | } |
304 | 0 | if (*str == '-') { |
305 | 0 | return false; |
306 | 0 | } |
307 | 0 | *value = strtoull(str, &endp, 10); |
308 | 0 | return (*endp == '\0') && (endp != str) && (errno == 0); |
309 | 0 | } |
310 | | |
311 | | bool cmStrToULongLong(std::string const& str, unsigned long long* value) |
312 | 0 | { |
313 | 0 | return cmStrToULongLong(str.c_str(), value); |
314 | 0 | } |
315 | | |
316 | | std::string cmJoin(std::vector<std::string> const& rng, |
317 | | cm::string_view separator, cm::string_view initial) |
318 | 33.8k | { |
319 | 33.8k | return cmJoinStrings(rng, separator, initial); |
320 | 33.8k | } |
321 | | |
322 | | std::string cmJoin(cmStringRange rng, cm::string_view separator, |
323 | | cm::string_view initial) |
324 | 1.59k | { |
325 | 1.59k | return cmJoinStrings(rng, separator, initial); |
326 | 1.59k | } |