Skip to content

Commit

Permalink
Reduce format specs size
Browse files Browse the repository at this point in the history
  • Loading branch information
vitaut committed Aug 10, 2024
1 parent 9831431 commit 3e07fa2
Show file tree
Hide file tree
Showing 4 changed files with 113 additions and 73 deletions.
103 changes: 62 additions & 41 deletions include/fmt/base.h
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -2168,49 +2171,43 @@ 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;
detail::fill_t fill;

enum { dynamic_width_mask = 3, dynamic_precision_mask = 12 };

auto dynamic_width() const -> detail::arg_id_kind {
return static_cast<detail::arg_id_kind>(dynamic & dynamic_width_mask);
}
auto dynamic_precision() const -> detail::arg_id_kind {
return static_cast<detail::arg_id_kind>(
(dynamic & dynamic_precision_mask) >> 2);
}

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) {}
};

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() {}

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) {}
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 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
Expand Down Expand Up @@ -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");
Expand All @@ -2354,31 +2360,46 @@ 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);
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);
specs.dynamic |= static_cast<unsigned char>(result.kind) << 2;
return result.end;
}

enum class state { start, align, sign, hash, zero, width, precision, locale };
Expand Down Expand Up @@ -2464,15 +2485,15 @@ FMT_CONSTEXPR auto parse_format_specs(const Char* begin, const Char* end,
case '7':
case '8':
case '9':
case '{':
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));
Expand Down
16 changes: 9 additions & 7 deletions include/fmt/chrono.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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);
Expand Down Expand Up @@ -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);
Expand All @@ -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;
}

Expand Down
58 changes: 37 additions & 21 deletions include/fmt/format.h
Original file line number Diff line number Diff line change
Expand Up @@ -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>()))
Expand All @@ -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
Expand Down Expand Up @@ -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);
}
};
Expand Down Expand Up @@ -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),
Expand Down Expand Up @@ -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);
}
Expand Down Expand Up @@ -4151,10 +4159,18 @@ template <typename Char> struct format_handler {

auto specs = detail::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) {
auto width_kind = specs.dynamic_width();
if (width_kind != arg_id_kind::none) {
specs.width =
detail::get_dynamic_spec(width_kind, specs.width_ref, context);
}
auto precision_kind = specs.dynamic_precision();
if (precision_kind != arg_id_kind::none) {
specs.precision = detail::get_dynamic_spec(
precision_kind, specs.precision_ref, context);
}
}

if (begin == end || *begin != '}')
report_error("missing '}' in format string");
Expand Down Expand Up @@ -4196,13 +4212,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());
}

Expand Down
9 changes: 5 additions & 4 deletions include/fmt/std.h
Original file line number Diff line number Diff line change
Expand Up @@ -669,10 +669,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());
Expand Down

0 comments on commit 3e07fa2

Please sign in to comment.