diff --git a/include/fmt/base.h b/include/fmt/base.h index e7446fc7..65eebab8 100644 --- a/include/fmt/base.h +++ b/include/fmt/base.h @@ -2092,51 +2092,6 @@ using unsigned_char = typename conditional_t::value, std::make_unsigned, type_identity>::type; -// Character (code unit) type is erased to prevent template bloat. -struct fill_t { - private: - enum { max_size = 4 }; - char data_[max_size] = {' '}; - unsigned char size_ = 1; - - public: - template - FMT_CONSTEXPR void operator=(basic_string_view s) { - auto size = s.size(); - size_ = static_cast(size); - if (size == 1) { - unsigned uchar = static_cast>(s[0]); - data_[0] = static_cast(uchar); - data_[1] = static_cast(uchar >> 8); - return; - } - FMT_ASSERT(size <= max_size, "invalid fill"); - for (size_t i = 0; i < size; ++i) data_[i] = static_cast(s[i]); - } - - FMT_CONSTEXPR void operator=(char c) { - data_[0] = c; - size_ = 1; - } - - constexpr auto size() const -> size_t { return size_; } - - template constexpr auto get() const -> Char { - using uchar = unsigned char; - return static_cast(static_cast(data_[0]) | - (static_cast(data_[1]) << 8)); - } - - template ::value)> - constexpr auto data() const -> const Char* { - return data_; - } - template ::value)> - constexpr auto data() const -> const Char* { - return nullptr; - } -}; - enum class arg_id_kind { none, index, name }; } // namespace detail @@ -2164,39 +2119,156 @@ enum class presentation_type : unsigned char { hexfloat // 'a' or 'A' }; +// Basic format specifiers for built-in and string types. +class basic_specs { + private: + // Upper 32-bit of data contain fill and lower 32-bit are arranged as follows: + // + // 0 1 2 3 + // 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + // |type |align| w | p | s |u|#|L| f | unused | + // +-----+-----+---+---+---+-+-+-+-----+---------------------------+ + // + // w - dynamic width info + // p - dynamic precision info + // s - sign + // u - uppercase (e.g. 'X' for 'x') + // # - alternate form ('#') + // L - localized + // f - fill size + // + // Bitfields are not used because of compiler bugs such as gcc bug 61414. + enum : unsigned { + type_mask = 0x00007, + align_mask = 0x00038, + width_mask = 0x000C0, + precision_mask = 0x00300, + sign_mask = 0x00C00, + uppercase_mask = 0x01000, + alternate_mask = 0x02000, + localized_mask = 0x04000, + fill_size_mask = 0x38000, + + align_shift = 3, + width_shift = 6, + precision_shift = 8, + sign_shift = 10, + fill_size_shift = 15, + + max_fill_size = 4 + }; + + unsigned long long data_ = 1 << fill_size_shift; + + // Character (code unit) type is erased to prevent template bloat. + char fill_data_[max_fill_size] = {' '}; + + FMT_CONSTEXPR void set_fill_size(size_t size) { + data_ = (data_ & ~fill_size_mask) | (size << fill_size_shift); + } + + public: + constexpr auto type() const -> presentation_type { + return static_cast(data_ & type_mask); + } + FMT_CONSTEXPR void set_type(presentation_type t) { + data_ = (data_ & ~type_mask) | static_cast(t); + } + + constexpr auto align() const -> align_t { + return static_cast((data_ & align_mask) >> align_shift); + } + FMT_CONSTEXPR void set_align(align_t a) { + data_ = (data_ & ~align_mask) | (static_cast(a) << align_shift); + } + + constexpr auto dynamic_width() const -> detail::arg_id_kind { + return static_cast((data_ & width_mask) >> + width_shift); + } + FMT_CONSTEXPR void set_dynamic_width(detail::arg_id_kind w) { + data_ = (data_ & ~width_mask) | (static_cast(w) << width_shift); + } + + FMT_CONSTEXPR auto dynamic_precision() const -> detail::arg_id_kind { + return static_cast((data_ & precision_mask) >> + precision_shift); + } + FMT_CONSTEXPR void set_dynamic_precision(detail::arg_id_kind p) { + data_ = (data_ & ~precision_mask) | + (static_cast(p) << precision_shift); + } + + constexpr bool dynamic() const { + return (data_ & (width_mask | precision_mask)) != 0; + } + + constexpr auto sign() const -> sign_t { + return static_cast((data_ & sign_mask) >> sign_shift); + } + FMT_CONSTEXPR void set_sign(sign_t a) { + data_ = (data_ & ~sign_mask) | (static_cast(a) << sign_shift); + } + + constexpr auto upper() const -> bool { return (data_ & uppercase_mask) != 0; } + FMT_CONSTEXPR void set_upper() { data_ |= uppercase_mask; } + + constexpr auto alt() const -> bool { return (data_ & alternate_mask) != 0; } + FMT_CONSTEXPR void set_alt() { data_ |= alternate_mask; } + FMT_CONSTEXPR void clear_alt() { data_ &= ~alternate_mask; } + + constexpr auto localized() const -> bool { + return (data_ & localized_mask) != 0; + } + FMT_CONSTEXPR void set_localized() { data_ |= localized_mask; } + + constexpr auto fill_size() const -> size_t { + return (data_ & fill_size_mask) >> fill_size_shift; + } + + template ::value)> + constexpr auto fill() const -> const Char* { + return fill_data_; + } + template ::value)> + constexpr auto fill() const -> const Char* { + return nullptr; + } + + template constexpr auto fill_unit() const -> Char { + using uchar = unsigned char; + return static_cast(static_cast(fill_data_[0]) | + (static_cast(fill_data_[1]) << 8)); + } + + FMT_CONSTEXPR void set_fill(char c) { + fill_data_[0] = c; + set_fill_size(1); + } + + template + FMT_CONSTEXPR void set_fill(basic_string_view s) { + auto size = s.size(); + set_fill_size(size); + if (size == 1) { + unsigned uchar = static_cast>(s[0]); + fill_data_[0] = static_cast(uchar); + fill_data_[1] = static_cast(uchar >> 8); + return; + } + FMT_ASSERT(size <= max_fill_size, "invalid fill"); + for (size_t i = 0; i < size; ++i) + fill_data_[i & 3] = static_cast(s[i]); + } +}; + // Format specifiers for built-in and string types. -struct format_specs { +struct format_specs : basic_specs { int width; int precision; - presentation_type type; - align_t align : 4; - sign_t sign : 3; - unsigned char dynamic : 4; - bool upper : 1; // An uppercase version e.g. 'X' for 'x'. - bool alt : 1; // Alternate form ('#'). - bool localized : 1; - detail::fill_t fill; - constexpr format_specs() - : width(0), - precision(-1), - type(presentation_type::none), - align(align::none), - sign(sign::none), - dynamic(0), - upper(false), - alt(false), - localized(false) {} - - FMT_CONSTEXPR auto dynamic_width() const -> detail::arg_id_kind { - enum { dynamic_width_mask = 3 }; - return static_cast(dynamic & dynamic_width_mask); - } - FMT_CONSTEXPR auto dynamic_precision() const -> detail::arg_id_kind { - enum { dynamic_precision_mask = 12 }; - return static_cast( - (dynamic & dynamic_precision_mask) >> 2); - } + constexpr format_specs() : width(0), precision(-1) {} }; namespace detail { @@ -2381,7 +2453,7 @@ FMT_CONSTEXPR auto parse_width(const Char* begin, const Char* end, basic_format_parse_context& ctx) -> const Char* { auto result = parse_dynamic_spec(begin, end, specs.width, width_ref, ctx); - specs.dynamic = static_cast(result.kind) & 0x3u; + specs.set_dynamic_width(result.kind); return result.end; } @@ -2398,9 +2470,7 @@ FMT_CONSTEXPR auto parse_precision(const Char* begin, const Char* end, } auto result = parse_dynamic_spec(begin, end, specs.precision, precision_ref, ctx); - auto kind_val = static_cast(result.kind); - specs.dynamic = - static_cast(specs.dynamic | (kind_val << 2)) & 0xfu; + specs.set_dynamic_precision(result.kind); return result.end; } @@ -2439,7 +2509,7 @@ FMT_CONSTEXPR auto parse_format_specs(const Char* begin, const Char* end, FMT_CONSTEXPR auto operator()(pres pres_type, int set) -> const Char* { if (!in(arg_type, set)) report_error("invalid format specifier"); - specs.type = pres_type; + specs.set_type(pres_type); return begin + 1; } } parse_presentation_type{begin, specs, arg_type}; @@ -2450,13 +2520,13 @@ FMT_CONSTEXPR auto parse_format_specs(const Char* begin, const Char* end, case '>': case '^': enter_state(state::align); - specs.align = parse_align(c); + specs.set_align(parse_align(c)); ++begin; break; case '+': FMT_FALLTHROUGH; case ' ': - specs.sign = c == ' ' ? sign::space : sign::plus; + specs.set_sign(c == ' ' ? sign::space : sign::plus); FMT_FALLTHROUGH; case '-': enter_state(state::sign, in(arg_type, sint_set | float_set)); @@ -2464,17 +2534,17 @@ FMT_CONSTEXPR auto parse_format_specs(const Char* begin, const Char* end, break; case '#': enter_state(state::hash, is_arithmetic_type(arg_type)); - specs.alt = true; + specs.set_alt(); ++begin; break; case '0': enter_state(state::zero); if (!is_arithmetic_type(arg_type)) report_error("format specifier requires numeric argument"); - if (specs.align == align::none) { + if (specs.align() == align::none) { // Ignore 0 if align is specified for compatibility with std::format. - specs.align = align::numeric; - specs.fill = '0'; + specs.set_align(align::numeric); + specs.set_fill('0'); } ++begin; break; @@ -2498,40 +2568,40 @@ FMT_CONSTEXPR auto parse_format_specs(const Char* begin, const Char* end, break; case 'L': enter_state(state::locale, is_arithmetic_type(arg_type)); - specs.localized = true; + specs.set_localized(); ++begin; break; case 'd': return parse_presentation_type(pres::dec, integral_set); case 'X': - specs.upper = true; + specs.set_upper(); FMT_FALLTHROUGH; case 'x': return parse_presentation_type(pres::hex, integral_set); case 'o': return parse_presentation_type(pres::oct, integral_set); case 'B': - specs.upper = true; + specs.set_upper(); FMT_FALLTHROUGH; case 'b': return parse_presentation_type(pres::bin, integral_set); case 'E': - specs.upper = true; + specs.set_upper(); FMT_FALLTHROUGH; case 'e': return parse_presentation_type(pres::exp, float_set); case 'F': - specs.upper = true; + specs.set_upper(); FMT_FALLTHROUGH; case 'f': return parse_presentation_type(pres::fixed, float_set); case 'G': - specs.upper = true; + specs.set_upper(); FMT_FALLTHROUGH; case 'g': return parse_presentation_type(pres::general, float_set); case 'A': - specs.upper = true; + specs.set_upper(); FMT_FALLTHROUGH; case 'a': return parse_presentation_type(pres::hexfloat, float_set); @@ -2562,9 +2632,9 @@ FMT_CONSTEXPR auto parse_format_specs(const Char* begin, const Char* end, } auto align = parse_align(to_ascii(*fill_end)); enter_state(state::align, align != align::none); - specs.fill = - basic_string_view(begin, to_unsigned(fill_end - begin)); - specs.align = align; + specs.set_fill( + basic_string_view(begin, to_unsigned(fill_end - begin))); + specs.set_align(align); begin = fill_end + 1; } } @@ -2706,13 +2776,15 @@ FMT_CONSTEXPR auto parse_format_specs(ParseContext& ctx) // Checks char specs and returns true iff the presentation type is char-like. FMT_CONSTEXPR inline auto check_char_specs(const format_specs& specs) -> bool { - if (specs.type != presentation_type::none && - specs.type != presentation_type::chr && - specs.type != presentation_type::debug) { + auto type = specs.type(); + if (type != presentation_type::none && type != presentation_type::chr && + type != presentation_type::debug) { return false; } - if (specs.align == align::numeric || specs.sign != sign::none || specs.alt) + if (specs.align() == align::numeric || specs.sign() != sign::none || + specs.alt()) { report_error("invalid format specifier for char"); + } return true; } @@ -2861,7 +2933,7 @@ template struct native_formatter { FMT_ENABLE_IF(U == type::string_type || U == type::cstring_type || U == type::char_type)> FMT_CONSTEXPR void set_debug_format(bool set = true) { - specs_.type = set ? presentation_type::debug : presentation_type::none; + specs_.set_type(set ? presentation_type::debug : presentation_type::none); } template diff --git a/include/fmt/chrono.h b/include/fmt/chrono.h index 499bf91f..404cf40c 100644 --- a/include/fmt/chrono.h +++ b/include/fmt/chrono.h @@ -1732,8 +1732,8 @@ template OutputIt { auto specs = format_specs(); specs.precision = precision; - specs.type = - precision >= 0 ? presentation_type::fixed : presentation_type::general; + specs.set_type(precision >= 0 ? presentation_type::fixed + : presentation_type::general); return write(out, val, specs); } diff --git a/include/fmt/format.h b/include/fmt/format.h index 72d0763a..1641d4c0 100644 --- a/include/fmt/format.h +++ b/include/fmt/format.h @@ -1718,11 +1718,11 @@ constexpr auto convert_float(T value) -> convert_float_result { } template -FMT_NOINLINE FMT_CONSTEXPR auto fill(OutputIt it, size_t n, const fill_t& fill) - -> OutputIt { - auto fill_size = fill.size(); - if (fill_size == 1) return detail::fill_n(it, n, fill.template get()); - if (const Char* data = fill.template data()) { +FMT_NOINLINE FMT_CONSTEXPR auto fill(OutputIt it, size_t n, + const basic_specs& specs) -> OutputIt { + auto fill_size = specs.fill_size(); + if (fill_size == 1) return detail::fill_n(it, n, specs.fill_unit()); + if (const Char* data = specs.fill()) { for (size_t i = 0; i < n; ++i) it = copy(data, data + fill_size, it); } return it; @@ -1741,12 +1741,12 @@ FMT_CONSTEXPR auto write_padded(OutputIt out, const format_specs& specs, // Shifts are encoded as string literals because static constexpr is not // supported in constexpr functions. auto* shifts = align == align::left ? "\x1f\x1f\x00\x01" : "\x00\x1f\x00\x01"; - size_t left_padding = padding >> shifts[specs.align]; + size_t left_padding = padding >> shifts[specs.align()]; size_t right_padding = padding - left_padding; - auto it = reserve(out, size + padding * specs.fill.size()); - if (left_padding != 0) it = fill(it, left_padding, specs.fill); + auto it = reserve(out, size + padding * specs.fill_size()); + if (left_padding != 0) it = fill(it, left_padding, specs); it = f(it); - if (right_padding != 0) it = fill(it, right_padding, specs.fill); + if (right_padding != 0) it = fill(it, right_padding, specs); return base_iterator(out, it); } @@ -1931,7 +1931,7 @@ auto write_escaped_char(OutputIt out, Char v) -> OutputIt { template FMT_CONSTEXPR auto write_char(OutputIt out, Char value, const format_specs& specs) -> OutputIt { - bool is_debug = specs.type == presentation_type::debug; + 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); *it++ = value; @@ -1958,7 +1958,7 @@ template struct write_int_data { FMT_CONSTEXPR write_int_data(int num_digits, unsigned prefix, const format_specs& specs) : size((prefix >> 24) + to_unsigned(num_digits)), padding(0) { - if (specs.align == align::numeric) { + if (specs.align() == align::numeric) { auto width = to_unsigned(specs.width); if (width > size) { padding = width - size; @@ -2076,7 +2076,7 @@ auto write_int(OutputIt out, UInt value, unsigned prefix, static_assert(std::is_same, UInt>::value, ""); int num_digits = 0; auto buffer = memory_buffer(); - switch (specs.type) { + switch (specs.type()) { default: FMT_ASSERT(false, ""); FMT_FALLTHROUGH; @@ -2086,22 +2086,22 @@ auto write_int(OutputIt out, UInt value, unsigned prefix, format_decimal(appender(buffer), value, num_digits); break; case presentation_type::hex: - if (specs.alt) - prefix_append(prefix, unsigned(specs.upper ? 'X' : 'x') << 8 | '0'); + if (specs.alt()) + 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, specs.upper); + format_uint<4, char>(appender(buffer), value, num_digits, specs.upper()); break; 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. - if (specs.alt && specs.precision <= num_digits && value != 0) + if (specs.alt() && specs.precision <= num_digits && value != 0) 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'); + 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; @@ -2158,7 +2158,7 @@ template struct loc_writer { template ::value)> auto operator()(T value) -> bool { - auto arg = make_write_int_arg(value, specs.sign); + 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; @@ -2177,7 +2177,7 @@ FMT_CONSTEXPR FMT_INLINE auto write_int(OutputIt out, write_int_arg arg, static_assert(std::is_same>::value, ""); auto abs_value = arg.abs_value; auto prefix = arg.prefix; - switch (specs.type) { + switch (specs.type()) { default: FMT_ASSERT(false, ""); FMT_FALLTHROUGH; @@ -2190,19 +2190,19 @@ FMT_CONSTEXPR FMT_INLINE auto write_int(OutputIt out, write_int_arg arg, }); } case presentation_type::hex: { - if (specs.alt) - prefix_append(prefix, unsigned(specs.upper ? 'X' : 'x') << 8 | '0'); + if (specs.alt()) + 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 it) { - return format_uint<4, Char>(it, abs_value, num_digits, specs.upper); + return format_uint<4, Char>(it, abs_value, num_digits, specs.upper()); }); } 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 // is not greater than the number of digits. - if (specs.alt && specs.precision <= num_digits && abs_value != 0) + if (specs.alt() && specs.precision <= num_digits && abs_value != 0) prefix_append(prefix, '0'); return write_int( out, num_digits, prefix, specs, [=](reserve_iterator it) { @@ -2210,8 +2210,8 @@ FMT_CONSTEXPR FMT_INLINE auto write_int(OutputIt out, write_int_arg arg, }); } case presentation_type::bin: { - if (specs.alt) - prefix_append(prefix, unsigned(specs.upper ? 'B' : 'b') << 8 | '0'); + 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 it) { @@ -2236,8 +2236,8 @@ template out, T value, const format_specs& specs, locale_ref loc) -> basic_appender { - if (specs.localized && write_loc(out, value, specs, loc)) return out; - return write_int_noinline(out, make_write_int_arg(value, specs.sign), + 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); } // An inlined version of write used in format string compilation. @@ -2249,8 +2249,8 @@ template OutputIt { - if (specs.localized && write_loc(out, value, specs, loc)) return out; - return write_int(out, make_write_int_arg(value, specs.sign), specs, + if (specs.localized() && write_loc(out, value, specs, loc)) return out; + return write_int(out, make_write_int_arg(value, specs.sign()), specs, loc); } @@ -2261,7 +2261,7 @@ FMT_CONSTEXPR auto write(OutputIt out, basic_string_view s, auto size = s.size(); if (specs.precision >= 0 && to_unsigned(specs.precision) < size) size = code_point_index(s, to_unsigned(specs.precision)); - bool is_debug = specs.type == presentation_type::debug; + bool is_debug = specs.type() == presentation_type::debug; size_t width = 0; if (is_debug) { @@ -2291,7 +2291,7 @@ FMT_CONSTEXPR auto write(OutputIt out, template FMT_CONSTEXPR auto write(OutputIt out, const Char* s, const format_specs& specs, locale_ref) -> OutputIt { - if (specs.type == presentation_type::pointer) + if (specs.type() == presentation_type::pointer) return write_ptr(out, bit_cast(s), &specs); if (!s) report_error("string pointer is null"); return write(out, basic_string_view(s), specs, {}); @@ -2344,7 +2344,7 @@ FMT_CONSTEXPR auto parse_align(const Char* begin, const Char* end, report_error("invalid fill character '{'"); return begin; } - specs.fill = basic_string_view(begin, to_unsigned(p - begin)); + specs.set_fill(basic_string_view(begin, to_unsigned(p - begin))); begin = p + 1; } else { ++begin; @@ -2355,7 +2355,7 @@ FMT_CONSTEXPR auto parse_align(const Char* begin, const Char* end, } p = begin; } - specs.align = align; + specs.set_align(align); return begin; } @@ -2364,13 +2364,13 @@ FMT_CONSTEXPR20 auto write_nonfinite(OutputIt out, bool isnan, format_specs specs, sign_t sign) -> OutputIt { auto str = - isnan ? (specs.upper ? "NAN" : "nan") : (specs.upper ? "INF" : "inf"); + isnan ? (specs.upper() ? "NAN" : "nan") : (specs.upper() ? "INF" : "inf"); constexpr size_t str_size = 3; auto size = str_size + (sign ? 1 : 0); // Replace '0'-padding with space for non-finite values. const bool is_zero_fill = - specs.fill.size() == 1 && specs.fill.template get() == '0'; - if (is_zero_fill) specs.fill = ' '; + specs.fill_size() == 1 && specs.fill_unit() == '0'; + if (is_zero_fill) specs.set_fill(' '); return write_padded(out, specs, size, [=](reserve_iterator it) { if (sign) *it++ = detail::sign(sign); @@ -2492,13 +2492,13 @@ FMT_CONSTEXPR20 auto do_write_float(OutputIt out, const DecimalFP& f, size_t size = to_unsigned(significand_size) + (sign ? 1 : 0); using iterator = reserve_iterator; - Char decimal_point = specs.localized ? detail::decimal_point(loc) - : static_cast('.'); + Char decimal_point = specs.localized() ? detail::decimal_point(loc) + : static_cast('.'); int output_exp = f.exponent + significand_size - 1; auto use_exp_format = [=]() { - if (specs.type == presentation_type::exp) return true; - if (specs.type == presentation_type::fixed) return false; + if (specs.type() == presentation_type::exp) return true; + if (specs.type() == presentation_type::fixed) return false; // Use the fixed notation if the exponent is in [exp_lower, exp_upper), // e.g. 0.0001 instead of 1e-04. Otherwise use the exponent notation. const int exp_lower = -4, exp_upper = 16; @@ -2507,7 +2507,7 @@ FMT_CONSTEXPR20 auto do_write_float(OutputIt out, const DecimalFP& f, }; if (use_exp_format()) { int num_zeros = 0; - if (specs.alt) { + if (specs.alt()) { num_zeros = specs.precision - significand_size; if (num_zeros < 0) num_zeros = 0; size += to_unsigned(num_zeros); @@ -2519,7 +2519,7 @@ FMT_CONSTEXPR20 auto do_write_float(OutputIt out, const DecimalFP& f, if (abs_output_exp >= 100) exp_digits = abs_output_exp >= 1000 ? 4 : 3; size += to_unsigned((decimal_point ? 1 : 0) + 2 + exp_digits); - char exp_char = specs.upper ? 'E' : 'e'; + char exp_char = specs.upper() ? 'E' : 'e'; auto write = [=](iterator it) { if (sign) *it++ = detail::sign(sign); // Insert a decimal point after the first digit and add an exponent. @@ -2540,27 +2540,27 @@ FMT_CONSTEXPR20 auto do_write_float(OutputIt out, const DecimalFP& f, size += to_unsigned(f.exponent); int num_zeros = specs.precision - exp; abort_fuzzing_if(num_zeros > 5000); - if (specs.alt) { + if (specs.alt()) { ++size; - if (num_zeros <= 0 && specs.type != presentation_type::fixed) + if (num_zeros <= 0 && specs.type() != presentation_type::fixed) num_zeros = 0; if (num_zeros > 0) size += to_unsigned(num_zeros); } - auto grouping = Grouping(loc, specs.localized); + auto grouping = Grouping(loc, specs.localized()); size += to_unsigned(grouping.count_separators(exp)); return write_padded(out, specs, size, [&](iterator it) { if (sign) *it++ = detail::sign(sign); it = write_significand(it, significand, significand_size, f.exponent, grouping); - if (!specs.alt) return it; + if (!specs.alt()) return it; *it++ = decimal_point; return num_zeros > 0 ? detail::fill_n(it, num_zeros, zero) : it; }); } else if (exp > 0) { // 1234e-2 -> 12.34[0+] - int num_zeros = specs.alt ? specs.precision - significand_size : 0; + int num_zeros = specs.alt() ? specs.precision - significand_size : 0; size += 1 + to_unsigned(num_zeros > 0 ? num_zeros : 0); - auto grouping = Grouping(loc, specs.localized); + auto grouping = Grouping(loc, specs.localized()); size += to_unsigned(grouping.count_separators(exp)); return write_padded(out, specs, size, [&](iterator it) { if (sign) *it++ = detail::sign(sign); @@ -2575,7 +2575,7 @@ FMT_CONSTEXPR20 auto do_write_float(OutputIt out, const DecimalFP& f, specs.precision < num_zeros) { num_zeros = specs.precision; } - bool pointy = num_zeros != 0 || significand_size != 0 || specs.alt; + bool pointy = num_zeros != 0 || significand_size != 0 || specs.alt(); size += 1 + (pointy ? 1 : 0) + to_unsigned(num_zeros); return write_padded(out, specs, size, [&](iterator it) { if (sign) *it++ = detail::sign(sign); @@ -3116,20 +3116,20 @@ FMT_CONSTEXPR20 void format_hexfloat(Float value, format_specs specs, char xdigits[num_bits() / 4]; detail::fill_n(xdigits, sizeof(xdigits), '0'); - format_uint<4>(xdigits, f.f, num_xdigits, specs.upper); + 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(specs.upper() ? 'X' : 'x'); buf.push_back(xdigits[0]); - if (specs.alt || print_xdigits > 0 || print_xdigits < specs.precision) + if (specs.alt() || print_xdigits > 0 || print_xdigits < specs.precision) buf.push_back('.'); buf.append(xdigits + 1, xdigits + 1 + print_xdigits); for (; print_xdigits < specs.precision; ++print_xdigits) buf.push_back('0'); - buf.push_back(specs.upper ? 'P' : 'p'); + buf.push_back(specs.upper() ? 'P' : 'p'); uint32_t abs_e; if (f.e < 0) { @@ -3167,7 +3167,7 @@ FMT_CONSTEXPR20 auto format_float(Float value, int precision, static_assert(!std::is_same::value, ""); auto converted_value = convert_float(value); - const bool fixed = specs.type == presentation_type::fixed; + const bool fixed = specs.type() == presentation_type::fixed; if (value == 0) { if (precision <= 0 || !fixed) { buf.push_back('0'); @@ -3446,7 +3446,7 @@ FMT_CONSTEXPR20 auto format_float(Float value, int precision, if (precision > max_double_digits) precision = max_double_digits; format_dragon(f, dragon_flags, precision, buf, exp); } - if (!fixed && !specs.alt) { + if (!fixed && !specs.alt()) { // Remove trailing zeros. auto num_digits = buf.size(); while (num_digits > 0 && buf[num_digits - 1] == '0') { @@ -3462,12 +3462,12 @@ template FMT_CONSTEXPR20 auto write_float(OutputIt out, T value, format_specs specs, locale_ref loc) -> OutputIt { // Use signbit because value < 0 is false for NaN. - sign_t sign = detail::signbit(value) ? sign::minus : specs.sign; + sign_t sign = detail::signbit(value) ? sign::minus : specs.sign(); if (!detail::isfinite(value)) return write_nonfinite(out, detail::isnan(value), specs, sign); - if (specs.align == align::numeric && sign) { + if (specs.align() == align::numeric && sign) { *out++ = detail::sign(sign); sign = sign::none; if (specs.width != 0) --specs.width; @@ -3475,7 +3475,7 @@ FMT_CONSTEXPR20 auto write_float(OutputIt out, T value, format_specs specs, int precision = specs.precision; if (precision < 0) { - if (specs.type != presentation_type::none) { + if (specs.type() != presentation_type::none) { precision = 6; } else if (is_fast_float::value && !is_constant_evaluated()) { // Use Dragonbox for the shortest format. @@ -3486,21 +3486,21 @@ FMT_CONSTEXPR20 auto write_float(OutputIt out, T value, format_specs specs, } memory_buffer buffer; - if (specs.type == presentation_type::hexfloat) { + if (specs.type() == presentation_type::hexfloat) { if (sign) buffer.push_back(detail::sign(sign)); format_hexfloat(convert_float(value), specs, buffer); return write_bytes(out, {buffer.data(), buffer.size()}, specs); } - if (specs.type == presentation_type::exp) { + if (specs.type() == presentation_type::exp) { if (precision == max_value()) report_error("number is too big"); else ++precision; - specs.alt |= specs.precision != 0; - } else if (specs.type == presentation_type::fixed) { - specs.alt |= specs.precision != 0; + if (specs.precision != 0) specs.set_alt(); + } else if (specs.type() == presentation_type::fixed) { + if (specs.precision != 0) specs.set_alt(); } else if (precision == 0) { precision = 1; } @@ -3517,7 +3517,7 @@ template OutputIt { if (const_check(!is_supported_floating_point(value))) return out; - return specs.localized && write_loc(out, value, specs, loc) + return specs.localized() && write_loc(out, value, specs, loc) ? out : write_float(out, value, specs, loc); } @@ -3583,8 +3583,8 @@ template ::value)> FMT_CONSTEXPR auto write(OutputIt out, T value, const format_specs& specs = {}, locale_ref = {}) -> OutputIt { - return specs.type != presentation_type::none && - specs.type != presentation_type::string + return specs.type() != presentation_type::none && + specs.type() != presentation_type::string ? write(out, value ? 1 : 0, specs, {}) : write_bytes(out, value ? "true" : "false", specs); } @@ -4012,7 +4012,7 @@ template struct formatter> : formatter { specs.width_ref, ctx); detail::handle_dynamic_spec(specs.dynamic_precision(), specs.precision, specs.precision_ref, ctx); - auto arg = detail::make_write_int_arg(t.value, specs.sign); + auto arg = detail::make_write_int_arg(t.value, specs.sign()); return detail::write_int( ctx.out(), static_cast>(arg.abs_value), arg.prefix, specs, detail::digit_grouping("\3", ",")); @@ -4039,13 +4039,12 @@ struct formatter, Char> { template struct nested_formatter { private: + basic_specs specs_; int width_; - detail::fill_t fill_; - align_t align_ : 4; formatter formatter_; public: - constexpr nested_formatter() : width_(0), align_(align_t::none) {} + constexpr nested_formatter() : width_(0) {} FMT_CONSTEXPR auto parse(basic_format_parse_context& ctx) -> decltype(ctx.begin()) { @@ -4053,8 +4052,7 @@ template struct nested_formatter { if (it == end) return it; auto specs = format_specs(); it = detail::parse_align(it, end, specs); - fill_ = specs.fill; - align_ = specs.align; + specs_ = specs; Char c = *it; auto width_ref = detail::arg_ref(); if ((c >= '0' && c <= '9') || c == '{') { @@ -4072,8 +4070,9 @@ template struct nested_formatter { write(basic_appender(buf)); auto specs = format_specs(); specs.width = width_; - specs.fill = fill_; - specs.align = align_; + specs.set_fill( + basic_string_view(specs_.fill(), specs_.fill_size())); + specs.set_align(specs_.align()); return detail::write( ctx.out(), basic_string_view(buf.data(), buf.size()), specs); } @@ -4159,7 +4158,7 @@ template struct format_handler { auto specs = dynamic_format_specs(); begin = parse_format_specs(begin, end, specs, parse_context, arg.type()); - if (specs.dynamic != 0) { + if (specs.dynamic()) { handle_dynamic_spec(specs.dynamic_width(), specs.width, specs.width_ref, context); handle_dynamic_spec(specs.dynamic_precision(), specs.precision, @@ -4206,7 +4205,7 @@ template template FMT_CONSTEXPR FMT_INLINE auto native_formatter::format( const T& val, FormatContext& ctx) const -> decltype(ctx.out()) { - if (specs_.dynamic == 0) + if (!specs_.dynamic()) return write(ctx.out(), val, specs_, ctx.locale()); auto specs = format_specs(specs_); handle_dynamic_spec(specs.dynamic_width(), specs.width, specs_.width_ref, diff --git a/include/fmt/printf.h b/include/fmt/printf.h index 072cc6b3..77f7e9b1 100644 --- a/include/fmt/printf.h +++ b/include/fmt/printf.h @@ -200,7 +200,7 @@ class printf_width_handler { auto operator()(T value) -> unsigned { auto width = static_cast>(value); if (detail::is_negative(value)) { - specs_.align = align::left; + specs_.set_align(align::left); width = 0 - width; } unsigned int_max = to_unsigned(max_value()); @@ -234,7 +234,7 @@ class printf_arg_formatter : public arg_formatter { void write_null_pointer(bool is_string = false) { auto s = this->specs; - s.type = presentation_type::none; + s.set_type(presentation_type::none); write_bytes(this->out, is_string ? "(null)" : "(nil)", s); } @@ -254,16 +254,17 @@ class printf_arg_formatter : public arg_formatter { return; } format_specs s = this->specs; - if (s.type != presentation_type::none && s.type != presentation_type::chr) { + if (s.type() != presentation_type::none && + s.type() != presentation_type::chr) { return (*this)(static_cast(value)); } - s.sign = sign::none; - s.alt = false; - s.fill = ' '; // Ignore '0' flag for char types. + s.set_sign(sign::none); + s.clear_alt(); + s.set_fill(' '); // Ignore '0' flag for char types. // align::numeric needs to be overwritten here since the '0' flag is // ignored for non-numeric types - if (s.align == align::none || s.align == align::numeric) - s.align = align::right; + if (s.align() == align::none || s.align() == align::numeric) + s.set_align(align::right); write(this->out, static_cast(value), s); } @@ -276,14 +277,14 @@ class printf_arg_formatter : public arg_formatter { if (value) base::operator()(value); else - write_null_pointer(this->specs.type != presentation_type::pointer); + write_null_pointer(this->specs.type() != presentation_type::pointer); } void operator()(const wchar_t* value) { if (value) base::operator()(value); else - write_null_pointer(this->specs.type != presentation_type::pointer); + write_null_pointer(this->specs.type() != presentation_type::pointer); } void operator()(basic_string_view value) { base::operator()(value); } @@ -306,19 +307,19 @@ void parse_flags(format_specs& specs, const Char*& it, const Char* end) { for (; it != end; ++it) { switch (*it) { case '-': - specs.align = align::left; + specs.set_align(align::left); break; case '+': - specs.sign = sign::plus; + specs.set_sign(sign::plus); break; case '0': - specs.fill = '0'; + specs.set_fill('0'); break; case ' ': - if (specs.sign != sign::plus) specs.sign = sign::space; + if (specs.sign() != sign::plus) specs.set_sign(sign::space); break; case '#': - specs.alt = true; + specs.set_alt(); break; default: return; @@ -339,7 +340,7 @@ auto parse_header(const Char*& it, const Char* end, format_specs& specs, ++it; arg_index = value != -1 ? value : max_value(); } else { - if (c == '0') specs.fill = '0'; + if (c == '0') specs.set_fill('0'); if (value != 0) { // Nonzero value means that we parsed width and don't need to // parse it or flags again, so return now. @@ -444,7 +445,7 @@ void vprintf(buffer& buf, basic_string_view format, write(out, basic_string_view(start, to_unsigned(it - 1 - start))); auto specs = format_specs(); - specs.align = align::right; + specs.set_align(align::right); // Parse argument index, flags and width. int arg_index = parse_header(it, end, specs, get_arg); @@ -470,7 +471,7 @@ void vprintf(buffer& buf, basic_string_view format, // specified, the '0' flag is ignored if (specs.precision >= 0 && arg.is_integral()) { // Ignore '0' for non-numeric types or if '-' present. - specs.fill = ' '; + specs.set_fill(' '); } if (specs.precision >= 0 && arg.type() == type::cstring_type) { auto str = arg.visit(get_cstring()); @@ -480,13 +481,14 @@ void vprintf(buffer& buf, basic_string_view format, str, to_unsigned(nul != str_end ? nul - str : specs.precision)); arg = make_arg>(sv); } - if (specs.alt && arg.visit(is_zero_int())) specs.alt = false; - if (specs.fill.template get() == '0') { - if (arg.is_arithmetic() && specs.align != align::left) - specs.align = align::numeric; - else - specs.fill = ' '; // Ignore '0' flag for non-numeric types or if '-' - // flag is also present. + if (specs.alt() && arg.visit(is_zero_int())) specs.clear_alt(); + if (specs.fill_unit() == '0') { + if (arg.is_arithmetic() && specs.align() != align::left) { + specs.set_align(align::numeric); + } else { + // Ignore '0' flag for non-numeric types or if '-' flag is also present. + specs.set_fill(' '); + } } // Parse length and convert the argument to the required type. @@ -545,10 +547,10 @@ void vprintf(buffer& buf, basic_string_view format, } } bool upper = false; - specs.type = parse_printf_presentation_type(type, arg.type(), upper); - if (specs.type == presentation_type::none) + specs.set_type(parse_printf_presentation_type(type, arg.type(), upper)); + if (specs.type() == presentation_type::none) report_error("invalid format specifier"); - specs.upper = upper; + if (upper) specs.set_upper(); start = it; diff --git a/include/fmt/ranges.h b/include/fmt/ranges.h index 100a5b27..47d9f4aa 100644 --- a/include/fmt/ranges.h +++ b/include/fmt/ranges.h @@ -422,7 +422,7 @@ struct range_formatter< auto buf = basic_memory_buffer(); for (; it != end; ++it) buf.push_back(*it); auto specs = format_specs(); - specs.type = presentation_type::debug; + specs.set_type(presentation_type::debug); return detail::write( out, basic_string_view(buf.data(), buf.size()), specs); } diff --git a/include/fmt/std.h b/include/fmt/std.h index 308bbc88..1e69b8b2 100644 --- a/include/fmt/std.h +++ b/include/fmt/std.h @@ -645,7 +645,7 @@ template struct formatter, Char> { if (c.real() != 0) { *out++ = Char('('); out = detail::write(out, c.real(), specs, ctx.locale()); - specs.sign = sign::plus; + specs.set_sign(sign::plus); out = detail::write(out, c.imag(), specs, ctx.locale()); if (!detail::isfinite(c.imag())) *out++ = Char(' '); *out++ = Char('i'); @@ -670,7 +670,7 @@ template struct formatter, Char> { auto format(const std::complex& c, FormatContext& ctx) const -> decltype(ctx.out()) { auto specs = specs_; - if (specs.dynamic != 0) { + if (specs.dynamic()) { detail::handle_dynamic_spec(specs.dynamic_width(), specs.width, specs.width_ref, ctx); detail::handle_dynamic_spec(specs.dynamic_precision(), specs.precision, @@ -682,12 +682,14 @@ template struct formatter, Char> { auto outer_specs = format_specs(); outer_specs.width = specs.width; - outer_specs.fill = specs.fill; - outer_specs.align = specs.align; + auto fill = specs.template fill(); + if (fill) + outer_specs.set_fill(basic_string_view(fill, specs.fill_size())); + outer_specs.set_align(specs.align()); specs.width = 0; - specs.fill = {}; - specs.align = align::none; + specs.set_fill({}); + specs.set_align(align::none); do_format(c, specs, ctx, basic_appender(buf)); return detail::write(ctx.out(), diff --git a/test/base-test.cc b/test/base-test.cc index e173af21..54c97024 100644 --- a/test/base-test.cc +++ b/test/base-test.cc @@ -516,19 +516,19 @@ template constexpr auto parse_test_specs(const char (&s)[N]) { } TEST(base_test, constexpr_parse_format_specs) { - static_assert(parse_test_specs("<").align == fmt::align::left, ""); - static_assert(parse_test_specs("*^").fill.get() == '*', ""); - static_assert(parse_test_specs("+").sign == fmt::sign::plus, ""); - static_assert(parse_test_specs("-").sign == fmt::sign::none, ""); - static_assert(parse_test_specs(" ").sign == fmt::sign::space, ""); - static_assert(parse_test_specs("#").alt, ""); - static_assert(parse_test_specs("0").align == fmt::align::numeric, ""); - static_assert(parse_test_specs("L").localized, ""); + static_assert(parse_test_specs("<").align() == fmt::align::left, ""); + static_assert(parse_test_specs("*^").fill_unit() == '*', ""); + static_assert(parse_test_specs("+").sign() == fmt::sign::plus, ""); + static_assert(parse_test_specs("-").sign() == fmt::sign::none, ""); + static_assert(parse_test_specs(" ").sign() == fmt::sign::space, ""); + static_assert(parse_test_specs("#").alt(), ""); + static_assert(parse_test_specs("0").align() == fmt::align::numeric, ""); + static_assert(parse_test_specs("L").localized(), ""); static_assert(parse_test_specs("42").width == 42, ""); static_assert(parse_test_specs("{42}").width_ref.index == 42, ""); static_assert(parse_test_specs(".42").precision == 42, ""); static_assert(parse_test_specs(".{42}").precision_ref.index == 42, ""); - static_assert(parse_test_specs("f").type == fmt::presentation_type::fixed, + static_assert(parse_test_specs("f").type() == fmt::presentation_type::fixed, ""); } diff --git a/test/scan.h b/test/scan.h index 1bcdc548..304e692e 100644 --- a/test/scan.h +++ b/test/scan.h @@ -368,7 +368,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; + specs.set_type(presentation_type::hex); ++begin; break; case '}': @@ -437,7 +437,7 @@ auto read_hex(scan_iterator it, T& value) -> scan_iterator { template ::value)> auto read(scan_iterator it, T& value, const format_specs& specs) -> scan_iterator { - if (specs.type == presentation_type::hex) return read_hex(it, value); + if (specs.type() == presentation_type::hex) return read_hex(it, value); return read(it, value); }