From 50a8c3e9bfacb6a6b1a3ab742d9014d731acd329 Mon Sep 17 00:00:00 2001 From: Victor Zverovich Date: Sat, 10 Aug 2024 08:10:55 -0700 Subject: [PATCH] Reduce format specs size --- include/fmt/base.h | 102 +++++++++++++++++++++++++----------------- include/fmt/chrono.h | 16 ++++--- include/fmt/compile.h | 19 ++++---- include/fmt/format.h | 54 +++++++++++++--------- include/fmt/std.h | 14 +++--- test/base-test.cc | 4 +- 6 files changed, 124 insertions(+), 85 deletions(-) diff --git a/include/fmt/base.h b/include/fmt/base.h index 9906b43d..1df274ea 100644 --- a/include/fmt/base.h +++ b/include/fmt/base.h @@ -2136,6 +2136,9 @@ struct fill_t { return nullptr; } }; + +enum class arg_id_kind { none, index, name }; + } // namespace detail enum class presentation_type : unsigned char { @@ -2168,6 +2171,7 @@ struct format_specs { 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; @@ -2179,38 +2183,31 @@ struct format_specs { type(presentation_type::none), align(align::none), sign(sign::none), + dynamic(0), upper(false), alt(false), localized(false) {} + + enum { dynamic_width_mask = 3, dynamic_precision_mask = 12 }; + + constexpr auto dynamic_width() const -> detail::arg_id_kind { + return static_cast(dynamic & dynamic_width_mask); + } + constexpr auto dynamic_precision() const -> detail::arg_id_kind { + return static_cast( + (dynamic & dynamic_precision_mask) >> 2); + } }; namespace detail { -enum class arg_id_kind { none, index, name }; - // An argument reference. -template struct arg_ref { - FMT_CONSTEXPR arg_ref() : kind(arg_id_kind::none), val() {} +template union arg_ref { + FMT_CONSTEXPR arg_ref(int idx = 0) : index(idx) {} + FMT_CONSTEXPR arg_ref(basic_string_view n) : name(n) {} - FMT_CONSTEXPR explicit arg_ref(int index) - : kind(arg_id_kind::index), val(index) {} - FMT_CONSTEXPR explicit arg_ref(basic_string_view name) - : kind(arg_id_kind::name), val(name) {} - - FMT_CONSTEXPR auto operator=(int idx) -> arg_ref& { - kind = arg_id_kind::index; - val.index = idx; - return *this; - } - - arg_id_kind kind; - union value { - FMT_CONSTEXPR value(int idx = 0) : index(idx) {} - FMT_CONSTEXPR value(basic_string_view n) : name(n) {} - - int index; - basic_string_view name; - } val; + int index; + basic_string_view name; }; // Format specifiers with width and precision resolved at formatting rather @@ -2321,28 +2318,37 @@ FMT_CONSTEXPR auto parse_arg_id(const Char* begin, const Char* end, return it; } -template struct dynamic_spec_id_handler { +template struct dynamic_spec_handler { basic_format_parse_context& ctx; arg_ref& ref; + arg_id_kind& kind; FMT_CONSTEXPR void on_index(int id) { - ref = arg_ref(id); + ref = id; + kind = arg_id_kind::index; ctx.check_arg_id(id); ctx.check_dynamic_spec(id); } FMT_CONSTEXPR void on_name(basic_string_view id) { - ref = arg_ref(id); + ref = id; + kind = arg_id_kind::name; ctx.check_arg_id(id); } }; +template struct parse_dynamic_spec_result { + const Char* end; + arg_id_kind kind; +}; + // 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* { + -> parse_dynamic_spec_result { FMT_ASSERT(begin != end, ""); + auto kind = arg_id_kind::none; if ('0' <= *begin && *begin <= '9') { int val = parse_nonnegative_int(begin, end, -1); if (val == -1) report_error("number is too big"); @@ -2354,31 +2360,48 @@ FMT_CONSTEXPR auto parse_dynamic_spec(const Char* begin, const Char* end, Char c = *begin; if (c == '}' || c == ':') { int id = ctx.next_arg_id(); - ref = arg_ref(id); + ref = id; + kind = arg_id_kind::index; ctx.check_dynamic_spec(id); } else { - begin = - parse_arg_id(begin, end, dynamic_spec_id_handler{ctx, ref}); + begin = parse_arg_id(begin, end, + dynamic_spec_handler{ctx, ref, kind}); } } - if (begin != end && *begin == '}') return ++begin; + if (begin != end && *begin == '}') return {++begin, kind}; } report_error("invalid format string"); } - return begin; + return {begin, kind}; +} + +template +FMT_CONSTEXPR auto parse_width(const Char* begin, const Char* end, + format_specs& specs, arg_ref& width_ref, + 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; + return result.end; } template FMT_CONSTEXPR auto parse_precision(const Char* begin, const Char* end, - int& value, arg_ref& ref, + format_specs& specs, + arg_ref& precision_ref, basic_format_parse_context& ctx) -> const Char* { ++begin; - if (begin != end) - begin = parse_dynamic_spec(begin, end, value, ref, ctx); - else + if (begin == end) { report_error("invalid precision"); - return begin; + return begin; + } + 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; + return result.end; } enum class state { start, align, sign, hash, zero, width, precision, locale }; @@ -2466,13 +2489,12 @@ FMT_CONSTEXPR auto parse_format_specs(const Char* begin, const Char* end, case '9': case '{': enter_state(state::width); - begin = parse_dynamic_spec(begin, end, specs.width, specs.width_ref, ctx); + begin = parse_width(begin, end, specs, 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); + begin = parse_precision(begin, end, specs, specs.precision_ref, ctx); break; case 'L': enter_state(state::locale, is_arithmetic_type(arg_type)); diff --git a/include/fmt/chrono.h b/include/fmt/chrono.h index 06b20933..499bf91f 100644 --- a/include/fmt/chrono.h +++ b/include/fmt/chrono.h @@ -2253,15 +2253,14 @@ struct formatter, Char> { Char c = *it; if ((c >= '0' && c <= '9') || c == '{') { - it = detail::parse_dynamic_spec(it, end, specs_.width, width_ref_, ctx); + it = detail::parse_width(it, end, specs_, width_ref_, ctx); if (it == end) return it; } auto checker = detail::chrono_format_checker(); if (*it == '.') { checker.has_precision_integral = !std::is_floating_point::value; - it = detail::parse_precision(it, end, specs_.precision, precision_ref_, - ctx); + it = detail::parse_precision(it, end, specs_, precision_ref_, ctx); } if (it != end && *it == 'L') { localized_ = true; @@ -2283,8 +2282,10 @@ struct formatter, Char> { // is not specified. auto buf = basic_memory_buffer(); auto out = basic_appender(buf); - detail::handle_dynamic_spec(specs.width, width_ref_, ctx); - detail::handle_dynamic_spec(precision, precision_ref_, ctx); + detail::handle_dynamic_spec(specs.dynamic_width(), specs.width, width_ref_, + ctx); + detail::handle_dynamic_spec(specs.dynamic_precision(), precision, + precision_ref_, ctx); if (begin == end || *begin == '}') { out = detail::format_duration_value(out, d.count(), precision); detail::format_duration_unit(out); @@ -2390,7 +2391,8 @@ template struct formatter { auto specs = specs_; auto buf = basic_memory_buffer(); auto out = basic_appender(buf); - detail::handle_dynamic_spec(specs.width, width_ref_, ctx); + detail::handle_dynamic_spec(specs.dynamic_width(), specs.width, width_ref_, + ctx); auto loc_ref = ctx.locale(); detail::get_locale loc(static_cast(loc_ref), loc_ref); @@ -2412,7 +2414,7 @@ template struct formatter { Char c = *it; if ((c >= '0' && c <= '9') || c == '{') { - it = detail::parse_dynamic_spec(it, end, specs_.width, width_ref_, ctx); + it = detail::parse_width(it, end, specs_, width_ref_, ctx); if (it == end) return it; } diff --git a/include/fmt/compile.h b/include/fmt/compile.h index 49d077e0..36dd367d 100644 --- a/include/fmt/compile.h +++ b/include/fmt/compile.h @@ -269,6 +269,7 @@ constexpr parse_specs_result parse_specs(basic_string_view str, } template struct arg_id_handler { + arg_id_kind kind; arg_ref arg_id; constexpr int on_auto() { @@ -276,25 +277,28 @@ template struct arg_id_handler { return 0; } constexpr int on_index(int id) { + kind = arg_id_kind::index; arg_id = arg_ref(id); return 0; } constexpr int on_name(basic_string_view id) { + kind = arg_id_kind::name; arg_id = arg_ref(id); return 0; } }; template struct parse_arg_id_result { + arg_id_kind kind; arg_ref arg_id; const Char* arg_id_end; }; template constexpr auto parse_arg_id(const Char* begin, const Char* end) { - auto handler = arg_id_handler{arg_ref{}}; + auto handler = arg_id_handler{arg_id_kind::none, arg_ref{}}; auto arg_id_end = parse_arg_id(begin, end, handler); - return parse_arg_id_result{handler.arg_id, arg_id_end}; + return parse_arg_id_result{handler.kind, handler.arg_id, arg_id_end}; } template struct field_type { @@ -357,18 +361,18 @@ constexpr auto compile_format_string(S fmt) { constexpr char_type c = arg_id_end_pos != str.size() ? str[arg_id_end_pos] : char_type(); static_assert(c == '}' || c == ':', "missing '}' in format string"); - if constexpr (arg_id_result.arg_id.kind == arg_id_kind::index) { + if constexpr (arg_id_result.kind == arg_id_kind::index) { static_assert( ID == manual_indexing_id || ID == 0, "cannot switch from automatic to manual argument indexing"); - constexpr auto arg_index = arg_id_result.arg_id.val.index; + constexpr auto arg_index = arg_id_result.arg_id.index; return parse_replacement_field_then_tail, Args, arg_id_end_pos, arg_index, manual_indexing_id>( fmt); - } else if constexpr (arg_id_result.arg_id.kind == arg_id_kind::name) { + } else if constexpr (arg_id_result.kind == arg_id_kind::name) { constexpr auto arg_index = - get_arg_index_by_name(arg_id_result.arg_id.val.name, Args{}); + get_arg_index_by_name(arg_id_result.arg_id.name, Args{}); if constexpr (arg_index >= 0) { constexpr auto next_id = ID != manual_indexing_id ? ID + 1 : manual_indexing_id; @@ -377,8 +381,7 @@ constexpr auto compile_format_string(S fmt) { arg_index, next_id>(fmt); } else if constexpr (c == '}') { return parse_tail( - runtime_named_field{arg_id_result.arg_id.val.name}, - fmt); + runtime_named_field{arg_id_result.arg_id.name}, fmt); } else if constexpr (c == ':') { return unknown_format(); // no type info for specs parsing } diff --git a/include/fmt/format.h b/include/fmt/format.h index 879390d8..72d0763a 100644 --- a/include/fmt/format.h +++ b/include/fmt/format.h @@ -3696,10 +3696,11 @@ FMT_CONSTEXPR auto get_arg(Context& ctx, ID id) -> basic_format_arg { template FMT_CONSTEXPR int get_dynamic_spec( - const arg_ref& ref, Context& ctx) { - FMT_ASSERT(ref.kind != arg_id_kind::none, ""); - auto arg = ref.kind == arg_id_kind::index ? ctx.arg(ref.val.index) - : ctx.arg(ref.val.name); + arg_id_kind kind, const arg_ref& ref, + Context& ctx) { + FMT_ASSERT(kind != arg_id_kind::none, ""); + auto arg = + kind == arg_id_kind::index ? ctx.arg(ref.index) : ctx.arg(ref.name); if (!arg) report_error("argument not found"); unsigned long long value = arg.visit(dynamic_spec_getter()); if (value > to_unsigned(max_value())) @@ -3709,8 +3710,9 @@ FMT_CONSTEXPR int get_dynamic_spec( template FMT_CONSTEXPR void handle_dynamic_spec( - int& value, const arg_ref& ref, Context& ctx) { - if (ref.kind != arg_id_kind::none) value = get_dynamic_spec(ref, ctx); + arg_id_kind kind, int& value, + const arg_ref& ref, Context& ctx) { + if (kind != arg_id_kind::none) value = get_dynamic_spec(kind, ref, ctx); } #if FMT_USE_USER_DEFINED_LITERALS @@ -3965,8 +3967,10 @@ template <> struct formatter { template auto format(bytes b, FormatContext& ctx) const -> decltype(ctx.out()) { auto specs = specs_; - detail::handle_dynamic_spec(specs.width, specs.width_ref, ctx); - detail::handle_dynamic_spec(specs.precision, specs.precision_ref, ctx); + detail::handle_dynamic_spec(specs.dynamic_width(), specs.width, + specs.width_ref, ctx); + detail::handle_dynamic_spec(specs.dynamic_precision(), specs.precision, + specs.precision_ref, ctx); return detail::write_bytes(ctx.out(), b.data_, specs); } }; @@ -4004,8 +4008,10 @@ template struct formatter> : formatter { auto format(group_digits_view t, FormatContext& ctx) const -> decltype(ctx.out()) { auto specs = specs_; - detail::handle_dynamic_spec(specs.width, specs.width_ref, ctx); - detail::handle_dynamic_spec(specs.precision, specs.precision_ref, ctx); + detail::handle_dynamic_spec(specs.dynamic_width(), specs.width, + 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); return detail::write_int( ctx.out(), static_cast>(arg.abs_value), @@ -4051,8 +4057,10 @@ template struct nested_formatter { align_ = specs.align; Char c = *it; auto width_ref = detail::arg_ref(); - if ((c >= '0' && c <= '9') || c == '{') - it = detail::parse_dynamic_spec(it, end, width_, width_ref, ctx); + if ((c >= '0' && c <= '9') || c == '{') { + it = detail::parse_width(it, end, specs, width_ref, ctx); + width_ = specs.width; + } ctx.advance_to(it); return formatter_.parse(ctx); } @@ -4149,12 +4157,14 @@ template struct format_handler { if (arg.format_custom(begin, parse_context, context)) return parse_context.begin(); - auto specs = detail::dynamic_format_specs(); + auto specs = dynamic_format_specs(); begin = parse_format_specs(begin, end, specs, parse_context, arg.type()); - if (specs.width_ref.kind != detail::arg_id_kind::none) - specs.width = detail::get_dynamic_spec(specs.width_ref, context); - if (specs.precision_ref.kind != detail::arg_id_kind::none) - specs.precision = detail::get_dynamic_spec(specs.precision_ref, context); + if (specs.dynamic != 0) { + handle_dynamic_spec(specs.dynamic_width(), specs.width, specs.width_ref, + context); + handle_dynamic_spec(specs.dynamic_precision(), specs.precision, + specs.precision_ref, context); + } if (begin == end || *begin != '}') report_error("missing '}' in format string"); @@ -4196,13 +4206,13 @@ template template FMT_CONSTEXPR FMT_INLINE auto native_formatter::format( const T& val, FormatContext& ctx) const -> decltype(ctx.out()) { - if (specs_.width_ref.kind == arg_id_kind::none && - specs_.precision_ref.kind == arg_id_kind::none) { + if (specs_.dynamic == 0) return write(ctx.out(), val, specs_, ctx.locale()); - } auto specs = format_specs(specs_); - handle_dynamic_spec(specs.width, specs_.width_ref, ctx); - handle_dynamic_spec(specs.precision, specs_.precision_ref, ctx); + handle_dynamic_spec(specs.dynamic_width(), specs.width, specs_.width_ref, + ctx); + handle_dynamic_spec(specs.dynamic_precision(), specs.precision, + specs_.precision_ref, ctx); return write(ctx.out(), val, specs, ctx.locale()); } diff --git a/include/fmt/std.h b/include/fmt/std.h index 8629ee14..308bbc88 100644 --- a/include/fmt/std.h +++ b/include/fmt/std.h @@ -131,7 +131,7 @@ template struct formatter { Char c = *it; if ((c >= '0' && c <= '9') || c == '{') - it = detail::parse_dynamic_spec(it, end, specs_.width, width_ref_, ctx); + it = detail::parse_width(it, end, specs_, width_ref_, ctx); if (it != end && *it == '?') { debug_ = true; ++it; @@ -147,7 +147,8 @@ template struct formatter { !path_type_ ? p.native() : p.generic_string(); - detail::handle_dynamic_spec(specs.width, width_ref_, ctx); + detail::handle_dynamic_spec(specs.dynamic_width(), specs.width, width_ref_, + ctx); if (!debug_) { auto s = detail::get_path_string(p, path_string); return detail::write(ctx.out(), basic_string_view(s), specs); @@ -669,10 +670,11 @@ template struct formatter, Char> { auto format(const std::complex& c, FormatContext& ctx) const -> decltype(ctx.out()) { auto specs = specs_; - if (specs.width_ref.kind != detail::arg_id_kind::none || - specs.precision_ref.kind != detail::arg_id_kind::none) { - detail::handle_dynamic_spec(specs.width, specs.width_ref, ctx); - detail::handle_dynamic_spec(specs.precision, specs.precision_ref, ctx); + if (specs.dynamic != 0) { + detail::handle_dynamic_spec(specs.dynamic_width(), specs.width, + specs.width_ref, ctx); + detail::handle_dynamic_spec(specs.dynamic_precision(), specs.precision, + specs.precision_ref, ctx); } if (specs.width == 0) return do_format(c, specs, ctx, ctx.out()); diff --git a/test/base-test.cc b/test/base-test.cc index 662fc2ea..e173af21 100644 --- a/test/base-test.cc +++ b/test/base-test.cc @@ -525,9 +525,9 @@ TEST(base_test, constexpr_parse_format_specs) { 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.val.index == 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.val.index == 42, ""); + static_assert(parse_test_specs(".{42}").precision_ref.index == 42, ""); static_assert(parse_test_specs("f").type == fmt::presentation_type::fixed, ""); }