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