Branch data Line data Source code
1 : : // Copyright (c) 2019-present The Bitcoin Core developers
2 : : // Distributed under the MIT software license, see the accompanying
3 : : // file COPYING or http://www.opensource.org/licenses/mit-license.php.
4 : :
5 : : #ifndef BITCOIN_UTIL_STRING_H
6 : : #define BITCOIN_UTIL_STRING_H
7 : :
8 : : #include <span.h>
9 : :
10 : : #include <array>
11 : : #include <cstdint>
12 : : #include <cstring>
13 : : #include <locale>
14 : : #include <sstream>
15 : : #include <string> // IWYU pragma: export
16 : : #include <string_view> // IWYU pragma: export
17 : : #include <vector>
18 : :
19 : : namespace util {
20 : : namespace detail {
21 : : template <unsigned num_params>
22 : 72 : constexpr static void CheckNumFormatSpecifiers(const char* str)
23 : : {
24 : 72 : unsigned count_normal{0}; // Number of "normal" specifiers, like %s
25 : 72 : unsigned count_pos{0}; // Max number in positional specifier, like %8$s
26 [ + + ]: 149 : for (auto it{str}; *it != '\0'; ++it) {
27 [ + + + + ]: 98 : if (*it != '%' || *++it == '%') continue; // Skip escaped %%
28 : :
29 : 270 : auto add_arg = [&] {
30 : 104 : unsigned maybe_num{0};
31 [ + + + + : 172 : while ('0' <= *it && *it <= '9') {
+ + + + +
+ + + ]
32 : 68 : maybe_num *= 10;
33 : 68 : maybe_num += *it - '0';
34 : 68 : ++it;
35 : : }
36 : :
37 [ + - + + : 104 : if (*it == '$') {
+ - + + +
+ + + ]
38 : 56 : ++it;
39 : : // Positional specifier, like %8$s
40 [ - + + + : 56 : if (maybe_num == 0) throw "Positional format specifier must have position of at least 1";
- + + + +
+ + + ]
41 [ + + + + : 89 : count_pos = std::max(count_pos, maybe_num);
+ + + + +
+ + - ]
42 : : } else {
43 : : // Non-positional specifier, like %s
44 : 48 : ++count_normal;
45 : : }
46 : : };
47 : :
48 : : // Increase argument count and consume positional specifier, if present.
49 : 70 : add_arg();
50 : :
51 : : // Consume flags.
52 [ + + ]: 80 : while (*it == '#' || *it == '0' || *it == '-' || *it == ' ' || *it == '+') ++it;
53 : :
54 : 242 : auto parse_size = [&] {
55 [ - + + + : 90 : if (*it == '*') {
+ - + + +
+ + - ]
56 : 34 : ++it;
57 : 34 : add_arg();
58 : : } else {
59 [ - - + + : 68 : while ('0' <= *it && *it <= '9') ++it;
- + + + +
+ - + ]
60 : : }
61 : : };
62 : :
63 : : // Consume dynamic or static width value.
64 : 66 : parse_size();
65 : :
66 : : // Consume dynamic or static precision value.
67 [ + + ]: 64 : if (*it == '.') {
68 : 24 : ++it;
69 : 24 : parse_size();
70 : : }
71 : :
72 [ + + ]: 62 : if (*it == '\0') throw "Format specifier incorrectly terminated by end of string";
73 : :
74 : : // Length and type in "[flags][width][.precision][length]type"
75 : : // is not checked. Parsing continues with the next '%'.
76 : : }
77 [ + + + + ]: 51 : if (count_normal && count_pos) throw "Format specifiers must be all positional or all non-positional!";
78 : 46 : unsigned count{count_normal | count_pos};
79 [ + + ]: 46 : if (num_params != count) throw "Format specifier count must match the argument count!";
80 : 40 : }
81 : : } // namespace detail
82 : :
83 : : /**
84 : : * @brief A wrapper for a compile-time partially validated format string
85 : : *
86 : : * This struct can be used to enforce partial compile-time validation of format
87 : : * strings, to reduce the likelihood of tinyformat throwing exceptions at
88 : : * run-time. Validation is partial to try and prevent the most common errors
89 : : * while avoiding re-implementing the entire parsing logic.
90 : : */
91 : : template <unsigned num_params>
92 : : struct ConstevalFormatString {
93 : : const char* const fmt;
94 : : consteval ConstevalFormatString(const char* str) : fmt{str} { detail::CheckNumFormatSpecifiers<num_params>(fmt); }
95 : : };
96 : :
97 : : void ReplaceAll(std::string& in_out, const std::string& search, const std::string& substitute);
98 : :
99 : : /** Split a string on any char found in separators, returning a vector.
100 : : *
101 : : * If sep does not occur in sp, a singleton with the entirety of sp is returned.
102 : : *
103 : : * @param[in] include_sep Whether to include the separator at the end of the left side of the splits.
104 : : *
105 : : * Note that this function does not care about braces, so splitting
106 : : * "foo(bar(1),2),3) on ',' will return {"foo(bar(1)", "2)", "3)"}.
107 : : *
108 : : * If include_sep == true, splitting "foo(bar(1),2),3) on ','
109 : : * will return:
110 : : * - foo(bar(1),
111 : : * - 2),
112 : : * - 3)
113 : : */
114 : : template <typename T = std::span<const char>>
115 : 1218340 : std::vector<T> Split(const std::span<const char>& sp, std::string_view separators, bool include_sep = false)
116 : : {
117 : 1218340 : std::vector<T> ret;
118 : 1218340 : auto it = sp.begin();
119 : 1218340 : auto start = it;
120 [ + + ]: 19471561 : while (it != sp.end()) {
121 [ + + ]: 18253221 : if (separators.find(*it) != std::string::npos) {
122 [ + + ]: 77670 : if (include_sep) {
123 [ + - ]: 62 : ret.emplace_back(start, it + 1);
124 : : } else {
125 [ + - ]: 77608 : ret.emplace_back(start, it);
126 : : }
127 : 77670 : start = it + 1;
128 : : }
129 : 18253221 : ++it;
130 : : }
131 [ + - ]: 1218340 : ret.emplace_back(start, it);
132 : 1218340 : return ret;
133 : 0 : }
134 : :
135 : : /** Split a string on every instance of sep, returning a vector.
136 : : *
137 : : * If sep does not occur in sp, a singleton with the entirety of sp is returned.
138 : : *
139 : : * Note that this function does not care about braces, so splitting
140 : : * "foo(bar(1),2),3) on ',' will return {"foo(bar(1)", "2)", "3)"}.
141 : : */
142 : : template <typename T = std::span<const char>>
143 : 1215364 : std::vector<T> Split(const std::span<const char>& sp, char sep, bool include_sep = false)
144 : : {
145 : 1215364 : return Split<T>(sp, std::string_view{&sep, 1}, include_sep);
146 : : }
147 : :
148 : 1146396 : [[nodiscard]] inline std::vector<std::string> SplitString(std::string_view str, char sep)
149 : : {
150 [ + - + - ]: 1146345 : return Split<std::string>(str, sep);
[ + - # #
# # # # #
# ][ + - +
- + - + -
+ - ][ + -
+ - + - +
- ]
151 : : }
152 : :
153 : 2785 : [[nodiscard]] inline std::vector<std::string> SplitString(std::string_view str, std::string_view separators)
154 : : {
155 [ + - + - : 2785 : return Split<std::string>(str, separators);
+ - + - +
- + - +
- ][ + - ]
156 : : }
157 : :
158 : 1237944 : [[nodiscard]] inline std::string_view TrimStringView(std::string_view str, std::string_view pattern = " \f\n\r\t\v")
159 : : {
160 : 1237944 : std::string::size_type front = str.find_first_not_of(pattern);
161 [ + + ]: 1237944 : if (front == std::string::npos) {
162 : 76 : return {};
163 : : }
164 : 1237868 : std::string::size_type end = str.find_last_not_of(pattern);
165 : 1237868 : return str.substr(front, end - front + 1);
166 : : }
167 : :
168 : 462067 : [[nodiscard]] inline std::string TrimString(std::string_view str, std::string_view pattern = " \f\n\r\t\v")
169 : : {
170 : 462067 : return std::string(TrimStringView(str, pattern));
171 : : }
172 : :
173 : 1458 : [[nodiscard]] inline std::string_view RemoveSuffixView(std::string_view str, std::string_view suffix)
174 : : {
175 [ + - ]: 1458 : if (str.ends_with(suffix)) {
176 : 1458 : return str.substr(0, str.size() - suffix.size());
177 : : }
178 : 0 : return str;
179 : : }
180 : :
181 : 5495223 : [[nodiscard]] inline std::string_view RemovePrefixView(std::string_view str, std::string_view prefix)
182 : : {
183 [ + + ]: 5495223 : if (str.starts_with(prefix)) {
184 : 5495193 : return str.substr(prefix.size());
185 : : }
186 : 30 : return str;
187 : : }
188 : :
189 : 11 : [[nodiscard]] inline std::string RemovePrefix(std::string_view str, std::string_view prefix)
190 : : {
191 : 11 : return std::string(RemovePrefixView(str, prefix));
192 : : }
193 : :
194 : : /**
195 : : * Join all container items. Typically used to concatenate strings but accepts
196 : : * containers with elements of any type.
197 : : *
198 : : * @param container The items to join
199 : : * @param separator The separator
200 : : * @param unary_op Apply this operator to each item
201 : : */
202 : : template <typename C, typename S, typename UnaryOp>
203 : : // NOLINTNEXTLINE(misc-no-recursion)
204 : 268760 : auto Join(const C& container, const S& separator, UnaryOp unary_op)
205 : : {
206 : 268760 : decltype(unary_op(*container.begin())) ret;
207 : 268760 : bool first{true};
208 [ + + ]: 1152707 : for (const auto& item : container) {
209 [ + + + - ]: 1064336 : if (!first) ret += separator;
[ + + + - ]
210 [ + - + - ]: 1767749 : ret += unary_op(item);
[ + - + - ]
211 : 883947 : first = false;
212 : : }
213 : 268760 : return ret;
214 : 0 : }
215 : :
216 : : template <typename C, typename S>
217 : 224764 : auto Join(const C& container, const S& separator)
218 : : {
219 [ + - ]: 809663 : return Join(container, separator, [](const auto& i) { return i; });
220 : : }
221 : :
222 : : /**
223 : : * Create an unordered multi-line list of items.
224 : : */
225 : 2231 : inline std::string MakeUnorderedList(const std::vector<std::string>& items)
226 : : {
227 [ + - + - ]: 15602 : return Join(items, "\n", [](const std::string& item) { return "- " + item; });
228 : : }
229 : :
230 : : /**
231 : : * Check if a string does not contain any embedded NUL (\0) characters
232 : : */
233 : 2497026 : [[nodiscard]] inline bool ContainsNoNUL(std::string_view str) noexcept
234 : : {
235 [ + + ]: 26833478 : for (auto c : str) {
[ + + + + ]
[ + + + +
+ + + + +
+ ]
236 [ + + ]: 24336474 : if (c == 0) return false;
[ + + + + ]
[ + + + -
+ + + + +
- ]
237 : : }
238 : : return true;
239 : : }
240 : :
241 : : /**
242 : : * Locale-independent version of std::to_string
243 : : */
244 : : template <typename T>
245 : 258186 : std::string ToString(const T& t)
246 : : {
247 : 258186 : std::ostringstream oss;
248 [ + - + - ]: 258186 : oss.imbue(std::locale::classic());
249 [ # # ]: 258186 : oss << t;
[ + - + - ]
250 : 258186 : return oss.str();
251 : 258186 : }
252 : :
253 : : /**
254 : : * Check whether a container begins with the given prefix.
255 : : */
256 : : template <typename T1, size_t PREFIX_LEN>
257 [ + + ]: 4025785 : [[nodiscard]] inline bool HasPrefix(const T1& obj,
258 : : const std::array<uint8_t, PREFIX_LEN>& prefix)
259 : : {
260 [ + - + + ]: 8037351 : return obj.size() >= PREFIX_LEN &&
261 [ + + ]: 4025785 : std::equal(std::begin(prefix), std::end(prefix), std::begin(obj));
262 : : }
263 : : } // namespace util
264 : :
265 : : #endif // BITCOIN_UTIL_STRING_H
|