diff --git a/fmt/format.h b/fmt/format.h index bcecc337..bd90e187 100644 --- a/fmt/format.h +++ b/fmt/format.h @@ -1796,7 +1796,7 @@ class basic_format_specs : public align_spec { public: unsigned flags_; int precision_; - char type_; + Char type_; basic_format_specs(unsigned width = 0, char type = 0, wchar_t fill = ' ') : align_spec(width, fill), flags_(0), precision_(-1), type_(type) {} @@ -1809,7 +1809,7 @@ class basic_format_specs : public align_spec { bool flag(unsigned f) const { return (flags_ & f) != 0; } int precision() const { return precision_; } - char type() const { return type_; } + Char type() const { return type_; } }; typedef basic_format_specs format_specs; @@ -3135,24 +3135,6 @@ unsigned parse_nonnegative_int(Iterator &it) { return value; } -inline void require_numeric_argument(Type type, char spec) { - if (!is_numeric(type)) { - FMT_THROW(fmt::format_error( - fmt::format("format specifier '{}' requires numeric argument", spec))); - } -} - -template -void check_sign(Iterator &it, Type type) { - char sign = static_cast(*it); - require_numeric_argument(type, sign); - if (is_integral(type) && type != INT && type != LONG_LONG && type != CHAR) { - FMT_THROW(format_error(fmt::format( - "format specifier '{}' requires signed argument", sign))); - } - ++it; -} - template class custom_formatter { private: @@ -3216,11 +3198,11 @@ struct precision_handler { } }; +// A format specifier handler that sets fields in basic_format_specs. template -class specs_handler_base { +class specs_setter { public: - explicit specs_handler_base(basic_format_specs &specs) - : specs_(specs) {} + explicit specs_setter(basic_format_specs &specs): specs_(specs) {} void on_align(alignment align) { specs_.align_ = align; } void on_fill(Char fill) { specs_.fill_ = fill; } @@ -3236,14 +3218,89 @@ class specs_handler_base { void on_width(unsigned width) { specs_.width_ = width; } void on_precision(unsigned precision) { specs_.precision_ = precision; } - void on_type(char type) { specs_.type_ = type; } + void end_precision() {} + + void on_type(Char type) { specs_.type_ = type; } protected: - ~specs_handler_base() {} + ~specs_setter() {} basic_format_specs &specs_; }; +// A format specifier handler that checks if specifiers are consistent with the +// argument type. +template +class specs_checker : public Handler { + public: + explicit specs_checker(const Handler& handler, Type arg_type) + : Handler(handler), arg_type_(arg_type) {} + + void on_align(alignment align) { + if (align == ALIGN_NUMERIC) + require_numeric_argument('='); + Handler::on_align(align); + } + + void on_plus() { + check_sign('+'); + Handler::on_plus(); + } + + void on_minus() { + check_sign('-'); + Handler::on_minus(); + } + + void on_space() { + check_sign(' '); + Handler::on_space(); + } + + void on_hash() { + require_numeric_argument('#'); + Handler::on_hash(); + } + + void on_zero() { + require_numeric_argument('0'); + Handler::on_zero(); + } + + void end_precision() { + if (is_integral(arg_type_) || arg_type_ == POINTER) { + report_error("precision not allowed in {} format specifier", + arg_type_ == POINTER ? "pointer" : "integer"); + } + } + + private: + template + static void report_error(string_view format_str, const Args &... args) { + FMT_THROW(format_error(format(format_str, args...))); + } + + template + void require_numeric_argument(Char spec) const { + if (!is_numeric(arg_type_)) { + report_error("format specifier '{}' requires numeric argument", + static_cast(spec)); + } + } + + template + void check_sign(Char sign) const { + require_numeric_argument(sign); + if (is_integral(arg_type_) && arg_type_ != INT && arg_type_ != LONG_LONG && + arg_type_ != CHAR) { + report_error("format specifier '{}' requires signed argument", + static_cast(sign)); + } + } + + Type arg_type_; +}; + template inline void set_dynamic_spec(T &value, basic_arg arg) { unsigned long long big_value = visit(Handler(), arg); @@ -3252,13 +3309,16 @@ inline void set_dynamic_spec(T &value, basic_arg arg) { value = static_cast(big_value); } +struct auto_id {}; + +// The standard format specifier handler with checking. template -class specs_handler : public specs_handler_base { +class specs_handler: public specs_setter { public: typedef typename Context::char_type char_type; specs_handler(basic_format_specs &specs, Context &ctx) - : specs_handler_base(specs), context_(ctx) {} + : specs_setter(specs), context_(ctx) {} template void on_dynamic_width(Id arg_id) { @@ -3273,7 +3333,7 @@ class specs_handler : public specs_handler_base { } private: - basic_arg get_arg(monostate) { + basic_arg get_arg(auto_id) { return context_.next_arg(); } @@ -3301,6 +3361,9 @@ struct arg_ref { }; }; +// Format specifiers with width and precision resolved at formatting rather +// than parsing time to allow re-using the same parsed specifiers with +// differents sets of arguments (precompilation of format strings). template struct dynamic_format_specs : basic_format_specs { arg_ref width_ref; @@ -3308,10 +3371,10 @@ struct dynamic_format_specs : basic_format_specs { }; template -class dynamic_specs_handler : public specs_handler_base { +class dynamic_specs_handler: public specs_setter { public: - explicit dynamic_specs_handler(dynamic_format_specs &specs) - : specs_handler_base(specs), specs_(specs) {} + dynamic_specs_handler(dynamic_format_specs &specs) + : specs_setter(specs), specs_(specs) {} template void on_dynamic_width(Id arg_id) { @@ -3329,7 +3392,7 @@ class dynamic_specs_handler : public specs_handler_base { ref = arg_ref(arg_id); } - void set(arg_ref &ref, monostate) { + void set(arg_ref &ref, auto_id) { ref.kind = arg_ref::NONE; } @@ -3367,7 +3430,7 @@ Iterator parse_arg_id(Iterator it, Handler handler) { // characters, possibly emulated via null_terminating_iterator, representing // format specifiers. template -Iterator parse_format_specs(Iterator it, Type arg_type, Handler &handler) { +Iterator parse_format_specs(Iterator it, Handler &handler) { typedef typename Iterator::value_type char_type; // Parse fill and alignment. if (char_type c = *it) { @@ -3397,8 +3460,6 @@ Iterator parse_format_specs(Iterator it, Type arg_type, Handler &handler) { it += 2; handler.on_fill(c); } else ++it; - if (align == ALIGN_NUMERIC) - require_numeric_argument(arg_type, '='); break; } } while (--p >= it); @@ -3407,28 +3468,26 @@ Iterator parse_format_specs(Iterator it, Type arg_type, Handler &handler) { // Parse sign. switch (*it) { case '+': - check_sign(it, arg_type); handler.on_plus(); + ++it; break; case '-': - check_sign(it, arg_type); handler.on_minus(); + ++it; break; case ' ': - check_sign(it, arg_type); handler.on_space(); + ++it; break; } if (*it == '#') { - require_numeric_argument(arg_type, '#'); handler.on_hash(); ++it; } // Parse zero flag. if (*it == '0') { - require_numeric_argument(arg_type, '0'); handler.on_zero(); ++it; } @@ -3440,7 +3499,7 @@ Iterator parse_format_specs(Iterator it, Type arg_type, Handler &handler) { struct width_handler { explicit width_handler(Handler &h) : handler(h) {} - void operator()() { handler.on_dynamic_width(monostate()); } + void operator()() { handler.on_dynamic_width(auto_id()); } void operator()(unsigned id) { handler.on_dynamic_width(id); } void operator()(basic_string_view id) { handler.on_dynamic_width(id); @@ -3462,7 +3521,7 @@ Iterator parse_format_specs(Iterator it, Type arg_type, Handler &handler) { struct precision_handler { explicit precision_handler(Handler &h) : handler(h) {} - void operator()() { handler.on_dynamic_precision(monostate()); } + void operator()() { handler.on_dynamic_precision(auto_id()); } void operator()(unsigned id) { handler.on_dynamic_precision(id); } void operator()(basic_string_view id) { handler.on_dynamic_precision(id); @@ -3476,16 +3535,12 @@ Iterator parse_format_specs(Iterator it, Type arg_type, Handler &handler) { } else { FMT_THROW(format_error("missing precision specifier")); } - if (is_integral(arg_type) || arg_type == POINTER) { - FMT_THROW(format_error( - fmt::format("precision not allowed in {} format specifier", - arg_type == POINTER ? "pointer" : "integer"))); - } + handler.end_precision(); } // Parse type. if (*it != '}' && *it) - handler.on_type(static_cast(*it++)); + handler.on_type(*it++); return it; } @@ -3501,8 +3556,9 @@ const Char *do_format_arg(basic_buffer &buffer, format.remove_prefix(1); if (visit(custom_formatter(buffer, format, ctx), arg)) return begin(format); - specs_handler handler(specs, ctx); - it = parse_format_specs(it + 1, arg.type(), handler); + specs_checker> + handler(specs_handler(specs, ctx), arg.type()); + it = parse_format_specs(it + 1, handler); } if (*it != '}') @@ -3522,6 +3578,23 @@ struct format_type : std::integral_constant() != CUSTOM> {}; // Specifies whether to format enums. template struct format_enum : std::integral_constant::value> {}; + +template +static void handle_dynamic_spec( + Spec &value, arg_ref ref, basic_context &ctx) { + switch (ref.kind) { + case arg_ref::NONE: + // Do nothing. + break; + case arg_ref::INDEX: + internal::set_dynamic_spec(value, ctx.get_arg(ref.index)); + break; + case arg_ref::NAME: + internal::set_dynamic_spec(value, ctx.get_arg(ref.name)); + break; + // TODO: handle automatic numbering + } +} } // namespace internal // Formatter of objects of type T. @@ -3534,15 +3607,17 @@ struct formatter< template auto parse(Range format) -> decltype(begin(format)) { auto it = internal::null_terminating_iterator(format); - internal::dynamic_specs_handler handler(specs_); - it = parse_format_specs(it, internal::gettype(), handler); + using handler_type = internal::dynamic_specs_handler; + internal::specs_checker + handler(handler_type(specs_), internal::gettype()); + it = parse_format_specs(it, handler); return pointer_from(it); } void format(basic_buffer &buf, const T &val, basic_context &ctx) { - handle_dynamic_spec( + internal::handle_dynamic_spec( specs_.width_, specs_.width_ref, ctx); - handle_dynamic_spec( + internal::handle_dynamic_spec( specs_.precision_, specs_.precision_ref, ctx); visit(arg_formatter(buf, ctx, specs_), internal::make_arg>(val)); @@ -3551,23 +3626,6 @@ struct formatter< private: using arg_ref = internal::arg_ref; - template - static void handle_dynamic_spec( - Spec &value, arg_ref ref, basic_context &ctx) { - switch (ref.kind) { - case arg_ref::NONE: - // Do nothing. - break; - case arg_ref::INDEX: - internal::set_dynamic_spec(value, ctx.get_arg(ref.index)); - break; - case arg_ref::NAME: - internal::set_dynamic_spec(value, ctx.get_arg(ref.name)); - break; - // TODO: handle automatic numbering - } - } - internal::dynamic_format_specs specs_; }; @@ -3581,6 +3639,69 @@ struct formatter; +// template <> +// struct formatter: dynamic_formatter<> { +// void format(buffer &buf, const variant &v, context &ctx) { +// visit([&](const auto &val) { format(buf, val, ctx); }, v); +// } +// }; +template +struct dynamic_formatter { + template + auto parse(Range format) -> decltype(begin(format)) { + auto it = internal::null_terminating_iterator(format); + // Checks are deferred to formatting time when the argument type is known. + internal::dynamic_specs_handler handler(specs_); + it = parse_format_specs(it, handler); + return pointer_from(it); + } + + template + void format(basic_buffer &buf, const T &val, basic_context &ctx) { + handle_specs(ctx); + struct null_handler { + void on_align(alignment) {} + void on_plus() {} + void on_minus() {} + void on_space() {} + void on_hash() {} + }; + internal::specs_checker + checker(null_handler(), internal::gettype()); + checker.on_align(specs_.align()); + if (specs_.flags_ == 0) { + // Do nothing. + } else if (specs_.flag(SIGN_FLAG)) { + if (specs_.flag(PLUS_FLAG)) + checker.on_plus(); + else + checker.on_space(); + } else if (specs_.flag(MINUS_FLAG)) { + checker.on_minus(); + } else if (specs_.flag(HASH_FLAG)) { + checker.on_hash(); + } + if (specs_.precision_ != -1) + checker.end_precision(); + visit(arg_formatter(buf, ctx, specs_), + internal::make_arg>(val)); + } + + private: + void handle_specs(basic_context &ctx) { + internal::handle_dynamic_spec( + specs_.width_, specs_.width_ref, ctx); + internal::handle_dynamic_spec( + specs_.precision_, specs_.precision_ref, ctx); + } + + internal::dynamic_format_specs specs_; +}; + template inline typename basic_context::format_arg basic_context::get_arg(basic_string_view name) { diff --git a/test/format-test.cc b/test/format-test.cc index 8ab7e46b..5a14950c 100644 --- a/test/format-test.cc +++ b/test/format-test.cc @@ -1536,3 +1536,42 @@ TEST(FormatTest, CustomArgFormatter) { TEST(FormatTest, NonNullTerminatedFormatString) { EXPECT_EQ("42", format(string_view("{}foo", 2), 42)); } + +struct variant { + enum {INT, STRING} type; + explicit variant(int) : type(INT) {} + explicit variant(const char *) : type(STRING) {} +}; + +namespace fmt { +template <> +struct formatter : dynamic_formatter<> { + void format(buffer& buf, variant value, context& ctx) { + if (value.type == variant::INT) + dynamic_formatter::format(buf, 42, ctx); + else + dynamic_formatter::format(buf, "foo", ctx); + } +}; +} + +TEST(FormatTest, DynamicFormatter) { + auto num = variant(42); + auto str = variant("foo"); + EXPECT_EQ("42", format("{:d}", num)); + EXPECT_EQ("foo", format("{:s}", str)); + EXPECT_THROW_MSG(format("{:=}", str), + format_error, "format specifier '=' requires numeric argument"); + EXPECT_THROW_MSG(format("{:+}", str), + format_error, "format specifier '+' requires numeric argument"); + EXPECT_THROW_MSG(format("{:-}", str), + format_error, "format specifier '-' requires numeric argument"); + EXPECT_THROW_MSG(format("{: }", str), + format_error, "format specifier ' ' requires numeric argument"); + EXPECT_THROW_MSG(format("{:#}", str), + format_error, "format specifier '#' requires numeric argument"); + EXPECT_THROW_MSG(format("{:0}", str), + format_error, "format specifier '=' requires numeric argument"); + EXPECT_THROW_MSG(format("{:.2}", num), + format_error, "precision not allowed in integer format specifier"); +}