diff --git a/include/fmt/format.h b/include/fmt/format.h index 9b4249da..af002718 100644 --- a/include/fmt/format.h +++ b/include/fmt/format.h @@ -1771,8 +1771,8 @@ using wparse_context = basic_parse_context; namespace internal { template -constexpr void handle_integral_type_spec(char c, Handler &&handler) { - switch (c) { +constexpr void handle_int_type_spec(char spec, Handler &&handler) { + switch (spec) { case 0: case 'd': handler.on_dec(); break; @@ -1793,6 +1793,27 @@ constexpr void handle_integral_type_spec(char c, Handler &&handler) { } } +template +constexpr void handle_float_type_spec(char spec, Handler &&handler) { + switch (spec) { + case 0: case 'g': case 'G': + handler.on_general(); + break; + case 'e': case 'E': + handler.on_exp(); + break; + case 'f': case 'F': + handler.on_fixed(); + break; + case 'a': case 'A': + handler.on_hex(); + break; + default: + handler.on_error(); + break; + } +} + template class int_type_checker : private ErrorHandler { public: @@ -1809,6 +1830,21 @@ class int_type_checker : private ErrorHandler { } }; +template +class float_type_checker : private ErrorHandler { + public: + constexpr float_type_checker(ErrorHandler eh) : ErrorHandler(eh) {} + + constexpr void on_general() {} + constexpr void on_exp() {} + constexpr void on_fixed() {} + constexpr void on_hex() {} + + constexpr void on_error() { + ErrorHandler::on_error("invalid type specifier"); + } +}; + template class arg_map { private: @@ -3256,35 +3292,52 @@ void basic_writer::write_int(T value, const Spec& spec) { spec.type(), spec.flag(CHAR_FLAG) ? "char" : "integer"); } }; - internal::handle_integral_type_spec( - spec.type(), spec_handler(*this, value, spec)); + internal::handle_int_type_spec(spec.type(), spec_handler(*this, value, spec)); } template template void basic_writer::write_double(T value, const format_specs &spec) { // Check type. - char type = spec.type(); - bool upper = false; - switch (type) { - case 0: - type = 'g'; - break; - case 'e': case 'f': case 'g': case 'a': - break; - case 'F': + struct spec_handler { + char type; + bool upper = false; + + explicit spec_handler(char t) : type(t) {} + + void on_general() { + if (type == 'G') + upper = true; + else + type = 'g'; + } + + void on_exp() { + if (type == 'E') + upper = true; + } + + void on_fixed() { + if (type == 'F') { + upper = true; #if FMT_MSC_VER - // MSVC's printf doesn't support 'F'. - type = 'f'; + // MSVC's printf doesn't support 'F'. + type = 'f'; #endif - // Fall through. - case 'E': case 'G': case 'A': - upper = true; - break; - default: - internal::report_unknown_type(type, "double"); - break; - } + } + } + + void on_hex() { + if (type == 'A') + upper = true; + } + + void on_error() { + internal::report_unknown_type(type, "double"); + } + }; + spec_handler handler(spec.type()); + internal::handle_float_type_spec(spec.type(), handler); char sign = 0; // Use isnegative instead of value < 0 because the latter is always @@ -3300,7 +3353,7 @@ void basic_writer::write_double(T value, const format_specs &spec) { // Format NaN ourselves because sprintf's output is not consistent // across platforms. std::size_t nan_size = 4; - const char *nan = upper ? " NAN" : " nan"; + const char *nan = handler.upper ? " NAN" : " nan"; if (!sign) { --nan_size; ++nan; @@ -3315,7 +3368,7 @@ void basic_writer::write_double(T value, const format_specs &spec) { // Format infinity ourselves because sprintf's output is not consistent // across platforms. std::size_t inf_size = 4; - const char *inf = upper ? " INF" : " inf"; + const char *inf = handler.upper ? " INF" : " inf"; if (!sign) { --inf_size; ++inf; @@ -3357,7 +3410,7 @@ void basic_writer::write_double(T value, const format_specs &spec) { } append_float_length(format_ptr, value); - *format_ptr++ = type; + *format_ptr++ = handler.type; *format_ptr = '\0'; // Format using snprintf. @@ -3729,13 +3782,43 @@ struct formatter< constexpr typename ParseContext::iterator parse(ParseContext &ctx) { auto it = internal::null_terminating_iterator(ctx); using handler_type = internal::dynamic_specs_handler; + auto type = internal::get_type(); internal::specs_checker - handler(handler_type(specs_, ctx), internal::get_type()); + handler(handler_type(specs_, ctx), type); it = parse_format_specs(it, handler); - if (std::is_integral::value) { - auto eh = ctx.error_handler(); - handle_integral_type_spec( + auto eh = ctx.error_handler(); + switch (type) { + case internal::NONE: + case internal::NAMED_ARG: + FMT_ASSERT(false, "invalid argument type"); + break; + case internal::INT: + case internal::UINT: + case internal::LONG_LONG: + case internal::ULONG_LONG: + case internal::BOOL: + handle_int_type_spec( specs_.type(), internal::int_type_checker(eh)); + break; + case internal::CHAR: + // TODO + break; + case internal::DOUBLE: + case internal::LONG_DOUBLE: + handle_float_type_spec( + specs_.type(), internal::float_type_checker(eh)); + break; + case internal::CSTRING: + case internal::STRING: + // TODO + break; + case internal::POINTER: + // TODO + break; + case internal::CUSTOM: + // Custom format specifiers should be checked in parse functions of + // formatter specializations. + break; } return pointer_from(it); } diff --git a/test/format-test.cc b/test/format-test.cc index afba7aa2..d812ff56 100644 --- a/test/format-test.cc +++ b/test/format-test.cc @@ -1844,8 +1844,8 @@ constexpr size_t len(const char *s) { } constexpr bool equal(const char *s1, const char *s2) { - if (!s1 && !s2) - return true; + if (!s1 || !s2) + return s1 == s2; while (*s1 && *s1 == *s2) { ++s1; ++s2; @@ -1892,6 +1892,8 @@ TEST(FormatTest, FormatStringErrors) { EXPECT_ERROR("{: }", "format specifier requires signed argument", unsigned); EXPECT_ERROR("{:.2}", "precision not allowed for this argument type", int); EXPECT_ERROR("{:s}", "invalid type specifier", int); + EXPECT_ERROR("{:s}", "invalid type specifier", bool); + EXPECT_ERROR("{:s}", "invalid type specifier", double); #endif EXPECT_ERROR("{foo", "missing '}' in format string", int); EXPECT_ERROR("{10000000000}", "number is too big");