Improve handling of format specs

This commit is contained in:
Victor Zverovich
2024-01-15 05:56:15 -08:00
parent c98a5a599f
commit f9294f0e60
7 changed files with 129 additions and 133 deletions

View File

@@ -163,6 +163,17 @@
# define FMT_CATCH(x) if (false) # define FMT_CATCH(x) if (false)
#endif #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
// Disable [[noreturn]] on MSVC/NVCC because of bogus unreachable code warnings. // Disable [[noreturn]] on MSVC/NVCC because of bogus unreachable code warnings.
#if FMT_HAS_CPP_ATTRIBUTE(noreturn) && FMT_EXCEPTIONS && !FMT_MSC_VERSION && \ #if FMT_HAS_CPP_ATTRIBUTE(noreturn) && FMT_EXCEPTIONS && !FMT_MSC_VERSION && \
!defined(__NVCC__) !defined(__NVCC__)
@@ -2038,25 +2049,26 @@ template <typename Char> struct fill_t {
} // namespace detail } // namespace detail
enum class presentation_type : unsigned char { enum class presentation_type : unsigned char {
none, // Common specifiers:
dec, // 'd' none = 0,
oct, // 'o' debug = 1, // '?'
hex_lower, // 'x' string = 2, // 's' (string, bool)
hex_upper, // 'X'
bin_lower, // 'b' // Integral, bool and character specifiers:
bin_upper, // 'B' dec = 3, // 'd'
hexfloat_lower, // 'a' hex, // 'x' or 'X'
hexfloat_upper, // 'A' oct, // 'o'
exp_lower, // 'e' bin, // 'b' or 'B'
exp_upper, // 'E' chr, // 'c'
fixed_lower, // 'f'
fixed_upper, // 'F' // String and pointer specifiers:
general_lower, // 'g' pointer = 3, // 'p'
general_upper, // 'G'
chr, // 'c' // Floating-point specifiers:
string, // 's' exp = 1, // 'e' or 'E' (1 since there is no FP debug presentation)
pointer, // 'p' fixed, // 'f' or 'F'
debug // '?' general, // 'g' or 'G'
hexfloat // 'a' or 'A'
}; };
// Format specifiers for built-in and string types. // Format specifiers for built-in and string types.
@@ -2066,7 +2078,8 @@ template <typename Char = char> struct format_specs {
presentation_type type; presentation_type type;
align_t align : 4; align_t align : 4;
sign_t sign : 3; sign_t sign : 3;
bool alt : 1; // Alternate form ('#'). bool upper : 1; // An uppercase version e.g. 'X' for 'x'.
bool alt : 1; // Alternate form ('#').
bool localized : 1; bool localized : 1;
detail::fill_t<Char> fill; detail::fill_t<Char> fill;
@@ -2076,6 +2089,7 @@ template <typename Char = char> struct format_specs {
type(presentation_type::none), type(presentation_type::none),
align(align::none), align(align::none),
sign(sign::none), sign(sign::none),
upper(false),
alt(false), alt(false),
localized(false) {} localized(false) {}
}; };
@@ -2401,32 +2415,38 @@ FMT_CONSTEXPR auto parse_format_specs(const Char* begin, const Char* end,
break; break;
case 'd': case 'd':
return parse_presentation_type(pres::dec, integral_set); return parse_presentation_type(pres::dec, integral_set);
case 'X':
specs.upper = true;
FMT_FALLTHROUGH;
case 'x':
return parse_presentation_type(pres::hex, integral_set);
case 'o': case 'o':
return parse_presentation_type(pres::oct, integral_set); 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': case 'B':
return parse_presentation_type(pres::bin_upper, integral_set); specs.upper = true;
case 'a': FMT_FALLTHROUGH;
return parse_presentation_type(pres::hexfloat_lower, float_set); case 'b':
case 'A': return parse_presentation_type(pres::bin, integral_set);
return parse_presentation_type(pres::hexfloat_upper, float_set);
case 'e':
return parse_presentation_type(pres::exp_lower, float_set);
case 'E': case 'E':
return parse_presentation_type(pres::exp_upper, float_set); specs.upper = true;
case 'f': FMT_FALLTHROUGH;
return parse_presentation_type(pres::fixed_lower, float_set); case 'e':
return parse_presentation_type(pres::exp, float_set);
case 'F': case 'F':
return parse_presentation_type(pres::fixed_upper, float_set); specs.upper = true;
case 'g': FMT_FALLTHROUGH;
return parse_presentation_type(pres::general_lower, float_set); case 'f':
return parse_presentation_type(pres::fixed, float_set);
case 'G': case 'G':
return parse_presentation_type(pres::general_upper, float_set); specs.upper = true;
FMT_FALLTHROUGH;
case 'g':
return parse_presentation_type(pres::general, float_set);
case 'A':
specs.upper = true;
FMT_FALLTHROUGH;
case 'a':
return parse_presentation_type(pres::hexfloat, float_set);
case 'c': case 'c':
if (arg_type == type::bool_type) if (arg_type == type::bool_type)
throw_format_error("invalid format specifier"); throw_format_error("invalid format specifier");

View File

@@ -1735,8 +1735,8 @@ template <typename Char, typename Rep, typename OutputIt,
auto format_duration_value(OutputIt out, Rep val, int precision) -> OutputIt { auto format_duration_value(OutputIt out, Rep val, int precision) -> OutputIt {
auto specs = format_specs<Char>(); auto specs = format_specs<Char>();
specs.precision = precision; specs.precision = precision;
specs.type = precision >= 0 ? presentation_type::fixed_lower specs.type =
: presentation_type::general_lower; precision >= 0 ? presentation_type::fixed : presentation_type::general;
return write<Char>(out, val, specs); return write<Char>(out, val, specs);
} }

View File

@@ -71,17 +71,6 @@
# define FMT_INLINE_VARIABLE # define FMT_INLINE_VARIABLE
#endif #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_NO_UNIQUE_ADDRESS #ifndef FMT_NO_UNIQUE_ADDRESS
# if FMT_CPLUSPLUS >= 202002L # if FMT_CPLUSPLUS >= 202002L
# if FMT_HAS_CPP_ATTRIBUTE(no_unique_address) # if FMT_HAS_CPP_ATTRIBUTE(no_unique_address)
@@ -2096,30 +2085,17 @@ auto write_int(OutputIt out, UInt value, unsigned prefix,
FMT_ASSERT(false, ""); FMT_ASSERT(false, "");
FMT_FALLTHROUGH; FMT_FALLTHROUGH;
case presentation_type::none: case presentation_type::none:
case presentation_type::dec: { case presentation_type::dec:
num_digits = count_digits(value); num_digits = count_digits(value);
format_decimal<char>(appender(buffer), value, num_digits); format_decimal<char>(appender(buffer), value, num_digits);
break; break;
} case presentation_type::hex:
case presentation_type::hex_lower:
case presentation_type::hex_upper: {
bool upper = specs.type == presentation_type::hex_upper;
if (specs.alt) if (specs.alt)
prefix_append(prefix, unsigned(upper ? 'X' : 'x') << 8 | '0'); prefix_append(prefix, unsigned(specs.upper ? 'X' : 'x') << 8 | '0');
num_digits = count_digits<4>(value); num_digits = count_digits<4>(value);
format_uint<4, char>(appender(buffer), value, num_digits, upper); format_uint<4, char>(appender(buffer), value, num_digits, specs.upper);
break; break;
} case presentation_type::oct:
case presentation_type::bin_lower:
case presentation_type::bin_upper: {
bool upper = specs.type == presentation_type::bin_upper;
if (specs.alt)
prefix_append(prefix, unsigned(upper ? 'B' : 'b') << 8 | '0');
num_digits = count_digits<1>(value);
format_uint<1, char>(appender(buffer), value, num_digits);
break;
}
case presentation_type::oct: {
num_digits = count_digits<3>(value); num_digits = count_digits<3>(value);
// Octal prefix '0' is counted as a digit, so only add it if precision // Octal prefix '0' is counted as a digit, so only add it if precision
// is not greater than the number of digits. // is not greater than the number of digits.
@@ -2127,7 +2103,12 @@ auto write_int(OutputIt out, UInt value, unsigned prefix,
prefix_append(prefix, '0'); prefix_append(prefix, '0');
format_uint<3, char>(appender(buffer), value, num_digits); format_uint<3, char>(appender(buffer), value, num_digits);
break; break;
} case presentation_type::bin:
if (specs.alt)
prefix_append(prefix, unsigned(specs.upper ? 'B' : 'b') << 8 | '0');
num_digits = count_digits<1>(value);
format_uint<1, char>(appender(buffer), value, num_digits);
break;
case presentation_type::chr: case presentation_type::chr:
return write_char(out, static_cast<Char>(value), specs); return write_char(out, static_cast<Char>(value), specs);
} }
@@ -2206,34 +2187,21 @@ FMT_CONSTEXPR FMT_INLINE auto write_int(OutputIt out, write_int_arg<T> arg,
FMT_FALLTHROUGH; FMT_FALLTHROUGH;
case presentation_type::none: case presentation_type::none:
case presentation_type::dec: { case presentation_type::dec: {
auto num_digits = count_digits(abs_value); int num_digits = count_digits(abs_value);
return write_int( return write_int(
out, num_digits, prefix, specs, [=](reserve_iterator<OutputIt> it) { out, num_digits, prefix, specs, [=](reserve_iterator<OutputIt> it) {
return format_decimal<Char>(it, abs_value, num_digits).end; return format_decimal<Char>(it, abs_value, num_digits).end;
}); });
} }
case presentation_type::hex_lower: case presentation_type::hex: {
case presentation_type::hex_upper: {
bool upper = specs.type == presentation_type::hex_upper;
if (specs.alt) if (specs.alt)
prefix_append(prefix, unsigned(upper ? 'X' : 'x') << 8 | '0'); prefix_append(prefix, unsigned(specs.upper ? 'X' : 'x') << 8 | '0');
int num_digits = count_digits<4>(abs_value); int num_digits = count_digits<4>(abs_value);
return write_int( return write_int(
out, num_digits, prefix, specs, [=](reserve_iterator<OutputIt> it) { out, num_digits, prefix, specs, [=](reserve_iterator<OutputIt> it) {
return format_uint<4, Char>(it, abs_value, num_digits, upper); return format_uint<4, Char>(it, abs_value, num_digits, specs.upper);
}); });
} }
case presentation_type::bin_lower:
case presentation_type::bin_upper: {
bool upper = specs.type == presentation_type::bin_upper;
if (specs.alt)
prefix_append(prefix, unsigned(upper ? 'B' : 'b') << 8 | '0');
int num_digits = count_digits<1>(abs_value);
return write_int(out, num_digits, prefix, specs,
[=](reserve_iterator<OutputIt> it) {
return format_uint<1, Char>(it, abs_value, num_digits);
});
}
case presentation_type::oct: { case presentation_type::oct: {
int num_digits = count_digits<3>(abs_value); int num_digits = count_digits<3>(abs_value);
// Octal prefix '0' is counted as a digit, so only add it if precision // Octal prefix '0' is counted as a digit, so only add it if precision
@@ -2245,6 +2213,15 @@ FMT_CONSTEXPR FMT_INLINE auto write_int(OutputIt out, write_int_arg<T> arg,
return format_uint<3, Char>(it, abs_value, num_digits); return format_uint<3, Char>(it, abs_value, num_digits);
}); });
} }
case presentation_type::bin: {
if (specs.alt)
prefix_append(prefix, unsigned(specs.upper ? 'B' : 'b') << 8 | '0');
int num_digits = count_digits<1>(abs_value);
return write_int(out, num_digits, prefix, specs,
[=](reserve_iterator<OutputIt> it) {
return format_uint<1, Char>(it, abs_value, num_digits);
});
}
case presentation_type::chr: case presentation_type::chr:
return write_char(out, static_cast<Char>(abs_value), specs); return write_char(out, static_cast<Char>(abs_value), specs);
} }
@@ -2456,31 +2433,23 @@ FMT_CONSTEXPR auto parse_float_type_spec(const format_specs<Char>& specs)
case presentation_type::none: case presentation_type::none:
result.format = float_format::general; result.format = float_format::general;
break; break;
case presentation_type::general_upper: case presentation_type::exp:
result.upper = true; result.format = float_format::exp;
FMT_FALLTHROUGH; result.upper = specs.upper;
case presentation_type::general_lower: result.showpoint |= specs.precision != 0;
break;
case presentation_type::fixed:
result.format = float_format::fixed;
result.upper = specs.upper;
result.showpoint |= specs.precision != 0;
break;
case presentation_type::general:
result.upper = specs.upper;
result.format = float_format::general; result.format = float_format::general;
break; break;
case presentation_type::exp_upper: case presentation_type::hexfloat:
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; result.format = float_format::hex;
result.upper = specs.upper;
break; break;
} }
return result; return result;

View File

@@ -373,7 +373,7 @@ auto parse_header(const Char*& it, const Char* end, format_specs<Char>& specs,
return arg_index; return arg_index;
} }
inline auto parse_printf_presentation_type(char c, type t) inline auto parse_printf_presentation_type(char c, type t, bool& upper)
-> presentation_type { -> presentation_type {
using pt = presentation_type; using pt = presentation_type;
constexpr auto integral_set = sint_set | uint_set | bool_set | char_set; constexpr auto integral_set = sint_set | uint_set | bool_set | char_set;
@@ -382,26 +382,31 @@ inline auto parse_printf_presentation_type(char c, type t)
return in(t, integral_set) ? pt::dec : pt::none; return in(t, integral_set) ? pt::dec : pt::none;
case 'o': case 'o':
return in(t, integral_set) ? pt::oct : pt::none; return in(t, integral_set) ? pt::oct : pt::none;
case 'x':
return in(t, integral_set) ? pt::hex_lower : pt::none;
case 'X': case 'X':
return in(t, integral_set) ? pt::hex_upper : pt::none; upper = true;
case 'a': FMT_FALLTHROUGH;
return in(t, float_set) ? pt::hexfloat_lower : pt::none; case 'x':
case 'A': return in(t, integral_set) ? pt::hex : pt::none;
return in(t, float_set) ? pt::hexfloat_upper : pt::none;
case 'e':
return in(t, float_set) ? pt::exp_lower : pt::none;
case 'E': case 'E':
return in(t, float_set) ? pt::exp_upper : pt::none; upper = true;
case 'f': FMT_FALLTHROUGH;
return in(t, float_set) ? pt::fixed_lower : pt::none; case 'e':
return in(t, float_set) ? pt::exp : pt::none;
case 'F': case 'F':
return in(t, float_set) ? pt::fixed_upper : pt::none; upper = true;
case 'g': FMT_FALLTHROUGH;
return in(t, float_set) ? pt::general_lower : pt::none; case 'f':
return in(t, float_set) ? pt::fixed : pt::none;
case 'G': case 'G':
return in(t, float_set) ? pt::general_upper : pt::none; upper = true;
FMT_FALLTHROUGH;
case 'g':
return in(t, float_set) ? pt::general : pt::none;
case 'A':
upper = true;
FMT_FALLTHROUGH;
case 'a':
return in(t, float_set) ? pt::hexfloat : pt::none;
case 'c': case 'c':
return in(t, integral_set) ? pt::chr : pt::none; return in(t, integral_set) ? pt::chr : pt::none;
case 's': case 's':
@@ -548,9 +553,11 @@ void vprintf(buffer<Char>& buf, basic_string_view<Char> format,
break; break;
} }
} }
specs.type = parse_printf_presentation_type(type, arg.type()); bool upper = false;
specs.type = parse_printf_presentation_type(type, arg.type(), upper);
if (specs.type == presentation_type::none) if (specs.type == presentation_type::none)
throw_format_error("invalid format specifier"); throw_format_error("invalid format specifier");
specs.upper = upper;
start = it; start = it;

View File

@@ -516,7 +516,7 @@ TEST(core_test, constexpr_parse_format_specs) {
static_assert(parse_test_specs(".42").precision == 42, ""); static_assert(parse_test_specs(".42").precision == 42, "");
static_assert(parse_test_specs(".{42}").precision_ref.val.index == 42, ""); static_assert(parse_test_specs(".{42}").precision_ref.val.index == 42, "");
static_assert( static_assert(
parse_test_specs("f").type == fmt::presentation_type::fixed_lower, ""); parse_test_specs("f").type == fmt::presentation_type::fixed, "");
} }
struct test_format_string_handler { struct test_format_string_handler {

View File

@@ -439,7 +439,7 @@ const char* parse_scan_specs(const char* begin, const char* end,
switch (to_ascii(*begin)) { switch (to_ascii(*begin)) {
// TODO: parse more scan format specifiers // TODO: parse more scan format specifiers
case 'x': case 'x':
specs.type = presentation_type::hex_lower; specs.type = presentation_type::hex;
++begin; ++begin;
break; break;
case '}': case '}':
@@ -508,7 +508,7 @@ auto read_hex(scan_iterator it, T& value) -> scan_iterator {
template <typename T, FMT_ENABLE_IF(std::is_unsigned<T>::value)> template <typename T, FMT_ENABLE_IF(std::is_unsigned<T>::value)>
auto read(scan_iterator it, T& value, const format_specs<>& specs) auto read(scan_iterator it, T& value, const format_specs<>& specs)
-> scan_iterator { -> scan_iterator {
if (specs.type == presentation_type::hex_lower) return read_hex(it, value); if (specs.type == presentation_type::hex) return read_hex(it, value);
return read(it, value); return read(it, value);
} }

View File

@@ -584,7 +584,7 @@ template <class charT> struct formatter<std::complex<double>, charT> {
specs.precision, specs.precision_ref, ctx); specs.precision, specs.precision_ref, ctx);
auto fspecs = std::string(); auto fspecs = std::string();
if (specs.precision > 0) fspecs = fmt::format(".{}", specs.precision); if (specs.precision > 0) fspecs = fmt::format(".{}", specs.precision);
if (specs.type == presentation_type::fixed_lower) fspecs += 'f'; if (specs.type == presentation_type::fixed) fspecs += 'f';
auto real = fmt::format(ctx.locale().template get<std::locale>(), auto real = fmt::format(ctx.locale().template get<std::locale>(),
fmt::runtime("{:" + fspecs + "}"), c.real()); fmt::runtime("{:" + fspecs + "}"), c.real());
auto imag = fmt::format(ctx.locale().template get<std::locale>(), auto imag = fmt::format(ctx.locale().template get<std::locale>(),