diff --git a/include/spdlog/fmt/bundled/chrono.h b/include/spdlog/fmt/bundled/chrono.h index 55e8a506..b112f76e 100644 --- a/include/spdlog/fmt/bundled/chrono.h +++ b/include/spdlog/fmt/bundled/chrono.h @@ -22,24 +22,6 @@ FMT_BEGIN_NAMESPACE -// Check if std::chrono::local_t is available. -#ifndef FMT_USE_LOCAL_TIME -# ifdef __cpp_lib_chrono -# define FMT_USE_LOCAL_TIME (__cpp_lib_chrono >= 201907L) -# else -# define FMT_USE_LOCAL_TIME 0 -# endif -#endif - -// Check if std::chrono::utc_timestamp is available. -#ifndef FMT_USE_UTC_TIME -# ifdef __cpp_lib_chrono -# define FMT_USE_UTC_TIME (__cpp_lib_chrono >= 201907L) -# else -# define FMT_USE_UTC_TIME 0 -# endif -#endif - // Enable tzset. #ifndef FMT_USE_TZSET // UWP doesn't provide _tzset. @@ -221,8 +203,7 @@ To safe_duration_cast(std::chrono::duration from, } const auto min1 = (std::numeric_limits::min)() / Factor::num; - if (detail::const_check(!std::is_unsigned::value) && - count < min1) { + if (!std::is_unsigned::value && count < min1) { ec = 1; return {}; } @@ -377,11 +358,37 @@ auto write_encoded_tm_str(OutputIt out, string_view in, const std::locale& loc) unit_t unit; write_codecvt(unit, in, loc); // In UTF-8 is used one to four one-byte code units. - unicode_to_utf8> - u; - if (!u.convert({unit.buf, to_unsigned(unit.end - unit.buf)})) - FMT_THROW(format_error("failed to format time")); - return copy_str(u.c_str(), u.c_str() + u.size(), out); + auto&& buf = basic_memory_buffer(); + for (code_unit* p = unit.buf; p != unit.end; ++p) { + uint32_t c = static_cast(*p); + if (sizeof(code_unit) == 2 && c >= 0xd800 && c <= 0xdfff) { + // surrogate pair + ++p; + if (p == unit.end || (c & 0xfc00) != 0xd800 || + (*p & 0xfc00) != 0xdc00) { + FMT_THROW(format_error("failed to format time")); + } + c = (c << 10) + static_cast(*p) - 0x35fdc00; + } + if (c < 0x80) { + buf.push_back(static_cast(c)); + } else if (c < 0x800) { + buf.push_back(static_cast(0xc0 | (c >> 6))); + buf.push_back(static_cast(0x80 | (c & 0x3f))); + } else if ((c >= 0x800 && c <= 0xd7ff) || (c >= 0xe000 && c <= 0xffff)) { + buf.push_back(static_cast(0xe0 | (c >> 12))); + buf.push_back(static_cast(0x80 | ((c & 0xfff) >> 6))); + buf.push_back(static_cast(0x80 | (c & 0x3f))); + } else if (c >= 0x10000 && c <= 0x10ffff) { + buf.push_back(static_cast(0xf0 | (c >> 18))); + buf.push_back(static_cast(0x80 | ((c & 0x3ffff) >> 12))); + buf.push_back(static_cast(0x80 | ((c & 0xfff) >> 6))); + buf.push_back(static_cast(0x80 | (c & 0x3f))); + } else { + FMT_THROW(format_error("failed to format time")); + } + } + return copy_str(buf.data(), buf.data() + buf.size(), out); } return copy_str(in.data(), in.data() + in.size(), out); } @@ -420,7 +427,7 @@ auto write(OutputIt out, const std::tm& time, const std::locale& loc, char format, char modifier = 0) -> OutputIt { auto&& buf = get_buffer(out); do_write(buf, time, loc, format, modifier); - return get_iterator(buf, out); + return buf.out(); } template -inline auto localtime(std::chrono::local_time time) -> std::tm { - return localtime(std::chrono::system_clock::to_time_t( - std::chrono::current_zone()->to_sys(time))); +inline std::tm localtime( + std::chrono::time_point time_point) { + return localtime(std::chrono::system_clock::to_time_t(time_point)); } -#endif /** Converts given time since epoch as ``std::time_t`` value into calendar time, @@ -532,49 +536,6 @@ inline std::tm gmtime( FMT_BEGIN_DETAIL_NAMESPACE -// DEPRECATED! -template -FMT_CONSTEXPR auto parse_align(const Char* begin, const Char* end, - format_specs& specs) -> const Char* { - FMT_ASSERT(begin != end, ""); - auto align = align::none; - auto p = begin + code_point_length(begin); - if (end - p <= 0) p = begin; - for (;;) { - switch (to_ascii(*p)) { - case '<': - align = align::left; - break; - case '>': - align = align::right; - break; - case '^': - align = align::center; - break; - } - if (align != align::none) { - if (p != begin) { - auto c = *begin; - if (c == '}') return begin; - if (c == '{') { - throw_format_error("invalid fill character '{'"); - return begin; - } - specs.fill = {begin, to_unsigned(p - begin)}; - begin = p + 1; - } else { - ++begin; - } - break; - } else if (p == begin) { - break; - } - p = begin; - } - specs.align = align; - return begin; -} - // Writes two-digit numbers a, b and c separated by sep to buf. // The method by Pavel Novikov based on // https://johnnylee-sde.github.io/Fast-unsigned-integer-to-time-string/. @@ -638,39 +599,12 @@ enum class numeric_system { alternative }; -// Glibc extensions for formatting numeric values. -enum class pad_type { - unspecified, - // Do not pad a numeric result string. - none, - // Pad a numeric result string with zeros even if the conversion specifier - // character uses space-padding by default. - zero, - // Pad a numeric result string with spaces. - space, -}; - -template -auto write_padding(OutputIt out, pad_type pad, int width) -> OutputIt { - if (pad == pad_type::none) return out; - return std::fill_n(out, width, pad == pad_type::space ? ' ' : '0'); -} - -template -auto write_padding(OutputIt out, pad_type pad) -> OutputIt { - if (pad != pad_type::none) *out++ = pad == pad_type::space ? ' ' : '0'; - return out; -} - // Parses a put_time-like format string and invokes handler actions. template FMT_CONSTEXPR const Char* parse_chrono_format(const Char* begin, const Char* end, Handler&& handler) { - if (begin == end || *begin == '}') return begin; - if (*begin != '%') FMT_THROW(format_error("invalid format")); auto ptr = begin; - pad_type pad = pad_type::unspecified; while (ptr != end) { auto c = *ptr; if (c == '}') break; @@ -681,22 +615,6 @@ FMT_CONSTEXPR const Char* parse_chrono_format(const Char* begin, if (begin != ptr) handler.on_text(begin, ptr); ++ptr; // consume '%' if (ptr == end) FMT_THROW(format_error("invalid format")); - c = *ptr; - switch (c) { - case '_': - pad = pad_type::space; - ++ptr; - break; - case '-': - pad = pad_type::none; - ++ptr; - break; - case '0': - pad = pad_type::zero; - ++ptr; - break; - } - if (ptr == end) FMT_THROW(format_error("invalid format")); c = *ptr++; switch (c) { case '%': @@ -773,16 +691,16 @@ FMT_CONSTEXPR const Char* parse_chrono_format(const Char* begin, break; // Hour, minute, second: case 'H': - handler.on_24_hour(numeric_system::standard, pad); + handler.on_24_hour(numeric_system::standard); break; case 'I': - handler.on_12_hour(numeric_system::standard, pad); + handler.on_12_hour(numeric_system::standard); break; case 'M': - handler.on_minute(numeric_system::standard, pad); + handler.on_minute(numeric_system::standard); break; case 'S': - handler.on_second(numeric_system::standard, pad); + handler.on_second(numeric_system::standard); break; // Other: case 'c': @@ -819,7 +737,7 @@ FMT_CONSTEXPR const Char* parse_chrono_format(const Char* begin, handler.on_duration_unit(); break; case 'z': - handler.on_utc_offset(numeric_system::standard); + handler.on_utc_offset(); break; case 'Z': handler.on_tz_name(); @@ -847,9 +765,6 @@ FMT_CONSTEXPR const Char* parse_chrono_format(const Char* begin, case 'X': handler.on_loc_time(numeric_system::alternative); break; - case 'z': - handler.on_utc_offset(numeric_system::alternative); - break; default: FMT_THROW(format_error("invalid format")); } @@ -887,19 +802,16 @@ FMT_CONSTEXPR const Char* parse_chrono_format(const Char* begin, handler.on_dec1_weekday(numeric_system::alternative); break; case 'H': - handler.on_24_hour(numeric_system::alternative, pad); + handler.on_24_hour(numeric_system::alternative); break; case 'I': - handler.on_12_hour(numeric_system::alternative, pad); + handler.on_12_hour(numeric_system::alternative); break; case 'M': - handler.on_minute(numeric_system::alternative, pad); + handler.on_minute(numeric_system::alternative); break; case 'S': - handler.on_second(numeric_system::alternative, pad); - break; - case 'z': - handler.on_utc_offset(numeric_system::alternative); + handler.on_second(numeric_system::alternative); break; default: FMT_THROW(format_error("invalid format")); @@ -952,7 +864,7 @@ template struct null_chrono_spec_handler { FMT_CONSTEXPR void on_am_pm() { unsupported(); } FMT_CONSTEXPR void on_duration_value() { unsupported(); } FMT_CONSTEXPR void on_duration_unit() { unsupported(); } - FMT_CONSTEXPR void on_utc_offset(numeric_system) { unsupported(); } + FMT_CONSTEXPR void on_utc_offset() { unsupported(); } FMT_CONSTEXPR void on_tz_name() { unsupported(); } }; @@ -980,10 +892,10 @@ struct tm_format_checker : null_chrono_spec_handler { FMT_CONSTEXPR void on_day_of_year() {} FMT_CONSTEXPR void on_day_of_month(numeric_system) {} FMT_CONSTEXPR void on_day_of_month_space(numeric_system) {} - FMT_CONSTEXPR void on_24_hour(numeric_system, pad_type) {} - FMT_CONSTEXPR void on_12_hour(numeric_system, pad_type) {} - FMT_CONSTEXPR void on_minute(numeric_system, pad_type) {} - FMT_CONSTEXPR void on_second(numeric_system, pad_type) {} + FMT_CONSTEXPR void on_24_hour(numeric_system) {} + FMT_CONSTEXPR void on_12_hour(numeric_system) {} + FMT_CONSTEXPR void on_minute(numeric_system) {} + FMT_CONSTEXPR void on_second(numeric_system) {} FMT_CONSTEXPR void on_datetime(numeric_system) {} FMT_CONSTEXPR void on_loc_date(numeric_system) {} FMT_CONSTEXPR void on_loc_time(numeric_system) {} @@ -993,7 +905,7 @@ struct tm_format_checker : null_chrono_spec_handler { FMT_CONSTEXPR void on_24_hour_time() {} FMT_CONSTEXPR void on_iso_time() {} FMT_CONSTEXPR void on_am_pm() {} - FMT_CONSTEXPR void on_utc_offset(numeric_system) {} + FMT_CONSTEXPR void on_utc_offset() {} FMT_CONSTEXPR void on_tz_name() {} }; @@ -1045,130 +957,13 @@ inline void tzset_once() { } #endif -// Converts value to Int and checks that it's in the range [0, upper). -template ::value)> -inline Int to_nonnegative_int(T value, Int upper) { - FMT_ASSERT(std::is_unsigned::value || - (value >= 0 && to_unsigned(value) <= to_unsigned(upper)), - "invalid value"); - (void)upper; - return static_cast(value); -} -template ::value)> -inline Int to_nonnegative_int(T value, Int upper) { - if (value < 0 || value > static_cast(upper)) - FMT_THROW(format_error("invalid value")); - return static_cast(value); -} - -constexpr long long pow10(std::uint32_t n) { - return n == 0 ? 1 : 10 * pow10(n - 1); -} - -// Counts the number of fractional digits in the range [0, 18] according to the -// C++20 spec. If more than 18 fractional digits are required then returns 6 for -// microseconds precision. -template () / 10)> -struct count_fractional_digits { - static constexpr int value = - Num % Den == 0 ? N : count_fractional_digits::value; -}; - -// Base case that doesn't instantiate any more templates -// in order to avoid overflow. -template -struct count_fractional_digits { - static constexpr int value = (Num % Den == 0) ? N : 6; -}; - -// Format subseconds which are given as an integer type with an appropriate -// number of digits. -template -void write_fractional_seconds(OutputIt& out, Duration d, int precision = -1) { - constexpr auto num_fractional_digits = - count_fractional_digits::value; - - using subsecond_precision = std::chrono::duration< - typename std::common_type::type, - std::ratio<1, detail::pow10(num_fractional_digits)>>; - - const auto fractional = - d - std::chrono::duration_cast(d); - const auto subseconds = - std::chrono::treat_as_floating_point< - typename subsecond_precision::rep>::value - ? fractional.count() - : std::chrono::duration_cast(fractional).count(); - auto n = static_cast>(subseconds); - const int num_digits = detail::count_digits(n); - - int leading_zeroes = (std::max)(0, num_fractional_digits - num_digits); - if (precision < 0) { - FMT_ASSERT(!std::is_floating_point::value, ""); - if (std::ratio_less::value) { - *out++ = '.'; - out = std::fill_n(out, leading_zeroes, '0'); - out = format_decimal(out, n, num_digits).end; - } - } else { - *out++ = '.'; - leading_zeroes = (std::min)(leading_zeroes, precision); - out = std::fill_n(out, leading_zeroes, '0'); - int remaining = precision - leading_zeroes; - if (remaining != 0 && remaining < num_digits) { - n /= to_unsigned(detail::pow10(to_unsigned(num_digits - remaining))); - out = format_decimal(out, n, remaining).end; - return; - } - out = format_decimal(out, n, num_digits).end; - remaining -= num_digits; - out = std::fill_n(out, remaining, '0'); - } -} - -// Format subseconds which are given as a floating point type with an -// appropriate number of digits. We cannot pass the Duration here, as we -// explicitly need to pass the Rep value in the chrono_formatter. -template -void write_floating_seconds(memory_buffer& buf, Duration duration, - int num_fractional_digits = -1) { - using rep = typename Duration::rep; - FMT_ASSERT(std::is_floating_point::value, ""); - - auto val = duration.count(); - - if (num_fractional_digits < 0) { - // For `std::round` with fallback to `round`: - // On some toolchains `std::round` is not available (e.g. GCC 6). - using namespace std; - num_fractional_digits = - count_fractional_digits::value; - if (num_fractional_digits < 6 && static_cast(round(val)) != val) - num_fractional_digits = 6; - } - - format_to(std::back_inserter(buf), FMT_STRING("{:.{}f}"), - std::fmod(val * static_cast(Duration::period::num) / - static_cast(Duration::period::den), - static_cast(60)), - num_fractional_digits); -} - -template -class tm_writer { +template class tm_writer { private: static constexpr int days_per_week = 7; const std::locale& loc_; const bool is_classic_; OutputIt out_; - const Duration* subsecs_; const std::tm& tm_; auto tm_sec() const noexcept -> int { @@ -1256,17 +1051,6 @@ class tm_writer { *out_++ = *d++; *out_++ = *d; } - void write2(int value, pad_type pad) { - unsigned int v = to_unsigned(value) % 100; - if (v >= 10) { - const char* d = digits2(v); - *out_++ = *d++; - *out_++ = *d; - } else { - out_ = detail::write_padding(out_, pad); - *out_++ = static_cast('0' + v); - } - } void write_year_extended(long long year) { // At least 4 characters. @@ -1290,7 +1074,7 @@ class tm_writer { } } - void write_utc_offset(long offset, numeric_system ns) { + void write_utc_offset(long offset) { if (offset < 0) { *out_++ = '-'; offset = -offset; @@ -1299,15 +1083,14 @@ class tm_writer { } offset /= 60; write2(static_cast(offset / 60)); - if (ns != numeric_system::standard) *out_++ = ':'; write2(static_cast(offset % 60)); } template ::value)> - void format_utc_offset_impl(const T& tm, numeric_system ns) { - write_utc_offset(tm.tm_gmtoff, ns); + void format_utc_offset_impl(const T& tm) { + write_utc_offset(tm.tm_gmtoff); } template ::value)> - void format_utc_offset_impl(const T& tm, numeric_system ns) { + void format_utc_offset_impl(const T& tm) { #if defined(_WIN32) && defined(_UCRT) # if FMT_USE_TZSET tzset_once(); @@ -1319,17 +1102,10 @@ class tm_writer { _get_dstbias(&dstbias); offset += dstbias; } - write_utc_offset(-offset, ns); + write_utc_offset(-offset); #else - if (ns == numeric_system::standard) return format_localized('z'); - - // Extract timezone offset from timezone conversion functions. - std::tm gtm = tm; - std::time_t gt = std::mktime(>m); - std::tm ltm = gmtime(gt); - std::time_t lt = std::mktime(<m); - long offset = gt - lt; - write_utc_offset(offset, ns); + ignore_unused(tm); + format_localized('z'); #endif } @@ -1350,12 +1126,10 @@ class tm_writer { } public: - tm_writer(const std::locale& loc, OutputIt out, const std::tm& tm, - const Duration* subsecs = nullptr) + tm_writer(const std::locale& loc, OutputIt out, const std::tm& tm) : loc_(loc), is_classic_(loc_ == get_classic_locale()), out_(out), - subsecs_(subsecs), tm_(tm) {} OutputIt out() const { return out_; } @@ -1453,7 +1227,7 @@ class tm_writer { out_ = copy_str(std::begin(buf) + offset, std::end(buf), out_); } - void on_utc_offset(numeric_system ns) { format_utc_offset_impl(tm_, ns); } + void on_utc_offset() { format_utc_offset_impl(tm_); } void on_tz_name() { format_tz_name_impl(tm_); } void on_year(numeric_system ns) { @@ -1541,41 +1315,22 @@ class tm_writer { } } - void on_24_hour(numeric_system ns, pad_type pad) { - if (is_classic_ || ns == numeric_system::standard) - return write2(tm_hour(), pad); + void on_24_hour(numeric_system ns) { + if (is_classic_ || ns == numeric_system::standard) return write2(tm_hour()); format_localized('H', 'O'); } - void on_12_hour(numeric_system ns, pad_type pad) { + void on_12_hour(numeric_system ns) { if (is_classic_ || ns == numeric_system::standard) - return write2(tm_hour12(), pad); + return write2(tm_hour12()); format_localized('I', 'O'); } - void on_minute(numeric_system ns, pad_type pad) { - if (is_classic_ || ns == numeric_system::standard) - return write2(tm_min(), pad); + void on_minute(numeric_system ns) { + if (is_classic_ || ns == numeric_system::standard) return write2(tm_min()); format_localized('M', 'O'); } - - void on_second(numeric_system ns, pad_type pad) { - if (is_classic_ || ns == numeric_system::standard) { - write2(tm_sec(), pad); - if (subsecs_) { - if (std::is_floating_point::value) { - auto buf = memory_buffer(); - write_floating_seconds(buf, *subsecs_); - if (buf.size() > 1) { - // Remove the leading "0", write something like ".123". - out_ = std::copy(buf.begin() + 1, buf.end(), out_); - } - } else { - write_fractional_seconds(out_, *subsecs_); - } - } - } else { - // Currently no formatting of subseconds when a locale is set. - format_localized('S', 'O'); - } + void on_second(numeric_system ns) { + if (is_classic_ || ns == numeric_system::standard) return write2(tm_sec()); + format_localized('S', 'O'); } void on_12_hour_time() { @@ -1596,9 +1351,10 @@ class tm_writer { write2(tm_min()); } void on_iso_time() { - on_24_hour_time(); - *out_++ = ':'; - on_second(numeric_system::standard, pad_type::unspecified); + char buf[8]; + write_digit2_separated(buf, to_unsigned(tm_hour()), to_unsigned(tm_min()), + to_unsigned(tm_sec()), ':'); + out_ = copy_str(std::begin(buf), std::end(buf), out_); } void on_am_pm() { @@ -1616,34 +1372,43 @@ class tm_writer { }; struct chrono_format_checker : null_chrono_spec_handler { - bool has_precision_integral = false; - FMT_NORETURN void unsupported() { FMT_THROW(format_error("no date")); } template FMT_CONSTEXPR void on_text(const Char*, const Char*) {} - FMT_CONSTEXPR void on_24_hour(numeric_system, pad_type) {} - FMT_CONSTEXPR void on_12_hour(numeric_system, pad_type) {} - FMT_CONSTEXPR void on_minute(numeric_system, pad_type) {} - FMT_CONSTEXPR void on_second(numeric_system, pad_type) {} + FMT_CONSTEXPR void on_24_hour(numeric_system) {} + FMT_CONSTEXPR void on_12_hour(numeric_system) {} + FMT_CONSTEXPR void on_minute(numeric_system) {} + FMT_CONSTEXPR void on_second(numeric_system) {} FMT_CONSTEXPR void on_12_hour_time() {} FMT_CONSTEXPR void on_24_hour_time() {} FMT_CONSTEXPR void on_iso_time() {} FMT_CONSTEXPR void on_am_pm() {} - FMT_CONSTEXPR void on_duration_value() const { - if (has_precision_integral) { - FMT_THROW(format_error("precision not allowed for this argument type")); - } - } + FMT_CONSTEXPR void on_duration_value() {} FMT_CONSTEXPR void on_duration_unit() {} }; -template ::value&& has_isfinite::value)> +template ::value)> inline bool isfinite(T) { return true; } +// Converts value to Int and checks that it's in the range [0, upper). +template ::value)> +inline Int to_nonnegative_int(T value, Int upper) { + FMT_ASSERT(std::is_unsigned::value || + (value >= 0 && to_unsigned(value) <= to_unsigned(upper)), + "invalid value"); + (void)upper; + return static_cast(value); +} +template ::value)> +inline Int to_nonnegative_int(T value, Int upper) { + if (value < 0 || value > static_cast(upper)) + FMT_THROW(format_error("invalid value")); + return static_cast(value); +} + template ::value)> inline T mod(T x, int y) { return x % static_cast(y); @@ -1698,6 +1463,47 @@ inline std::chrono::duration get_milliseconds( #endif } +// Counts the number of fractional digits in the range [0, 18] according to the +// C++20 spec. If more than 18 fractional digits are required then returns 6 for +// microseconds precision. +template () / 10)> +struct count_fractional_digits { + static constexpr int value = + Num % Den == 0 ? N : count_fractional_digits::value; +}; + +// Base case that doesn't instantiate any more templates +// in order to avoid overflow. +template +struct count_fractional_digits { + static constexpr int value = (Num % Den == 0) ? N : 6; +}; + +constexpr long long pow10(std::uint32_t n) { + return n == 0 ? 1 : 10 * pow10(n - 1); +} + +template ::is_signed)> +constexpr std::chrono::duration abs( + std::chrono::duration d) { + // We need to compare the duration using the count() method directly + // due to a compiler bug in clang-11 regarding the spaceship operator, + // when -Wzero-as-null-pointer-constant is enabled. + // In clang-12 the bug has been fixed. See + // https://bugs.llvm.org/show_bug.cgi?id=46235 and the reproducible example: + // https://www.godbolt.org/z/Knbb5joYx. + return d.count() >= d.zero().count() ? d : -d; +} + +template ::is_signed)> +constexpr std::chrono::duration abs( + std::chrono::duration d) { + return d; +} + template ::value)> OutputIt format_duration_value(OutputIt out, Rep val, int) { @@ -1707,7 +1513,7 @@ OutputIt format_duration_value(OutputIt out, Rep val, int) { template ::value)> OutputIt format_duration_value(OutputIt out, Rep val, int precision) { - auto specs = format_specs(); + auto specs = basic_format_specs(); specs.precision = precision; specs.type = precision >= 0 ? presentation_type::fixed_lower : presentation_type::general_lower; @@ -1848,18 +1654,46 @@ struct chrono_formatter { } } - void write(Rep value, int width, pad_type pad = pad_type::unspecified) { + void write(Rep value, int width) { write_sign(); if (isnan(value)) return write_nan(); uint32_or_64_or_128_t n = to_unsigned(to_nonnegative_int(value, max_value())); int num_digits = detail::count_digits(n); - if (width > num_digits) { - out = detail::write_padding(out, pad, width - num_digits); - } + if (width > num_digits) out = std::fill_n(out, width - num_digits, '0'); out = format_decimal(out, n, num_digits).end; } + template void write_fractional_seconds(Duration d) { + FMT_ASSERT(!std::is_floating_point::value, ""); + constexpr auto num_fractional_digits = + count_fractional_digits::value; + + using subsecond_precision = std::chrono::duration< + typename std::common_type::type, + std::ratio<1, detail::pow10(num_fractional_digits)>>; + if (std::ratio_less::value) { + *out++ = '.'; + auto fractional = + detail::abs(d) - std::chrono::duration_cast(d); + auto subseconds = + std::chrono::treat_as_floating_point< + typename subsecond_precision::rep>::value + ? fractional.count() + : std::chrono::duration_cast(fractional) + .count(); + uint32_or_64_or_128_t n = + to_unsigned(to_nonnegative_int(subseconds, max_value())); + int num_digits = detail::count_digits(n); + if (num_fractional_digits > num_digits) + out = std::fill_n(out, num_fractional_digits - num_digits, '0'); + out = format_decimal(out, n, num_digits).end; + } + } + void write_nan() { std::copy_n("nan", 3, out); } void write_pinf() { std::copy_n("inf", 3, out); } void write_ninf() { std::copy_n("-inf", 4, out); } @@ -1889,7 +1723,7 @@ struct chrono_formatter { void on_loc_time(numeric_system) {} void on_us_date() {} void on_iso_date() {} - void on_utc_offset(numeric_system) {} + void on_utc_offset() {} void on_tz_name() {} void on_year(numeric_system) {} void on_short_year(numeric_system) {} @@ -1905,56 +1739,58 @@ struct chrono_formatter { void on_day_of_month(numeric_system) {} void on_day_of_month_space(numeric_system) {} - void on_24_hour(numeric_system ns, pad_type pad) { + void on_24_hour(numeric_system ns) { if (handle_nan_inf()) return; - if (ns == numeric_system::standard) return write(hour(), 2, pad); + if (ns == numeric_system::standard) return write(hour(), 2); auto time = tm(); time.tm_hour = to_nonnegative_int(hour(), 24); - format_tm(time, &tm_writer_type::on_24_hour, ns, pad); + format_tm(time, &tm_writer_type::on_24_hour, ns); } - void on_12_hour(numeric_system ns, pad_type pad) { + void on_12_hour(numeric_system ns) { if (handle_nan_inf()) return; - if (ns == numeric_system::standard) return write(hour12(), 2, pad); + if (ns == numeric_system::standard) return write(hour12(), 2); auto time = tm(); time.tm_hour = to_nonnegative_int(hour12(), 12); - format_tm(time, &tm_writer_type::on_12_hour, ns, pad); + format_tm(time, &tm_writer_type::on_12_hour, ns); } - void on_minute(numeric_system ns, pad_type pad) { + void on_minute(numeric_system ns) { if (handle_nan_inf()) return; - if (ns == numeric_system::standard) return write(minute(), 2, pad); + if (ns == numeric_system::standard) return write(minute(), 2); auto time = tm(); time.tm_min = to_nonnegative_int(minute(), 60); - format_tm(time, &tm_writer_type::on_minute, ns, pad); + format_tm(time, &tm_writer_type::on_minute, ns); } - void on_second(numeric_system ns, pad_type pad) { + void on_second(numeric_system ns) { if (handle_nan_inf()) return; if (ns == numeric_system::standard) { if (std::is_floating_point::value) { + constexpr auto num_fractional_digits = + count_fractional_digits::value; auto buf = memory_buffer(); - write_floating_seconds(buf, std::chrono::duration(val), - precision); + format_to(std::back_inserter(buf), runtime("{:.{}f}"), + std::fmod(val * static_cast(Period::num) / + static_cast(Period::den), + static_cast(60)), + num_fractional_digits); if (negative) *out++ = '-'; - if (buf.size() < 2 || buf[1] == '.') { - out = detail::write_padding(out, pad); - } + if (buf.size() < 2 || buf[1] == '.') *out++ = '0'; out = std::copy(buf.begin(), buf.end(), out); } else { - write(second(), 2, pad); - write_fractional_seconds( - out, std::chrono::duration(val), precision); + write(second(), 2); + write_fractional_seconds(std::chrono::duration(val)); } return; } auto time = tm(); time.tm_sec = to_nonnegative_int(second(), 60); - format_tm(time, &tm_writer_type::on_second, ns, pad); + format_tm(time, &tm_writer_type::on_second, ns); } void on_12_hour_time() { @@ -1978,7 +1814,7 @@ struct chrono_formatter { on_24_hour_time(); *out++ = ':'; if (handle_nan_inf()) return; - on_second(numeric_system::standard, pad_type::unspecified); + on_second(numeric_system::standard); } void on_am_pm() { @@ -2047,7 +1883,7 @@ template struct formatter { template struct formatter, Char> { private: - format_specs specs; + basic_format_specs specs; int precision = -1; using arg_ref_type = detail::arg_ref; arg_ref_type width_ref; @@ -2056,6 +1892,45 @@ struct formatter, Char> { basic_string_view format_str; using duration = std::chrono::duration; + struct spec_handler { + formatter& f; + basic_format_parse_context& context; + basic_string_view format_str; + + template FMT_CONSTEXPR arg_ref_type make_arg_ref(Id arg_id) { + context.check_arg_id(arg_id); + return arg_ref_type(arg_id); + } + + FMT_CONSTEXPR arg_ref_type make_arg_ref(basic_string_view arg_id) { + context.check_arg_id(arg_id); + return arg_ref_type(arg_id); + } + + FMT_CONSTEXPR arg_ref_type make_arg_ref(detail::auto_id) { + return arg_ref_type(context.next_arg_id()); + } + + void on_error(const char* msg) { FMT_THROW(format_error(msg)); } + FMT_CONSTEXPR void on_fill(basic_string_view fill) { + f.specs.fill = fill; + } + FMT_CONSTEXPR void on_align(align_t align) { f.specs.align = align; } + FMT_CONSTEXPR void on_width(int width) { f.specs.width = width; } + FMT_CONSTEXPR void on_precision(int _precision) { + f.precision = _precision; + } + FMT_CONSTEXPR void end_precision() {} + + template FMT_CONSTEXPR void on_dynamic_width(Id arg_id) { + f.width_ref = make_arg_ref(arg_id); + } + + template FMT_CONSTEXPR void on_dynamic_precision(Id arg_id) { + f.precision_ref = make_arg_ref(arg_id); + } + }; + using iterator = typename basic_format_parse_context::iterator; struct parse_range { iterator begin; @@ -2065,24 +1940,23 @@ struct formatter, Char> { FMT_CONSTEXPR parse_range do_parse(basic_format_parse_context& ctx) { auto begin = ctx.begin(), end = ctx.end(); if (begin == end || *begin == '}') return {begin, begin}; - - begin = detail::parse_align(begin, end, specs); + spec_handler handler{*this, ctx, format_str}; + begin = detail::parse_align(begin, end, handler); if (begin == end) return {begin, begin}; - - begin = detail::parse_dynamic_spec(begin, end, specs.width, width_ref, ctx); + begin = detail::parse_width(begin, end, handler); if (begin == end) return {begin, begin}; - - auto checker = detail::chrono_format_checker(); if (*begin == '.') { - checker.has_precision_integral = !std::is_floating_point::value; - begin = - detail::parse_precision(begin, end, precision, precision_ref, ctx); + if (std::is_floating_point::value) + begin = detail::parse_precision(begin, end, handler); + else + handler.on_error("precision not allowed for this argument type"); } if (begin != end && *begin == 'L') { ++begin; localized = true; } - end = detail::parse_chrono_format(begin, end, checker); + end = detail::parse_chrono_format(begin, end, + detail::chrono_format_checker()); return {begin, end}; } @@ -2128,140 +2002,68 @@ template struct formatter, Char> : formatter { FMT_CONSTEXPR formatter() { - this->format_str = detail::string_literal{}; + basic_string_view default_specs = + detail::string_literal{}; + this->do_parse(default_specs.begin(), default_specs.end()); } template - auto format(std::chrono::time_point val, + auto format(std::chrono::time_point val, FormatContext& ctx) const -> decltype(ctx.out()) { - using period = typename Duration::period; - if (period::num != 1 || period::den != 1 || - std::is_floating_point::value) { - const auto epoch = val.time_since_epoch(); - auto subsecs = std::chrono::duration_cast( - epoch - std::chrono::duration_cast(epoch)); - - if (subsecs.count() < 0) { - auto second = std::chrono::seconds(1); - if (epoch.count() < ((Duration::min)() + second).count()) - FMT_THROW(format_error("duration is too small")); - subsecs += second; - val -= second; - } - - return formatter::do_format( - gmtime(std::chrono::time_point_cast(val)), ctx, - &subsecs); - } - - return formatter::format( - gmtime(std::chrono::time_point_cast(val)), ctx); + return formatter::format(localtime(val), ctx); } }; -#if FMT_USE_LOCAL_TIME -template -struct formatter, Char> - : formatter { - FMT_CONSTEXPR formatter() { - this->format_str = detail::string_literal{}; - } - - template - auto format(std::chrono::local_time val, FormatContext& ctx) const - -> decltype(ctx.out()) { - using period = typename Duration::period; - if (period::num != 1 || period::den != 1 || - std::is_floating_point::value) { - const auto epoch = val.time_since_epoch(); - const auto subsecs = std::chrono::duration_cast( - epoch - std::chrono::duration_cast(epoch)); - - return formatter::do_format( - localtime(std::chrono::time_point_cast(val)), - ctx, &subsecs); - } - - return formatter::format( - localtime(std::chrono::time_point_cast(val)), - ctx); - } -}; -#endif - -#if FMT_USE_UTC_TIME -template -struct formatter, - Char> - : formatter, - Char> { - template - auto format(std::chrono::time_point val, - FormatContext& ctx) const -> decltype(ctx.out()) { - return formatter< - std::chrono::time_point, - Char>::format(std::chrono::utc_clock::to_sys(val), ctx); - } -}; -#endif - template struct formatter { private: - format_specs specs; - detail::arg_ref width_ref; + enum class spec { + unknown, + year_month_day, + hh_mm_ss, + }; + spec spec_ = spec::unknown; + basic_string_view specs; protected: - basic_string_view format_str; - - FMT_CONSTEXPR auto do_parse(basic_format_parse_context& ctx) - -> decltype(ctx.begin()) { - auto begin = ctx.begin(), end = ctx.end(); - if (begin == end || *begin == '}') return begin; - - begin = detail::parse_align(begin, end, specs); - if (begin == end) return end; - - begin = detail::parse_dynamic_spec(begin, end, specs.width, width_ref, ctx); - if (begin == end) return end; - + template FMT_CONSTEXPR auto do_parse(It begin, It end) -> It { + if (begin != end && *begin == ':') ++begin; end = detail::parse_chrono_format(begin, end, detail::tm_format_checker()); - // Replace default format_str only if the new spec is not empty. - if (end != begin) format_str = {begin, detail::to_unsigned(end - begin)}; + // Replace default spec only if the new spec is not empty. + if (end != begin) specs = {begin, detail::to_unsigned(end - begin)}; return end; } - template - auto do_format(const std::tm& tm, FormatContext& ctx, - const Duration* subsecs) const -> decltype(ctx.out()) { - auto specs_copy = specs; - basic_memory_buffer buf; - auto out = std::back_inserter(buf); - detail::handle_dynamic_spec(specs_copy.width, - width_ref, ctx); - - const auto loc_ref = ctx.locale(); - detail::get_locale loc(static_cast(loc_ref), loc_ref); - auto w = - detail::tm_writer(loc, out, tm, subsecs); - detail::parse_chrono_format(format_str.begin(), format_str.end(), w); - return detail::write( - ctx.out(), basic_string_view(buf.data(), buf.size()), specs_copy); - } - public: FMT_CONSTEXPR auto parse(basic_format_parse_context& ctx) -> decltype(ctx.begin()) { - return this->do_parse(ctx); + auto end = this->do_parse(ctx.begin(), ctx.end()); + // basic_string_view<>::compare isn't constexpr before C++17. + if (specs.size() == 2 && specs[0] == Char('%')) { + if (specs[1] == Char('F')) + spec_ = spec::year_month_day; + else if (specs[1] == Char('T')) + spec_ = spec::hh_mm_ss; + } + return end; } template auto format(const std::tm& tm, FormatContext& ctx) const -> decltype(ctx.out()) { - return do_format(tm, ctx, nullptr); + const auto loc_ref = ctx.locale(); + detail::get_locale loc(static_cast(loc_ref), loc_ref); + auto w = detail::tm_writer(loc, ctx.out(), tm); + if (spec_ == spec::year_month_day) + w.on_iso_date(); + else if (spec_ == spec::hh_mm_ss) + w.on_iso_time(); + else + detail::parse_chrono_format(specs.begin(), specs.end(), w); + return w.out(); } }; -FMT_END_EXPORT +FMT_MODULE_EXPORT_END FMT_END_NAMESPACE #endif // FMT_CHRONO_H_ diff --git a/include/spdlog/fmt/bundled/color.h b/include/spdlog/fmt/bundled/color.h index d175448a..4c163277 100644 --- a/include/spdlog/fmt/bundled/color.h +++ b/include/spdlog/fmt/bundled/color.h @@ -11,7 +11,7 @@ #include "format.h" FMT_BEGIN_NAMESPACE -FMT_BEGIN_EXPORT +FMT_MODULE_EXPORT_BEGIN enum class color : uint32_t { alice_blue = 0xF0F8FF, // rgb(240,248,255) @@ -423,6 +423,26 @@ FMT_CONSTEXPR ansi_color_escape make_emphasis(emphasis em) noexcept { return ansi_color_escape(em); } +template inline void fputs(const Char* chars, FILE* stream) { + int result = std::fputs(chars, stream); + if (result < 0) + FMT_THROW(system_error(errno, FMT_STRING("cannot write to file"))); +} + +template <> inline void fputs(const wchar_t* chars, FILE* stream) { + int result = std::fputws(chars, stream); + if (result < 0) + FMT_THROW(system_error(errno, FMT_STRING("cannot write to file"))); +} + +template inline void reset_color(FILE* stream) { + fputs("\x1b[0m", stream); +} + +template <> inline void reset_color(FILE* stream) { + fputs(L"\x1b[0m", stream); +} + template inline void reset_color(buffer& buffer) { auto reset_color = string_view("\x1b[0m"); buffer.append(reset_color.begin(), reset_color.end()); @@ -459,19 +479,17 @@ void vformat_to(buffer& buf, const text_style& ts, FMT_END_DETAIL_NAMESPACE -inline void vprint(std::FILE* f, const text_style& ts, string_view fmt, - format_args args) { - // Legacy wide streams are not supported. - auto buf = memory_buffer(); - detail::vformat_to(buf, ts, fmt, args); +template > +void vprint(std::FILE* f, const text_style& ts, const S& format, + basic_format_args>> args) { + basic_memory_buffer buf; + detail::vformat_to(buf, ts, detail::to_string_view(format), args); if (detail::is_utf8()) { - detail::print(f, string_view(buf.begin(), buf.size())); - return; + detail::print(f, basic_string_view(buf.begin(), buf.size())); + } else { + buf.push_back(Char(0)); + detail::fputs(buf.data(), f); } - buf.push_back('\0'); - int result = std::fputs(buf.data(), f); - if (result < 0) - FMT_THROW(system_error(errno, FMT_STRING("cannot write to file"))); } /** @@ -548,7 +566,7 @@ OutputIt vformat_to( basic_format_args>> args) { auto&& buf = detail::get_buffer(out); detail::vformat_to(buf, ts, format_str, args); - return detail::get_iterator(buf, out); + return detail::get_iterator(buf); } /** @@ -627,7 +645,7 @@ FMT_CONSTEXPR auto styled(const T& value, text_style ts) return detail::styled_arg>{value, ts}; } -FMT_END_EXPORT +FMT_MODULE_EXPORT_END FMT_END_NAMESPACE #endif // FMT_COLOR_H_ diff --git a/include/spdlog/fmt/bundled/compile.h b/include/spdlog/fmt/bundled/compile.h index 94e13c02..933668c4 100644 --- a/include/spdlog/fmt/bundled/compile.h +++ b/include/spdlog/fmt/bundled/compile.h @@ -331,14 +331,14 @@ template struct parse_specs_result { int next_arg_id; }; -enum { manual_indexing_id = -1 }; +constexpr int manual_indexing_id = -1; template constexpr parse_specs_result parse_specs(basic_string_view str, size_t pos, int next_arg_id) { str.remove_prefix(pos); - auto ctx = - compile_parse_context(str, max_value(), nullptr, next_arg_id); + auto ctx = compile_parse_context(str, max_value(), nullptr, {}, + next_arg_id); auto f = formatter(); auto end = f.parse(ctx); return {f, pos + fmt::detail::to_unsigned(end - str.data()), @@ -348,18 +348,22 @@ constexpr parse_specs_result parse_specs(basic_string_view str, template struct arg_id_handler { arg_ref arg_id; - constexpr int on_auto() { + constexpr int operator()() { FMT_ASSERT(false, "handler cannot be used with automatic indexing"); return 0; } - constexpr int on_index(int id) { + constexpr int operator()(int id) { arg_id = arg_ref(id); return 0; } - constexpr int on_name(basic_string_view id) { + constexpr int operator()(basic_string_view id) { arg_id = arg_ref(id); return 0; } + + constexpr void on_error(const char* message) { + FMT_THROW(format_error(message)); + } }; template struct parse_arg_id_result { @@ -497,7 +501,7 @@ constexpr auto compile(S format_str) { #endif // defined(__cpp_if_constexpr) && defined(__cpp_return_type_deduction) } // namespace detail -FMT_BEGIN_EXPORT +FMT_MODULE_EXPORT_BEGIN #if defined(__cpp_if_constexpr) && defined(__cpp_return_type_deduction) @@ -601,7 +605,7 @@ template constexpr auto operator""_cf() { } // namespace literals #endif -FMT_END_EXPORT +FMT_MODULE_EXPORT_END FMT_END_NAMESPACE #endif // FMT_COMPILE_H_ diff --git a/include/spdlog/fmt/bundled/core.h b/include/spdlog/fmt/bundled/core.h index 46723d59..f6a37af9 100644 --- a/include/spdlog/fmt/bundled/core.h +++ b/include/spdlog/fmt/bundled/core.h @@ -17,7 +17,7 @@ #include // The fmt library version in the form major * 10000 + minor * 100 + patch. -#define FMT_VERSION 100000 +#define FMT_VERSION 90100 #if defined(__clang__) && !defined(__ibmxl__) # define FMT_CLANG_VERSION (__clang_major__ * 100 + __clang_minor__) @@ -69,7 +69,9 @@ # define FMT_HAS_FEATURE(x) 0 #endif -#if defined(__has_include) || FMT_ICC_VERSION >= 1600 || FMT_MSC_VERSION > 1900 +#if (defined(__has_include) || FMT_ICC_VERSION >= 1600 || \ + FMT_MSC_VERSION > 1900) && \ + !defined(__INTELLISENSE__) # define FMT_HAS_INCLUDE(x) __has_include(x) #else # define FMT_HAS_INCLUDE(x) 0 @@ -138,7 +140,22 @@ # endif #endif -// Disable [[noreturn]] on MSVC/NVCC because of bogus unreachable code warnings. +#ifndef FMT_DEPRECATED +# if FMT_HAS_CPP14_ATTRIBUTE(deprecated) || FMT_MSC_VERSION >= 1900 +# define FMT_DEPRECATED [[deprecated]] +# else +# if (defined(__GNUC__) && !defined(__LCC__)) || defined(__clang__) +# define FMT_DEPRECATED __attribute__((deprecated)) +# elif FMT_MSC_VERSION +# define FMT_DEPRECATED __declspec(deprecated) +# else +# define FMT_DEPRECATED /* deprecated */ +# endif +# endif +#endif + +// [[noreturn]] is disabled on MSVC and NVCC because of bogus unreachable code +// warnings. #if FMT_EXCEPTIONS && FMT_HAS_CPP_ATTRIBUTE(noreturn) && !FMT_MSC_VERSION && \ !defined(__NVCC__) # define FMT_NORETURN [[noreturn]] @@ -146,6 +163,17 @@ # define FMT_NORETURN #endif +#if FMT_HAS_CPP17_ATTRIBUTE(fallthrough) +# define FMT_FALLTHROUGH [[fallthrough]] +#elif defined(__clang__) +# define FMT_FALLTHROUGH [[clang::fallthrough]] +#elif FMT_GCC_VERSION >= 700 && \ + (!defined(__EDG_VERSION__) || __EDG_VERSION__ >= 520) +# define FMT_FALLTHROUGH [[gnu::fallthrough]] +#else +# define FMT_FALLTHROUGH +#endif + #ifndef FMT_NODISCARD # if FMT_HAS_CPP17_ATTRIBUTE(nodiscard) # define FMT_NODISCARD [[nodiscard]] @@ -154,6 +182,16 @@ # endif #endif +#ifndef FMT_USE_FLOAT +# define FMT_USE_FLOAT 1 +#endif +#ifndef FMT_USE_DOUBLE +# define FMT_USE_DOUBLE 1 +#endif +#ifndef FMT_USE_LONG_DOUBLE +# define FMT_USE_LONG_DOUBLE 1 +#endif + #ifndef FMT_INLINE # if FMT_GCC_VERSION || FMT_CLANG_VERSION # define FMT_INLINE inline __attribute__((always_inline)) @@ -175,7 +213,7 @@ #ifndef FMT_BEGIN_NAMESPACE # define FMT_BEGIN_NAMESPACE \ namespace fmt { \ - inline namespace v10 { + inline namespace v9 { # define FMT_END_NAMESPACE \ } \ } @@ -183,18 +221,22 @@ #ifndef FMT_MODULE_EXPORT # define FMT_MODULE_EXPORT -# define FMT_BEGIN_EXPORT -# define FMT_END_EXPORT +# define FMT_MODULE_EXPORT_BEGIN +# define FMT_MODULE_EXPORT_END +# define FMT_BEGIN_DETAIL_NAMESPACE namespace detail { +# define FMT_END_DETAIL_NAMESPACE } #endif #if !defined(FMT_HEADER_ONLY) && defined(_WIN32) -# ifdef FMT_LIB_EXPORT +# define FMT_CLASS_API FMT_MSC_WARNING(suppress : 4275) +# ifdef FMT_EXPORT # define FMT_API __declspec(dllexport) # elif defined(FMT_SHARED) # define FMT_API __declspec(dllimport) # endif #else -# if defined(FMT_LIB_EXPORT) || defined(FMT_SHARED) +# define FMT_CLASS_API +# if defined(FMT_EXPORT) || defined(FMT_SHARED) # if defined(__GNUC__) || defined(__clang__) # define FMT_API __attribute__((visibility("default"))) # endif @@ -219,13 +261,11 @@ #endif #ifndef FMT_CONSTEVAL -# if ((FMT_GCC_VERSION >= 1000 || FMT_CLANG_VERSION >= 1101) && \ - (!defined(__apple_build_version__) || \ - __apple_build_version__ >= 14000029L) && \ - FMT_CPLUSPLUS >= 202002L) || \ - (defined(__cpp_consteval) && \ +# if ((FMT_GCC_VERSION >= 1000 || FMT_CLANG_VERSION >= 1101) && \ + FMT_CPLUSPLUS >= 202002L && !defined(__apple_build_version__)) || \ + (defined(__cpp_consteval) && \ (!FMT_MSC_VERSION || _MSC_FULL_VER >= 193030704)) -// consteval is broken in MSVC before VS2022 and Apple clang before 14. +// consteval is broken in MSVC before VS2022 and Apple clang 13. # define FMT_CONSTEVAL consteval # define FMT_HAS_CONSTEVAL # else @@ -237,27 +277,21 @@ # if defined(__cpp_nontype_template_args) && \ ((FMT_GCC_VERSION >= 903 && FMT_CPLUSPLUS >= 201709L) || \ __cpp_nontype_template_args >= 201911L) && \ - !defined(__NVCOMPILER) && !defined(__LCC__) + !defined(__NVCOMPILER) # define FMT_USE_NONTYPE_TEMPLATE_ARGS 1 # else # define FMT_USE_NONTYPE_TEMPLATE_ARGS 0 # endif #endif -#if defined __cpp_inline_variables && __cpp_inline_variables >= 201606L -# define FMT_INLINE_VARIABLE inline -#else -# define FMT_INLINE_VARIABLE -#endif - // Enable minimal optimizations for more compact code in debug mode. FMT_GCC_PRAGMA("GCC push_options") -#if !defined(__OPTIMIZE__) && !defined(__NVCOMPILER) && !defined(__LCC__) && \ - !defined(__CUDACC__) +#if !defined(__OPTIMIZE__) && !defined(__NVCOMPILER) FMT_GCC_PRAGMA("GCC optimize(\"Og\")") #endif FMT_BEGIN_NAMESPACE +FMT_MODULE_EXPORT_BEGIN // Implementations of enable_if_t and other metafunctions for older systems. template @@ -276,6 +310,18 @@ template using type_identity_t = typename type_identity::type; template using underlying_t = typename std::underlying_type::type; +template struct disjunction : std::false_type {}; +template struct disjunction

: P {}; +template +struct disjunction + : conditional_t> {}; + +template struct conjunction : std::true_type {}; +template struct conjunction

: P {}; +template +struct conjunction + : conditional_t, P1> {}; + struct monostate { constexpr monostate() {} }; @@ -286,16 +332,11 @@ struct monostate { #ifdef FMT_DOC # define FMT_ENABLE_IF(...) #else -# define FMT_ENABLE_IF(...) fmt::enable_if_t<(__VA_ARGS__), int> = 0 +# define FMT_ENABLE_IF(...) enable_if_t<(__VA_ARGS__), int> = 0 #endif -#ifdef __cpp_lib_byte -inline auto format_as(std::byte b) -> unsigned char { - return static_cast(b); -} -#endif +FMT_BEGIN_DETAIL_NAMESPACE -namespace detail { // Suppresses "unused variable" warnings with the method described in // https://herbsutter.com/2009/10/18/mailbag-shutting-up-compiler-warnings/. // (void)var does not work on many Intel compilers. @@ -303,15 +344,7 @@ template FMT_CONSTEXPR void ignore_unused(const T&...) {} constexpr FMT_INLINE auto is_constant_evaluated( bool default_value = false) noexcept -> bool { -// Workaround for incompatibility between libstdc++ consteval-based -// std::is_constant_evaluated() implementation and clang-14. -// https://github.com/fmtlib/fmt/issues/3247 -#if FMT_CPLUSPLUS >= 202002L && defined(_GLIBCXX_RELEASE) && \ - _GLIBCXX_RELEASE >= 12 && \ - (FMT_CLANG_VERSION >= 1400 && FMT_CLANG_VERSION < 1500) - ignore_unused(default_value); - return __builtin_is_constant_evaluated(); -#elif defined(__cpp_lib_is_constant_evaluated) +#ifdef __cpp_lib_is_constant_evaluated ignore_unused(default_value); return std::is_constant_evaluated(); #else @@ -331,12 +364,12 @@ FMT_NORETURN FMT_API void assert_fail(const char* file, int line, # ifdef NDEBUG // FMT_ASSERT is not empty to avoid -Wempty-body. # define FMT_ASSERT(condition, message) \ - fmt::detail::ignore_unused((condition), (message)) + ::fmt::detail::ignore_unused((condition), (message)) # else # define FMT_ASSERT(condition, message) \ ((condition) /* void() fails with -Winvalid-constexpr on clang 4.0.1 */ \ ? (void)0 \ - : fmt::detail::assert_fail(__FILE__, __LINE__, (message))) + : ::fmt::detail::assert_fail(__FILE__, __LINE__, (message))) # endif #endif @@ -377,15 +410,15 @@ FMT_CONSTEXPR auto to_unsigned(Int value) -> return static_cast::type>(value); } -FMT_CONSTEXPR inline auto is_utf8() -> bool { - FMT_MSC_WARNING(suppress : 4566) constexpr unsigned char section[] = "\u00A7"; +FMT_MSC_WARNING(suppress : 4566) constexpr unsigned char micro[] = "\u00B5"; +constexpr auto is_utf8() -> bool { // Avoid buggy sign extensions in MSVC's constant evaluation mode (#2297). using uchar = unsigned char; - return FMT_UNICODE || (sizeof(section) == 3 && uchar(section[0]) == 0xC2 && - uchar(section[1]) == 0xA7); + return FMT_UNICODE || (sizeof(micro) == 3 && uchar(micro[0]) == 0xC2 && + uchar(micro[1]) == 0xB5); } -} // namespace detail +FMT_END_DETAIL_NAMESPACE /** An implementation of ``std::basic_string_view`` for pre-C++17. It provides a @@ -394,7 +427,6 @@ FMT_CONSTEXPR inline auto is_utf8() -> bool { compiled with a different ``-std`` option than the client code (which is not recommended). */ -FMT_MODULE_EXPORT template class basic_string_view { private: const Char* data_; @@ -454,18 +486,6 @@ template class basic_string_view { size_ -= n; } - FMT_CONSTEXPR_CHAR_TRAITS bool starts_with( - basic_string_view sv) const noexcept { - return size_ >= sv.size_ && - std::char_traits::compare(data_, sv.data_, sv.size_) == 0; - } - FMT_CONSTEXPR_CHAR_TRAITS bool starts_with(Char c) const noexcept { - return size_ >= 1 && std::char_traits::eq(*data_, c); - } - FMT_CONSTEXPR_CHAR_TRAITS bool starts_with(const Char* s) const { - return starts_with(basic_string_view(s)); - } - // Lexicographically compare this string reference to other. FMT_CONSTEXPR_CHAR_TRAITS auto compare(basic_string_view other) const -> int { size_t str_size = size_ < other.size_ ? size_ : other.size_; @@ -497,15 +517,13 @@ template class basic_string_view { } }; -FMT_MODULE_EXPORT using string_view = basic_string_view; /** Specifies if ``T`` is a character type. Can be specialized by users. */ -FMT_MODULE_EXPORT template struct is_char : std::false_type {}; template <> struct is_char : std::true_type {}; -namespace detail { +FMT_BEGIN_DETAIL_NAMESPACE // A base class for compile-time strings. struct compile_string {}; @@ -513,6 +531,7 @@ struct compile_string {}; template struct is_compile_string : std::is_base_of {}; +// Returns a string view of `s`. template ::value)> FMT_INLINE auto to_string_view(const Char* s) -> basic_string_view { return s; @@ -542,10 +561,10 @@ void to_string_view(...); // Specifies whether S is a string type convertible to fmt::basic_string_view. // It should be a constexpr function but MSVC 2017 fails to compile it in // enable_if and MSVC 2015 fails to compile it as an alias template. -// ADL is intentionally disabled as to_string_view is not an extension point. +// ADL invocation of to_string_view is DEPRECATED! template -struct is_string - : std::is_class()))> {}; +struct is_string : std::is_class()))> { +}; template struct char_t_impl {}; template struct char_t_impl::value>> { @@ -603,41 +622,23 @@ FMT_TYPE_CONSTANT(const void*, pointer_type); constexpr bool is_integral_type(type t) { return t > type::none_type && t <= type::last_integer_type; } + constexpr bool is_arithmetic_type(type t) { return t > type::none_type && t <= type::last_numeric_type; } -constexpr auto set(type rhs) -> int { return 1 << static_cast(rhs); } -constexpr auto in(type t, int set) -> bool { - return ((set >> static_cast(t)) & 1) != 0; -} - -// Bitsets of types. -enum { - sint_set = - set(type::int_type) | set(type::long_long_type) | set(type::int128_type), - uint_set = set(type::uint_type) | set(type::ulong_long_type) | - set(type::uint128_type), - bool_set = set(type::bool_type), - char_set = set(type::char_type), - float_set = set(type::float_type) | set(type::double_type) | - set(type::long_double_type), - string_set = set(type::string_type), - cstring_set = set(type::cstring_type), - pointer_set = set(type::pointer_type) -}; - FMT_NORETURN FMT_API void throw_format_error(const char* message); struct error_handler { constexpr error_handler() = default; + constexpr error_handler(const error_handler&) = default; // This function is intentionally not constexpr to give a compile-time error. FMT_NORETURN void on_error(const char* message) { throw_format_error(message); } }; -} // namespace detail +FMT_END_DETAIL_NAMESPACE /** String's character type. */ template using char_t = typename detail::char_t_impl::type; @@ -649,8 +650,8 @@ template using char_t = typename detail::char_t_impl::type; You can use the ``format_parse_context`` type alias for ``char`` instead. \endrst */ -FMT_MODULE_EXPORT -template class basic_format_parse_context { +template +class basic_format_parse_context : private ErrorHandler { private: basic_string_view format_str_; int next_arg_id_; @@ -659,11 +660,12 @@ template class basic_format_parse_context { public: using char_type = Char; - using iterator = const Char*; + using iterator = typename basic_string_view::iterator; explicit constexpr basic_format_parse_context( - basic_string_view format_str, int next_arg_id = 0) - : format_str_(format_str), next_arg_id_(next_arg_id) {} + basic_string_view format_str, ErrorHandler eh = {}, + int next_arg_id = 0) + : ErrorHandler(eh), format_str_(format_str), next_arg_id_(next_arg_id) {} /** Returns an iterator to the beginning of the format string range being @@ -689,8 +691,7 @@ template class basic_format_parse_context { */ FMT_CONSTEXPR auto next_arg_id() -> int { if (next_arg_id_ < 0) { - detail::throw_format_error( - "cannot switch from manual to automatic argument indexing"); + on_error("cannot switch from manual to automatic argument indexing"); return 0; } int id = next_arg_id_++; @@ -704,8 +705,7 @@ template class basic_format_parse_context { */ FMT_CONSTEXPR void check_arg_id(int id) { if (next_arg_id_ > 0) { - detail::throw_format_error( - "cannot switch from automatic to manual argument indexing"); + on_error("cannot switch from automatic to manual argument indexing"); return; } next_arg_id_ = -1; @@ -713,79 +713,80 @@ template class basic_format_parse_context { } FMT_CONSTEXPR void check_arg_id(basic_string_view) {} FMT_CONSTEXPR void check_dynamic_spec(int arg_id); + + FMT_CONSTEXPR void on_error(const char* message) { + ErrorHandler::on_error(message); + } + + constexpr auto error_handler() const -> ErrorHandler { return *this; } }; -FMT_MODULE_EXPORT using format_parse_context = basic_format_parse_context; -namespace detail { +FMT_BEGIN_DETAIL_NAMESPACE // A parse context with extra data used only in compile-time checks. -template -class compile_parse_context : public basic_format_parse_context { +template +class compile_parse_context + : public basic_format_parse_context { private: int num_args_; const type* types_; - using base = basic_format_parse_context; + using base = basic_format_parse_context; public: explicit FMT_CONSTEXPR compile_parse_context( basic_string_view format_str, int num_args, const type* types, - int next_arg_id = 0) - : base(format_str, next_arg_id), num_args_(num_args), types_(types) {} + ErrorHandler eh = {}, int next_arg_id = 0) + : base(format_str, eh, next_arg_id), num_args_(num_args), types_(types) {} constexpr auto num_args() const -> int { return num_args_; } constexpr auto arg_type(int id) const -> type { return types_[id]; } FMT_CONSTEXPR auto next_arg_id() -> int { int id = base::next_arg_id(); - if (id >= num_args_) throw_format_error("argument not found"); + if (id >= num_args_) this->on_error("argument not found"); return id; } FMT_CONSTEXPR void check_arg_id(int id) { base::check_arg_id(id); - if (id >= num_args_) throw_format_error("argument not found"); + if (id >= num_args_) this->on_error("argument not found"); } using base::check_arg_id; FMT_CONSTEXPR void check_dynamic_spec(int arg_id) { - detail::ignore_unused(arg_id); -#if !defined(__LCC__) if (arg_id < num_args_ && types_ && !is_integral_type(types_[arg_id])) - throw_format_error("width/precision is not integer"); -#endif + this->on_error("width/precision is not integer"); } }; -} // namespace detail +FMT_END_DETAIL_NAMESPACE -template -FMT_CONSTEXPR void basic_format_parse_context::do_check_arg_id(int id) { +template +FMT_CONSTEXPR void +basic_format_parse_context::do_check_arg_id(int id) { // Argument id is only checked at compile-time during parsing because // formatting has its own validation. - if (detail::is_constant_evaluated() && - (!FMT_GCC_VERSION || FMT_GCC_VERSION >= 1200)) { - using context = detail::compile_parse_context; + if (detail::is_constant_evaluated() && FMT_GCC_VERSION >= 1200) { + using context = detail::compile_parse_context; if (id >= static_cast(this)->num_args()) - detail::throw_format_error("argument not found"); + on_error("argument not found"); } } -template -FMT_CONSTEXPR void basic_format_parse_context::check_dynamic_spec( - int arg_id) { - if (detail::is_constant_evaluated() && - (!FMT_GCC_VERSION || FMT_GCC_VERSION >= 1200)) { - using context = detail::compile_parse_context; +template +FMT_CONSTEXPR void +basic_format_parse_context::check_dynamic_spec(int arg_id) { + if (detail::is_constant_evaluated()) { + using context = detail::compile_parse_context; static_cast(this)->check_dynamic_spec(arg_id); } } -FMT_MODULE_EXPORT template class basic_format_arg; -FMT_MODULE_EXPORT template class basic_format_args; -FMT_MODULE_EXPORT template class dynamic_format_arg_store; +template class basic_format_arg; +template class basic_format_args; +template class dynamic_format_arg_store; // A formatter for objects of type T. -FMT_MODULE_EXPORT template struct formatter { // A deleted default constructor indicates a disabled formatter. @@ -805,7 +806,7 @@ struct is_contiguous> : std::true_type {}; class appender; -namespace detail { +FMT_BEGIN_DETAIL_NAMESPACE template constexpr auto has_const_formatter_impl(T*) @@ -848,7 +849,7 @@ template U* { if (is_constant_evaluated()) return copy_str(begin, end, out); auto size = to_unsigned(end - begin); - if (size > 0) memcpy(out, begin, size * sizeof(U)); + memcpy(out, begin, size * sizeof(U)); return out + size; } @@ -891,11 +892,11 @@ template class buffer { buffer(const buffer&) = delete; void operator=(const buffer&) = delete; - FMT_INLINE auto begin() noexcept -> T* { return ptr_; } - FMT_INLINE auto end() noexcept -> T* { return ptr_ + size_; } + auto begin() noexcept -> T* { return ptr_; } + auto end() noexcept -> T* { return ptr_ + size_; } - FMT_INLINE auto begin() const noexcept -> const T* { return ptr_; } - FMT_INLINE auto end() const noexcept -> const T* { return ptr_ + size_; } + auto begin() const noexcept -> const T* { return ptr_; } + auto end() const noexcept -> const T* { return ptr_ + size_; } /** Returns the size of this buffer. */ constexpr auto size() const noexcept -> size_t { return size_; } @@ -1109,21 +1110,29 @@ template auto get_buffer(OutputIt out) -> iterator_buffer { return iterator_buffer(out); } -template , Buf>::value)> -auto get_buffer(std::back_insert_iterator out) -> buffer& { - return get_container(out); -} -template -FMT_INLINE auto get_iterator(Buf& buf, OutputIt) -> decltype(buf.out()) { +template +auto get_iterator(Buffer& buf) -> decltype(buf.out()) { return buf.out(); } -template -auto get_iterator(buffer&, OutputIt out) -> OutputIt { - return out; +template auto get_iterator(buffer& buf) -> buffer_appender { + return buffer_appender(buf); } +template +struct fallback_formatter { + fallback_formatter() = delete; +}; + +// Specifies if T has an enabled fallback_formatter specialization. +template +using has_fallback_formatter = +#ifdef FMT_DEPRECATED_OSTREAM + std::is_constructible>; +#else + std::false_type; +#endif + struct view {}; template struct named_arg : view { @@ -1208,6 +1217,7 @@ constexpr auto count_statically_named_args() -> size_t { struct unformattable {}; struct unformattable_char : unformattable {}; +struct unformattable_const : unformattable {}; struct unformattable_pointer : unformattable {}; template struct string_value { @@ -1281,10 +1291,14 @@ template class value { // have different extension points, e.g. `formatter` for `format` and // `printf_formatter` for `printf`. custom.format = format_custom_arg< - value_type, typename Context::template formatter_type>; + value_type, + conditional_t::value, + typename Context::template formatter_type, + fallback_formatter>>; } value(unformattable); value(unformattable_char); + value(unformattable_const); value(unformattable_pointer); private: @@ -1310,19 +1324,20 @@ enum { long_short = sizeof(long) == sizeof(int) }; using long_type = conditional_t; using ulong_type = conditional_t; -template struct format_as_result { - template ::value || std::is_class::value)> - static auto map(U*) -> decltype(format_as(std::declval())); - static auto map(...) -> void; +#ifdef __cpp_lib_byte +inline auto format_as(std::byte b) -> unsigned char { + return static_cast(b); +} +#endif - using type = decltype(map(static_cast(nullptr))); +template struct has_format_as { + template ::value&& std::is_integral::value)> + static auto check(U*) -> std::true_type; + static auto check(...) -> std::false_type; + + enum { value = decltype(check(static_cast(nullptr)))::value }; }; -template using format_as_t = typename format_as_result::type; - -template -struct has_format_as - : bool_constant, void>::value> {}; // Maps formatting arguments to core types. // arg_mapper reports errors by returning unformattable instead of using @@ -1399,6 +1414,25 @@ template struct arg_mapper { FMT_CONSTEXPR FMT_INLINE auto map(const T&) -> unformattable_char { return {}; } + template >::value && + !is_string::value && !has_formatter::value && + !has_fallback_formatter::value)> + FMT_CONSTEXPR FMT_INLINE auto map(const T& val) + -> basic_string_view { + return basic_string_view(val); + } + template >::value && + !std::is_convertible>::value && + !is_string::value && !has_formatter::value && + !has_fallback_formatter::value)> + FMT_CONSTEXPR FMT_INLINE auto map(const T& val) + -> basic_string_view { + return std_string_view(val); + } FMT_CONSTEXPR FMT_INLINE auto map(void* val) -> const void* { return val; } FMT_CONSTEXPR FMT_INLINE auto map(const void* val) -> const void* { @@ -1408,8 +1442,8 @@ template struct arg_mapper { return val; } - // Use SFINAE instead of a const T* parameter to avoid a conflict with the - // array overload. + // We use SFINAE instead of a const T* parameter to avoid conflicting with + // the C array overload. template < typename T, FMT_ENABLE_IF( @@ -1428,34 +1462,54 @@ template struct arg_mapper { return values; } - // Only map owning types because mapping views can be unsafe. - template , - FMT_ENABLE_IF(std::is_arithmetic::value)> - FMT_CONSTEXPR FMT_INLINE auto map(const T& val) -> decltype(this->map(U())) { + template ::value&& std::is_convertible::value && + !has_format_as::value && !has_formatter::value && + !has_fallback_formatter::value)> + FMT_DEPRECATED FMT_CONSTEXPR FMT_INLINE auto map(const T& val) + -> decltype(std::declval().map( + static_cast>(val))) { + return map(static_cast>(val)); + } + + template ::value && + !has_formatter::value)> + FMT_CONSTEXPR FMT_INLINE auto map(const T& val) + -> decltype(std::declval().map(format_as(T()))) { return map(format_as(val)); } template > struct formattable : bool_constant() || - (has_formatter::value && - !std::is_const>::value)> {}; + !std::is_const>::value || + has_fallback_formatter::value> {}; +#if (FMT_MSC_VERSION != 0 && FMT_MSC_VERSION < 1910) || \ + FMT_ICC_VERSION != 0 || defined(__NVCC__) + // Workaround a bug in MSVC and Intel (Issue 2746). + template FMT_CONSTEXPR FMT_INLINE auto do_map(T&& val) -> T& { + return val; + } +#else template ::value)> FMT_CONSTEXPR FMT_INLINE auto do_map(T&& val) -> T& { return val; } template ::value)> - FMT_CONSTEXPR FMT_INLINE auto do_map(T&&) -> unformattable { + FMT_CONSTEXPR FMT_INLINE auto do_map(T&&) -> unformattable_const { return {}; } +#endif template , - FMT_ENABLE_IF((std::is_class::value || std::is_enum::value || - std::is_union::value) && - !is_string::value && !is_char::value && - !is_named_arg::value && - !std::is_arithmetic>::value)> + FMT_ENABLE_IF(!is_string::value && !is_char::value && + !std::is_array::value && + !std::is_pointer::value && + !has_format_as::value && + (has_formatter::value || + has_fallback_formatter::value))> FMT_CONSTEXPR FMT_INLINE auto map(T&& val) -> decltype(this->do_map(std::forward(val))) { return do_map(std::forward(val)); @@ -1463,7 +1517,7 @@ template struct arg_mapper { template ::value)> FMT_CONSTEXPR FMT_INLINE auto map(const T& named_arg) - -> decltype(this->map(named_arg.value)) { + -> decltype(std::declval().map(named_arg.value)) { return map(named_arg.value); } @@ -1481,13 +1535,19 @@ enum { packed_arg_bits = 4 }; enum { max_packed_args = 62 / packed_arg_bits }; enum : unsigned long long { is_unpacked_bit = 1ULL << 63 }; enum : unsigned long long { has_named_args_bit = 1ULL << 62 }; -} // namespace detail + +FMT_END_DETAIL_NAMESPACE // An output iterator that appends to a buffer. // It is used to reduce symbol sizes for the common case. class appender : public std::back_insert_iterator> { using base = std::back_insert_iterator>; + template + friend auto get_buffer(appender out) -> detail::buffer& { + return detail::get_container(out); + } + public: using std::back_insert_iterator>::back_insert_iterator; appender(base it) noexcept : base(it) {} @@ -1559,7 +1619,6 @@ template class basic_format_arg { ``vis(value)`` will be called with the value of type ``double``. \endrst */ -FMT_MODULE_EXPORT template FMT_CONSTEXPR FMT_INLINE auto visit_format_arg( Visitor&& vis, const basic_format_arg& arg) -> decltype(vis(0)) { @@ -1601,7 +1660,7 @@ FMT_CONSTEXPR FMT_INLINE auto visit_format_arg( return vis(monostate()); } -namespace detail { +FMT_BEGIN_DETAIL_NAMESPACE template auto copy_str(InputIt begin, InputIt end, appender out) -> appender { @@ -1616,8 +1675,9 @@ FMT_CONSTEXPR auto copy_str(R&& rng, OutputIt out) -> OutputIt { #if FMT_GCC_VERSION && FMT_GCC_VERSION < 500 // A workaround for gcc 4.8 to make void_t work in a SFINAE context. -template struct void_t_impl { using type = void; }; -template using void_t = typename void_t_impl::type; +template struct void_t_impl { using type = void; }; +template +using void_t = typename detail::void_t_impl::type; #else template using void_t = void; #endif @@ -1632,12 +1692,13 @@ struct is_output_iterator< decltype(*std::declval() = std::declval())>> : std::true_type {}; -template struct is_back_insert_iterator : std::false_type {}; +template +struct is_back_insert_iterator : std::false_type {}; template struct is_back_insert_iterator> : std::true_type {}; -template +template struct is_contiguous_back_insert_iterator : std::false_type {}; template struct is_contiguous_back_insert_iterator> @@ -1651,7 +1712,7 @@ class locale_ref { const void* locale_; // A type-erased pointer to std::locale. public: - constexpr FMT_INLINE locale_ref() : locale_(nullptr) {} + constexpr locale_ref() : locale_(nullptr) {} template explicit locale_ref(const Locale& loc); explicit operator bool() const noexcept { return locale_ != nullptr; } @@ -1671,22 +1732,27 @@ constexpr auto encode_types() -> unsigned long long { template FMT_CONSTEXPR FMT_INLINE auto make_value(T&& val) -> value { - auto&& arg = arg_mapper().map(FMT_FORWARD(val)); - using arg_type = remove_cvref_t; + const auto& arg = arg_mapper().map(FMT_FORWARD(val)); constexpr bool formattable_char = - !std::is_same::value; + !std::is_same::value; static_assert(formattable_char, "Mixing character types is disallowed."); - // Formatting of arbitrary pointers is disallowed. If you want to format a - // pointer cast it to `void*` or `const void*`. In particular, this forbids - // formatting of `[const] volatile char*` printed as bool by iostreams. + constexpr bool formattable_const = + !std::is_same::value; + static_assert(formattable_const, "Cannot format a const argument."); + + // Formatting of arbitrary pointers is disallowed. If you want to output + // a pointer cast it to "void *" or "const void *". In particular, this + // forbids formatting of "[const] volatile char *" which is printed as bool + // by iostreams. constexpr bool formattable_pointer = - !std::is_same::value; + !std::is_same::value; static_assert(formattable_pointer, "Formatting of non-void pointers is disallowed."); - constexpr bool formattable = !std::is_same::value; + constexpr bool formattable = + !std::is_same::value; static_assert( formattable, "Cannot format an argument. To make type T formattable provide a " @@ -1696,15 +1762,15 @@ FMT_CONSTEXPR FMT_INLINE auto make_value(T&& val) -> value { template FMT_CONSTEXPR auto make_arg(T&& value) -> basic_format_arg { - auto arg = basic_format_arg(); + basic_format_arg arg; arg.type_ = mapped_type_constant::value; arg.value_ = make_value(value); return arg; } -// The DEPRECATED type template parameter is there to avoid an ODR violation -// when using a fallback formatter in one translation unit and an implicit -// conversion in another (not recommended). +// The type template parameter is there to avoid an ODR violation when using +// a fallback formatter in one translation unit and an implicit conversion in +// another (not recommended). template FMT_CONSTEXPR FMT_INLINE auto make_arg(T&& val) -> value { @@ -1716,11 +1782,14 @@ template basic_format_arg { return make_arg(value); } -} // namespace detail -FMT_BEGIN_EXPORT +FMT_END_DETAIL_NAMESPACE // Formatting context. template class basic_format_context { + public: + /** The character type for the output. */ + using char_type = Char; + private: OutputIt out_; basic_format_args args_; @@ -1729,32 +1798,31 @@ template class basic_format_context { public: using iterator = OutputIt; using format_arg = basic_format_arg; - using format_args = basic_format_args; using parse_context_type = basic_format_parse_context; - template using formatter_type = formatter; - - /** The character type for the output. */ - using char_type = Char; + template using formatter_type = formatter; basic_format_context(basic_format_context&&) = default; basic_format_context(const basic_format_context&) = delete; void operator=(const basic_format_context&) = delete; /** - Constructs a ``basic_format_context`` object. References to the arguments - are stored in the object so make sure they have appropriate lifetimes. + Constructs a ``basic_format_context`` object. References to the arguments are + stored in the object so make sure they have appropriate lifetimes. */ - constexpr basic_format_context(OutputIt out, format_args ctx_args, - detail::locale_ref loc = {}) + constexpr basic_format_context( + OutputIt out, basic_format_args ctx_args, + detail::locale_ref loc = detail::locale_ref()) : out_(out), args_(ctx_args), loc_(loc) {} constexpr auto arg(int id) const -> format_arg { return args_.get(id); } - FMT_CONSTEXPR auto arg(basic_string_view name) -> format_arg { + FMT_CONSTEXPR auto arg(basic_string_view name) -> format_arg { return args_.get(name); } - FMT_CONSTEXPR auto arg_id(basic_string_view name) -> int { + FMT_CONSTEXPR auto arg_id(basic_string_view name) -> int { return args_.get_id(name); } - auto args() const -> const format_args& { return args_; } + auto args() const -> const basic_format_args& { + return args_; + } FMT_CONSTEXPR auto error_handler() -> detail::error_handler { return {}; } void on_error(const char* message) { error_handler().on_error(message); } @@ -1775,10 +1843,16 @@ using buffer_context = basic_format_context, Char>; using format_context = buffer_context; +// Workaround an alias issue: https://stackoverflow.com/q/62767544/471164. +#define FMT_BUFFER_CONTEXT(Char) \ + basic_format_context, Char> + template -using is_formattable = bool_constant>() - .map(std::declval()))>::value>; +using is_formattable = bool_constant< + !std::is_base_of>().map( + std::declval()))>::value && + !detail::has_fallback_formatter::value>; /** \rst @@ -1838,9 +1912,9 @@ class format_arg_store See `~fmt::arg` for lifetime considerations. \endrst */ -template -constexpr auto make_format_args(T&&... args) - -> format_arg_store...> { +template +constexpr auto make_format_args(Args&&... args) + -> format_arg_store...> { return {FMT_FORWARD(args)...}; } @@ -1860,7 +1934,6 @@ inline auto arg(const Char* name, const T& arg) -> detail::named_arg { static_assert(!detail::is_named_arg(), "nested named arguments"); return {name, arg}; } -FMT_END_EXPORT /** \rst @@ -1986,7 +2059,7 @@ template class basic_format_args { /** An alias to ``basic_format_args``. */ // A separate type would result in shorter symbols but break ABI compatibility // between clang and gcc on ARM (#1919). -FMT_MODULE_EXPORT using format_args = basic_format_args; +using format_args = basic_format_args; // We cannot use enum classes as bit fields because of a gcc bug, so we put them // in namespaces instead (https://gcc.gnu.org/bugzilla/show_bug.cgi?id=61414). @@ -2007,7 +2080,7 @@ enum type FMT_ENUM_UNDERLYING_TYPE(unsigned char){none, minus, plus, space}; } using sign_t = sign::type; -namespace detail { +FMT_BEGIN_DETAIL_NAMESPACE // Workaround an array initialization issue in gcc 4.8. template struct fill_t { @@ -2019,7 +2092,7 @@ template struct fill_t { public: FMT_CONSTEXPR void operator=(basic_string_view s) { auto size = s.size(); - FMT_ASSERT(size <= max_size, "invalid fill"); + if (size > max_size) return throw_format_error("invalid fill"); for (size_t i = 0; i < size; ++i) data_[i] = s[i]; size_ = static_cast(size); } @@ -2032,10 +2105,11 @@ template struct fill_t { return data_[index]; } }; -} // namespace detail +FMT_END_DETAIL_NAMESPACE enum class presentation_type : unsigned char { none, + // Integer types should go first, dec, // 'd' oct, // 'o' hex_lower, // 'x' @@ -2057,7 +2131,7 @@ enum class presentation_type : unsigned char { }; // Format specifiers for built-in and string types. -template struct format_specs { +template struct basic_format_specs { int width; int precision; presentation_type type; @@ -2067,7 +2141,7 @@ template struct format_specs { bool localized : 1; detail::fill_t fill; - constexpr format_specs() + constexpr basic_format_specs() : width(0), precision(-1), type(presentation_type::none), @@ -2077,7 +2151,9 @@ template struct format_specs { localized(false) {} }; -namespace detail { +using format_specs = basic_format_specs; + +FMT_BEGIN_DETAIL_NAMESPACE enum class arg_id_kind { none, index, name }; @@ -2098,7 +2174,7 @@ template struct arg_ref { arg_id_kind kind; union value { - FMT_CONSTEXPR value(int idx = 0) : index(idx) {} + FMT_CONSTEXPR value(int id = 0) : index{id} {} FMT_CONSTEXPR value(basic_string_view n) : name(n) {} int index; @@ -2107,30 +2183,134 @@ template struct arg_ref { }; // Format specifiers with width and precision resolved at formatting rather -// than parsing time to allow reusing the same parsed specifiers with +// than parsing time to allow re-using the same parsed specifiers with // different sets of arguments (precompilation of format strings). -template -struct dynamic_format_specs : format_specs { +template +struct dynamic_format_specs : basic_format_specs { arg_ref width_ref; arg_ref precision_ref; }; -// Converts a character to ASCII. Returns '\0' on conversion failure. -template ::value)> -constexpr auto to_ascii(Char c) -> char { - return c <= 0xff ? static_cast(c) : '\0'; -} -template ::value)> -constexpr auto to_ascii(Char c) -> char { - return c <= 0xff ? static_cast(c) : '\0'; +struct auto_id {}; + +// A format specifier handler that sets fields in basic_format_specs. +template class specs_setter { + protected: + basic_format_specs& specs_; + + public: + explicit FMT_CONSTEXPR specs_setter(basic_format_specs& specs) + : specs_(specs) {} + + FMT_CONSTEXPR specs_setter(const specs_setter& other) + : specs_(other.specs_) {} + + FMT_CONSTEXPR void on_align(align_t align) { specs_.align = align; } + FMT_CONSTEXPR void on_fill(basic_string_view fill) { + specs_.fill = fill; + } + FMT_CONSTEXPR void on_sign(sign_t s) { specs_.sign = s; } + FMT_CONSTEXPR void on_hash() { specs_.alt = true; } + FMT_CONSTEXPR void on_localized() { specs_.localized = true; } + + FMT_CONSTEXPR void on_zero() { + if (specs_.align == align::none) specs_.align = align::numeric; + specs_.fill[0] = Char('0'); + } + + FMT_CONSTEXPR void on_width(int width) { specs_.width = width; } + FMT_CONSTEXPR void on_precision(int precision) { + specs_.precision = precision; + } + FMT_CONSTEXPR void end_precision() {} + + FMT_CONSTEXPR void on_type(presentation_type type) { specs_.type = type; } +}; + +// Format spec handler that saves references to arguments representing dynamic +// width and precision to be resolved at formatting time. +template +class dynamic_specs_handler + : public specs_setter { + public: + using char_type = typename ParseContext::char_type; + + FMT_CONSTEXPR dynamic_specs_handler(dynamic_format_specs& specs, + ParseContext& ctx) + : specs_setter(specs), specs_(specs), context_(ctx) {} + + FMT_CONSTEXPR dynamic_specs_handler(const dynamic_specs_handler& other) + : specs_setter(other), + specs_(other.specs_), + context_(other.context_) {} + + template FMT_CONSTEXPR void on_dynamic_width(Id arg_id) { + specs_.width_ref = make_arg_ref(arg_id); + } + + template FMT_CONSTEXPR void on_dynamic_precision(Id arg_id) { + specs_.precision_ref = make_arg_ref(arg_id); + } + + FMT_CONSTEXPR void on_error(const char* message) { + context_.on_error(message); + } + + private: + dynamic_format_specs& specs_; + ParseContext& context_; + + using arg_ref_type = arg_ref; + + FMT_CONSTEXPR auto make_arg_ref(int arg_id) -> arg_ref_type { + context_.check_arg_id(arg_id); + context_.check_dynamic_spec(arg_id); + return arg_ref_type(arg_id); + } + + FMT_CONSTEXPR auto make_arg_ref(auto_id) -> arg_ref_type { + int arg_id = context_.next_arg_id(); + context_.check_dynamic_spec(arg_id); + return arg_ref_type(arg_id); + } + + FMT_CONSTEXPR auto make_arg_ref(basic_string_view arg_id) + -> arg_ref_type { + context_.check_arg_id(arg_id); + basic_string_view format_str( + context_.begin(), to_unsigned(context_.end() - context_.begin())); + return arg_ref_type(arg_id); + } +}; + +template constexpr bool is_ascii_letter(Char c) { + return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z'); +} + +// Converts a character to ASCII. Returns a number > 127 on conversion failure. +template ::value)> +constexpr auto to_ascii(Char c) -> Char { + return c; +} +template ::value)> +constexpr auto to_ascii(Char c) -> underlying_t { + return c; +} + +FMT_CONSTEXPR inline auto code_point_length_impl(char c) -> int { + return "\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\0\0\0\0\0\0\0\0\2\2\2\2\3\3\4" + [static_cast(c) >> 3]; } -// Returns the number of code units in a code point or 1 on error. template FMT_CONSTEXPR auto code_point_length(const Char* begin) -> int { if (const_check(sizeof(Char) != 1)) return 1; - auto c = static_cast(*begin); - return static_cast((0x3a55000000000000ull >> (2 * (c >> 3))) & 0x3) + 1; + int len = code_point_length_impl(static_cast(*begin)); + + // Compute the pointer to the next character early so that the next + // iteration can start working on the next character. Neither Clang + // nor GCC figure out this reordering on their own. + return len + !len; } // Return the result via the out param to workaround gcc bug 77539. @@ -2175,284 +2355,279 @@ FMT_CONSTEXPR auto parse_nonnegative_int(const Char*& begin, const Char* end, : error_value; } -FMT_CONSTEXPR inline auto parse_align(char c) -> align_t { - switch (c) { - case '<': - return align::left; - case '>': - return align::right; - case '^': - return align::center; - } - return align::none; -} - -template constexpr auto is_name_start(Char c) -> bool { - return ('a' <= c && c <= 'z') || ('A' <= c && c <= 'Z') || c == '_'; -} - +// Parses fill and alignment. template +FMT_CONSTEXPR auto parse_align(const Char* begin, const Char* end, + Handler&& handler) -> const Char* { + FMT_ASSERT(begin != end, ""); + auto align = align::none; + auto p = begin + code_point_length(begin); + if (end - p <= 0) p = begin; + for (;;) { + switch (to_ascii(*p)) { + case '<': + align = align::left; + break; + case '>': + align = align::right; + break; + case '^': + align = align::center; + break; + default: + break; + } + if (align != align::none) { + if (p != begin) { + auto c = *begin; + if (c == '{') + return handler.on_error("invalid fill character '{'"), begin; + handler.on_fill(basic_string_view(begin, to_unsigned(p - begin))); + begin = p + 1; + } else + ++begin; + handler.on_align(align); + break; + } else if (p == begin) { + break; + } + p = begin; + } + return begin; +} + +template FMT_CONSTEXPR bool is_name_start(Char c) { + return ('a' <= c && c <= 'z') || ('A' <= c && c <= 'Z') || '_' == c; +} + +template FMT_CONSTEXPR auto do_parse_arg_id(const Char* begin, const Char* end, - Handler&& handler) -> const Char* { + IDHandler&& handler) -> const Char* { + FMT_ASSERT(begin != end, ""); Char c = *begin; if (c >= '0' && c <= '9') { int index = 0; - constexpr int max = (std::numeric_limits::max)(); if (c != '0') - index = parse_nonnegative_int(begin, end, max); + index = + parse_nonnegative_int(begin, end, (std::numeric_limits::max)()); else ++begin; if (begin == end || (*begin != '}' && *begin != ':')) - throw_format_error("invalid format string"); + handler.on_error("invalid format string"); else - handler.on_index(index); + handler(index); return begin; } if (!is_name_start(c)) { - throw_format_error("invalid format string"); + handler.on_error("invalid format string"); return begin; } auto it = begin; do { ++it; - } while (it != end && (is_name_start(*it) || ('0' <= *it && *it <= '9'))); - handler.on_name({begin, to_unsigned(it - begin)}); + } while (it != end && (is_name_start(c = *it) || ('0' <= c && c <= '9'))); + handler(basic_string_view(begin, to_unsigned(it - begin))); return it; } -template +template FMT_CONSTEXPR FMT_INLINE auto parse_arg_id(const Char* begin, const Char* end, - Handler&& handler) -> const Char* { - FMT_ASSERT(begin != end, ""); + IDHandler&& handler) -> const Char* { Char c = *begin; if (c != '}' && c != ':') return do_parse_arg_id(begin, end, handler); - handler.on_auto(); + handler(); return begin; } -template struct dynamic_spec_id_handler { - basic_format_parse_context& ctx; - arg_ref& ref; +template +FMT_CONSTEXPR auto parse_width(const Char* begin, const Char* end, + Handler&& handler) -> const Char* { + using detail::auto_id; + struct width_adapter { + Handler& handler; - FMT_CONSTEXPR void on_auto() { - int id = ctx.next_arg_id(); - ref = arg_ref(id); - ctx.check_dynamic_spec(id); - } - FMT_CONSTEXPR void on_index(int id) { - ref = arg_ref(id); - ctx.check_arg_id(id); - ctx.check_dynamic_spec(id); - } - FMT_CONSTEXPR void on_name(basic_string_view id) { - ref = arg_ref(id); - ctx.check_arg_id(id); - } -}; + FMT_CONSTEXPR void operator()() { handler.on_dynamic_width(auto_id()); } + FMT_CONSTEXPR void operator()(int id) { handler.on_dynamic_width(id); } + FMT_CONSTEXPR void operator()(basic_string_view id) { + handler.on_dynamic_width(id); + } + FMT_CONSTEXPR void on_error(const char* message) { + if (message) handler.on_error(message); + } + }; -// Parses [integer | "{" [arg_id] "}"]. -template -FMT_CONSTEXPR auto parse_dynamic_spec(const Char* begin, const Char* end, - int& value, arg_ref& ref, - basic_format_parse_context& ctx) - -> const Char* { FMT_ASSERT(begin != end, ""); if ('0' <= *begin && *begin <= '9') { - int val = parse_nonnegative_int(begin, end, -1); - if (val != -1) - value = val; + int width = parse_nonnegative_int(begin, end, -1); + if (width != -1) + handler.on_width(width); else - throw_format_error("number is too big"); + handler.on_error("number is too big"); } else if (*begin == '{') { ++begin; - auto handler = dynamic_spec_id_handler{ctx, ref}; - if (begin != end) begin = parse_arg_id(begin, end, handler); - if (begin != end && *begin == '}') return ++begin; - throw_format_error("invalid format string"); + if (begin != end) begin = parse_arg_id(begin, end, width_adapter{handler}); + if (begin == end || *begin != '}') + return handler.on_error("invalid format string"), begin; + ++begin; } return begin; } -template +template FMT_CONSTEXPR auto parse_precision(const Char* begin, const Char* end, - int& value, arg_ref& ref, - basic_format_parse_context& ctx) - -> const Char* { + Handler&& handler) -> const Char* { + using detail::auto_id; + struct precision_adapter { + Handler& handler; + + FMT_CONSTEXPR void operator()() { handler.on_dynamic_precision(auto_id()); } + FMT_CONSTEXPR void operator()(int id) { handler.on_dynamic_precision(id); } + FMT_CONSTEXPR void operator()(basic_string_view id) { + handler.on_dynamic_precision(id); + } + FMT_CONSTEXPR void on_error(const char* message) { + if (message) handler.on_error(message); + } + }; + ++begin; - if (begin == end || *begin == '}') { - throw_format_error("invalid precision"); - return begin; + auto c = begin != end ? *begin : Char(); + if ('0' <= c && c <= '9') { + auto precision = parse_nonnegative_int(begin, end, -1); + if (precision != -1) + handler.on_precision(precision); + else + handler.on_error("number is too big"); + } else if (c == '{') { + ++begin; + if (begin != end) + begin = parse_arg_id(begin, end, precision_adapter{handler}); + if (begin == end || *begin++ != '}') + return handler.on_error("invalid format string"), begin; + } else { + return handler.on_error("missing precision specifier"), begin; } - return parse_dynamic_spec(begin, end, value, ref, ctx); + handler.end_precision(); + return begin; } -enum class state { start, align, sign, hash, zero, width, precision, locale }; - -// Parses standard format specifiers. template -FMT_CONSTEXPR FMT_INLINE auto parse_format_specs( - const Char* begin, const Char* end, dynamic_format_specs& specs, - basic_format_parse_context& ctx, type arg_type) -> const Char* { - auto c = '\0'; - if (end - begin > 1) { - auto next = to_ascii(begin[1]); - c = parse_align(next) == align::none ? to_ascii(*begin) : '\0'; - } else { - if (begin == end) return begin; - c = to_ascii(*begin); +FMT_CONSTEXPR auto parse_presentation_type(Char type) -> presentation_type { + switch (to_ascii(type)) { + case 'd': + return presentation_type::dec; + case 'o': + return presentation_type::oct; + case 'x': + return presentation_type::hex_lower; + case 'X': + return presentation_type::hex_upper; + case 'b': + return presentation_type::bin_lower; + case 'B': + return presentation_type::bin_upper; + case 'a': + return presentation_type::hexfloat_lower; + case 'A': + return presentation_type::hexfloat_upper; + case 'e': + return presentation_type::exp_lower; + case 'E': + return presentation_type::exp_upper; + case 'f': + return presentation_type::fixed_lower; + case 'F': + return presentation_type::fixed_upper; + case 'g': + return presentation_type::general_lower; + case 'G': + return presentation_type::general_upper; + case 'c': + return presentation_type::chr; + case 's': + return presentation_type::string; + case 'p': + return presentation_type::pointer; + case '?': + return presentation_type::debug; + default: + return presentation_type::none; + } +} + +// Parses standard format specifiers and sends notifications about parsed +// components to handler. +template +FMT_CONSTEXPR FMT_INLINE auto parse_format_specs(const Char* begin, + const Char* end, + SpecHandler&& handler) + -> const Char* { + if (1 < end - begin && begin[1] == '}' && is_ascii_letter(*begin) && + *begin != 'L') { + presentation_type type = parse_presentation_type(*begin++); + if (type == presentation_type::none) + handler.on_error("invalid type specifier"); + handler.on_type(type); + return begin; } - struct { - state current_state = state::start; - FMT_CONSTEXPR void operator()(state s, bool valid = true) { - if (current_state >= s || !valid) - throw_format_error("invalid format specifier"); - current_state = s; - } - } enter_state; + if (begin == end) return begin; - using pres = presentation_type; - constexpr auto integral_set = sint_set | uint_set | bool_set | char_set; - struct { - const Char*& begin; - dynamic_format_specs& specs; - type arg_type; + begin = parse_align(begin, end, handler); + if (begin == end) return begin; - FMT_CONSTEXPR auto operator()(pres type, int set) -> const Char* { - if (!in(arg_type, set)) throw_format_error("invalid format specifier"); - specs.type = type; - return begin + 1; - } - } parse_presentation_type{begin, specs, arg_type}; - - for (;;) { - switch (c) { - case '<': - case '>': - case '^': - enter_state(state::align); - specs.align = parse_align(c); - ++begin; - break; - case '+': - case '-': - case ' ': - enter_state(state::sign, in(arg_type, sint_set | float_set)); - switch (c) { - case '+': - specs.sign = sign::plus; - break; - case '-': - specs.sign = sign::minus; - break; - case ' ': - specs.sign = sign::space; - break; - } - ++begin; - break; - case '#': - enter_state(state::hash, is_arithmetic_type(arg_type)); - specs.alt = true; - ++begin; - break; - case '0': - enter_state(state::zero); - if (!is_arithmetic_type(arg_type)) - throw_format_error("format specifier requires numeric argument"); - if (specs.align == align::none) { - // Ignore 0 if align is specified for compatibility with std::format. - specs.align = align::numeric; - specs.fill[0] = Char('0'); - } - ++begin; - break; - case '1': - case '2': - case '3': - case '4': - case '5': - case '6': - case '7': - case '8': - case '9': - case '{': - enter_state(state::width); - begin = parse_dynamic_spec(begin, end, specs.width, specs.width_ref, ctx); - break; - case '.': - enter_state(state::precision, - in(arg_type, float_set | string_set | cstring_set)); - begin = parse_precision(begin, end, specs.precision, specs.precision_ref, - ctx); - break; - case 'L': - enter_state(state::locale, is_arithmetic_type(arg_type)); - specs.localized = true; - ++begin; - break; - case 'd': - return parse_presentation_type(pres::dec, integral_set); - case 'o': - return parse_presentation_type(pres::oct, integral_set); - case 'x': - return parse_presentation_type(pres::hex_lower, integral_set); - case 'X': - return parse_presentation_type(pres::hex_upper, integral_set); - case 'b': - return parse_presentation_type(pres::bin_lower, integral_set); - case 'B': - return parse_presentation_type(pres::bin_upper, integral_set); - case 'a': - return parse_presentation_type(pres::hexfloat_lower, float_set); - case 'A': - return parse_presentation_type(pres::hexfloat_upper, float_set); - case 'e': - return parse_presentation_type(pres::exp_lower, float_set); - case 'E': - return parse_presentation_type(pres::exp_upper, float_set); - case 'f': - return parse_presentation_type(pres::fixed_lower, float_set); - case 'F': - return parse_presentation_type(pres::fixed_upper, float_set); - case 'g': - return parse_presentation_type(pres::general_lower, float_set); - case 'G': - return parse_presentation_type(pres::general_upper, float_set); - case 'c': - return parse_presentation_type(pres::chr, integral_set); - case 's': - return parse_presentation_type(pres::string, - bool_set | string_set | cstring_set); - case 'p': - return parse_presentation_type(pres::pointer, pointer_set | cstring_set); - case '?': - return parse_presentation_type(pres::debug, - char_set | string_set | cstring_set); - case '}': - return begin; - default: { - if (*begin == '}') return begin; - // Parse fill and alignment. - auto fill_end = begin + code_point_length(begin); - if (end - fill_end <= 0) { - throw_format_error("invalid format specifier"); - return begin; - } - if (*begin == '{') { - throw_format_error("invalid fill character '{'"); - return begin; - } - auto align = parse_align(to_ascii(*fill_end)); - enter_state(state::align, align != align::none); - specs.fill = {begin, to_unsigned(fill_end - begin)}; - specs.align = align; - begin = fill_end + 1; - } - } - if (begin == end) return begin; - c = to_ascii(*begin); + // Parse sign. + switch (to_ascii(*begin)) { + case '+': + handler.on_sign(sign::plus); + ++begin; + break; + case '-': + handler.on_sign(sign::minus); + ++begin; + break; + case ' ': + handler.on_sign(sign::space); + ++begin; + break; + default: + break; } + if (begin == end) return begin; + + if (*begin == '#') { + handler.on_hash(); + if (++begin == end) return begin; + } + + // Parse zero flag. + if (*begin == '0') { + handler.on_zero(); + if (++begin == end) return begin; + } + + begin = parse_width(begin, end, handler); + if (begin == end) return begin; + + // Parse precision. + if (*begin == '.') { + begin = parse_precision(begin, end, handler); + if (begin == end) return begin; + } + + if (*begin == 'L') { + handler.on_localized(); + ++begin; + } + + // Parse type. + if (begin != end && *begin != '}') { + presentation_type type = parse_presentation_type(*begin++); + if (type == presentation_type::none) + handler.on_error("invalid type specifier"); + handler.on_type(type); + } + return begin; } template @@ -2462,11 +2637,14 @@ FMT_CONSTEXPR auto parse_replacement_field(const Char* begin, const Char* end, Handler& handler; int arg_id; - FMT_CONSTEXPR void on_auto() { arg_id = handler.on_arg_id(); } - FMT_CONSTEXPR void on_index(int id) { arg_id = handler.on_arg_id(id); } - FMT_CONSTEXPR void on_name(basic_string_view id) { + FMT_CONSTEXPR void operator()() { arg_id = handler.on_arg_id(); } + FMT_CONSTEXPR void operator()(int id) { arg_id = handler.on_arg_id(id); } + FMT_CONSTEXPR void operator()(basic_string_view id) { arg_id = handler.on_arg_id(id); } + FMT_CONSTEXPR void on_error(const char* message) { + if (message) handler.on_error(message); + } }; ++begin; @@ -2495,6 +2673,9 @@ FMT_CONSTEXPR auto parse_replacement_field(const Char* begin, const Char* end, template FMT_CONSTEXPR FMT_INLINE void parse_format_string( basic_string_view format_str, Handler&& handler) { + // Workaround a name-lookup bug in MSVC's modules implementation. + using detail::find; + auto begin = format_str.data(); auto end = begin + format_str.size(); if (end - begin < 32) { @@ -2554,32 +2735,183 @@ FMT_CONSTEXPR auto parse_format_specs(ParseContext& ctx) -> decltype(ctx.begin()) { using char_type = typename ParseContext::char_type; using context = buffer_context; + using stripped_type = typename strip_named_arg::type; using mapped_type = conditional_t< mapped_type_constant::value != type::custom_type, decltype(arg_mapper().map(std::declval())), - typename strip_named_arg::type>; - return formatter().parse(ctx); + stripped_type>; + auto f = conditional_t::value, + formatter, + fallback_formatter>(); + return f.parse(ctx); } -// Checks char specs and returns true iff the presentation type is char-like. -template -FMT_CONSTEXPR auto check_char_specs(const format_specs& specs) -> bool { +template +FMT_CONSTEXPR void check_int_type_spec(presentation_type type, + ErrorHandler&& eh) { + if (type > presentation_type::bin_upper && type != presentation_type::chr) + eh.on_error("invalid type specifier"); +} + +// Checks char specs and returns true if the type spec is char (and not int). +template +FMT_CONSTEXPR auto check_char_specs(const basic_format_specs& specs, + ErrorHandler&& eh = {}) -> bool { if (specs.type != presentation_type::none && specs.type != presentation_type::chr && specs.type != presentation_type::debug) { + check_int_type_spec(specs.type, eh); return false; } if (specs.align == align::numeric || specs.sign != sign::none || specs.alt) - throw_format_error("invalid format specifier for char"); + eh.on_error("invalid format specifier for char"); return true; } -constexpr FMT_INLINE_VARIABLE int invalid_arg_index = -1; +// A floating-point presentation format. +enum class float_format : unsigned char { + general, // General: exponent notation or fixed point based on magnitude. + exp, // Exponent notation with the default precision of 6, e.g. 1.2e-3. + fixed, // Fixed point with the default precision of 6, e.g. 0.0012. + hex +}; + +struct float_specs { + int precision; + float_format format : 8; + sign_t sign : 8; + bool upper : 1; + bool locale : 1; + bool binary32 : 1; + bool showpoint : 1; +}; + +template +FMT_CONSTEXPR auto parse_float_type_spec(const basic_format_specs& specs, + ErrorHandler&& eh = {}) + -> float_specs { + auto result = float_specs(); + result.showpoint = specs.alt; + result.locale = specs.localized; + switch (specs.type) { + case presentation_type::none: + result.format = float_format::general; + break; + case presentation_type::general_upper: + result.upper = true; + FMT_FALLTHROUGH; + case presentation_type::general_lower: + result.format = float_format::general; + break; + case presentation_type::exp_upper: + result.upper = true; + FMT_FALLTHROUGH; + case presentation_type::exp_lower: + result.format = float_format::exp; + result.showpoint |= specs.precision != 0; + break; + case presentation_type::fixed_upper: + result.upper = true; + FMT_FALLTHROUGH; + case presentation_type::fixed_lower: + result.format = float_format::fixed; + result.showpoint |= specs.precision != 0; + break; + case presentation_type::hexfloat_upper: + result.upper = true; + FMT_FALLTHROUGH; + case presentation_type::hexfloat_lower: + result.format = float_format::hex; + break; + default: + eh.on_error("invalid type specifier"); + break; + } + return result; +} + +template +FMT_CONSTEXPR auto check_cstring_type_spec(presentation_type type, + ErrorHandler&& eh = {}) -> bool { + if (type == presentation_type::none || type == presentation_type::string || + type == presentation_type::debug) + return true; + if (type != presentation_type::pointer) eh.on_error("invalid type specifier"); + return false; +} + +template +FMT_CONSTEXPR void check_string_type_spec(presentation_type type, + ErrorHandler&& eh = {}) { + if (type != presentation_type::none && type != presentation_type::string && + type != presentation_type::debug) + eh.on_error("invalid type specifier"); +} + +template +FMT_CONSTEXPR void check_pointer_type_spec(presentation_type type, + ErrorHandler&& eh) { + if (type != presentation_type::none && type != presentation_type::pointer) + eh.on_error("invalid type specifier"); +} + +// A parse_format_specs handler that checks if specifiers are consistent with +// the argument type. +template class specs_checker : public Handler { + private: + detail::type arg_type_; + + FMT_CONSTEXPR void require_numeric_argument() { + if (!is_arithmetic_type(arg_type_)) + this->on_error("format specifier requires numeric argument"); + } + + public: + FMT_CONSTEXPR specs_checker(const Handler& handler, detail::type arg_type) + : Handler(handler), arg_type_(arg_type) {} + + FMT_CONSTEXPR void on_align(align_t align) { + if (align == align::numeric) require_numeric_argument(); + Handler::on_align(align); + } + + FMT_CONSTEXPR void on_sign(sign_t s) { + require_numeric_argument(); + if (is_integral_type(arg_type_) && arg_type_ != type::int_type && + arg_type_ != type::long_long_type && arg_type_ != type::int128_type && + arg_type_ != type::char_type) { + this->on_error("format specifier requires signed argument"); + } + Handler::on_sign(s); + } + + FMT_CONSTEXPR void on_hash() { + require_numeric_argument(); + Handler::on_hash(); + } + + FMT_CONSTEXPR void on_localized() { + require_numeric_argument(); + Handler::on_localized(); + } + + FMT_CONSTEXPR void on_zero() { + require_numeric_argument(); + Handler::on_zero(); + } + + FMT_CONSTEXPR void end_precision() { + if (is_integral_type(arg_type_) || arg_type_ == type::pointer_type) + this->on_error("precision not allowed for this argument type"); + } +}; + +constexpr int invalid_arg_index = -1; #if FMT_USE_NONTYPE_TEMPLATE_ARGS template constexpr auto get_arg_index_by_name(basic_string_view name) -> int { - if constexpr (is_statically_named_arg()) { + if constexpr (detail::is_statically_named_arg()) { if (name == T::name) return N; } if constexpr (sizeof...(Args) > 0) @@ -2599,15 +2931,16 @@ FMT_CONSTEXPR auto get_arg_index_by_name(basic_string_view name) -> int { return invalid_arg_index; } -template class format_string_checker { +template +class format_string_checker { private: - using parse_context_type = compile_parse_context; - static constexpr int num_args = sizeof...(Args); - - // Format specifier parsing function. // In the future basic_format_parse_context will replace compile_parse_context // here and will use is_constant_evaluated and downcasting to access the data // needed for compile-time checks: https://godbolt.org/z/GvWzcTjh1. + using parse_context_type = compile_parse_context; + static constexpr int num_args = sizeof...(Args); + + // Format specifier parsing function. using parse_func = const Char* (*)(parse_context_type&); parse_context_type context_; @@ -2615,10 +2948,14 @@ template class format_string_checker { type types_[num_args > 0 ? static_cast(num_args) : 1]; public: - explicit FMT_CONSTEXPR format_string_checker(basic_string_view fmt) - : context_(fmt, num_args, types_), + explicit FMT_CONSTEXPR format_string_checker( + basic_string_view format_str, ErrorHandler eh) + : context_(format_str, num_args, types_, eh), parse_funcs_{&parse_format_specs...}, - types_{mapped_type_constant>::value...} {} + types_{ + mapped_type_constant>::value...} { + } FMT_CONSTEXPR void on_text(const Char*, const Char*) {} @@ -2630,7 +2967,7 @@ template class format_string_checker { #if FMT_USE_NONTYPE_TEMPLATE_ARGS auto index = get_arg_index_by_name(id); if (index == invalid_arg_index) on_error("named argument is not found"); - return index; + return context_.check_arg_id(index), index; #else (void)id; on_error("compile-time checks for named arguments require C++20 support"); @@ -2642,13 +2979,13 @@ template class format_string_checker { FMT_CONSTEXPR auto on_format_specs(int id, const Char* begin, const Char*) -> const Char* { - context_.advance_to(begin); + context_.advance_to(context_.begin() + (begin - &*context_.begin())); // id >= 0 check is a workaround for gcc 10 bug (#2065). return id >= 0 && id < num_args ? parse_funcs_[id](context_) : begin; } FMT_CONSTEXPR void on_error(const char* message) { - throw_format_error(message); + context_.on_error(message); } }; @@ -2664,33 +3001,28 @@ FMT_INLINE void check_format_string(const S&) { template ::value)> void check_format_string(S format_str) { - using char_t = typename S::char_type; - FMT_CONSTEXPR auto s = basic_string_view(format_str); - using checker = format_string_checker...>; - FMT_CONSTEXPR bool error = (parse_format_string(s, checker(s)), true); - ignore_unused(error); + FMT_CONSTEXPR auto s = basic_string_view(format_str); + using checker = format_string_checker...>; + FMT_CONSTEXPR bool invalid_format = + (parse_format_string(s, checker(s, {})), true); + ignore_unused(invalid_format); } -template struct vformat_args { - using type = basic_format_args< - basic_format_context>, Char>>; -}; -template <> struct vformat_args { using type = format_args; }; - -// Use vformat_args and avoid type_identity to keep symbols short. template -void vformat_to(buffer& buf, basic_string_view fmt, - typename vformat_args::type args, locale_ref loc = {}); +void vformat_to( + buffer& buf, basic_string_view fmt, + basic_format_args)> args, + locale_ref loc = {}); FMT_API void vprint_mojibake(std::FILE*, string_view, format_args); #ifndef _WIN32 inline void vprint_mojibake(std::FILE*, string_view, format_args) {} #endif -} // namespace detail +FMT_END_DETAIL_NAMESPACE -FMT_BEGIN_EXPORT - -// A formatter specialization for natively supported types. +// A formatter specialization for the core types corresponding to detail::type +// constants. template struct formatter::value != @@ -2699,21 +3031,81 @@ struct formatter specs_; public: + // Parses format specifiers stopping either at the end of the range or at the + // terminating '}'. template - FMT_CONSTEXPR auto parse(ParseContext& ctx) -> const Char* { + FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) { + auto begin = ctx.begin(), end = ctx.end(); + if (begin == end) return begin; + using handler_type = detail::dynamic_specs_handler; auto type = detail::type_constant::value; - auto end = - detail::parse_format_specs(ctx.begin(), ctx.end(), specs_, ctx, type); - if (type == detail::type::char_type) detail::check_char_specs(specs_); - return end; + auto checker = + detail::specs_checker(handler_type(specs_, ctx), type); + auto it = detail::parse_format_specs(begin, end, checker); + auto eh = ctx.error_handler(); + switch (type) { + case detail::type::none_type: + FMT_ASSERT(false, "invalid argument type"); + break; + case detail::type::bool_type: + if (specs_.type == presentation_type::none || + specs_.type == presentation_type::string) { + break; + } + FMT_FALLTHROUGH; + case detail::type::int_type: + case detail::type::uint_type: + case detail::type::long_long_type: + case detail::type::ulong_long_type: + case detail::type::int128_type: + case detail::type::uint128_type: + detail::check_int_type_spec(specs_.type, eh); + break; + case detail::type::char_type: + detail::check_char_specs(specs_, eh); + break; + case detail::type::float_type: + if (detail::const_check(FMT_USE_FLOAT)) + detail::parse_float_type_spec(specs_, eh); + else + FMT_ASSERT(false, "float support disabled"); + break; + case detail::type::double_type: + if (detail::const_check(FMT_USE_DOUBLE)) + detail::parse_float_type_spec(specs_, eh); + else + FMT_ASSERT(false, "double support disabled"); + break; + case detail::type::long_double_type: + if (detail::const_check(FMT_USE_LONG_DOUBLE)) + detail::parse_float_type_spec(specs_, eh); + else + FMT_ASSERT(false, "long double support disabled"); + break; + case detail::type::cstring_type: + detail::check_cstring_type_spec(specs_.type, eh); + break; + case detail::type::string_type: + detail::check_string_type_spec(specs_.type, eh); + break; + case detail::type::pointer_type: + detail::check_pointer_type_spec(specs_.type, eh); + break; + case detail::type::custom_type: + // Custom format specifiers are checked in parse functions of + // formatter specializations. + break; + } + return it; } template ::value, - FMT_ENABLE_IF(U == detail::type::string_type || - U == detail::type::cstring_type || - U == detail::type::char_type)> - FMT_CONSTEXPR void set_debug_format(bool set = true) { - specs_.type = set ? presentation_type::debug : presentation_type::none; + enable_if_t<(U == detail::type::string_type || + U == detail::type::cstring_type || + U == detail::type::char_type), + int> = 0> + FMT_CONSTEXPR void set_debug_format() { + specs_.type = presentation_type::debug; } template @@ -2725,7 +3117,7 @@ struct formatter \ struct formatter : formatter { \ template \ - auto format(const Type& val, FormatContext& ctx) const \ + auto format(Type const& val, FormatContext& ctx) const \ -> decltype(ctx.out()) { \ return formatter::format(static_cast(val), ctx); \ } \ @@ -2742,9 +3134,7 @@ FMT_FORMAT_AS(std::basic_string, basic_string_view); FMT_FORMAT_AS(std::nullptr_t, const void*); FMT_FORMAT_AS(detail::std_string_view, basic_string_view); -template struct runtime_format_string { - basic_string_view str; -}; +template struct basic_runtime { basic_string_view str; }; /** A compile-time format string. */ template class basic_format_string { @@ -2764,18 +3154,17 @@ template class basic_format_string { #ifdef FMT_HAS_CONSTEVAL if constexpr (detail::count_named_args() == detail::count_statically_named_args()) { - using checker = - detail::format_string_checker...>; - detail::parse_format_string(str_, checker(s)); + using checker = detail::format_string_checker...>; + detail::parse_format_string(str_, checker(s, {})); } #else detail::check_format_string(s); #endif } - basic_format_string(runtime_format_string fmt) : str_(fmt.str) {} + basic_format_string(basic_runtime r) : str_(r.str) {} FMT_INLINE operator basic_string_view() const { return str_; } - FMT_INLINE auto get() const -> basic_string_view { return str_; } }; #if FMT_GCC_VERSION && FMT_GCC_VERSION < 409 @@ -2795,7 +3184,7 @@ using format_string = basic_format_string...>; fmt::print(fmt::runtime("{:d}"), "I am not a number"); \endrst */ -inline auto runtime(string_view s) -> runtime_format_string<> { return {{s}}; } +inline auto runtime(string_view s) -> basic_runtime { return {{s}}; } #endif FMT_API auto vformat(string_view fmt, format_args args) -> std::string; @@ -2821,9 +3210,10 @@ FMT_NODISCARD FMT_INLINE auto format(format_string fmt, T&&... args) template ::value)> auto vformat_to(OutputIt out, string_view fmt, format_args args) -> OutputIt { - auto&& buf = detail::get_buffer(out); + using detail::get_buffer; + auto&& buf = get_buffer(out); detail::vformat_to(buf, fmt, args, {}); - return detail::get_iterator(buf, out); + return detail::get_iterator(buf); } /** @@ -2882,7 +3272,7 @@ template FMT_NODISCARD FMT_INLINE auto formatted_size(format_string fmt, T&&... args) -> size_t { auto buf = detail::counting_buffer<>(); - detail::vformat_to(buf, fmt, fmt::make_format_args(args...), {}); + detail::vformat_to(buf, string_view(fmt), fmt::make_format_args(args...), {}); return buf.count(); } @@ -2923,25 +3313,7 @@ FMT_INLINE void print(std::FILE* f, format_string fmt, T&&... args) { : detail::vprint_mojibake(f, fmt, vargs); } -/** - Formats ``args`` according to specifications in ``fmt`` and writes the - output to the file ``f`` followed by a newline. - */ -template -FMT_INLINE void println(std::FILE* f, format_string fmt, T&&... args) { - return fmt::print(f, "{}\n", fmt::format(fmt, std::forward(args)...)); -} - -/** - Formats ``args`` according to specifications in ``fmt`` and writes the output - to ``stdout`` followed by a newline. - */ -template -FMT_INLINE void println(format_string fmt, T&&... args) { - return fmt::println(stdout, fmt, std::forward(args)...); -} - -FMT_END_EXPORT +FMT_MODULE_EXPORT_END FMT_GCC_PRAGMA("GCC pop_options") FMT_END_NAMESPACE diff --git a/include/spdlog/fmt/bundled/format-inl.h b/include/spdlog/fmt/bundled/format-inl.h index 5bae3c7b..22b1ec8d 100644 --- a/include/spdlog/fmt/bundled/format-inl.h +++ b/include/spdlog/fmt/bundled/format-inl.h @@ -9,9 +9,13 @@ #define FMT_FORMAT_INL_H_ #include +#include #include // errno #include #include +#include +#include // std::memmove +#include #include #ifndef FMT_STATIC_THOUSANDS_SEPARATOR @@ -111,43 +115,16 @@ template FMT_FUNC Char decimal_point_impl(locale_ref) { return '.'; } #endif - -FMT_FUNC auto write_loc(appender out, loc_value value, - const format_specs<>& specs, locale_ref loc) -> bool { -#ifndef FMT_STATIC_THOUSANDS_SEPARATOR - auto locale = loc.get(); - // We cannot use the num_put facet because it may produce output in - // a wrong encoding. - using facet = format_facet; - if (std::has_facet(locale)) - return std::use_facet(locale).put(out, value, specs); - return facet(locale).put(out, value, specs); -#endif - return false; -} } // namespace detail -template typename Locale::id format_facet::id; - -#ifndef FMT_STATIC_THOUSANDS_SEPARATOR -template format_facet::format_facet(Locale& loc) { - auto& numpunct = std::use_facet>(loc); - grouping_ = numpunct.grouping(); - if (!grouping_.empty()) separator_ = std::string(1, numpunct.thousands_sep()); -} - -template <> -FMT_API FMT_FUNC auto format_facet::do_put( - appender out, loc_value val, const format_specs<>& specs) const -> bool { - return val.visit( - detail::loc_writer<>{out, specs, separator_, grouping_, decimal_point_}); -} +#if !FMT_MSC_VERSION +FMT_API FMT_FUNC format_error::~format_error() noexcept = default; #endif -FMT_FUNC std::system_error vsystem_error(int error_code, string_view fmt, +FMT_FUNC std::system_error vsystem_error(int error_code, string_view format_str, format_args args) { auto ec = std::error_code(error_code, std::generic_category()); - return std::system_error(ec, vformat(fmt, args)); + return std::system_error(ec, vformat(format_str, args)); } namespace detail { @@ -166,8 +143,58 @@ FMT_CONSTEXPR inline uint64_t rotr(uint64_t n, uint32_t r) noexcept { return (n >> r) | (n << (64 - r)); } +// Computes 128-bit result of multiplication of two 64-bit unsigned integers. +inline uint128_fallback umul128(uint64_t x, uint64_t y) noexcept { +#if FMT_USE_INT128 + auto p = static_cast(x) * static_cast(y); + return {static_cast(p >> 64), static_cast(p)}; +#elif defined(_MSC_VER) && defined(_M_X64) + auto result = uint128_fallback(); + result.lo_ = _umul128(x, y, &result.hi_); + return result; +#else + const uint64_t mask = static_cast(max_value()); + + uint64_t a = x >> 32; + uint64_t b = x & mask; + uint64_t c = y >> 32; + uint64_t d = y & mask; + + uint64_t ac = a * c; + uint64_t bc = b * c; + uint64_t ad = a * d; + uint64_t bd = b * d; + + uint64_t intermediate = (bd >> 32) + (ad & mask) + (bc & mask); + + return {ac + (intermediate >> 32) + (ad >> 32) + (bc >> 32), + (intermediate << 32) + (bd & mask)}; +#endif +} + // Implementation of Dragonbox algorithm: https://github.com/jk-jeon/dragonbox. namespace dragonbox { +// Computes upper 64 bits of multiplication of two 64-bit unsigned integers. +inline uint64_t umul128_upper64(uint64_t x, uint64_t y) noexcept { +#if FMT_USE_INT128 + auto p = static_cast(x) * static_cast(y); + return static_cast(p >> 64); +#elif defined(_MSC_VER) && defined(_M_X64) + return __umulh(x, y); +#else + return umul128(x, y).high(); +#endif +} + +// Computes upper 128 bits of multiplication of a 64-bit unsigned integer and a +// 128-bit unsigned integer. +inline uint128_fallback umul192_upper128(uint64_t x, + uint128_fallback y) noexcept { + uint128_fallback r = umul128(x, y.high()); + r += umul128_upper64(x, y.low()); + return r; +} + // Computes upper 64 bits of multiplication of a 32-bit unsigned integer and a // 64-bit unsigned integer. inline uint64_t umul96_upper64(uint32_t x, uint64_t y) noexcept { @@ -189,13 +216,25 @@ inline uint64_t umul96_lower64(uint32_t x, uint64_t y) noexcept { return x * y; } +// Computes floor(log10(pow(2, e))) for e in [-2620, 2620] using the method from +// https://fmt.dev/papers/Dragonbox.pdf#page=28, section 6.1. +inline int floor_log10_pow2(int e) noexcept { + FMT_ASSERT(e <= 2620 && e >= -2620, "too large exponent"); + static_assert((-1 >> 1) == -1, "right shift is not arithmetic"); + return (e * 315653) >> 20; +} + // Various fast log computations. +inline int floor_log2_pow10(int e) noexcept { + FMT_ASSERT(e <= 1233 && e >= -1233, "too large exponent"); + return (e * 1741647) >> 19; +} inline int floor_log10_pow2_minus_log10_4_over_3(int e) noexcept { FMT_ASSERT(e <= 2936 && e >= -2985, "too large exponent"); return (e * 631305 - 261663) >> 21; } -FMT_INLINE_VARIABLE constexpr struct { +static constexpr struct { uint32_t divisor; int shift_amount; } div_small_pow10_infos[] = {{10, 16}, {100, 16}}; @@ -249,7 +288,7 @@ inline uint64_t divide_by_10_to_kappa_plus_1(uint64_t n) noexcept { } // Various subroutines using pow10 cache -template struct cache_accessor; +template struct cache_accessor; template <> struct cache_accessor { using carrier_uint = float_info::carrier_uint; @@ -970,23 +1009,8 @@ template <> struct cache_accessor { {0xfcf62c1dee382c42, 0x46729e03dd9ed7b6}, {0x9e19db92b4e31ba9, 0x6c07a2c26a8346d2}, {0xc5a05277621be293, 0xc7098b7305241886}, - {0xf70867153aa2db38, 0xb8cbee4fc66d1ea8}, - {0x9a65406d44a5c903, 0x737f74f1dc043329}, - {0xc0fe908895cf3b44, 0x505f522e53053ff3}, - {0xf13e34aabb430a15, 0x647726b9e7c68ff0}, - {0x96c6e0eab509e64d, 0x5eca783430dc19f6}, - {0xbc789925624c5fe0, 0xb67d16413d132073}, - {0xeb96bf6ebadf77d8, 0xe41c5bd18c57e890}, - {0x933e37a534cbaae7, 0x8e91b962f7b6f15a}, - {0xb80dc58e81fe95a1, 0x723627bbb5a4adb1}, - {0xe61136f2227e3b09, 0xcec3b1aaa30dd91d}, - {0x8fcac257558ee4e6, 0x213a4f0aa5e8a7b2}, - {0xb3bd72ed2af29e1f, 0xa988e2cd4f62d19e}, - {0xe0accfa875af45a7, 0x93eb1b80a33b8606}, - {0x8c6c01c9498d8b88, 0xbc72f130660533c4}, - {0xaf87023b9bf0ee6a, 0xeb8fad7c7f8680b5}, - { 0xdb68c2ca82ed2a05, - 0xa67398db9f6820e2 } + { 0xf70867153aa2db38, + 0xb8cbee4fc66d1ea8 } #else {0xff77b1fcbebcdc4f, 0x25e8e89c13bb0f7b}, {0xce5d73ff402d98e3, 0xfb0a3d212dc81290}, @@ -1010,8 +1034,8 @@ template <> struct cache_accessor { {0x8da471a9de737e24, 0x5ceaecfed289e5d3}, {0xe4d5e82392a40515, 0x0fabaf3feaa5334b}, {0xb8da1662e7b00a17, 0x3d6a751f3b936244}, - {0x95527a5202df0ccb, 0x0f37801e0c43ebc9}, - {0xf13e34aabb430a15, 0x647726b9e7c68ff0} + { 0x95527a5202df0ccb, + 0x0f37801e0c43ebc9 } #endif }; @@ -1114,12 +1138,8 @@ template <> struct cache_accessor { } }; -FMT_FUNC uint128_fallback get_cached_power(int k) noexcept { - return cache_accessor::get_cached_power(k); -} - // Various integer checks -template +template bool is_left_endpoint_integer_shorter_interval(int exponent) noexcept { const int case_shorter_interval_left_endpoint_lower_threshold = 2; const int case_shorter_interval_left_endpoint_upper_threshold = 3; @@ -1130,12 +1150,8 @@ bool is_left_endpoint_integer_shorter_interval(int exponent) noexcept { // Remove trailing zeros from n and return the number of zeros removed (float) FMT_INLINE int remove_trailing_zeros(uint32_t& n) noexcept { FMT_ASSERT(n != 0, ""); - // Modular inverse of 5 (mod 2^32): (mod_inv_5 * 5) mod 2^32 = 1. - // See https://github.com/fmtlib/fmt/issues/3163 for more details. const uint32_t mod_inv_5 = 0xcccccccd; - // Casts are needed to workaround a bug in MSVC 19.22 and older. - const uint32_t mod_inv_25 = - static_cast(uint64_t(mod_inv_5) * mod_inv_5); + const uint32_t mod_inv_25 = mod_inv_5 * mod_inv_5; int s = 0; while (true) { @@ -1149,6 +1165,7 @@ FMT_INLINE int remove_trailing_zeros(uint32_t& n) noexcept { n = q; s |= 1; } + return s; } @@ -1206,7 +1223,7 @@ FMT_INLINE int remove_trailing_zeros(uint64_t& n) noexcept { } // The main algorithm for shorter interval case -template +template FMT_INLINE decimal_fp shorter_interval_case(int exponent) noexcept { decimal_fp ret_value; // Compute k and beta @@ -1377,6 +1394,17 @@ small_divisor_case_label: return ret_value; } } // namespace dragonbox + +#ifdef _MSC_VER +FMT_FUNC auto fmt_snprintf(char* buf, size_t size, const char* fmt, ...) + -> int { + auto args = va_list(); + va_start(args, fmt); + int result = vsnprintf_s(buf, size, _TRUNCATE, fmt, args); + va_end(args); + return result; +} +#endif } // namespace detail template <> struct formatter { @@ -1385,8 +1413,9 @@ template <> struct formatter { return ctx.begin(); } - auto format(const detail::bigint& n, format_context& ctx) const - -> format_context::iterator { + template + auto format(const detail::bigint& n, FormatContext& ctx) const -> + typename FormatContext::iterator { auto out = ctx.out(); bool first = true; for (auto i = n.bigits_.size(); i > 0; --i) { @@ -1445,44 +1474,57 @@ FMT_FUNC std::string vformat(string_view fmt, format_args args) { } namespace detail { -#ifndef _WIN32 -FMT_FUNC bool write_console(std::FILE*, string_view) { return false; } -#else +#ifdef _WIN32 using dword = conditional_t; extern "C" __declspec(dllimport) int __stdcall WriteConsoleW( // void*, const void*, dword, dword*, void*); FMT_FUNC bool write_console(std::FILE* f, string_view text) { auto fd = _fileno(f); - if (!_isatty(fd)) return false; - auto u16 = utf8_to_utf16(text); - auto written = dword(); - return WriteConsoleW(reinterpret_cast(_get_osfhandle(fd)), u16.c_str(), - static_cast(u16.size()), &written, nullptr); + if (_isatty(fd)) { + detail::utf8_to_utf16 u16(string_view(text.data(), text.size())); + auto written = detail::dword(); + if (detail::WriteConsoleW(reinterpret_cast(_get_osfhandle(fd)), + u16.c_str(), static_cast(u16.size()), + &written, nullptr)) { + return true; + } + } + // We return false if the file descriptor was not TTY, or it was but + // SetConsoleW failed which can happen if the output has been redirected to + // NUL. In both cases when we return false, we should attempt to do regular + // write via fwrite or std::ostream::write. + return false; +} +#endif + +FMT_FUNC void print(std::FILE* f, string_view text) { +#ifdef _WIN32 + if (write_console(f, text)) return; +#endif + detail::fwrite_fully(text.data(), 1, text.size(), f); +} +} // namespace detail + +FMT_FUNC void vprint(std::FILE* f, string_view format_str, format_args args) { + memory_buffer buffer; + detail::vformat_to(buffer, format_str, args); + detail::print(f, {buffer.data(), buffer.size()}); } +#ifdef _WIN32 // Print assuming legacy (non-Unicode) encoding. -FMT_FUNC void vprint_mojibake(std::FILE* f, string_view fmt, format_args args) { - auto buffer = memory_buffer(); - detail::vformat_to(buffer, fmt, +FMT_FUNC void detail::vprint_mojibake(std::FILE* f, string_view format_str, + format_args args) { + memory_buffer buffer; + detail::vformat_to(buffer, format_str, basic_format_args>(args)); fwrite_fully(buffer.data(), 1, buffer.size(), f); } #endif -FMT_FUNC void print(std::FILE* f, string_view text) { - if (!write_console(f, text)) fwrite_fully(text.data(), 1, text.size(), f); -} -} // namespace detail - -FMT_FUNC void vprint(std::FILE* f, string_view fmt, format_args args) { - auto buffer = memory_buffer(); - detail::vformat_to(buffer, fmt, args); - detail::print(f, {buffer.data(), buffer.size()}); -} - -FMT_FUNC void vprint(string_view fmt, format_args args) { - vprint(stdout, fmt, args); +FMT_FUNC void vprint(string_view format_str, format_args args) { + vprint(stdout, format_str, args); } namespace detail { diff --git a/include/spdlog/fmt/bundled/format.h b/include/spdlog/fmt/bundled/format.h index ed8b29eb..7c607dbd 100644 --- a/include/spdlog/fmt/bundled/format.h +++ b/include/spdlog/fmt/bundled/format.h @@ -33,14 +33,13 @@ #ifndef FMT_FORMAT_H_ #define FMT_FORMAT_H_ -#include // std::signbit -#include // uint32_t -#include // std::memcpy -#include // std::initializer_list -#include // std::numeric_limits -#include // std::uninitialized_copy -#include // std::runtime_error -#include // std::system_error +#include // std::signbit +#include // uint32_t +#include // std::memcpy +#include // std::numeric_limits +#include // std::uninitialized_copy +#include // std::runtime_error +#include // std::system_error #ifdef __cpp_lib_bit_cast # include // std::bitcast @@ -48,36 +47,6 @@ #include "core.h" -#ifndef FMT_BEGIN_DETAIL_NAMESPACE -# define FMT_BEGIN_DETAIL_NAMESPACE namespace detail { -# define FMT_END_DETAIL_NAMESPACE } -#endif - -#if FMT_HAS_CPP17_ATTRIBUTE(fallthrough) -# define FMT_FALLTHROUGH [[fallthrough]] -#elif defined(__clang__) -# define FMT_FALLTHROUGH [[clang::fallthrough]] -#elif FMT_GCC_VERSION >= 700 && \ - (!defined(__EDG_VERSION__) || __EDG_VERSION__ >= 520) -# define FMT_FALLTHROUGH [[gnu::fallthrough]] -#else -# define FMT_FALLTHROUGH -#endif - -#ifndef FMT_DEPRECATED -# if FMT_HAS_CPP14_ATTRIBUTE(deprecated) || FMT_MSC_VERSION >= 1900 -# define FMT_DEPRECATED [[deprecated]] -# else -# if (defined(__GNUC__) && !defined(__LCC__)) || defined(__clang__) -# define FMT_DEPRECATED __attribute__((deprecated)) -# elif FMT_MSC_VERSION -# define FMT_DEPRECATED __declspec(deprecated) -# else -# define FMT_DEPRECATED /* deprecated */ -# endif -# endif -#endif - #if FMT_GCC_VERSION # define FMT_GCC_VISIBILITY_HIDDEN __attribute__((visibility("hidden"))) #else @@ -102,6 +71,12 @@ # define FMT_NOINLINE #endif +#if FMT_MSC_VERSION +# define FMT_MSC_DEFAULT = default +#else +# define FMT_MSC_DEFAULT +#endif + #ifndef FMT_THROW # if FMT_EXCEPTIONS # if FMT_MSC_VERSION || defined(__NVCC__) @@ -225,8 +200,7 @@ inline auto clzll(uint64_t x) -> int { _BitScanReverse64(&r, x); # else // Scan the high 32 bits. - if (_BitScanReverse(&r, static_cast(x >> 32))) - return 63 ^ static_cast(r + 32); + if (_BitScanReverse(&r, static_cast(x >> 32))) return 63 ^ (r + 32); // Scan the low 32 bits. _BitScanReverse(&r, static_cast(x)); # endif @@ -266,19 +240,6 @@ FMT_END_NAMESPACE #endif FMT_BEGIN_NAMESPACE - -template struct disjunction : std::false_type {}; -template struct disjunction

: P {}; -template -struct disjunction - : conditional_t> {}; - -template struct conjunction : std::true_type {}; -template struct conjunction

: P {}; -template -struct conjunction - : conditional_t, P1> {}; - namespace detail { FMT_CONSTEXPR inline void abort_fuzzing_if(bool condition) { @@ -398,10 +359,6 @@ class uint128_fallback { -> uint128_fallback { return {lhs.hi_ & rhs.hi_, lhs.lo_ & rhs.lo_}; } - friend constexpr auto operator~(const uint128_fallback& n) - -> uint128_fallback { - return {~n.hi_, ~n.lo_}; - } friend auto operator+(const uint128_fallback& lhs, const uint128_fallback& rhs) -> uint128_fallback { auto result = uint128_fallback(lhs); @@ -440,10 +397,6 @@ class uint128_fallback { lo_ = new_lo; hi_ = new_hi; } - FMT_CONSTEXPR void operator&=(uint128_fallback n) { - lo_ &= n.lo_; - hi_ &= n.hi_; - } FMT_CONSTEXPR20 uint128_fallback& operator+=(uint64_t n) noexcept { if (is_constant_evaluated()) { @@ -510,28 +463,6 @@ inline auto bit_cast(const From& from) -> To { return result; } -template -FMT_CONSTEXPR20 inline auto countl_zero_fallback(UInt n) -> int { - int lz = 0; - constexpr UInt msb_mask = static_cast(1) << (num_bits() - 1); - for (; (n & msb_mask) == 0; n <<= 1) lz++; - return lz; -} - -FMT_CONSTEXPR20 inline auto countl_zero(uint32_t n) -> int { -#ifdef FMT_BUILTIN_CLZ - if (!is_constant_evaluated()) return FMT_BUILTIN_CLZ(n); -#endif - return countl_zero_fallback(n); -} - -FMT_CONSTEXPR20 inline auto countl_zero(uint64_t n) -> int { -#ifdef FMT_BUILTIN_CLZLL - if (!is_constant_evaluated()) return FMT_BUILTIN_CLZLL(n); -#endif - return countl_zero_fallback(n); -} - FMT_INLINE void assume(bool condition) { (void)condition; #if FMT_HAS_BUILTIN(__builtin_assume) && !FMT_ICC_VERSION @@ -676,8 +607,7 @@ FMT_CONSTEXPR inline auto utf8_decode(const char* s, uint32_t* c, int* e) constexpr const int shiftc[] = {0, 18, 12, 6, 0}; constexpr const int shifte[] = {0, 6, 4, 2, 0}; - int len = "\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\0\0\0\0\0\0\0\0\2\2\2\2\3\3\4" - [static_cast(*s) >> 3]; + int len = code_point_length_impl(*s); // Compute the pointer to the next character early so that the next // iteration can start working on the next character. Neither Clang // nor GCC figure out this reordering on their own. @@ -706,7 +636,7 @@ FMT_CONSTEXPR inline auto utf8_decode(const char* s, uint32_t* c, int* e) return next; } -constexpr FMT_INLINE_VARIABLE uint32_t invalid_code_point = ~uint32_t(); +constexpr uint32_t invalid_code_point = ~uint32_t(); // Invokes f(cp, sv) for every code point cp in s with sv being the string view // corresponding to the code point. cp is invalid_code_point on error. @@ -776,7 +706,6 @@ FMT_CONSTEXPR inline size_t compute_width(string_view s) { return true; } }; - // We could avoid branches by using utf8_decode directly. for_each_codepoint(s, count_code_points{&num_code_points}); return num_code_points; } @@ -808,48 +737,13 @@ inline auto code_point_index(basic_string_view s, size_t n) string_view(reinterpret_cast(s.data()), s.size()), n); } -template struct is_integral : std::is_integral {}; -template <> struct is_integral : std::true_type {}; -template <> struct is_integral : std::true_type {}; - -template -using is_signed = - std::integral_constant::is_signed || - std::is_same::value>; - -template -using is_integer = - bool_constant::value && !std::is_same::value && - !std::is_same::value && - !std::is_same::value>; - -#ifndef FMT_USE_FLOAT -# define FMT_USE_FLOAT 1 -#endif -#ifndef FMT_USE_DOUBLE -# define FMT_USE_DOUBLE 1 -#endif -#ifndef FMT_USE_LONG_DOUBLE -# define FMT_USE_LONG_DOUBLE 1 -#endif - #ifndef FMT_USE_FLOAT128 -# ifdef __clang__ -// Clang emulates GCC, so it has to appear early. -# if FMT_HAS_INCLUDE() -# define FMT_USE_FLOAT128 1 -# endif -# elif defined(__GNUC__) -// GNU C++: -# if defined(_GLIBCXX_USE_FLOAT128) && !defined(__STRICT_ANSI__) -# define FMT_USE_FLOAT128 1 -# endif -# endif -# ifndef FMT_USE_FLOAT128 +# ifdef __SIZEOF_FLOAT128__ +# define FMT_USE_FLOAT128 1 +# else # define FMT_USE_FLOAT128 0 # endif #endif - #if FMT_USE_FLOAT128 using float128 = __float128; #else @@ -893,7 +787,7 @@ template struct is_locale> : std::true_type {}; } // namespace detail -FMT_BEGIN_EXPORT +FMT_MODULE_EXPORT_BEGIN // The number of characters to store in the basic_memory_buffer object itself // to avoid dynamic memory allocation. @@ -936,27 +830,7 @@ class basic_memory_buffer final : public detail::buffer { } protected: - FMT_CONSTEXPR20 void grow(size_t size) override { - detail::abort_fuzzing_if(size > 5000); - const size_t max_size = std::allocator_traits::max_size(alloc_); - size_t old_capacity = this->capacity(); - size_t new_capacity = old_capacity + old_capacity / 2; - if (size > new_capacity) - new_capacity = size; - else if (new_capacity > max_size) - new_capacity = size > max_size ? size : max_size; - T* old_data = this->data(); - T* new_data = - std::allocator_traits::allocate(alloc_, new_capacity); - // The following code doesn't throw, so the raw pointer above doesn't leak. - std::uninitialized_copy(old_data, old_data + this->size(), - detail::make_checked(new_data, new_capacity)); - this->set(new_data, new_capacity); - // deallocate must not throw according to the standard, but even if it does, - // the buffer already uses the new storage and will deallocate it in - // destructor. - if (old_data != store_) alloc_.deallocate(old_data, old_capacity); - } + FMT_CONSTEXPR20 void grow(size_t size) override; public: using value_type = T; @@ -1033,28 +907,55 @@ class basic_memory_buffer final : public detail::buffer { } }; +template +FMT_CONSTEXPR20 void basic_memory_buffer::grow( + size_t size) { + detail::abort_fuzzing_if(size > 5000); + const size_t max_size = std::allocator_traits::max_size(alloc_); + size_t old_capacity = this->capacity(); + size_t new_capacity = old_capacity + old_capacity / 2; + if (size > new_capacity) + new_capacity = size; + else if (new_capacity > max_size) + new_capacity = size > max_size ? size : max_size; + T* old_data = this->data(); + T* new_data = + std::allocator_traits::allocate(alloc_, new_capacity); + // The following code doesn't throw, so the raw pointer above doesn't leak. + std::uninitialized_copy(old_data, old_data + this->size(), + detail::make_checked(new_data, new_capacity)); + this->set(new_data, new_capacity); + // deallocate must not throw according to the standard, but even if it does, + // the buffer already uses the new storage and will deallocate it in + // destructor. + if (old_data != store_) alloc_.deallocate(old_data, old_capacity); +} + using memory_buffer = basic_memory_buffer; template struct is_contiguous> : std::true_type { }; -FMT_END_EXPORT namespace detail { +#ifdef _WIN32 FMT_API bool write_console(std::FILE* f, string_view text); +#endif FMT_API void print(std::FILE*, string_view); } // namespace detail -FMT_BEGIN_EXPORT -// Suppress a misleading warning in older versions of clang. -#if FMT_CLANG_VERSION -# pragma clang diagnostic ignored "-Wweak-vtables" -#endif - -/** An error reported from a formatting function. */ +/** A formatting error such as invalid format string. */ +FMT_CLASS_API class FMT_API format_error : public std::runtime_error { public: - using std::runtime_error::runtime_error; + explicit format_error(const char* message) : std::runtime_error(message) {} + explicit format_error(const std::string& message) + : std::runtime_error(message) {} + format_error(const format_error&) = default; + format_error& operator=(const format_error&) = default; + format_error(format_error&&) = default; + format_error& operator=(format_error&&) = default; + ~format_error() noexcept override FMT_MSC_DEFAULT; }; namespace detail_exported { @@ -1083,53 +984,17 @@ constexpr auto compile_string_to_view(detail::std_string_view s) } } // namespace detail_exported -class loc_value { - private: - basic_format_arg value_; - - public: - template ::value)> - loc_value(T value) : value_(detail::make_arg(value)) {} - - template ::value)> - loc_value(T) {} - - template auto visit(Visitor&& vis) -> decltype(vis(0)) { - return visit_format_arg(vis, value_); - } -}; - -// A locale facet that formats values in UTF-8. -// It is parameterized on the locale to avoid the heavy include. -template class format_facet : public Locale::facet { - private: - std::string separator_; - std::string grouping_; - std::string decimal_point_; - - protected: - virtual auto do_put(appender out, loc_value val, - const format_specs<>& specs) const -> bool; - - public: - static FMT_API typename Locale::id id; - - explicit format_facet(Locale& loc); - explicit format_facet(string_view sep = "", - std::initializer_list g = {3}, - std::string decimal_point = ".") - : separator_(sep.data(), sep.size()), - grouping_(g.begin(), g.end()), - decimal_point_(decimal_point) {} - - auto put(appender out, loc_value val, const format_specs<>& specs) const - -> bool { - return do_put(out, val, specs); - } -}; - FMT_BEGIN_DETAIL_NAMESPACE +template struct is_integral : std::is_integral {}; +template <> struct is_integral : std::true_type {}; +template <> struct is_integral : std::true_type {}; + +template +using is_signed = + std::integral_constant::is_signed || + std::is_same::value>; + // Returns true if value is negative, false otherwise. // Same as `value < 0` but doesn't produce warnings if T is an unsigned type. template ::value)> @@ -1373,7 +1238,7 @@ template format_decimal_result { // Buffer is large enough to hold all digits (digits10 + 1). - Char buffer[digits10() + 1] = {}; + Char buffer[digits10() + 1]; auto end = format_decimal(buffer, value, size).end; return {out, detail::copy_str_noinline(buffer, end, out)}; } @@ -1418,133 +1283,7 @@ class utf8_to_utf16 { auto str() const -> std::wstring { return {&buffer_[0], size()}; } }; -// A converter from UTF-16/UTF-32 (host endian) to UTF-8. -template -class unicode_to_utf8 { - private: - Buffer buffer_; - - public: - unicode_to_utf8() {} - explicit unicode_to_utf8(basic_string_view s) { - static_assert(sizeof(WChar) == 2 || sizeof(WChar) == 4, - "Expect utf16 or utf32"); - - if (!convert(s)) - FMT_THROW(std::runtime_error(sizeof(WChar) == 2 ? "invalid utf16" - : "invalid utf32")); - } - operator string_view() const { return string_view(&buffer_[0], size()); } - size_t size() const { return buffer_.size() - 1; } - const char* c_str() const { return &buffer_[0]; } - std::string str() const { return std::string(&buffer_[0], size()); } - - // Performs conversion returning a bool instead of throwing exception on - // conversion error. This method may still throw in case of memory allocation - // error. - bool convert(basic_string_view s) { - if (!convert(buffer_, s)) return false; - buffer_.push_back(0); - return true; - } - static bool convert(Buffer& buf, basic_string_view s) { - for (auto p = s.begin(); p != s.end(); ++p) { - uint32_t c = static_cast(*p); - if (sizeof(WChar) == 2 && c >= 0xd800 && c <= 0xdfff) { - // surrogate pair - ++p; - if (p == s.end() || (c & 0xfc00) != 0xd800 || (*p & 0xfc00) != 0xdc00) { - return false; - } - c = (c << 10) + static_cast(*p) - 0x35fdc00; - } - if (c < 0x80) { - buf.push_back(static_cast(c)); - } else if (c < 0x800) { - buf.push_back(static_cast(0xc0 | (c >> 6))); - buf.push_back(static_cast(0x80 | (c & 0x3f))); - } else if ((c >= 0x800 && c <= 0xd7ff) || (c >= 0xe000 && c <= 0xffff)) { - buf.push_back(static_cast(0xe0 | (c >> 12))); - buf.push_back(static_cast(0x80 | ((c & 0xfff) >> 6))); - buf.push_back(static_cast(0x80 | (c & 0x3f))); - } else if (c >= 0x10000 && c <= 0x10ffff) { - buf.push_back(static_cast(0xf0 | (c >> 18))); - buf.push_back(static_cast(0x80 | ((c & 0x3ffff) >> 12))); - buf.push_back(static_cast(0x80 | ((c & 0xfff) >> 6))); - buf.push_back(static_cast(0x80 | (c & 0x3f))); - } else { - return false; - } - } - return true; - } -}; - -// Computes 128-bit result of multiplication of two 64-bit unsigned integers. -inline uint128_fallback umul128(uint64_t x, uint64_t y) noexcept { -#if FMT_USE_INT128 - auto p = static_cast(x) * static_cast(y); - return {static_cast(p >> 64), static_cast(p)}; -#elif defined(_MSC_VER) && defined(_M_X64) - auto result = uint128_fallback(); - result.lo_ = _umul128(x, y, &result.hi_); - return result; -#else - const uint64_t mask = static_cast(max_value()); - - uint64_t a = x >> 32; - uint64_t b = x & mask; - uint64_t c = y >> 32; - uint64_t d = y & mask; - - uint64_t ac = a * c; - uint64_t bc = b * c; - uint64_t ad = a * d; - uint64_t bd = b * d; - - uint64_t intermediate = (bd >> 32) + (ad & mask) + (bc & mask); - - return {ac + (intermediate >> 32) + (ad >> 32) + (bc >> 32), - (intermediate << 32) + (bd & mask)}; -#endif -} - namespace dragonbox { -// Computes floor(log10(pow(2, e))) for e in [-2620, 2620] using the method from -// https://fmt.dev/papers/Dragonbox.pdf#page=28, section 6.1. -inline int floor_log10_pow2(int e) noexcept { - FMT_ASSERT(e <= 2620 && e >= -2620, "too large exponent"); - static_assert((-1 >> 1) == -1, "right shift is not arithmetic"); - return (e * 315653) >> 20; -} - -inline int floor_log2_pow10(int e) noexcept { - FMT_ASSERT(e <= 1233 && e >= -1233, "too large exponent"); - return (e * 1741647) >> 19; -} - -// Computes upper 64 bits of multiplication of two 64-bit unsigned integers. -inline uint64_t umul128_upper64(uint64_t x, uint64_t y) noexcept { -#if FMT_USE_INT128 - auto p = static_cast(x) * static_cast(y); - return static_cast(p >> 64); -#elif defined(_MSC_VER) && defined(_M_X64) - return __umulh(x, y); -#else - return umul128(x, y).high(); -#endif -} - -// Computes upper 128 bits of multiplication of a 64-bit unsigned integer and a -// 128-bit unsigned integer. -inline uint128_fallback umul192_upper128(uint64_t x, - uint128_fallback y) noexcept { - uint128_fallback r = umul128(x, y.high()); - r += umul128_upper64(x, y.low()); - return r; -} - -FMT_API uint128_fallback get_cached_power(int k) noexcept; // Type-specific information that Dragonbox uses. template struct float_info; @@ -1568,7 +1307,7 @@ template <> struct float_info { static const int big_divisor = 1000; static const int small_divisor = 100; static const int min_k = -292; - static const int max_k = 341; + static const int max_k = 326; static const int shorter_interval_tie_lower_threshold = -77; static const int shorter_interval_tie_upper_threshold = -77; }; @@ -1615,8 +1354,8 @@ template constexpr int num_significand_bits() { template constexpr auto exponent_mask() -> typename dragonbox::float_info::carrier_uint { - using float_uint = typename dragonbox::float_info::carrier_uint; - return ((float_uint(1) << dragonbox::float_info::exponent_bits) - 1) + using uint = typename dragonbox::float_info::carrier_uint; + return ((uint(1) << dragonbox::float_info::exponent_bits) - 1) << num_significand_bits(); } template constexpr auto exponent_bias() -> int { @@ -1793,28 +1532,12 @@ template struct basic_data { static constexpr uint64_t power_of_10_64[20] = { 1, FMT_POWERS_OF_10(1ULL), FMT_POWERS_OF_10(1000000000ULL), 10000000000000000000ULL}; - - // For checking rounding thresholds. - // The kth entry is chosen to be the smallest integer such that the - // upper 32-bits of 10^(k+1) times it is strictly bigger than 5 * 10^k. - static constexpr uint32_t fractional_part_rounding_thresholds[8] = { - 2576980378, // ceil(2^31 + 2^32/10^1) - 2190433321, // ceil(2^31 + 2^32/10^2) - 2151778616, // ceil(2^31 + 2^32/10^3) - 2147913145, // ceil(2^31 + 2^32/10^4) - 2147526598, // ceil(2^31 + 2^32/10^5) - 2147487943, // ceil(2^31 + 2^32/10^6) - 2147484078, // ceil(2^31 + 2^32/10^7) - 2147483691 // ceil(2^31 + 2^32/10^8) - }; }; #if FMT_CPLUSPLUS < 201703L template constexpr uint64_t basic_data::pow10_significands[]; template constexpr int16_t basic_data::pow10_exponents[]; template constexpr uint64_t basic_data::power_of_10_64[]; -template -constexpr uint32_t basic_data::fractional_part_rounding_thresholds[]; #endif // This is a struct rather than an alias to avoid shadowing warnings in gcc. @@ -1844,11 +1567,65 @@ FMT_CONSTEXPR inline fp get_cached_power(int min_exponent, *(data::pow10_exponents + index)}; } +#ifndef _MSC_VER +# define FMT_SNPRINTF snprintf +#else +FMT_API auto fmt_snprintf(char* buf, size_t size, const char* fmt, ...) -> int; +# define FMT_SNPRINTF fmt_snprintf +#endif // _MSC_VER + +// Formats a floating-point number with snprintf using the hexfloat format. +template +auto snprintf_float(T value, int precision, float_specs specs, + buffer& buf) -> int { + // Buffer capacity must be non-zero, otherwise MSVC's vsnprintf_s will fail. + FMT_ASSERT(buf.capacity() > buf.size(), "empty buffer"); + FMT_ASSERT(specs.format == float_format::hex, ""); + static_assert(!std::is_same::value, ""); + + // Build the format string. + char format[7]; // The longest format is "%#.*Le". + char* format_ptr = format; + *format_ptr++ = '%'; + if (specs.showpoint) *format_ptr++ = '#'; + if (precision >= 0) { + *format_ptr++ = '.'; + *format_ptr++ = '*'; + } + if (std::is_same()) *format_ptr++ = 'L'; + *format_ptr++ = specs.upper ? 'A' : 'a'; + *format_ptr = '\0'; + + // Format using snprintf. + auto offset = buf.size(); + for (;;) { + auto begin = buf.data() + offset; + auto capacity = buf.capacity() - offset; + abort_fuzzing_if(precision > 100000); + // Suppress the warning about a nonliteral format string. + // Cannot use auto because of a bug in MinGW (#1532). + int (*snprintf_ptr)(char*, size_t, const char*, ...) = FMT_SNPRINTF; + int result = precision >= 0 + ? snprintf_ptr(begin, capacity, format, precision, value) + : snprintf_ptr(begin, capacity, format, value); + if (result < 0) { + // The buffer will grow exponentially. + buf.try_reserve(buf.capacity() + 1); + continue; + } + auto size = to_unsigned(result); + // Size equal to capacity means that the last character was truncated. + if (size < capacity) { + buf.try_resize(size + offset); + return 0; + } + buf.try_reserve(size + offset + 1); // Add 1 for the terminating '\0'. + } +} + template using convert_float_result = - conditional_t::value || - std::numeric_limits::digits == - std::numeric_limits::digits, + conditional_t::value || sizeof(T) == sizeof(double), double, T>; template @@ -1872,7 +1649,8 @@ FMT_NOINLINE FMT_CONSTEXPR auto fill(OutputIt it, size_t n, // width: output display width in (terminal) column positions. template -FMT_CONSTEXPR auto write_padded(OutputIt out, const format_specs& specs, +FMT_CONSTEXPR auto write_padded(OutputIt out, + const basic_format_specs& specs, size_t size, size_t width, F&& f) -> OutputIt { static_assert(align == align::left || align == align::right, ""); unsigned spec_width = to_unsigned(specs.width); @@ -1891,14 +1669,15 @@ FMT_CONSTEXPR auto write_padded(OutputIt out, const format_specs& specs, template -constexpr auto write_padded(OutputIt out, const format_specs& specs, +constexpr auto write_padded(OutputIt out, const basic_format_specs& specs, size_t size, F&& f) -> OutputIt { return write_padded(out, specs, size, size, f); } template FMT_CONSTEXPR auto write_bytes(OutputIt out, string_view bytes, - const format_specs& specs) -> OutputIt { + const basic_format_specs& specs) + -> OutputIt { return write_padded( out, specs, bytes.size(), [bytes](reserve_iterator it) { const char* data = bytes.data(); @@ -1907,8 +1686,8 @@ FMT_CONSTEXPR auto write_bytes(OutputIt out, string_view bytes, } template -auto write_ptr(OutputIt out, UIntPtr value, const format_specs* specs) - -> OutputIt { +auto write_ptr(OutputIt out, UIntPtr value, + const basic_format_specs* specs) -> OutputIt { int num_digits = count_digits<4>(value); auto size = to_unsigned(num_digits) + size_t(2); auto write = [=](reserve_iterator it) { @@ -2027,14 +1806,16 @@ auto write_escaped_cp(OutputIt out, const find_escape_result& escape) *out++ = static_cast('\\'); break; default: - if (escape.cp < 0x100) { - return write_codepoint<2, Char>(out, 'x', escape.cp); - } - if (escape.cp < 0x10000) { - return write_codepoint<4, Char>(out, 'u', escape.cp); - } - if (escape.cp < 0x110000) { - return write_codepoint<8, Char>(out, 'U', escape.cp); + if (is_utf8()) { + if (escape.cp < 0x100) { + return write_codepoint<2, Char>(out, 'x', escape.cp); + } + if (escape.cp < 0x10000) { + return write_codepoint<4, Char>(out, 'u', escape.cp); + } + if (escape.cp < 0x110000) { + return write_codepoint<8, Char>(out, 'U', escape.cp); + } } for (Char escape_char : basic_string_view( escape.begin, to_unsigned(escape.end - escape.begin))) { @@ -2079,7 +1860,8 @@ auto write_escaped_char(OutputIt out, Char v) -> OutputIt { template FMT_CONSTEXPR auto write_char(OutputIt out, Char value, - const format_specs& specs) -> OutputIt { + const basic_format_specs& specs) + -> OutputIt { bool is_debug = specs.type == presentation_type::debug; return write_padded(out, specs, 1, [=](reserve_iterator it) { if (is_debug) return write_escaped_char(it, value); @@ -2089,14 +1871,11 @@ FMT_CONSTEXPR auto write_char(OutputIt out, Char value, } template FMT_CONSTEXPR auto write(OutputIt out, Char value, - const format_specs& specs, locale_ref loc = {}) - -> OutputIt { - // char is formatted as unsigned char for consistency across platforms. - using unsigned_type = - conditional_t::value, unsigned char, unsigned>; + const basic_format_specs& specs, + locale_ref loc = {}) -> OutputIt { return check_char_specs(specs) ? write_char(out, value, specs) - : write(out, static_cast(value), specs, loc); + : write(out, static_cast(value), specs, loc); } // Data for write_int that doesn't depend on output iterator type. It is used to @@ -2106,7 +1885,7 @@ template struct write_int_data { size_t padding; FMT_CONSTEXPR write_int_data(int num_digits, unsigned prefix, - const format_specs& specs) + const basic_format_specs& specs) : size((prefix >> 24) + to_unsigned(num_digits)), padding(0) { if (specs.align == align::numeric) { auto width = to_unsigned(specs.width); @@ -2128,7 +1907,7 @@ template struct write_int_data { template FMT_CONSTEXPR FMT_INLINE auto write_int(OutputIt out, int num_digits, unsigned prefix, - const format_specs& specs, + const basic_format_specs& specs, W write_digits) -> OutputIt { // Slightly faster check for specs.width == 0 && specs.precision == -1. if ((specs.width | (specs.precision + 1)) == 0) { @@ -2151,19 +1930,19 @@ FMT_CONSTEXPR FMT_INLINE auto write_int(OutputIt out, int num_digits, template class digit_grouping { private: - std::string grouping_; - std::basic_string thousands_sep_; + thousands_sep_result sep_; struct next_state { std::string::const_iterator group; int pos; }; - next_state initial_state() const { return {grouping_.begin(), 0}; } + next_state initial_state() const { return {sep_.grouping.begin(), 0}; } // Returns the next digit group separator position. int next(next_state& state) const { - if (thousands_sep_.empty()) return max_value(); - if (state.group == grouping_.end()) return state.pos += grouping_.back(); + if (!sep_.thousands_sep) return max_value(); + if (state.group == sep_.grouping.end()) + return state.pos += sep_.grouping.back(); if (*state.group <= 0 || *state.group == max_value()) return max_value(); state.pos += *state.group++; @@ -2172,15 +1951,14 @@ template class digit_grouping { public: explicit digit_grouping(locale_ref loc, bool localized = true) { - if (!localized) return; - auto sep = thousands_sep(loc); - grouping_ = sep.grouping; - if (sep.thousands_sep) thousands_sep_.assign(1, sep.thousands_sep); + if (localized) + sep_ = thousands_sep(loc); + else + sep_.thousands_sep = Char(); } - digit_grouping(std::string grouping, std::basic_string sep) - : grouping_(std::move(grouping)), thousands_sep_(std::move(sep)) {} + explicit digit_grouping(thousands_sep_result sep) : sep_(sep) {} - bool has_separator() const { return !thousands_sep_.empty(); } + Char separator() const { return sep_.thousands_sep; } int count_separators(int num_digits) const { int count = 0; @@ -2203,9 +1981,7 @@ template class digit_grouping { for (int i = 0, sep_index = static_cast(separators.size() - 1); i < num_digits; ++i) { if (num_digits - i == separators[sep_index]) { - out = - copy_str(thousands_sep_.data(), - thousands_sep_.data() + thousands_sep_.size(), out); + *out++ = separator(); --sep_index; } *out++ = static_cast(digits[to_unsigned(i)]); @@ -2214,11 +1990,10 @@ template class digit_grouping { } }; -// Writes a decimal integer with digit grouping. template -auto write_int(OutputIt out, UInt value, unsigned prefix, - const format_specs& specs, - const digit_grouping& grouping) -> OutputIt { +auto write_int_localized(OutputIt out, UInt value, unsigned prefix, + const basic_format_specs& specs, + const digit_grouping& grouping) -> OutputIt { static_assert(std::is_same, UInt>::value, ""); int num_digits = count_digits(value); char digits[40]; @@ -2235,13 +2010,13 @@ auto write_int(OutputIt out, UInt value, unsigned prefix, }); } -// Writes a localized value. -FMT_API auto write_loc(appender out, loc_value value, - const format_specs<>& specs, locale_ref loc) -> bool; -template -inline auto write_loc(OutputIt, loc_value, const format_specs&, - locale_ref) -> bool { - return false; +template +auto write_int_localized(OutputIt& out, UInt value, unsigned prefix, + const basic_format_specs& specs, locale_ref loc) + -> bool { + auto grouping = digit_grouping(loc); + out = write_int_localized(out, value, prefix, specs, grouping); + return true; } FMT_CONSTEXPR inline void prefix_append(unsigned& prefix, unsigned value) { @@ -2270,37 +2045,21 @@ FMT_CONSTEXPR auto make_write_int_arg(T value, sign_t sign) return {abs_value, prefix}; } -template struct loc_writer { - buffer_appender out; - const format_specs& specs; - std::basic_string sep; - std::string grouping; - std::basic_string decimal_point; - - template ::value)> - auto operator()(T value) -> bool { - auto arg = make_write_int_arg(value, specs.sign); - write_int(out, static_cast>(arg.abs_value), arg.prefix, - specs, digit_grouping(grouping, sep)); - return true; - } - - template ::value)> - auto operator()(T) -> bool { - return false; - } -}; - template FMT_CONSTEXPR FMT_INLINE auto write_int(OutputIt out, write_int_arg arg, - const format_specs& specs, - locale_ref) -> OutputIt { + const basic_format_specs& specs, + locale_ref loc) -> OutputIt { static_assert(std::is_same>::value, ""); auto abs_value = arg.abs_value; auto prefix = arg.prefix; switch (specs.type) { case presentation_type::none: case presentation_type::dec: { + if (specs.localized && + write_int_localized(out, static_cast>(abs_value), + prefix, specs, loc)) { + return out; + } auto num_digits = count_digits(abs_value); return write_int( out, num_digits, prefix, specs, [=](reserve_iterator it) { @@ -2343,13 +2102,13 @@ FMT_CONSTEXPR FMT_INLINE auto write_int(OutputIt out, write_int_arg arg, case presentation_type::chr: return write_char(out, static_cast(abs_value), specs); default: - throw_format_error("invalid format specifier"); + throw_format_error("invalid type specifier"); } return out; } template FMT_CONSTEXPR FMT_NOINLINE auto write_int_noinline( - OutputIt out, write_int_arg arg, const format_specs& specs, + OutputIt out, write_int_arg arg, const basic_format_specs& specs, locale_ref loc) -> OutputIt { return write_int(out, arg, specs, loc); } @@ -2358,9 +2117,8 @@ template ::value && std::is_same>::value)> FMT_CONSTEXPR FMT_INLINE auto write(OutputIt out, T value, - const format_specs& specs, + const basic_format_specs& specs, locale_ref loc) -> OutputIt { - if (specs.localized && write_loc(out, value, specs, loc)) return out; return write_int_noinline(out, make_write_int_arg(value, specs.sign), specs, loc); } @@ -2370,9 +2128,8 @@ template ::value && !std::is_same>::value)> FMT_CONSTEXPR FMT_INLINE auto write(OutputIt out, T value, - const format_specs& specs, + const basic_format_specs& specs, locale_ref loc) -> OutputIt { - if (specs.localized && write_loc(out, value, specs, loc)) return out; return write_int(out, make_write_int_arg(value, specs.sign), specs, loc); } @@ -2418,7 +2175,7 @@ class counting_iterator { template FMT_CONSTEXPR auto write(OutputIt out, basic_string_view s, - const format_specs& specs) -> OutputIt { + const basic_format_specs& specs) -> OutputIt { auto data = s.data(); auto size = s.size(); if (specs.precision >= 0 && to_unsigned(specs.precision) < size) @@ -2440,15 +2197,16 @@ FMT_CONSTEXPR auto write(OutputIt out, basic_string_view s, template FMT_CONSTEXPR auto write(OutputIt out, basic_string_view> s, - const format_specs& specs, locale_ref) + const basic_format_specs& specs, locale_ref) -> OutputIt { + check_string_type_spec(specs.type); return write(out, s, specs); } template FMT_CONSTEXPR auto write(OutputIt out, const Char* s, - const format_specs& specs, locale_ref) + const basic_format_specs& specs, locale_ref) -> OutputIt { - return specs.type != presentation_type::pointer + return check_cstring_type_spec(specs.type) ? write(out, basic_string_view(s), specs, {}) : write_ptr(out, bit_cast(s), &specs); } @@ -2475,71 +2233,9 @@ FMT_CONSTEXPR auto write(OutputIt out, T value) -> OutputIt { return base_iterator(out, it); } -// A floating-point presentation format. -enum class float_format : unsigned char { - general, // General: exponent notation or fixed point based on magnitude. - exp, // Exponent notation with the default precision of 6, e.g. 1.2e-3. - fixed, // Fixed point with the default precision of 6, e.g. 0.0012. - hex -}; - -struct float_specs { - int precision; - float_format format : 8; - sign_t sign : 8; - bool upper : 1; - bool locale : 1; - bool binary32 : 1; - bool showpoint : 1; -}; - -template -FMT_CONSTEXPR auto parse_float_type_spec(const format_specs& specs, - ErrorHandler&& eh = {}) - -> float_specs { - auto result = float_specs(); - result.showpoint = specs.alt; - result.locale = specs.localized; - switch (specs.type) { - case presentation_type::none: - result.format = float_format::general; - break; - case presentation_type::general_upper: - result.upper = true; - FMT_FALLTHROUGH; - case presentation_type::general_lower: - result.format = float_format::general; - break; - case presentation_type::exp_upper: - result.upper = true; - FMT_FALLTHROUGH; - case presentation_type::exp_lower: - result.format = float_format::exp; - result.showpoint |= specs.precision != 0; - break; - case presentation_type::fixed_upper: - result.upper = true; - FMT_FALLTHROUGH; - case presentation_type::fixed_lower: - result.format = float_format::fixed; - result.showpoint |= specs.precision != 0; - break; - case presentation_type::hexfloat_upper: - result.upper = true; - FMT_FALLTHROUGH; - case presentation_type::hexfloat_lower: - result.format = float_format::hex; - break; - default: - eh.on_error("invalid format specifier"); - break; - } - return result; -} - template FMT_CONSTEXPR20 auto write_nonfinite(OutputIt out, bool isnan, - format_specs specs, + basic_format_specs specs, const float_specs& fspecs) -> OutputIt { auto str = isnan ? (fspecs.upper ? "NAN" : "nan") : (fspecs.upper ? "INF" : "inf"); @@ -2585,7 +2281,7 @@ template FMT_CONSTEXPR20 auto write_significand(OutputIt out, T significand, int significand_size, int exponent, const Grouping& grouping) -> OutputIt { - if (!grouping.has_separator()) { + if (!grouping.separator()) { out = write_significand(out, significand, significand_size); return detail::fill_n(out, exponent, static_cast('0')); } @@ -2647,7 +2343,7 @@ FMT_CONSTEXPR20 auto write_significand(OutputIt out, T significand, int significand_size, int integral_size, Char decimal_point, const Grouping& grouping) -> OutputIt { - if (!grouping.has_separator()) { + if (!grouping.separator()) { return write_significand(out, significand, significand_size, integral_size, decimal_point); } @@ -2663,7 +2359,7 @@ FMT_CONSTEXPR20 auto write_significand(OutputIt out, T significand, template > FMT_CONSTEXPR20 auto do_write_float(OutputIt out, const DecimalFP& f, - const format_specs& specs, + const basic_format_specs& specs, float_specs fspecs, locale_ref loc) -> OutputIt { auto significand = f.significand; @@ -2722,7 +2418,7 @@ FMT_CONSTEXPR20 auto do_write_float(OutputIt out, const DecimalFP& f, abort_fuzzing_if(num_zeros > 5000); if (fspecs.showpoint) { ++size; - if (num_zeros <= 0 && fspecs.format != float_format::fixed) num_zeros = 0; + if (num_zeros <= 0 && fspecs.format != float_format::fixed) num_zeros = 1; if (num_zeros > 0) size += to_unsigned(num_zeros); } auto grouping = Grouping(loc, fspecs.locale); @@ -2740,7 +2436,7 @@ FMT_CONSTEXPR20 auto do_write_float(OutputIt out, const DecimalFP& f, int num_zeros = fspecs.showpoint ? fspecs.precision - significand_size : 0; size += 1 + to_unsigned(num_zeros > 0 ? num_zeros : 0); auto grouping = Grouping(loc, fspecs.locale); - size += to_unsigned(grouping.count_separators(exp)); + size += to_unsigned(grouping.count_separators(significand_size)); return write_padded(out, specs, size, [&](iterator it) { if (sign) *it++ = detail::sign(sign); it = write_significand(it, significand, significand_size, exp, @@ -2770,7 +2466,7 @@ template class fallback_digit_grouping { public: constexpr fallback_digit_grouping(locale_ref, bool) {} - constexpr bool has_separator() const { return false; } + constexpr Char separator() const { return Char(); } constexpr int count_separators(int) const { return 0; } @@ -2782,7 +2478,7 @@ template class fallback_digit_grouping { template FMT_CONSTEXPR20 auto write_float(OutputIt out, const DecimalFP& f, - const format_specs& specs, + const basic_format_specs& specs, float_specs fspecs, locale_ref loc) -> OutputIt { if (is_constant_evaluated()) { @@ -2810,14 +2506,14 @@ template ::value&& FMT_CONSTEXPR20 bool isfinite(T value) { constexpr T inf = T(std::numeric_limits::infinity()); if (is_constant_evaluated()) - return !detail::isnan(value) && value < inf && value > -inf; + return !detail::isnan(value) && value != inf && value != -inf; return std::isfinite(value); } template ::value)> FMT_CONSTEXPR bool isfinite(T value) { T inf = T(std::numeric_limits::infinity()); // std::isfinite doesn't support __float128. - return !detail::isnan(value) && value < inf && value > -inf; + return !detail::isnan(value) && value != inf && value != -inf; } template ::value)> @@ -3398,94 +3094,6 @@ FMT_CONSTEXPR20 inline void format_dragon(basic_fp value, buf[num_digits - 1] = static_cast('0' + digit); } -// Formats a floating-point number using the hexfloat format. -template ::value)> -FMT_CONSTEXPR20 void format_hexfloat(Float value, int precision, - float_specs specs, buffer& buf) { - // float is passed as double to reduce the number of instantiations and to - // simplify implementation. - static_assert(!std::is_same::value, ""); - - using info = dragonbox::float_info; - - // Assume Float is in the format [sign][exponent][significand]. - using carrier_uint = typename info::carrier_uint; - - constexpr auto num_float_significand_bits = - detail::num_significand_bits(); - - basic_fp f(value); - f.e += num_float_significand_bits; - if (!has_implicit_bit()) --f.e; - - constexpr auto num_fraction_bits = - num_float_significand_bits + (has_implicit_bit() ? 1 : 0); - constexpr auto num_xdigits = (num_fraction_bits + 3) / 4; - - constexpr auto leading_shift = ((num_xdigits - 1) * 4); - const auto leading_mask = carrier_uint(0xF) << leading_shift; - const auto leading_xdigit = - static_cast((f.f & leading_mask) >> leading_shift); - if (leading_xdigit > 1) f.e -= (32 - countl_zero(leading_xdigit) - 1); - - int print_xdigits = num_xdigits - 1; - if (precision >= 0 && print_xdigits > precision) { - const int shift = ((print_xdigits - precision - 1) * 4); - const auto mask = carrier_uint(0xF) << shift; - const auto v = static_cast((f.f & mask) >> shift); - - if (v >= 8) { - const auto inc = carrier_uint(1) << (shift + 4); - f.f += inc; - f.f &= ~(inc - 1); - } - - // Check long double overflow - if (!has_implicit_bit()) { - const auto implicit_bit = carrier_uint(1) << num_float_significand_bits; - if ((f.f & implicit_bit) == implicit_bit) { - f.f >>= 4; - f.e += 4; - } - } - - print_xdigits = precision; - } - - char xdigits[num_bits() / 4]; - detail::fill_n(xdigits, sizeof(xdigits), '0'); - format_uint<4>(xdigits, f.f, num_xdigits, specs.upper); - - // Remove zero tail - while (print_xdigits > 0 && xdigits[print_xdigits] == '0') --print_xdigits; - - buf.push_back('0'); - buf.push_back(specs.upper ? 'X' : 'x'); - buf.push_back(xdigits[0]); - if (specs.showpoint || print_xdigits > 0 || print_xdigits < precision) - buf.push_back('.'); - buf.append(xdigits + 1, xdigits + 1 + print_xdigits); - for (; print_xdigits < precision; ++print_xdigits) buf.push_back('0'); - - buf.push_back(specs.upper ? 'P' : 'p'); - - uint32_t abs_e; - if (f.e < 0) { - buf.push_back('-'); - abs_e = static_cast(-f.e); - } else { - buf.push_back('+'); - abs_e = static_cast(f.e); - } - format_decimal(appender(buf), abs_e, detail::count_digits(abs_e)); -} - -template ::value)> -FMT_CONSTEXPR20 void format_hexfloat(Float value, int precision, - float_specs specs, buffer& buf) { - format_hexfloat(static_cast(value), precision, specs, buf); -} - template FMT_CONSTEXPR20 auto format_float(Float value, int precision, float_specs specs, buffer& buf) -> int { @@ -3529,7 +3137,7 @@ FMT_CONSTEXPR20 auto format_float(Float value, int precision, float_specs specs, auto dec = dragonbox::to_decimal(static_cast(value)); write(buffer_appender(buf), dec.significand); return dec.exponent; - } else if (is_constant_evaluated()) { + } else { // Use Grisu + Dragon4 for the given precision: // https://www.cs.tufts.edu/~nr/cs257/archive/florian-loitsch/printf.pdf. const int min_exp = -60; // alpha in Grisu. @@ -3548,248 +3156,6 @@ FMT_CONSTEXPR20 auto format_float(Float value, int precision, float_specs specs, exp += handler.size - cached_exp10 - 1; precision = handler.precision; } - } else { - // Extract significand bits and exponent bits. - using info = dragonbox::float_info; - auto br = bit_cast(static_cast(value)); - - const uint64_t significand_mask = - (static_cast(1) << num_significand_bits()) - 1; - uint64_t significand = (br & significand_mask); - int exponent = static_cast((br & exponent_mask()) >> - num_significand_bits()); - - if (exponent != 0) { // Check if normal. - exponent -= exponent_bias() + num_significand_bits(); - significand |= - (static_cast(1) << num_significand_bits()); - significand <<= 1; - } else { - // Normalize subnormal inputs. - FMT_ASSERT(significand != 0, "zeros should not appear hear"); - int shift = countl_zero(significand); - FMT_ASSERT(shift >= num_bits() - num_significand_bits(), - ""); - shift -= (num_bits() - num_significand_bits() - 2); - exponent = (std::numeric_limits::min_exponent - - num_significand_bits()) - - shift; - significand <<= shift; - } - - // Compute the first several nonzero decimal significand digits. - // We call the number we get the first segment. - const int k = info::kappa - dragonbox::floor_log10_pow2(exponent); - exp = -k; - const int beta = exponent + dragonbox::floor_log2_pow10(k); - uint64_t first_segment; - bool has_more_segments; - int digits_in_the_first_segment; - { - const auto r = dragonbox::umul192_upper128( - significand << beta, dragonbox::get_cached_power(k)); - first_segment = r.high(); - has_more_segments = r.low() != 0; - - // The first segment can have 18 ~ 19 digits. - if (first_segment >= 1000000000000000000ULL) { - digits_in_the_first_segment = 19; - } else { - // When it is of 18-digits, we align it to 19-digits by adding a bogus - // zero at the end. - digits_in_the_first_segment = 18; - first_segment *= 10; - } - } - - // Compute the actual number of decimal digits to print. - if (fixed) { - adjust_precision(precision, exp + digits_in_the_first_segment); - } - - // Use Dragon4 only when there might be not enough digits in the first - // segment. - if (digits_in_the_first_segment > precision) { - use_dragon = false; - - if (precision <= 0) { - exp += digits_in_the_first_segment; - - if (precision < 0) { - // Nothing to do, since all we have are just leading zeros. - buf.try_resize(0); - } else { - // We may need to round-up. - buf.try_resize(1); - if ((first_segment | static_cast(has_more_segments)) > - 5000000000000000000ULL) { - buf[0] = '1'; - } else { - buf[0] = '0'; - } - } - } // precision <= 0 - else { - exp += digits_in_the_first_segment - precision; - - // When precision > 0, we divide the first segment into three - // subsegments, each with 9, 9, and 0 ~ 1 digits so that each fits - // in 32-bits which usually allows faster calculation than in - // 64-bits. Since some compiler (e.g. MSVC) doesn't know how to optimize - // division-by-constant for large 64-bit divisors, we do it here - // manually. The magic number 7922816251426433760 below is equal to - // ceil(2^(64+32) / 10^10). - const uint32_t first_subsegment = static_cast( - dragonbox::umul128_upper64(first_segment, 7922816251426433760ULL) >> - 32); - const uint64_t second_third_subsegments = - first_segment - first_subsegment * 10000000000ULL; - - uint64_t prod; - uint32_t digits; - bool should_round_up; - int number_of_digits_to_print = precision > 9 ? 9 : precision; - - // Print a 9-digits subsegment, either the first or the second. - auto print_subsegment = [&](uint32_t subsegment, char* buffer) { - int number_of_digits_printed = 0; - - // If we want to print an odd number of digits from the subsegment, - if ((number_of_digits_to_print & 1) != 0) { - // Convert to 64-bit fixed-point fractional form with 1-digit - // integer part. The magic number 720575941 is a good enough - // approximation of 2^(32 + 24) / 10^8; see - // https://jk-jeon.github.io/posts/2022/12/fixed-precision-formatting/#fixed-length-case - // for details. - prod = ((subsegment * static_cast(720575941)) >> 24) + 1; - digits = static_cast(prod >> 32); - *buffer = static_cast('0' + digits); - number_of_digits_printed++; - } - // If we want to print an even number of digits from the - // first_subsegment, - else { - // Convert to 64-bit fixed-point fractional form with 2-digits - // integer part. The magic number 450359963 is a good enough - // approximation of 2^(32 + 20) / 10^7; see - // https://jk-jeon.github.io/posts/2022/12/fixed-precision-formatting/#fixed-length-case - // for details. - prod = ((subsegment * static_cast(450359963)) >> 20) + 1; - digits = static_cast(prod >> 32); - copy2(buffer, digits2(digits)); - number_of_digits_printed += 2; - } - - // Print all digit pairs. - while (number_of_digits_printed < number_of_digits_to_print) { - prod = static_cast(prod) * static_cast(100); - digits = static_cast(prod >> 32); - copy2(buffer + number_of_digits_printed, digits2(digits)); - number_of_digits_printed += 2; - } - }; - - // Print first subsegment. - print_subsegment(first_subsegment, buf.data()); - - // Perform rounding if the first subsegment is the last subsegment to - // print. - if (precision <= 9) { - // Rounding inside the subsegment. - // We round-up if: - // - either the fractional part is strictly larger than 1/2, or - // - the fractional part is exactly 1/2 and the last digit is odd. - // We rely on the following observations: - // - If fractional_part >= threshold, then the fractional part is - // strictly larger than 1/2. - // - If the MSB of fractional_part is set, then the fractional part - // must be at least 1/2. - // - When the MSB of fractional_part is set, either - // second_third_subsegments being nonzero or has_more_segments - // being true means there are further digits not printed, so the - // fractional part is strictly larger than 1/2. - if (precision < 9) { - uint32_t fractional_part = static_cast(prod); - should_round_up = fractional_part >= - data::fractional_part_rounding_thresholds - [8 - number_of_digits_to_print] || - ((fractional_part >> 31) & - ((digits & 1) | (second_third_subsegments != 0) | - has_more_segments)) != 0; - } - // Rounding at the subsegment boundary. - // In this case, the fractional part is at least 1/2 if and only if - // second_third_subsegments >= 5000000000ULL, and is strictly larger - // than 1/2 if we further have either second_third_subsegments > - // 5000000000ULL or has_more_segments == true. - else { - should_round_up = second_third_subsegments > 5000000000ULL || - (second_third_subsegments == 5000000000ULL && - ((digits & 1) != 0 || has_more_segments)); - } - } - // Otherwise, print the second subsegment. - else { - // Compilers are not aware of how to leverage the maximum value of - // second_third_subsegments to find out a better magic number which - // allows us to eliminate an additional shift. 1844674407370955162 = - // ceil(2^64/10) < ceil(2^64*(10^9/(10^10 - 1))). - const uint32_t second_subsegment = - static_cast(dragonbox::umul128_upper64( - second_third_subsegments, 1844674407370955162ULL)); - const uint32_t third_subsegment = - static_cast(second_third_subsegments) - - second_subsegment * 10; - - number_of_digits_to_print = precision - 9; - print_subsegment(second_subsegment, buf.data() + 9); - - // Rounding inside the subsegment. - if (precision < 18) { - // The condition third_subsegment != 0 implies that the segment was - // of 19 digits, so in this case the third segment should be - // consisting of a genuine digit from the input. - uint32_t fractional_part = static_cast(prod); - should_round_up = fractional_part >= - data::fractional_part_rounding_thresholds - [8 - number_of_digits_to_print] || - ((fractional_part >> 31) & - ((digits & 1) | (third_subsegment != 0) | - has_more_segments)) != 0; - } - // Rounding at the subsegment boundary. - else { - // In this case, the segment must be of 19 digits, thus - // the third subsegment should be consisting of a genuine digit from - // the input. - should_round_up = third_subsegment > 5 || - (third_subsegment == 5 && - ((digits & 1) != 0 || has_more_segments)); - } - } - - // Round-up if necessary. - if (should_round_up) { - ++buf[precision - 1]; - for (int i = precision - 1; i > 0 && buf[i] > '9'; --i) { - buf[i] = '0'; - ++buf[i - 1]; - } - if (buf[0] > '9') { - buf[0] = '1'; - if (fixed) - buf[precision++] = '0'; - else - ++exp; - } - } - buf.try_resize(to_unsigned(precision)); - } - } // if (digits_in_the_first_segment > precision) - else { - // Adjust the exponent for its use in Dragon4. - exp += digits_in_the_first_segment - 1; - } } if (use_dragon) { auto f = basic_fp(); @@ -3815,10 +3181,13 @@ FMT_CONSTEXPR20 auto format_float(Float value, int precision, float_specs specs, } return exp; } -template -FMT_CONSTEXPR20 auto write_float(OutputIt out, T value, - format_specs specs, locale_ref loc) + +template ::value)> +FMT_CONSTEXPR20 auto write(OutputIt out, T value, + basic_format_specs specs, locale_ref loc = {}) -> OutputIt { + if (const_check(!is_supported_floating_point(value))) return out; float_specs fspecs = parse_float_type_spec(specs); fspecs.sign = specs.sign; if (detail::signbit(value)) { // value < 0 is false for NaN so use signbit. @@ -3842,7 +3211,7 @@ FMT_CONSTEXPR20 auto write_float(OutputIt out, T value, memory_buffer buffer; if (fspecs.format == float_format::hex) { if (fspecs.sign) buffer.push_back(detail::sign(fspecs.sign)); - format_hexfloat(convert_float(value), specs.precision, fspecs, buffer); + snprintf_float(convert_float(value), specs.precision, fspecs, buffer); return write_bytes(out, {buffer.data(), buffer.size()}, specs); } @@ -3864,20 +3233,11 @@ FMT_CONSTEXPR20 auto write_float(OutputIt out, T value, return write_float(out, f, specs, fspecs, loc); } -template ::value)> -FMT_CONSTEXPR20 auto write(OutputIt out, T value, format_specs specs, - locale_ref loc = {}) -> OutputIt { - if (const_check(!is_supported_floating_point(value))) return out; - return specs.localized && write_loc(out, value, specs, loc) - ? out - : write_float(out, value, specs, loc); -} - template ::value)> FMT_CONSTEXPR20 auto write(OutputIt out, T value) -> OutputIt { - if (is_constant_evaluated()) return write(out, value, format_specs()); + if (is_constant_evaluated()) + return write(out, value, basic_format_specs()); if (const_check(!is_supported_floating_point(value))) return out; auto fspecs = float_specs(); @@ -3886,11 +3246,11 @@ FMT_CONSTEXPR20 auto write(OutputIt out, T value) -> OutputIt { value = -value; } - constexpr auto specs = format_specs(); + constexpr auto specs = basic_format_specs(); using floaty = conditional_t::value, double, T>; - using floaty_uint = typename dragonbox::float_info::carrier_uint; - floaty_uint mask = exponent_mask(); - if ((bit_cast(value) & mask) == mask) + using uint = typename dragonbox::float_info::carrier_uint; + uint mask = exponent_mask(); + if ((bit_cast(value) & mask) == mask) return write_nonfinite(out, std::isnan(value), specs, fspecs); auto dec = dragonbox::to_decimal(static_cast(value)); @@ -3901,12 +3261,12 @@ template ::value && !is_fast_float::value)> inline auto write(OutputIt out, T value) -> OutputIt { - return write(out, value, format_specs()); + return write(out, value, basic_format_specs()); } template -auto write(OutputIt out, monostate, format_specs = {}, locale_ref = {}) - -> OutputIt { +auto write(OutputIt out, monostate, basic_format_specs = {}, + locale_ref = {}) -> OutputIt { FMT_ASSERT(false, ""); return out; } @@ -3940,8 +3300,8 @@ FMT_CONSTEXPR auto write(OutputIt out, T value) -> OutputIt { template ::value)> FMT_CONSTEXPR auto write(OutputIt out, T value, - const format_specs& specs = {}, locale_ref = {}) - -> OutputIt { + const basic_format_specs& specs = {}, + locale_ref = {}) -> OutputIt { return specs.type != presentation_type::none && specs.type != presentation_type::string ? write(out, value ? 1 : 0, specs, {}) @@ -3958,15 +3318,20 @@ FMT_CONSTEXPR auto write(OutputIt out, Char value) -> OutputIt { template FMT_CONSTEXPR_CHAR_TRAITS auto write(OutputIt out, const Char* value) -> OutputIt { - if (value) return write(out, basic_string_view(value)); - throw_format_error("string pointer is null"); + if (!value) { + throw_format_error("string pointer is null"); + } else { + out = write(out, basic_string_view(value)); + } return out; } template ::value)> -auto write(OutputIt out, const T* value, const format_specs& specs = {}, - locale_ref = {}) -> OutputIt { +auto write(OutputIt out, const T* value, + const basic_format_specs& specs = {}, locale_ref = {}) + -> OutputIt { + check_pointer_type_spec(specs.type, error_handler()); return write_ptr(out, bit_cast(value), &specs); } @@ -3976,8 +3341,8 @@ template enable_if_t< std::is_class::value && !is_string::value && !is_floating_point::value && !std::is_same::value && - !std::is_same().map( - value))>>::value, + !std::is_same().map(value))>::value, OutputIt> { return write(out, arg_mapper().map(value)); } @@ -3987,8 +3352,12 @@ template enable_if_t::value == type::custom_type, OutputIt> { + using formatter_type = + conditional_t::value, + typename Context::template formatter_type, + fallback_formatter>; auto ctx = Context(out, {}, {}); - return typename Context::template formatter_type().format(value, ctx); + return formatter_type().format(value, ctx); } // An argument visitor that formats the argument and writes it via the output @@ -4017,7 +3386,7 @@ template struct arg_formatter { using context = buffer_context; iterator out; - const format_specs& specs; + const basic_format_specs& specs; locale_ref locale; template @@ -4042,6 +3411,12 @@ template struct custom_formatter { template void operator()(T) const {} }; +template +using is_integer = + bool_constant::value && !std::is_same::value && + !std::is_same::value && + !std::is_same::value>; + template class width_checker { public: explicit FMT_CONSTEXPR width_checker(ErrorHandler& eh) : handler_(eh) {} @@ -4098,6 +3473,48 @@ FMT_CONSTEXPR auto get_arg(Context& ctx, ID id) -> return arg; } +// The standard format specifier handler with checking. +template class specs_handler : public specs_setter { + private: + basic_format_parse_context& parse_context_; + buffer_context& context_; + + // This is only needed for compatibility with gcc 4.4. + using format_arg = basic_format_arg>; + + FMT_CONSTEXPR auto get_arg(auto_id) -> format_arg { + return detail::get_arg(context_, parse_context_.next_arg_id()); + } + + FMT_CONSTEXPR auto get_arg(int arg_id) -> format_arg { + parse_context_.check_arg_id(arg_id); + return detail::get_arg(context_, arg_id); + } + + FMT_CONSTEXPR auto get_arg(basic_string_view arg_id) -> format_arg { + parse_context_.check_arg_id(arg_id); + return detail::get_arg(context_, arg_id); + } + + public: + FMT_CONSTEXPR specs_handler(basic_format_specs& specs, + basic_format_parse_context& parse_ctx, + buffer_context& ctx) + : specs_setter(specs), parse_context_(parse_ctx), context_(ctx) {} + + template FMT_CONSTEXPR void on_dynamic_width(Id arg_id) { + this->specs_.width = get_dynamic_spec( + get_arg(arg_id), context_.error_handler()); + } + + template FMT_CONSTEXPR void on_dynamic_precision(Id arg_id) { + this->specs_.precision = get_dynamic_spec( + get_arg(arg_id), context_.error_handler()); + } + + void on_error(const char* message) { context_.on_error(message); } +}; + template