From fa72646f2b197810a324cb0544d9a1fac37d3f9c Mon Sep 17 00:00:00 2001 From: MarcoFalke <*~=`'#}+{/-|&$^_@721217.xyz> Date: Wed, 9 Oct 2024 11:42:44 +0200 Subject: [PATCH] move-only: Detail_CheckNumFormatSpecifiers and G_TRANSLATION_FUN This is required for a future commit. Can be reviewed via the git options --color-moved=dimmed-zebra --color-moved-ws=ignore-all-space Also move util::detail::Hex to a proper namespace instead of an inline namespace so it doesn't conflict with the new util::detail namespace, and won't create other problems for callers trying to use the inline namespaces. Also fix a misleading comment in util_string_tests.cpp. Co-Authored-By: Ryan Ofsky --- src/test/util_string_tests.cpp | 6 +- src/util/strencodings.h | 37 +++++----- src/util/string.h | 124 +++++++++++++++++---------------- src/util/translation.h | 6 +- 4 files changed, 88 insertions(+), 85 deletions(-) diff --git a/src/test/util_string_tests.cpp b/src/test/util_string_tests.cpp index 9f5b702acd..fbab9e7aba 100644 --- a/src/test/util_string_tests.cpp +++ b/src/test/util_string_tests.cpp @@ -16,13 +16,13 @@ BOOST_AUTO_TEST_SUITE(util_string_tests) template inline void PassFmt(util::ConstevalFormatString fmt) { - // This was already executed at compile-time, but is executed again at run-time to avoid -Wunused. - decltype(fmt)::Detail_CheckNumFormatSpecifiers(fmt.fmt); + // Execute compile-time check again at run-time to get code coverage stats + util::detail::CheckNumFormatSpecifiers(fmt.fmt); } template inline void FailFmtWithError(const char* wrong_fmt, std::string_view error) { - BOOST_CHECK_EXCEPTION(util::ConstevalFormatString::Detail_CheckNumFormatSpecifiers(wrong_fmt), const char*, HasReason(error)); + BOOST_CHECK_EXCEPTION(util::detail::CheckNumFormatSpecifiers(wrong_fmt), const char*, HasReason{error}); } BOOST_AUTO_TEST_CASE(ConstevalFormatString_NumSpec) diff --git a/src/util/strencodings.h b/src/util/strencodings.h index 1543de03ab..d0809162fa 100644 --- a/src/util/strencodings.h +++ b/src/util/strencodings.h @@ -377,6 +377,24 @@ consteval uint8_t ConstevalHexDigit(const char c) throw "Only lowercase hex digits are allowed, for consistency"; } +namespace detail { +template +struct Hex { + std::array bytes{}; + consteval Hex(const char (&hex_str)[N]) + // 2 hex digits required per byte + implicit null terminator + requires(N % 2 == 1) + { + if (hex_str[N - 1]) throw "null terminator required"; + for (std::size_t i = 0; i < bytes.size(); ++i) { + bytes[i] = static_cast( + (ConstevalHexDigit(hex_str[2 * i]) << 4) | + ConstevalHexDigit(hex_str[2 * i + 1])); + } + } +}; +} // namespace detail + /** * ""_hex is a compile-time user-defined literal returning a * `std::array`, equivalent to ParseHex(). Variants provided: @@ -407,25 +425,6 @@ consteval uint8_t ConstevalHexDigit(const char c) * time/runtime barrier. */ inline namespace hex_literals { -namespace detail { - -template -struct Hex { - std::array bytes{}; - consteval Hex(const char (&hex_str)[N]) - // 2 hex digits required per byte + implicit null terminator - requires(N % 2 == 1) - { - if (hex_str[N - 1]) throw "null terminator required"; - for (std::size_t i = 0; i < bytes.size(); ++i) { - bytes[i] = static_cast( - (ConstevalHexDigit(hex_str[2 * i]) << 4) | - ConstevalHexDigit(hex_str[2 * i + 1])); - } - } -}; - -} // namespace detail template constexpr auto operator""_hex() { return str.bytes; } diff --git a/src/util/string.h b/src/util/string.h index c9e33e6592..b523e6ef4e 100644 --- a/src/util/string.h +++ b/src/util/string.h @@ -17,6 +17,69 @@ #include namespace util { +namespace detail { +template +constexpr static void CheckNumFormatSpecifiers(const char* str) +{ + unsigned count_normal{0}; // Number of "normal" specifiers, like %s + unsigned count_pos{0}; // Max number in positional specifier, like %8$s + for (auto it{str}; *it != '\0'; ++it) { + if (*it != '%' || *++it == '%') continue; // Skip escaped %% + + auto add_arg = [&] { + unsigned maybe_num{0}; + while ('0' <= *it && *it <= '9') { + maybe_num *= 10; + maybe_num += *it - '0'; + ++it; + } + + if (*it == '$') { + ++it; + // Positional specifier, like %8$s + if (maybe_num == 0) throw "Positional format specifier must have position of at least 1"; + count_pos = std::max(count_pos, maybe_num); + } else { + // Non-positional specifier, like %s + ++count_normal; + } + }; + + // Increase argument count and consume positional specifier, if present. + add_arg(); + + // Consume flags. + while (*it == '#' || *it == '0' || *it == '-' || *it == ' ' || *it == '+') ++it; + + auto parse_size = [&] { + if (*it == '*') { + ++it; + add_arg(); + } else { + while ('0' <= *it && *it <= '9') ++it; + } + }; + + // Consume dynamic or static width value. + parse_size(); + + // Consume dynamic or static precision value. + if (*it == '.') { + ++it; + parse_size(); + } + + if (*it == '\0') throw "Format specifier incorrectly terminated by end of string"; + + // Length and type in "[flags][width][.precision][length]type" + // is not checked. Parsing continues with the next '%'. + } + if (count_normal && count_pos) throw "Format specifiers must be all positional or all non-positional!"; + unsigned count{count_normal | count_pos}; + if (num_params != count) throw "Format specifier count must match the argument count!"; +} +} // namespace detail + /** * @brief A wrapper for a compile-time partially validated format string * @@ -28,66 +91,7 @@ namespace util { template struct ConstevalFormatString { const char* const fmt; - consteval ConstevalFormatString(const char* str) : fmt{str} { Detail_CheckNumFormatSpecifiers(fmt); } - constexpr static void Detail_CheckNumFormatSpecifiers(const char* str) - { - unsigned count_normal{0}; // Number of "normal" specifiers, like %s - unsigned count_pos{0}; // Max number in positional specifier, like %8$s - for (auto it{str}; *it != '\0'; ++it) { - if (*it != '%' || *++it == '%') continue; // Skip escaped %% - - auto add_arg = [&] { - unsigned maybe_num{0}; - while ('0' <= *it && *it <= '9') { - maybe_num *= 10; - maybe_num += *it - '0'; - ++it; - } - - if (*it == '$') { - ++it; - // Positional specifier, like %8$s - if (maybe_num == 0) throw "Positional format specifier must have position of at least 1"; - count_pos = std::max(count_pos, maybe_num); - } else { - // Non-positional specifier, like %s - ++count_normal; - } - }; - - // Increase argument count and consume positional specifier, if present. - add_arg(); - - // Consume flags. - while (*it == '#' || *it == '0' || *it == '-' || *it == ' ' || *it == '+') ++it; - - auto parse_size = [&] { - if (*it == '*') { - ++it; - add_arg(); - } else { - while ('0' <= *it && *it <= '9') ++it; - } - }; - - // Consume dynamic or static width value. - parse_size(); - - // Consume dynamic or static precision value. - if (*it == '.') { - ++it; - parse_size(); - } - - if (*it == '\0') throw "Format specifier incorrectly terminated by end of string"; - - // Length and type in "[flags][width][.precision][length]type" - // is not checked. Parsing continues with the next '%'. - } - if (count_normal && count_pos) throw "Format specifiers must be all positional or all non-positional!"; - unsigned count{count_normal | count_pos}; - if (num_params != count) throw "Format specifier count must match the argument count!"; - } + consteval ConstevalFormatString(const char* str) : fmt{str} { detail::CheckNumFormatSpecifiers(fmt); } }; void ReplaceAll(std::string& in_out, const std::string& search, const std::string& substitute); diff --git a/src/util/translation.h b/src/util/translation.h index 6effe102f9..7c734a1766 100644 --- a/src/util/translation.h +++ b/src/util/translation.h @@ -10,6 +10,9 @@ #include #include +/** Translate a message to the native language of the user. */ +const extern std::function G_TRANSLATION_FUN; + /** * Bilingual messages: * - in GUI: user's native language + untranslated (i.e. English) @@ -64,9 +67,6 @@ bilingual_str format(const bilingual_str& fmt, const Args&... args) } } // namespace tinyformat -/** Translate a message to the native language of the user. */ -const extern std::function G_TRANSLATION_FUN; - struct ConstevalStringLiteral { const char* const lit; consteval ConstevalStringLiteral(const char* str) : lit{str} {}