|
1 | | -// Copyright (c) 2019-2022 The Bitcoin Core developers |
| 1 | +// Copyright (c) 2019-present The Bitcoin Core developers |
2 | 2 | // Distributed under the MIT software license, see the accompanying |
3 | 3 | // file COPYING or http://www.opensource.org/licenses/mit-license.php. |
4 | 4 |
|
5 | 5 | #ifndef BITCOIN_UTIL_STRING_H |
6 | 6 | #define BITCOIN_UTIL_STRING_H |
7 | 7 |
|
8 | 8 | #include <span.h> |
| 9 | +#include <tinyformat.h> |
9 | 10 |
|
10 | 11 | #include <array> |
11 | 12 | #include <cstdint> |
|
17 | 18 | #include <vector> |
18 | 19 |
|
19 | 20 | namespace util { |
| 21 | +/** |
| 22 | + * @brief A wrapper for a compile-time partially validated format string |
| 23 | + * |
| 24 | + * This struct can be used to enforce partial compile-time validation of format |
| 25 | + * strings, to reduce the likelihood of tinyformat throwing exceptions at |
| 26 | + * run-time. Validation is partial to try and prevent the most common errors |
| 27 | + * while avoiding re-implementing the entire parsing logic. |
| 28 | + * |
| 29 | + * @note Counting of `*` dynamic width and precision fields (such as `%*c`, |
| 30 | + * `%2$*3$d`, `%.*f`) is not implemented to minimize code complexity as long as |
| 31 | + * they are not used in the codebase. Usage of these fields is not counted and |
| 32 | + * can lead to run-time exceptions. Code wanting to use the `*` specifier can |
| 33 | + * side-step this struct and call tinyformat directly. |
| 34 | + */ |
| 35 | +template <unsigned num_params> |
| 36 | +struct ConstevalFormatString { |
| 37 | + const char* const fmt; |
| 38 | + consteval ConstevalFormatString(const char* str) : fmt{str} { Detail_CheckNumFormatSpecifiers(fmt); } |
| 39 | + constexpr static void Detail_CheckNumFormatSpecifiers(std::string_view str) |
| 40 | + { |
| 41 | + unsigned count_normal{0}; // Number of "normal" specifiers, like %s |
| 42 | + unsigned count_pos{0}; // Max number in positional specifier, like %8$s |
| 43 | + for (auto it{str.begin()}; it < str.end();) { |
| 44 | + if (*it != '%') { |
| 45 | + ++it; |
| 46 | + continue; |
| 47 | + } |
| 48 | + |
| 49 | + if (++it >= str.end()) throw "Format specifier incorrectly terminated by end of string"; |
| 50 | + if (*it == '%') { |
| 51 | + // Percent escape: %% |
| 52 | + ++it; |
| 53 | + continue; |
| 54 | + } |
| 55 | + |
| 56 | + unsigned maybe_num{0}; |
| 57 | + while ('0' <= *it && *it <= '9') { |
| 58 | + maybe_num *= 10; |
| 59 | + maybe_num += *it - '0'; |
| 60 | + ++it; |
| 61 | + }; |
| 62 | + |
| 63 | + if (*it == '$') { |
| 64 | + // Positional specifier, like %8$s |
| 65 | + if (maybe_num == 0) throw "Positional format specifier must have position of at least 1"; |
| 66 | + count_pos = std::max(count_pos, maybe_num); |
| 67 | + if (++it >= str.end()) throw "Format specifier incorrectly terminated by end of string"; |
| 68 | + } else { |
| 69 | + // Non-positional specifier, like %s |
| 70 | + ++count_normal; |
| 71 | + ++it; |
| 72 | + } |
| 73 | + // The remainder "[flags][width][.precision][length]type" of the |
| 74 | + // specifier is not checked. Parsing continues with the next '%'. |
| 75 | + } |
| 76 | + if (count_normal && count_pos) throw "Format specifiers must be all positional or all non-positional!"; |
| 77 | + unsigned count{count_normal | count_pos}; |
| 78 | + if (num_params != count) throw "Format specifier count must match the argument count!"; |
| 79 | + } |
| 80 | +}; |
| 81 | + |
20 | 82 | void ReplaceAll(std::string& in_out, const std::string& search, const std::string& substitute); |
21 | 83 |
|
22 | 84 | /** Split a string on any char found in separators, returning a vector. |
@@ -173,4 +235,12 @@ template <typename T1, size_t PREFIX_LEN> |
173 | 235 | } |
174 | 236 | } // namespace util |
175 | 237 |
|
| 238 | +namespace tinyformat { |
| 239 | +template <typename... Args> |
| 240 | +std::string format(util::ConstevalFormatString<sizeof...(Args)> fmt, const Args&... args) |
| 241 | +{ |
| 242 | + return format(fmt.fmt, args...); |
| 243 | +} |
| 244 | +} // namespace tinyformat |
| 245 | + |
176 | 246 | #endif // BITCOIN_UTIL_STRING_H |
0 commit comments