From bc070331c49410623783b274ce6c0d34c537c3ff Mon Sep 17 00:00:00 2001 From: js324 Date: Thu, 7 Mar 2024 13:51:46 -0500 Subject: [PATCH] Support character range formatting (#3863) --- include/fmt/ranges.h | 62 +++++++++++++++++++++++++++++++++++++++----- test/ranges-test.cc | 5 ++++ 2 files changed, 61 insertions(+), 6 deletions(-) diff --git a/include/fmt/ranges.h b/include/fmt/ranges.h index 03eb5184882d..af3609c0c6d1 100644 --- a/include/fmt/ranges.h +++ b/include/fmt/ranges.h @@ -13,7 +13,7 @@ #include #include -#include "base.h" +#include "format.h" FMT_BEGIN_NAMESPACE @@ -388,6 +388,8 @@ struct range_formatter< detail::string_literal{}; basic_string_view closing_bracket_ = detail::string_literal{}; + bool is_string_format = false; + bool is_debug = false; public: FMT_CONSTEXPR range_formatter() {} @@ -410,31 +412,79 @@ struct range_formatter< FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) { auto it = ctx.begin(); auto end = ctx.end(); + detail::maybe_set_debug_format(underlying_, true); + if (it == end) { + return underlying_.parse(ctx); + } - if (it != end && *it == 'n') { + switch (detail::to_ascii(*it)) { + case 'n': + set_brackets({}, {}); + ++it; + break; + case '?': + is_debug = true; set_brackets({}, {}); ++it; + if (it == end || *it != 's') { + report_error("invalid format specifier"); + } + FMT_FALLTHROUGH; + case 's': + if (!std::is_same::value) { + report_error("invalid format specifier"); + } + if (!is_debug) { + set_brackets(detail::string_literal{}, + detail::string_literal{}); + set_separator({}); + detail::maybe_set_debug_format(underlying_, false); + } + is_string_format = true; + ++it; + return it; } if (it != end && *it != '}') { if (*it != ':') report_error("invalid format specifier"); + detail::maybe_set_debug_format(underlying_, false); ++it; - } else { - detail::maybe_set_debug_format(underlying_, true); } ctx.advance_to(it); return underlying_.parse(ctx); } + template ::value)> + auto write_debug_string(Output& out, Iter& it, IterEnd& end) const -> Output { + auto buf = basic_memory_buffer(); + for (; it != end; ++it) { + buf.push_back(*it); + } + format_specs spec_str; + spec_str.type = presentation_type::debug; + return detail::write( + out, basic_string_view(buf.data(), buf.size()), spec_str); + } + template ::value)> + auto write_debug_string(Output& out, Iter&, IterEnd&) const -> Output { + return out; + } + template auto format(R&& range, FormatContext& ctx) const -> decltype(ctx.out()) { detail::range_mapper> mapper; auto out = ctx.out(); - out = detail::copy(opening_bracket_, out); - int i = 0; auto it = detail::range_begin(range); auto end = detail::range_end(range); + if (is_debug) { + return write_debug_string(out, it, end); + } + + out = detail::copy(opening_bracket_, out); + int i = 0; for (; it != end; ++it) { if (i > 0) out = detail::copy(separator_, out); ctx.advance_to(out); diff --git a/test/ranges-test.cc b/test/ranges-test.cc index 74cbc6194c0f..db86e4161d90 100644 --- a/test/ranges-test.cc +++ b/test/ranges-test.cc @@ -58,8 +58,13 @@ TEST(ranges_test, format_vector) { EXPECT_EQ(fmt::format("{:n:#x}", v), "0x1, 0x2, 0x3, 0x5, 0x7, 0xb"); auto vc = std::vector{'a', 'b', 'c'}; + auto vec = std::vector{'a', '\n', '\t'}; auto vvc = std::vector>{vc, vc}; EXPECT_EQ(fmt::format("{}", vc), "['a', 'b', 'c']"); + EXPECT_EQ(fmt::format("{:s}", vc), "\"abc\""); + EXPECT_EQ(fmt::format("{:?s}", vec), "\"a\\n\\t\""); + EXPECT_EQ(fmt::format("{:s}", vec), "\"a\n\t\""); + EXPECT_EQ(fmt::format("{::s}", vvc), "[\"abc\", \"abc\"]"); EXPECT_EQ(fmt::format("{}", vvc), "[['a', 'b', 'c'], ['a', 'b', 'c']]"); EXPECT_EQ(fmt::format("{:n}", vvc), "['a', 'b', 'c'], ['a', 'b', 'c']"); EXPECT_EQ(fmt::format("{:n:n}", vvc), "'a', 'b', 'c', 'a', 'b', 'c'");