From 0e01e46c11e390ef4dc9aa7978cc8af224920e20 Mon Sep 17 00:00:00 2001 From: Victor Zverovich Date: Mon, 18 Sep 2023 11:09:28 -0700 Subject: [PATCH] Implement nested formatter --- include/fmt/core.h | 17 +++++++++++++---- include/fmt/format.h | 24 +++++++++++++++++++++++- test/format-test.cc | 11 +++++++---- 3 files changed, 43 insertions(+), 9 deletions(-) diff --git a/include/fmt/core.h b/include/fmt/core.h index bcd9defb..c50822a5 100644 --- a/include/fmt/core.h +++ b/include/fmt/core.h @@ -2302,9 +2302,12 @@ FMT_CONSTEXPR FMT_INLINE auto parse_format_specs( dynamic_format_specs& specs; type arg_type; - FMT_CONSTEXPR auto operator()(pres type, int set) -> const Char* { - if (!in(arg_type, set)) throw_format_error("invalid format specifier"); - specs.type = type; + FMT_CONSTEXPR auto operator()(pres pres_type, int set) -> const Char* { + if (!in(arg_type, set)) { + if (arg_type == type::none_type) return begin; + throw_format_error("invalid format specifier"); + } + specs.type = pres_type; return begin + 1; } } parse_presentation_type{begin, specs, arg_type}; @@ -2321,6 +2324,7 @@ FMT_CONSTEXPR FMT_INLINE auto parse_format_specs( case '+': case '-': case ' ': + if (arg_type == type::none_type) return begin; enter_state(state::sign, in(arg_type, sint_set | float_set)); switch (c) { case '+': @@ -2336,14 +2340,17 @@ FMT_CONSTEXPR FMT_INLINE auto parse_format_specs( ++begin; break; case '#': + if (arg_type == type::none_type) return begin; enter_state(state::hash, is_arithmetic_type(arg_type)); specs.alt = true; ++begin; break; case '0': enter_state(state::zero); - if (!is_arithmetic_type(arg_type)) + if (!is_arithmetic_type(arg_type)) { + if (arg_type == type::none_type) return begin; throw_format_error("format specifier requires numeric argument"); + } if (specs.align == align::none) { // Ignore 0 if align is specified for compatibility with std::format. specs.align = align::numeric; @@ -2365,12 +2372,14 @@ FMT_CONSTEXPR FMT_INLINE auto parse_format_specs( begin = parse_dynamic_spec(begin, end, specs.width, specs.width_ref, ctx); break; case '.': + if (arg_type == type::none_type) return begin; enter_state(state::precision, in(arg_type, float_set | string_set | cstring_set)); begin = parse_precision(begin, end, specs.precision, specs.precision_ref, ctx); break; case 'L': + if (arg_type == type::none_type) return begin; enter_state(state::locale, is_arithmetic_type(arg_type)); specs.localized = true; ++begin; diff --git a/include/fmt/format.h b/include/fmt/format.h index 76dacc97..dd903b54 100644 --- a/include/fmt/format.h +++ b/include/fmt/format.h @@ -4222,13 +4222,35 @@ struct formatter> { template struct nested_formatter { private: + int width_; + detail::fill_t fill_; + align_t align_ : 4; formatter formatter_; - + public: FMT_CONSTEXPR auto parse(format_parse_context& ctx) -> const char* { + auto specs = detail::dynamic_format_specs(); + auto it = parse_format_specs( + ctx.begin(), ctx.end(), specs, ctx, detail::type::none_type); + width_ = specs.width; + fill_ = specs.fill; + align_ = specs.align; + ctx.advance_to(it); return formatter_.parse(ctx); } + template + auto write_padded(format_context& ctx, F write) const -> decltype(ctx.out()) { + if (width_ == 0) return write(ctx.out()); + auto buf = memory_buffer(); + write(std::back_inserter(buf)); + auto specs = format_specs<>(); + specs.width = width_; + specs.fill = fill_; + specs.align = align_; + return detail::write(ctx.out(), string_view(buf.data(), buf.size()), specs); + } + auto nested(const T& value) const -> nested_view { return nested_view{&formatter_, &value}; } diff --git a/test/format-test.cc b/test/format-test.cc index 5b99c5c7..47916f64 100644 --- a/test/format-test.cc +++ b/test/format-test.cc @@ -1779,22 +1779,25 @@ TEST(format_test, group_digits_view) { EXPECT_EQ(fmt::format("{:8}", fmt::group_digits(1000)), " 1,000"); } +#ifdef __cpp_generic_lambdas struct point { double x, y; }; FMT_BEGIN_NAMESPACE -template <> -struct formatter : nested_formatter { +template <> struct formatter : nested_formatter { auto format(point p, format_context& ctx) const -> decltype(ctx.out()) { - return format_to(ctx.out(), "({}, {})", nested(p.x), nested(p.y)); + return write_padded(ctx, [this, p](auto out) -> decltype(out) { + return format_to(out, "({}, {})", nested(p.x), nested(p.y)); + }); } }; FMT_END_NAMESPACE TEST(format_test, nested_formatter) { - EXPECT_EQ(fmt::format("{:.2f}", point{1, 2}), "(1.00, 2.00)"); + EXPECT_EQ(fmt::format("{:>16.2f}", point{1, 2}), " (1.00, 2.00)"); } +#endif // __cpp_generic_lambdas enum test_enum { foo, bar }; auto format_as(test_enum e) -> int { return e; }