From 9f70b034e1cbd39e6beee8de7cf0bafba8ee4f80 Mon Sep 17 00:00:00 2001 From: Daniela Engert Date: Sat, 12 Jan 2019 14:31:55 +0100 Subject: [PATCH] Implement precision for floating-point durations. The formatting syntax follows p1361r0, augmented by a precision field as proposed in #1004. Signed-off-by: Daniela Engert --- include/fmt/chrono.h | 51 ++++++++++++++++++++++++++++++++++---------- include/fmt/format.h | 39 +++++++++++++++++++-------------- test/chrono-test.cc | 29 +++++++++++++++++++++++++ 3 files changed, 92 insertions(+), 27 deletions(-) diff --git a/include/fmt/chrono.h b/include/fmt/chrono.h index 0d0e3486..c8bcbb71 100644 --- a/include/fmt/chrono.h +++ b/include/fmt/chrono.h @@ -362,8 +362,10 @@ template struct formatter, Char> { private: align_spec spec; + int precision; typedef internal::arg_ref arg_ref_type; arg_ref_type width_ref; + arg_ref_type precision_ref; mutable basic_string_view format_str; typedef std::chrono::duration duration; @@ -391,14 +393,36 @@ struct formatter, Char> { void on_fill(Char fill) { f.spec.fill_ = fill; } void on_align(alignment align) { f.spec.align_ = align; } void on_width(unsigned width) { f.spec.width_ = width; } + void on_precision(unsigned precision) { f.precision = precision; } + void end_precision() {} template void on_dynamic_width(Id arg_id) { f.width_ref = make_arg_ref(arg_id); } + + template void on_dynamic_precision(Id arg_id) { + f.precision_ref = make_arg_ref(arg_id); + } }; + template OutputIt format_value(OutputIt out, Rep val) { + if (precision < 0) + return format_to(out, "{}", val); + else + return format_to(out, "{:.{}f}", val, precision); + } + + template static void format_unit(OutputIt out) { + if (const char* unit = get_units()) + format_to(out, "{}", unit); + else if (Period::den == 1) + format_to(out, "[{}]s", Period::num); + else + format_to(out, "[{}/{}]s", Period::num, Period::den); + } + public: - formatter() : spec() {} + formatter() : spec(), precision(-1) {} FMT_CONSTEXPR auto parse(basic_parse_context& ctx) -> decltype(ctx.begin()) { @@ -408,6 +432,13 @@ struct formatter, Char> { begin = internal::parse_align(begin, end, handler); if (begin == end) return begin; begin = internal::parse_width(begin, end, handler); + if (begin == end) return begin; + if (*begin == '.') { + if (std::is_floating_point::value) + begin = internal::parse_precision(begin, end, handler); + else + handler.on_error("precision not allowed for this argument type"); + } end = parse_chrono_format(begin, end, internal::chrono_format_checker()); format_str = basic_string_view(&*begin, internal::to_unsigned(end - begin)); @@ -419,25 +450,23 @@ struct formatter, Char> { auto begin = format_str.begin(), end = format_str.end(); // As a possible future optimization, we could avoid extra copying if width // is not specified. - memory_buffer buf; + basic_memory_buffer buf; + auto out = std::back_inserter(buf); typedef output_range range; basic_writer w(range(ctx.out())); + internal::handle_dynamic_spec(spec.width_, + width_ref, ctx); if (begin == end || *begin == '}') { - if (const char* unit = get_units()) - format_to(buf, "{}{}", d.count(), unit); - else if (Period::den == 1) - format_to(buf, "{}[{}]s", d.count(), Period::num); - else - format_to(buf, "{}[{}/{}]s", d.count(), Period::num, Period::den); + internal::handle_dynamic_spec( + precision, precision_ref, ctx); + out = format_value(out, d.count()); + format_unit(out); } else { - auto out = std::back_inserter(buf); internal::chrono_formatter f(ctx, out); f.s = std::chrono::duration_cast(d); f.ms = std::chrono::duration_cast(d - f.s); parse_chrono_format(begin, end, f); } - internal::handle_dynamic_spec(spec.width_, - width_ref, ctx); w.write(buf.data(), buf.size(), spec); return w.out(); } diff --git a/include/fmt/format.h b/include/fmt/format.h index 3b84976a..1c05b0ea 100644 --- a/include/fmt/format.h +++ b/include/fmt/format.h @@ -1935,6 +1935,28 @@ FMT_CONSTEXPR const Char* parse_width(const Char* begin, const Char* end, return begin; } +template +FMT_CONSTEXPR const Char* parse_precision(const Char* begin, const Char* end, + Handler&& handler) { + ++begin; + auto c = begin != end ? *begin : 0; + if ('0' <= c && c <= '9') { + handler.on_precision(parse_nonnegative_int(begin, end, handler)); + } else if (c == '{') { + ++begin; + if (begin != end) { + begin = + parse_arg_id(begin, end, precision_adapter(handler)); + } + if (begin == end || *begin++ != '}') + return handler.on_error("invalid format string"), begin; + } else { + return handler.on_error("missing precision specifier"), begin; + } + handler.end_precision(); + return begin; +} + // Parses standard format specifiers and sends notifications about parsed // components to handler. template @@ -1978,22 +2000,7 @@ FMT_CONSTEXPR const Char* parse_format_specs(const Char* begin, const Char* end, // Parse precision. if (*begin == '.') { - ++begin; - auto c = begin != end ? *begin : 0; - if ('0' <= c && c <= '9') { - handler.on_precision(parse_nonnegative_int(begin, end, handler)); - } else if (c == '{') { - ++begin; - if (begin != end) { - begin = parse_arg_id(begin, end, - precision_adapter(handler)); - } - if (begin == end || *begin++ != '}') - return handler.on_error("invalid format string"), begin; - } else { - return handler.on_error("missing precision specifier"), begin; - } - handler.end_precision(); + begin = parse_precision(begin, end, handler); } // Parse type. diff --git a/test/chrono-test.cc b/test/chrono-test.cc index d6e9c1d3..b7aace4f 100644 --- a/test/chrono-test.cc +++ b/test/chrono-test.cc @@ -189,4 +189,33 @@ TEST(ChronoTest, Locale) { EXPECT_TIME("%p", time, sec); } +typedef std::chrono::duration dms; + +TEST(ChronoTest, FormatDefaultFP) { + typedef std::chrono::duration fs; + EXPECT_EQ("1.234s", fmt::format("{}", fs(1.234))); + typedef std::chrono::duration fms; + EXPECT_EQ("1.234ms", fmt::format("{}", fms(1.234))); + typedef std::chrono::duration ds; + EXPECT_EQ("1.234s", fmt::format("{}", ds(1.234))); + EXPECT_EQ("1.234ms", fmt::format("{}", dms(1.234))); +} + +TEST(ChronoTest, FormatPrecision) { + EXPECT_THROW_MSG(fmt::format("{:.2}", std::chrono::seconds(42)), + fmt::format_error, + "precision not allowed for this argument type"); + EXPECT_EQ("1.2ms", fmt::format("{:.1}", dms(1.234))); + EXPECT_EQ("1.23ms", fmt::format("{:.{}}", dms(1.234), 2)); +} + +TEST(ChronoTest, FormatFullSpecs) { + EXPECT_EQ("1.2ms ", fmt::format("{:6.1}", dms(1.234))); + EXPECT_EQ(" 1.23ms", fmt::format("{:>8.{}}", dms(1.234), 2)); + EXPECT_EQ(" 1.2ms ", fmt::format("{:^{}.{}}", dms(1.234), 7, 1)); + EXPECT_EQ(" 1.23ms ", fmt::format("{0:^{2}.{1}}", dms(1.234), 2, 8)); + EXPECT_EQ("=1.234ms=", fmt::format("{:=^{}.{}}", dms(1.234), 9, 3)); + EXPECT_EQ("*1.2340ms*", fmt::format("{:*^10.4}", dms(1.234), 10, 4)); +} + #endif // FMT_STATIC_THOUSANDS_SEPARATOR