diff --git a/include/fmt/format.h b/include/fmt/format.h index 2cd380b5..045260e2 100644 --- a/include/fmt/format.h +++ b/include/fmt/format.h @@ -4053,6 +4053,50 @@ FMT_CONSTEXPR auto native_formatter::format( specs_.precision_ref, ctx); return write(ctx.out(), val, specs, ctx.locale()); } + +// Parses and applies the outer alignment and width of a nested value. +template class nested_format_specs { + private: + format_specs specs_; + arg_ref width_ref_; + + public: + constexpr nested_format_specs() : specs_(), width_ref_() {} + + FMT_CONSTEXPR auto parse(const Char* begin, const Char* end, + parse_context& 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& 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 + 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(values)...); + + auto buf = basic_memory_buffer(); + auto buffer_ctx = + FormatContext(basic_appender(buf), ctx.args(), ctx.locale()); + f.write_body(buffer_ctx, static_cast(values)...); + return detail::write( + ctx.out(), basic_string_view(buf.data(), buf.size()), specs); + } +}; } // namespace detail FMT_BEGIN_EXPORT @@ -4298,8 +4342,7 @@ template struct nested_view { template struct nested_formatter { private: - format_specs specs_; - detail::arg_ref width_ref_; + detail::nested_format_specs specs_; formatter formatter_; template @@ -4315,23 +4358,21 @@ template struct nested_formatter { } template - 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; + public: - constexpr nested_formatter() : specs_(), width_ref_(), formatter_() {} + constexpr nested_formatter() : specs_(), formatter_() {} FMT_CONSTEXPR auto parse(parse_context& 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 struct nested_formatter { template 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(); - auto buffer_ctx = - FormatContext(basic_appender(buf), ctx.args(), ctx.locale()); - write_args(buffer_ctx, args...); - return detail::write( - ctx.out(), basic_string_view(buf.data(), buf.size()), specs); + return specs_.write(ctx, *this, args...); } auto nested(const U& value) const -> nested_view { return {&value}; } diff --git a/include/fmt/ranges.h b/include/fmt/ranges.h index a3230376..e516bc95 100644 --- a/include/fmt/ranges.h +++ b/include/fmt/ranges.h @@ -297,37 +297,6 @@ template struct is_tuple_formattable { static constexpr bool value = detail::is_tuple_formattable_::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 struct composed_specs { - format_specs specs; - arg_ref width_ref; - - FMT_CONSTEXPR auto parse(const Char* it, const Char* end, - parse_context& 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 - 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 struct formatter::value && @@ -341,10 +310,10 @@ struct formatter{}; basic_string_view closing_bracket_ = detail::string_literal{}; - detail::composed_specs composed_; + detail::nested_format_specs specs_; template - 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(opening_bracket_, ctx.out())); detail::for_each2( @@ -353,6 +322,8 @@ struct formatter(closing_bracket_, ctx.out()); } + friend class detail::nested_format_specs; + public: FMT_CONSTEXPR formatter() {} @@ -369,7 +340,7 @@ struct formatter& 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 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(); - auto nested_ctx = - FormatContext(basic_appender(buf), ctx.args(), ctx.locale()); - write_body(value, nested_ctx); - return detail::write( - ctx.out(), basic_string_view(buf.data(), buf.size()), specs); + return specs_.write(ctx, *this, value); } }; @@ -439,7 +403,7 @@ struct range_formatter< basic_string_view closing_bracket_ = detail::string_literal{}; bool is_debug = false; - detail::composed_specs composed_; + detail::nested_format_specs specs_; template ::value)> @@ -458,6 +422,8 @@ struct range_formatter< return out; } + friend class detail::nested_format_specs; + 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 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(); - auto nested_ctx = - FormatContext(basic_appender(buf), ctx.args(), ctx.locale()); - write_body(range, nested_ctx); - return detail::write( - ctx.out(), basic_string_view(buf.data(), buf.size()), specs); + return specs_.write(ctx, *this, range); } template - 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( detail::tuple_index_sequence())) formatters_; bool no_delimiters_ = false; - detail::composed_specs composed_; + detail::nested_format_specs specs_; + + friend class detail::nested_format_specs; 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 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(); - auto nested_ctx = - FormatContext(basic_appender(buf), ctx.args(), ctx.locale()); - write_body(map, nested_ctx); - return detail::write( - ctx.out(), basic_string_view(buf.data(), buf.size()), specs); + return specs_.write(ctx, *this, map); } template - 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 open = detail::string_literal{};