diff --git a/doc/DESIGN.md b/doc/DESIGN.md index 2f83e56f..d4cd942e 100644 --- a/doc/DESIGN.md +++ b/doc/DESIGN.md @@ -547,7 +547,8 @@ The productions `fill-and-align`, `sign`, `width`, and `precision` are described `precision` specification in the `units-format-spec` is valid only for `units::quantity` types where the representation type `Rep` is a floating point type. For all other `Rep` types, an exception of type `format_error` is thrown if the `units-format-spec` contains a precision -specification. +specification. An `format_error` is also thrown if `sign` is provided with a `conversion-spec` +to print quantity unit but not its value. Each conversion specifier `conversion-spec` is replaced by appropriate characters as described in the following table: @@ -560,7 +561,6 @@ in the following table: | `%t` | A horizontal-tab character | | `%%` | A `%` character | - If the `units-specs` is omitted, the `quantity` object is formatted as if by streaming it to `std::ostringstream os` and copying `os.str()` through the output iterator of the context with additional padding and adjustments as specified by the format specifiers. diff --git a/src/include/units/format.h b/src/include/units/format.h index d35535c2..1bbea90d 100644 --- a/src/include/units/format.h +++ b/src/include/units/format.h @@ -25,13 +25,14 @@ #include #include #include +#include namespace units { namespace detail { // units-format-spec: - // fill-and-align[opt] width[opt] precision[opt] units-specs[opt] + // fill-and-align[opt] sign[opt] width[opt] precision[opt] units-specs[opt] // units-specs: // conversion-spec // units-specs conversion-spec @@ -94,11 +95,25 @@ namespace units { } template - inline OutputIt format_units_quantity_value(OutputIt out, const Rep& val, int precision) + inline OutputIt format_units_quantity_value(OutputIt out, const Rep& val, fmt::sign_t sign, int precision) { + std::string sign_text; + switch(sign) { + case fmt::sign::none: + break; + case fmt::sign::plus: + sign_text = "+"; + break; + case fmt::sign::minus: + sign_text = "-"; + break; + case fmt::sign::space: + sign_text = " "; + break; + } if(precision >= 0) - return format_to(out, "{:.{}f}", val, precision); - return format_to(out, treat_as_floating_point ? "{:g}" : "{}", val); + return format_to(out, "{:" + sign_text + ".{}f}", val, precision); + return format_to(out, treat_as_floating_point ? "{:" + sign_text + "g}" : "{:" + sign_text + "}", val); } template @@ -111,10 +126,11 @@ namespace units { struct units_formatter { OutputIt out; Rep val; + fmt::sign_t sign; int precision; - explicit units_formatter(OutputIt o, quantity q, int prec): - out(o), val(q.count()), precision(prec) + explicit units_formatter(OutputIt o, quantity q, fmt::sign_t s, int prec): + out(o), val(q.count()), sign(s), precision(prec) { } @@ -126,7 +142,7 @@ namespace units { void on_quantity_value() { - out = format_units_quantity_value(out, val, precision); + out = format_units_quantity_value(out, val, sign, precision); } void on_quantity_unit() @@ -179,27 +195,30 @@ private: } void on_error(const char* msg) { throw fmt::format_error(msg); } - void on_fill(CharT fill) { f.specs.fill[0] = fill; } - void on_align(align_t align) { f.specs.align = align; } - void on_width(unsigned width) { f.specs.width = width; } - void on_precision(unsigned precision) { f.precision = precision; } - void end_precision() {} + constexpr void on_fill(CharT fill) { f.specs.fill[0] = fill; } + constexpr void on_plus() { f.specs.sign = fmt::sign::plus; } + constexpr void on_minus() { f.specs.sign = fmt::sign::minus; } + constexpr void on_space() { f.specs.sign = fmt::sign::space; } + constexpr void on_align(align_t align) { f.specs.align = align; } + constexpr void on_width(unsigned width) { f.specs.width = width; } + constexpr void on_precision(unsigned precision) { f.precision = precision; } + constexpr void end_precision() {} template - void on_dynamic_width(Id arg_id) + constexpr void on_dynamic_width(Id arg_id) { f.width_ref = make_arg_ref(arg_id); } template - void on_dynamic_precision(Id arg_id) + constexpr void on_dynamic_precision(Id arg_id) { f.precision_ref = make_arg_ref(arg_id); } - void on_text(const CharT*, const CharT*) {} - void on_quantity_value() { f.quantity_value = true; } - void on_quantity_unit() { f.quantity_unit = true; } + constexpr void on_text(const CharT*, const CharT*) {} + constexpr void on_quantity_value() { f.quantity_value = true; } + constexpr void on_quantity_unit() { f.quantity_unit = true; } }; struct parse_range { @@ -221,6 +240,24 @@ private: if(begin == end) return {begin, begin}; + // parse sign + switch(static_cast(*begin)) { + case '+': + handler.on_plus(); + ++begin; + break; + case '-': + handler.on_minus(); + ++begin; + break; + case ' ': + handler.on_space(); + ++begin; + break; + } + if(begin == end) + return {begin, begin}; + // parse width begin = fmt::internal::parse_width(begin, end, handler); if(begin == end) @@ -241,6 +278,9 @@ private: // quantity values should behave like numbers (by default aligned to right) specs.align = fmt::align_t::right; + if((quantity_unit && !quantity_value) && (specs.sign == fmt::sign::plus || specs.sign == fmt::sign::minus)) + handler.on_error("sign not allowed for a quantity unit"); + return {begin, end}; } @@ -268,13 +308,13 @@ public: // deal with quantity content if(begin == end || *begin == '}') { // default format should print value followed by the unit separeted with 1 space - out = units::detail::format_units_quantity_value(out, q.count(), precision); + out = units::detail::format_units_quantity_value(out, q.count(), specs.sign, precision); *out++ = CharT(' '); units::detail::format_units_quantity_unit(out); } else { // user provided format - units::detail::units_formatter f(out, q, precision); + units::detail::units_formatter f(out, q, specs.sign, precision); parse_units_format(begin, end, f); } diff --git a/test/unit_test/runtime/fmt_test.cpp b/test/unit_test/runtime/fmt_test.cpp index dcc46a7c..a214bb6f 100644 --- a/test/unit_test/runtime/fmt_test.cpp +++ b/test/unit_test/runtime/fmt_test.cpp @@ -755,25 +755,25 @@ TEST_CASE("sign specification", "[text][fmt]") SECTION("full format {:%Q %q} on a quantity") { - CHECK(fmt::format("{0:%Q%q},{0:+%Q%q},{0:-%Q%q},{0: %Q%q}", 1m) == "1 m,+1 m,1 m, 1 m"); - CHECK(fmt::format("{0:%Q%q},{0:+%Q%q},{0:-%Q%q},{0: %Q%q}", -1m) == "-1 m,-1 m,-1 m,-1 m"); - CHECK(fmt::format("{0:%Q%q},{0:+%Q%q},{0:-%Q%q},{0: %Q%q}", inf) == "inf m,+inf m,inf m, inf m"); - CHECK(fmt::format("{0:%Q%q},{0:+%Q%q},{0:-%Q%q},{0: %Q%q}", nan) == "nan m,+nan m,nan m, nan m"); + CHECK(fmt::format("{0:%Q%q},{0:+%Q%q},{0:-%Q%q},{0: %Q%q}", 1m) == "1m,+1m,1m, 1m"); + CHECK(fmt::format("{0:%Q%q},{0:+%Q%q},{0:-%Q%q},{0: %Q%q}", -1m) == "-1m,-1m,-1m,-1m"); + CHECK(fmt::format("{0:%Q%q},{0:+%Q%q},{0:-%Q%q},{0: %Q%q}", inf) == "infm,+infm,infm, infm"); + CHECK(fmt::format("{0:%Q%q},{0:+%Q%q},{0:-%Q%q},{0: %Q%q}", nan) == "nanm,+nanm,nanm, nanm"); } SECTION("value only format {:%Q} on a quantity") { - CHECK(fmt::format("{0:%Q},{0:+%Q},{0:-%Q},{0: %Q}", 1m) == "1 m,+1 m,1 m, 1 m"); - CHECK(fmt::format("{0:%Q},{0:+%Q},{0:-%Q},{0: %Q}", -1m) == "-1 m,-1 m,-1 m,-1 m"); - CHECK(fmt::format("{0:%Q},{0:+%Q},{0:-%Q},{0: %Q}", inf) == "inf m,+inf m,inf m, inf m"); - CHECK(fmt::format("{0:%Q},{0:+%Q},{0:-%Q},{0: %Q}", nan) == "nan m,+nan m,nan m, nan m"); + CHECK(fmt::format("{0:%Q},{0:+%Q},{0:-%Q},{0: %Q}", 1m) == "1,+1,1, 1"); + CHECK(fmt::format("{0:%Q},{0:+%Q},{0:-%Q},{0: %Q}", -1m) == "-1,-1,-1,-1"); + CHECK(fmt::format("{0:%Q},{0:+%Q},{0:-%Q},{0: %Q}", inf) == "inf,+inf,inf, inf"); + CHECK(fmt::format("{0:%Q},{0:+%Q},{0:-%Q},{0: %Q}", nan) == "nan,+nan,nan, nan"); } } TEST_CASE("sign specification for unit only", "[text][fmt][exception]") { - CHECK_THROWS_MATCHES(fmt::format("{:+%q}", 1m), fmt::format_error, Message("sign not allowed for quantity unit")); - CHECK_THROWS_MATCHES(fmt::format("{:-%q}", 1m), fmt::format_error, Message("sign not allowed for quantity unit")); + CHECK_THROWS_MATCHES(fmt::format("{:+%q}", 1m), fmt::format_error, Message("sign not allowed for a quantity unit")); + CHECK_THROWS_MATCHES(fmt::format("{:-%q}", 1m), fmt::format_error, Message("sign not allowed for a quantity unit")); }