Reduce format specs size

This commit is contained in:
Victor Zverovich 2024-08-10 08:10:55 -07:00
parent 98314319ad
commit 50a8c3e9bf
6 changed files with 124 additions and 85 deletions

View File

@ -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<detail::arg_id_kind>(dynamic & dynamic_width_mask);
}
constexpr auto dynamic_precision() const -> detail::arg_id_kind {
return static_cast<detail::arg_id_kind>(
(dynamic & dynamic_precision_mask) >> 2);
}
};
namespace detail {
enum class arg_id_kind { none, index, name };
// An argument reference.
template <typename Char> struct arg_ref {
FMT_CONSTEXPR arg_ref() : kind(arg_id_kind::none), val() {}
template <typename Char> union arg_ref {
FMT_CONSTEXPR arg_ref(int idx = 0) : index(idx) {}
FMT_CONSTEXPR arg_ref(basic_string_view<Char> 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<Char> 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<Char> n) : name(n) {}
int index;
basic_string_view<Char> name;
} val;
int index;
basic_string_view<Char> 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 <typename Char> struct dynamic_spec_id_handler {
template <typename Char> struct dynamic_spec_handler {
basic_format_parse_context<Char>& ctx;
arg_ref<Char>& ref;
arg_id_kind& kind;
FMT_CONSTEXPR void on_index(int id) {
ref = arg_ref<Char>(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<Char> id) {
ref = arg_ref<Char>(id);
ref = id;
kind = arg_id_kind::name;
ctx.check_arg_id(id);
}
};
template <typename Char> struct parse_dynamic_spec_result {
const Char* end;
arg_id_kind kind;
};
// Parses integer | "{" [arg_id] "}".
template <typename Char>
FMT_CONSTEXPR auto parse_dynamic_spec(const Char* begin, const Char* end,
int& value, arg_ref<Char>& ref,
basic_format_parse_context<Char>& ctx)
-> const Char* {
-> parse_dynamic_spec_result<Char> {
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<Char>(id);
ref = id;
kind = arg_id_kind::index;
ctx.check_dynamic_spec(id);
} else {
begin =
parse_arg_id(begin, end, dynamic_spec_id_handler<Char>{ctx, ref});
begin = parse_arg_id(begin, end,
dynamic_spec_handler<Char>{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 <typename Char>
FMT_CONSTEXPR auto parse_width(const Char* begin, const Char* end,
format_specs& specs, arg_ref<Char>& width_ref,
basic_format_parse_context<Char>& ctx)
-> const Char* {
auto result = parse_dynamic_spec(begin, end, specs.width, width_ref, ctx);
specs.dynamic = static_cast<unsigned char>(result.kind) & 0x3u;
return result.end;
}
template <typename Char>
FMT_CONSTEXPR auto parse_precision(const Char* begin, const Char* end,
int& value, arg_ref<Char>& ref,
format_specs& specs,
arg_ref<Char>& precision_ref,
basic_format_parse_context<Char>& 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<unsigned char>(result.kind);
specs.dynamic =
static_cast<unsigned char>(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));

View File

@ -2253,15 +2253,14 @@ struct formatter<std::chrono::duration<Rep, Period>, 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<Rep>::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<std::chrono::duration<Rep, Period>, Char> {
// is not specified.
auto buf = basic_memory_buffer<Char>();
auto out = basic_appender<Char>(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<Char>(out, d.count(), precision);
detail::format_duration_unit<Char, Period>(out);
@ -2390,7 +2391,8 @@ template <typename Char> struct formatter<std::tm, Char> {
auto specs = specs_;
auto buf = basic_memory_buffer<Char>();
auto out = basic_appender<Char>(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<bool>(loc_ref), loc_ref);
@ -2412,7 +2414,7 @@ template <typename Char> struct formatter<std::tm, 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;
}

View File

@ -269,6 +269,7 @@ constexpr parse_specs_result<T, Char> parse_specs(basic_string_view<Char> str,
}
template <typename Char> struct arg_id_handler {
arg_id_kind kind;
arg_ref<Char> arg_id;
constexpr int on_auto() {
@ -276,25 +277,28 @@ template <typename Char> struct arg_id_handler {
return 0;
}
constexpr int on_index(int id) {
kind = arg_id_kind::index;
arg_id = arg_ref<Char>(id);
return 0;
}
constexpr int on_name(basic_string_view<Char> id) {
kind = arg_id_kind::name;
arg_id = arg_ref<Char>(id);
return 0;
}
};
template <typename Char> struct parse_arg_id_result {
arg_id_kind kind;
arg_ref<Char> arg_id;
const Char* arg_id_end;
};
template <int ID, typename Char>
constexpr auto parse_arg_id(const Char* begin, const Char* end) {
auto handler = arg_id_handler<Char>{arg_ref<Char>{}};
auto handler = arg_id_handler<Char>{arg_id_kind::none, arg_ref<Char>{}};
auto arg_id_end = parse_arg_id(begin, end, handler);
return parse_arg_id_result<Char>{handler.arg_id, arg_id_end};
return parse_arg_id_result<Char>{handler.kind, handler.arg_id, arg_id_end};
}
template <typename T, typename Enable = void> 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<get_type<arg_index, Args>,
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<Args, arg_id_end_pos + 1, ID>(
runtime_named_field<char_type>{arg_id_result.arg_id.val.name},
fmt);
runtime_named_field<char_type>{arg_id_result.arg_id.name}, fmt);
} else if constexpr (c == ':') {
return unknown_format(); // no type info for specs parsing
}

View File

@ -3696,10 +3696,11 @@ FMT_CONSTEXPR auto get_arg(Context& ctx, ID id) -> basic_format_arg<Context> {
template <typename Context>
FMT_CONSTEXPR int get_dynamic_spec(
const arg_ref<typename Context::char_type>& 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<typename Context::char_type>& 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<int>()))
@ -3709,8 +3710,9 @@ FMT_CONSTEXPR int get_dynamic_spec(
template <typename Context>
FMT_CONSTEXPR void handle_dynamic_spec(
int& value, const arg_ref<typename Context::char_type>& 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<typename Context::char_type>& 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<bytes> {
template <typename FormatContext>
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<char>(ctx.out(), b.data_, specs);
}
};
@ -4004,8 +4008,10 @@ template <typename T> struct formatter<group_digits_view<T>> : formatter<T> {
auto format(group_digits_view<T> 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<detail::uint64_or_128_t<T>>(arg.abs_value),
@ -4051,8 +4057,10 @@ template <typename T, typename Char = char> struct nested_formatter {
align_ = specs.align;
Char c = *it;
auto width_ref = detail::arg_ref<Char>();
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 <typename Char> struct format_handler {
if (arg.format_custom(begin, parse_context, context))
return parse_context.begin();
auto specs = detail::dynamic_format_specs<Char>();
auto specs = dynamic_format_specs<Char>();
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 <typename T, typename Char, type TYPE>
template <typename FormatContext>
FMT_CONSTEXPR FMT_INLINE auto native_formatter<T, Char, TYPE>::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<Char>(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<Char>(ctx.out(), val, specs, ctx.locale());
}

View File

@ -131,7 +131,7 @@ template <typename Char> struct formatter<std::filesystem::path, 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 && *it == '?') {
debug_ = true;
++it;
@ -147,7 +147,8 @@ template <typename Char> struct formatter<std::filesystem::path, Char> {
!path_type_ ? p.native()
: p.generic_string<std::filesystem::path::value_type>();
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<Char>(p, path_string);
return detail::write(ctx.out(), basic_string_view<Char>(s), specs);
@ -669,10 +670,11 @@ template <typename T, typename Char> struct formatter<std::complex<T>, Char> {
auto format(const std::complex<T>& 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());

View File

@ -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,
"");
}