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 <optional>
15 : : #include <sstream>
16 : : #include <string>
17 : : #include <string_view>
18 : : #include <vector>
19 : :
20 : : namespace util {
21 : : namespace detail {
22 : : template <unsigned num_params>
23 : 72 : constexpr static void CheckNumFormatSpecifiers(const char* str)
24 : : {
25 : 72 : unsigned count_normal{0}; // Number of "normal" specifiers, like %s
26 : 72 : unsigned count_pos{0}; // Max number in positional specifier, like %8$s
27 [ + + ]: 149 : for (auto it{str}; *it != '\0'; ++it) {
28 [ + + + + ]: 98 : if (*it != '%' || *++it == '%') continue; // Skip escaped %%
29 : :
30 : 270 : auto add_arg = [&] {
31 : 104 : unsigned maybe_num{0};
32 [ + + + + : 172 : while ('0' <= *it && *it <= '9') {
+ + + + +
+ + + ]
33 : 68 : maybe_num *= 10;
34 : 68 : maybe_num += *it - '0';
35 : 68 : ++it;
36 : : }
37 : :
38 [ + - + + : 104 : if (*it == '$') {
+ - + + +
+ + + ]
39 : 56 : ++it;
40 : : // Positional specifier, like %8$s
41 [ - + + + : 56 : if (maybe_num == 0) throw "Positional format specifier must have position of at least 1";
- + + + +
+ + + ]
42 [ + + + + : 89 : count_pos = std::max(count_pos, maybe_num);
+ + + + +
+ + - ]
43 : : } else {
44 : : // Non-positional specifier, like %s
45 : 48 : ++count_normal;
46 : : }
47 : : };
48 : :
49 : : // Increase argument count and consume positional specifier, if present.
50 : 70 : add_arg();
51 : :
52 : : // Consume flags.
53 [ + + + + : 80 : while (*it == '#' || *it == '0' || *it == '-' || *it == ' ' || *it == '+') ++it;
+ + + + +
+ ]
54 : :
55 : 242 : auto parse_size = [&] {
56 [ - + + + : 90 : if (*it == '*') {
+ - + + +
+ + - ]
57 : 34 : ++it;
58 : 34 : add_arg();
59 : : } else {
60 [ - - + + : 68 : while ('0' <= *it && *it <= '9') ++it;
- + + + +
+ - + ]
61 : : }
62 : : };
63 : :
64 : : // Consume dynamic or static width value.
65 : 66 : parse_size();
66 : :
67 : : // Consume dynamic or static precision value.
68 [ + + ]: 64 : if (*it == '.') {
69 : 24 : ++it;
70 : 24 : parse_size();
71 : : }
72 : :
73 [ + + ]: 62 : if (*it == '\0') throw "Format specifier incorrectly terminated by end of string";
74 : :
75 : : // Length and type in "[flags][width][.precision][length]type"
76 : : // is not checked. Parsing continues with the next '%'.
77 : : }
78 [ + + + + ]: 51 : if (count_normal && count_pos) throw "Format specifiers must be all positional or all non-positional!";
79 : 46 : unsigned count{count_normal | count_pos};
80 [ + + ]: 46 : if (num_params != count) throw "Format specifier count must match the argument count!";
81 : 40 : }
82 : : } // namespace detail
83 : :
84 : : /**
85 : : * @brief A wrapper for a compile-time partially validated format string
86 : : *
87 : : * This struct can be used to enforce partial compile-time validation of format
88 : : * strings, to reduce the likelihood of tinyformat throwing exceptions at
89 : : * run-time. Validation is partial to try and prevent the most common errors
90 : : * while avoiding re-implementing the entire parsing logic.
91 : : */
92 : : template <unsigned num_params>
93 : : struct ConstevalFormatString {
94 : : const char* const fmt;
95 : : consteval ConstevalFormatString(const char* str) : fmt{str} { detail::CheckNumFormatSpecifiers<num_params>(fmt); }
96 : : };
97 : :
98 : : void ReplaceAll(std::string& in_out, const std::string& search, const std::string& substitute);
99 : :
100 : : /** Split a string on any char found in separators, returning a vector.
101 : : *
102 : : * If sep does not occur in sp, a singleton with the entirety of sp is returned.
103 : : *
104 : : * @param[in] include_sep Whether to include the separator at the end of the left side of the splits.
105 : : *
106 : : * Note that this function does not care about braces, so splitting
107 : : * "foo(bar(1),2),3) on ',' will return {"foo(bar(1)", "2)", "3)"}.
108 : : *
109 : : * If include_sep == true, splitting "foo(bar(1),2),3) on ','
110 : : * will return:
111 : : * - foo(bar(1),
112 : : * - 2),
113 : : * - 3)
114 : : */
115 : : template <typename T = std::span<const char>>
116 : 28339 : std::vector<T> Split(const std::span<const char>& sp, std::string_view separators, bool include_sep = false)
117 : : {
118 : 28339 : std::vector<T> ret;
119 : 28339 : auto it = sp.begin();
120 : 28339 : auto start = it;
121 [ + + ]: 751493 : while (it != sp.end()) {
122 [ + + ]: 723154 : if (separators.find(*it) != std::string::npos) {
123 [ + + ]: 13596 : if (include_sep) {
124 [ + - ]: 62 : ret.emplace_back(start, it + 1);
125 : : } else {
126 [ + - ]: 13534 : ret.emplace_back(start, it);
127 : : }
128 : 13596 : start = it + 1;
129 : : }
130 : 723154 : ++it;
131 : : }
132 [ + - ]: 28339 : ret.emplace_back(start, it);
133 : 28339 : return ret;
134 : 0 : }
135 : :
136 : : /** Split a string on every instance of sep, returning a vector.
137 : : *
138 : : * If sep does not occur in sp, a singleton with the entirety of sp is returned.
139 : : *
140 : : * Note that this function does not care about braces, so splitting
141 : : * "foo(bar(1),2),3) on ',' will return {"foo(bar(1)", "2)", "3)"}.
142 : : */
143 : : template <typename T = std::span<const char>>
144 : 25405 : std::vector<T> Split(const std::span<const char>& sp, char sep, bool include_sep = false)
145 : : {
146 : 25405 : return Split<T>(sp, std::string_view{&sep, 1}, include_sep);
147 : : }
148 : :
149 : 22184 : [[nodiscard]] inline std::vector<std::string> SplitString(std::string_view str, char sep)
150 : : {
151 [ # # # # ]: 22184 : return Split<std::string>(str, sep);
[ # # # #
# # # # #
# ][ + - ]
[ - - - -
+ - + - ]
152 : : }
153 : :
154 : 2755 : [[nodiscard]] inline std::vector<std::string> SplitString(std::string_view str, std::string_view separators)
155 : : {
156 [ + - ][ + - : 2755 : return Split<std::string>(str, separators);
+ - + - +
- + - + -
+ - ]
157 : : }
158 : :
159 : 662351 : [[nodiscard]] inline std::string_view TrimStringView(std::string_view str, std::string_view pattern = " \f\n\r\t\v")
160 : : {
161 : 662351 : std::string::size_type front = str.find_first_not_of(pattern);
162 [ + + ]: 662351 : if (front == std::string::npos) {
163 : 34 : return {};
164 : : }
165 : 662317 : std::string::size_type end = str.find_last_not_of(pattern);
166 : 662317 : return str.substr(front, end - front + 1);
167 : : }
168 : :
169 : 370709 : [[nodiscard]] inline std::string TrimString(std::string_view str, std::string_view pattern = " \f\n\r\t\v")
170 : : {
171 : 370709 : return std::string(TrimStringView(str, pattern));
172 : : }
173 : :
174 : 5 : [[nodiscard]] inline std::string_view RemoveSuffixView(std::string_view str, std::string_view suffix)
175 : : {
176 [ + - ]: 5 : if (str.ends_with(suffix)) {
177 : 5 : return str.substr(0, str.size() - suffix.size());
178 : : }
179 : 0 : return str;
180 : : }
181 : :
182 : 310146 : [[nodiscard]] inline std::string_view RemovePrefixView(std::string_view str, std::string_view prefix)
183 : : {
184 [ + + ]: 310146 : if (str.starts_with(prefix)) {
185 : 310119 : return str.substr(prefix.size());
186 : : }
187 : 27 : return str;
188 : : }
189 : :
190 : 11 : [[nodiscard]] inline std::string RemovePrefix(std::string_view str, std::string_view prefix)
191 : : {
192 : 11 : return std::string(RemovePrefixView(str, prefix));
193 : : }
194 : :
195 : : /**
196 : : * Join all container items. Typically used to concatenate strings but accepts
197 : : * containers with elements of any type.
198 : : *
199 : : * @param container The items to join
200 : : * @param separator The separator
201 : : * @param unary_op Apply this operator to each item
202 : : */
203 : : template <typename C, typename S, typename UnaryOp>
204 : : // NOLINTNEXTLINE(misc-no-recursion)
205 : 16987 : auto Join(const C& container, const S& separator, UnaryOp unary_op)
206 : : {
207 : 16987 : decltype(unary_op(*container.begin())) ret;
208 : 16987 : bool first{true};
209 [ + + ]: 93193 : for (const auto& item : container) {
210 [ + + ]: 116609 : if (!first) ret += separator;
[ + + + + ]
211 [ + + ]: 126398 : ret += unary_op(item);
[ + - - + ]
212 : 76206 : first = false;
213 : : }
214 : 16987 : return ret;
215 : 0 : }
216 : :
217 : : template <typename C, typename S>
218 : 13010 : auto Join(const C& container, const S& separator)
219 : : {
220 [ - + - + ]: 65038 : return Join(container, separator, [](const auto& i) { return i; });
[ - - ]
221 : : }
222 : :
223 : : /**
224 : : * Create an unordered multi-line list of items.
225 : : */
226 : 88 : inline std::string MakeUnorderedList(const std::vector<std::string>& items)
227 : : {
228 [ + - + - ]: 616 : return Join(items, "\n", [](const std::string& item) { return "- " + item; });
229 : : }
230 : :
231 : : /**
232 : : * Check if a string does not contain any embedded NUL (\0) characters
233 : : */
234 : 44732 : [[nodiscard]] inline bool ContainsNoNUL(std::string_view str) noexcept
235 : : {
236 [ + + ][ + + : 702588 : for (auto c : str) {
+ + + + +
+ + + ][ +
+ + + # #
# # # # ]
237 [ + + ][ + + : 657878 : if (c == 0) return false;
+ - + + +
+ + - ][ +
+ + + # #
# # # # ]
238 : : }
239 : : return true;
240 : : }
241 : :
242 : : /**
243 : : * Locale-independent version of std::to_string
244 : : */
245 : : template <typename T>
246 : 224747 : std::string ToString(const T& t)
247 : : {
248 : 224747 : std::ostringstream oss;
249 [ + - + - ]: 224747 : oss.imbue(std::locale::classic());
250 [ + - + - ]: 224747 : oss << t;
[ # # ]
251 : 224747 : return oss.str();
252 : 224747 : }
253 : :
254 : : /**
255 : : * Check whether a container begins with the given prefix.
256 : : */
257 : : template <typename T1, size_t PREFIX_LEN>
258 [ + + ]: 719696 : [[nodiscard]] inline bool HasPrefix(const T1& obj,
259 : : const std::array<uint8_t, PREFIX_LEN>& prefix)
260 : : {
261 [ + - + + ]: 1437532 : return obj.size() >= PREFIX_LEN &&
262 [ + + ]: 719696 : std::equal(std::begin(prefix), std::end(prefix), std::begin(obj));
263 : : }
264 : :
265 : : struct LineReader {
266 : : const std::span<const std::byte>::iterator start;
267 : : const std::span<const std::byte>::iterator end;
268 : : const size_t max_line_length;
269 : : std::span<const std::byte>::iterator it;
270 : :
271 : : explicit LineReader(std::span<const std::byte> buffer, size_t max_line_length);
272 : :
273 : : /**
274 : : * Returns a string from current iterator position up to (but not including) next \n
275 : : * and advances iterator to the character following the \n on success.
276 : : * Will not return a line longer than max_line_length.
277 : : * @returns the next string from the buffer.
278 : : * std::nullopt if end of buffer is reached without finding a \n.
279 : : * @throws a std::runtime_error if max_line_length + 1 bytes are read without finding \n.
280 : : */
281 : : std::optional<std::string> ReadLine();
282 : :
283 : : /**
284 : : * Returns string from current iterator position of specified length
285 : : * if possible and advances iterator on success.
286 : : * May exceed max_line_length but will not read past end of buffer.
287 : : * @param[in] len The number of bytes to read from the buffer
288 : : * @returns a string of the expected length.
289 : : * @throws a std::runtime_error if there is not enough data in the buffer.
290 : : */
291 : : std::string ReadLength(size_t len);
292 : :
293 : : /**
294 : : * Returns remaining size of bytes in buffer
295 : : */
296 : : size_t Remaining() const;
297 : : };
298 : : } // namespace util
299 : :
300 : : #endif // BITCOIN_UTIL_STRING_H
|