Refactor range outer formatting

This commit is contained in:
Victor Zverovich 2026-09-06 10:01:08 -07:00
parent 61e3b92396
commit d0aae8c15a
2 changed files with 69 additions and 84 deletions

View File

@ -4053,6 +4053,50 @@ FMT_CONSTEXPR auto native_formatter<T, Char, TYPE>::format(
specs_.precision_ref, ctx);
return write<Char>(ctx.out(), val, specs, ctx.locale());
}
// Parses and applies the outer alignment and width of a nested value.
template <typename Char> class nested_format_specs {
private:
format_specs specs_;
arg_ref<Char> width_ref_;
public:
constexpr nested_format_specs() : specs_(), width_ref_() {}
FMT_CONSTEXPR auto parse(const Char* begin, const Char* end,
parse_context<Char>& ctx) -> const Char* {
if (begin == end || *begin == '}') return begin;
begin = parse_align(begin, end, specs_);
if (begin == end) return begin;
Char c = *begin;
if ((c >= '0' && c <= '9') || c == '{')
begin = parse_width(begin, end, specs_, width_ref_, ctx);
return begin;
}
FMT_CONSTEXPR auto parse(const Char* begin, const Char* end,
parse_context<Char>& ctx, Char separator)
-> const Char* {
// A separator introduces the nested spec and is never a fill character.
if (begin != end && *begin == separator) return begin;
return parse(begin, end, ctx);
}
template <typename FormatContext, typename F, typename... T>
FMT_CONSTEXPR auto write(FormatContext& ctx, const F& f, T&&... values) const
-> decltype(ctx.out()) {
auto specs = specs_;
handle_dynamic_spec(specs.dynamic_width(), specs.width, width_ref_, ctx);
if (specs.width == 0) return f.write_body(ctx, static_cast<T&&>(values)...);
auto buf = basic_memory_buffer<Char>();
auto buffer_ctx =
FormatContext(basic_appender<Char>(buf), ctx.args(), ctx.locale());
f.write_body(buffer_ctx, static_cast<T&&>(values)...);
return detail::write<Char>(
ctx.out(), basic_string_view<Char>(buf.data(), buf.size()), specs);
}
};
} // namespace detail
FMT_BEGIN_EXPORT
@ -4298,8 +4342,7 @@ template <typename T> struct nested_view {
template <typename U, typename Char = char> struct nested_formatter {
private:
format_specs specs_;
detail::arg_ref<Char> width_ref_;
detail::nested_format_specs<Char> specs_;
formatter<U, Char> formatter_;
template <typename FormatContext>
@ -4315,23 +4358,21 @@ template <typename U, typename Char = char> struct nested_formatter {
}
template <typename FormatContext, typename... T>
auto write_args(FormatContext& ctx, const T&... args) const
auto write_body(FormatContext& ctx, const T&... args) const
-> decltype(ctx.out()) {
FMT_APPLY_VARIADIC(ctx.advance_to(write_arg(ctx, args)));
return ctx.out();
}
friend class detail::nested_format_specs<Char>;
public:
constexpr nested_formatter() : specs_(), width_ref_(), formatter_() {}
constexpr nested_formatter() : specs_(), formatter_() {}
FMT_CONSTEXPR auto parse(parse_context<Char>& ctx) -> const Char* {
auto it = ctx.begin(), end = ctx.end();
if (it == end) return it;
it = detail::parse_align(it, end, specs_);
if (it == end) return it;
Char c = *it;
if ((c >= '0' && c <= '9') || c == '{')
it = detail::parse_width(it, end, specs_, width_ref_, ctx);
it = specs_.parse(it, end, ctx);
ctx.advance_to(it);
return formatter_.parse(ctx);
}
@ -4339,17 +4380,7 @@ template <typename U, typename Char = char> struct nested_formatter {
template <typename FormatContext, typename... T>
auto write(FormatContext& ctx, const T&... args) const
-> decltype(ctx.out()) {
auto specs = specs_;
detail::handle_dynamic_spec(specs.dynamic_width(), specs.width, width_ref_,
ctx);
if (specs.width == 0) return write_args(ctx, args...);
auto buf = basic_memory_buffer<Char>();
auto buffer_ctx =
FormatContext(basic_appender<Char>(buf), ctx.args(), ctx.locale());
write_args(buffer_ctx, args...);
return detail::write<Char>(
ctx.out(), basic_string_view<Char>(buf.data(), buf.size()), specs);
return specs_.write(ctx, *this, args...);
}
auto nested(const U& value) const -> nested_view<U> { return {&value}; }

View File

@ -297,37 +297,6 @@ template <typename T, typename C> struct is_tuple_formattable {
static constexpr bool value = detail::is_tuple_formattable_<T, C>::value;
};
namespace detail {
// The fill, alignment and width that a range, tuple or map format spec may
// begin with. They apply to the composed output rather than to the elements.
template <typename Char> struct composed_specs {
format_specs specs;
arg_ref<Char> width_ref;
FMT_CONSTEXPR auto parse(const Char* it, const Char* end,
parse_context<Char>& ctx) -> const Char* {
// A leading ':' introduces the underlying spec, so it is never a fill.
if (it == end || *it == '}' || *it == ':') return it;
it = parse_align(it, end, specs);
if (it == end) return it;
Char c = *it;
if ((c >= '0' && c <= '9') || c == '{')
it = parse_width(it, end, specs, width_ref, ctx);
return it;
}
// Resolves a dynamic width. A width of 0 means no padding is needed.
template <typename FormatContext>
FMT_CONSTEXPR auto resolve(FormatContext& ctx) const -> format_specs {
auto s = specs;
handle_dynamic_spec(s.dynamic_width(), s.width, width_ref, ctx);
return s;
}
};
} // namespace detail
template <typename Tuple, typename Char>
struct formatter<Tuple, Char,
enable_if_t<fmt::is_tuple_like<Tuple>::value &&
@ -341,10 +310,10 @@ struct formatter<Tuple, Char,
detail::string_literal<Char, '('>{};
basic_string_view<Char> closing_bracket_ =
detail::string_literal<Char, ')'>{};
detail::composed_specs<Char> composed_;
detail::nested_format_specs<Char> specs_;
template <typename FormatContext>
auto write_body(const Tuple& value, FormatContext& ctx) const
auto write_body(FormatContext& ctx, const Tuple& value) const
-> decltype(ctx.out()) {
ctx.advance_to(detail::copy<Char>(opening_bracket_, ctx.out()));
detail::for_each2(
@ -353,6 +322,8 @@ struct formatter<Tuple, Char,
return detail::copy<Char>(closing_bracket_, ctx.out());
}
friend class detail::nested_format_specs<Char>;
public:
FMT_CONSTEXPR formatter() {}
@ -369,7 +340,7 @@ struct formatter<Tuple, Char,
FMT_CONSTEXPR auto parse(parse_context<Char>& ctx) -> const Char* {
auto it = ctx.begin();
auto end = ctx.end();
it = composed_.parse(it, end, ctx);
it = specs_.parse(it, end, ctx, ':');
if (it != end && detail::to_ascii(*it) == 'n') {
++it;
set_brackets({}, {});
@ -384,14 +355,7 @@ struct formatter<Tuple, Char,
template <typename FormatContext>
auto format(const Tuple& value, FormatContext& ctx) const
-> decltype(ctx.out()) {
auto specs = composed_.resolve(ctx);
if (specs.width == 0) return write_body(value, ctx);
auto buf = basic_memory_buffer<Char>();
auto nested_ctx =
FormatContext(basic_appender<Char>(buf), ctx.args(), ctx.locale());
write_body(value, nested_ctx);
return detail::write<Char>(
ctx.out(), basic_string_view<Char>(buf.data(), buf.size()), specs);
return specs_.write(ctx, *this, value);
}
};
@ -439,7 +403,7 @@ struct range_formatter<
basic_string_view<Char> closing_bracket_ =
detail::string_literal<Char, ']'>{};
bool is_debug = false;
detail::composed_specs<Char> composed_;
detail::nested_format_specs<Char> specs_;
template <typename Output, typename It, typename Sentinel, typename U = T,
FMT_ENABLE_IF(std::is_same<U, Char>::value)>
@ -458,6 +422,8 @@ struct range_formatter<
return out;
}
friend class detail::nested_format_specs<Char>;
public:
FMT_CONSTEXPR range_formatter() {}
@ -481,7 +447,7 @@ struct range_formatter<
detail::maybe_set_debug_format(underlying_, true);
if (it == end) return underlying_.parse(ctx);
it = composed_.parse(it, end, ctx);
it = specs_.parse(it, end, ctx, ':');
if (it == end) {
ctx.advance_to(it);
return underlying_.parse(ctx);
@ -524,18 +490,11 @@ struct range_formatter<
template <typename R, typename FormatContext>
FMT_CONSTEXPR auto format(R&& range, FormatContext& ctx) const
-> decltype(ctx.out()) {
auto specs = composed_.resolve(ctx);
if (specs.width == 0) return write_body(range, ctx);
auto buf = basic_memory_buffer<Char>();
auto nested_ctx =
FormatContext(basic_appender<Char>(buf), ctx.args(), ctx.locale());
write_body(range, nested_ctx);
return detail::write<Char>(
ctx.out(), basic_string_view<Char>(buf.data(), buf.size()), specs);
return specs_.write(ctx, *this, range);
}
template <typename R, typename FormatContext>
FMT_CONSTEXPR auto write_body(R&& range, FormatContext& ctx) const
FMT_CONSTEXPR auto write_body(FormatContext& ctx, R&& range) const
-> decltype(ctx.out()) {
auto out = ctx.out();
auto it = detail::range_begin(range);
@ -612,7 +571,9 @@ struct formatter<
decltype(detail::tuple::get_formatters<element_type, Char>(
detail::tuple_index_sequence<element_type>())) formatters_;
bool no_delimiters_ = false;
detail::composed_specs<Char> composed_;
detail::nested_format_specs<Char> specs_;
friend class detail::nested_format_specs<Char>;
public:
FMT_CONSTEXPR formatter() {}
@ -621,7 +582,7 @@ struct formatter<
auto it = ctx.begin();
auto end = ctx.end();
if (it != end) {
it = composed_.parse(it, end, ctx);
it = specs_.parse(it, end, ctx, ':');
if (it != end && detail::to_ascii(*it) == 'n') {
no_delimiters_ = true;
++it;
@ -638,18 +599,11 @@ struct formatter<
template <typename FormatContext>
auto format(map_type& map, FormatContext& ctx) const -> decltype(ctx.out()) {
auto specs = composed_.resolve(ctx);
if (specs.width == 0) return write_body(map, ctx);
auto buf = basic_memory_buffer<Char>();
auto nested_ctx =
FormatContext(basic_appender<Char>(buf), ctx.args(), ctx.locale());
write_body(map, nested_ctx);
return detail::write<Char>(
ctx.out(), basic_string_view<Char>(buf.data(), buf.size()), specs);
return specs_.write(ctx, *this, map);
}
template <typename FormatContext>
auto write_body(map_type& map, FormatContext& ctx) const
auto write_body(FormatContext& ctx, map_type& map) const
-> decltype(ctx.out()) {
auto out = ctx.out();
basic_string_view<Char> open = detail::string_literal<Char, '{'>{};