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

View File

@ -71,17 +71,6 @@
# define FMT_INLINE_VARIABLE
#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
# if FMT_CPLUSPLUS >= 202002L
# 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_FALLTHROUGH;
case presentation_type::none:
case presentation_type::dec: {
case presentation_type::dec:
num_digits = count_digits(value);
format_decimal<char>(appender(buffer), value, num_digits);
break;
}
case presentation_type::hex_lower:
case presentation_type::hex_upper: {
bool upper = specs.type == presentation_type::hex_upper;
case presentation_type::hex:
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);
format_uint<4, char>(appender(buffer), value, num_digits, upper);
format_uint<4, char>(appender(buffer), value, num_digits, specs.upper);
break;
}
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: {
case presentation_type::oct:
num_digits = count_digits<3>(value);
// Octal prefix '0' is counted as a digit, so only add it if precision
// 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');
format_uint<3, char>(appender(buffer), value, num_digits);
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:
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;
case presentation_type::none:
case presentation_type::dec: {
auto num_digits = count_digits(abs_value);
int num_digits = count_digits(abs_value);
return write_int(
out, num_digits, prefix, specs, [=](reserve_iterator<OutputIt> it) {
return format_decimal<Char>(it, abs_value, num_digits).end;
});
}
case presentation_type::hex_lower:
case presentation_type::hex_upper: {
bool upper = specs.type == presentation_type::hex_upper;
case presentation_type::hex: {
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);
return write_int(
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: {
int num_digits = count_digits<3>(abs_value);
// 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);
});
}
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:
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:
result.format = float_format::general;
break;
case presentation_type::general_upper:
result.upper = true;
FMT_FALLTHROUGH;
case presentation_type::general_lower:
case presentation_type::exp:
result.format = float_format::exp;
result.upper = specs.upper;
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;
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:
case presentation_type::hexfloat:
result.format = float_format::hex;
result.upper = specs.upper;
break;
}
return result;

View File

@ -373,7 +373,7 @@ auto parse_header(const Char*& it, const Char* end, format_specs<Char>& specs,
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 {
using pt = presentation_type;
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;
case 'o':
return in(t, integral_set) ? pt::oct : pt::none;
case 'x':
return in(t, integral_set) ? pt::hex_lower : pt::none;
case 'X':
return in(t, integral_set) ? pt::hex_upper : pt::none;
case 'a':
return in(t, float_set) ? pt::hexfloat_lower : pt::none;
case 'A':
return in(t, float_set) ? pt::hexfloat_upper : pt::none;
case 'e':
return in(t, float_set) ? pt::exp_lower : pt::none;
upper = true;
FMT_FALLTHROUGH;
case 'x':
return in(t, integral_set) ? pt::hex : pt::none;
case 'E':
return in(t, float_set) ? pt::exp_upper : pt::none;
case 'f':
return in(t, float_set) ? pt::fixed_lower : pt::none;
upper = true;
FMT_FALLTHROUGH;
case 'e':
return in(t, float_set) ? pt::exp : pt::none;
case 'F':
return in(t, float_set) ? pt::fixed_upper : pt::none;
case 'g':
return in(t, float_set) ? pt::general_lower : pt::none;
upper = true;
FMT_FALLTHROUGH;
case 'f':
return in(t, float_set) ? pt::fixed : pt::none;
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':
return in(t, integral_set) ? pt::chr : pt::none;
case 's':
@ -548,9 +553,11 @@ void vprintf(buffer<Char>& buf, basic_string_view<Char> format,
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)
throw_format_error("invalid format specifier");
specs.upper = upper;
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_ref.val.index == 42, "");
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 {

View File

@ -439,7 +439,7 @@ const char* parse_scan_specs(const char* begin, const char* end,
switch (to_ascii(*begin)) {
// TODO: parse more scan format specifiers
case 'x':
specs.type = presentation_type::hex_lower;
specs.type = presentation_type::hex;
++begin;
break;
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)>
auto read(scan_iterator it, T& value, const format_specs<>& specs)
-> 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);
}

View File

@ -584,7 +584,7 @@ template <class charT> struct formatter<std::complex<double>, charT> {
specs.precision, specs.precision_ref, ctx);
auto fspecs = std::string();
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>(),
fmt::runtime("{:" + fspecs + "}"), c.real());
auto imag = fmt::format(ctx.locale().template get<std::locale>(),