From 8d70c0edab4b2f68f52ab0644bab8e80dee7a604 Mon Sep 17 00:00:00 2001 From: Victor Zverovich Date: Mon, 17 May 2021 15:40:59 -0700 Subject: [PATCH] Refactor the format API --- include/fmt/core.h | 53 ++++++------ include/fmt/format.h | 187 +++++++------------------------------------ include/fmt/printf.h | 28 +++---- 3 files changed, 65 insertions(+), 203 deletions(-) diff --git a/include/fmt/core.h b/include/fmt/core.h index c74d44d2..ced03272 100644 --- a/include/fmt/core.h +++ b/include/fmt/core.h @@ -2439,26 +2439,17 @@ FMT_CONSTEXPR void check_int_type_spec(char spec, ErrorHandler&& eh) { } } -template -class char_specs_checker : public ErrorHandler { - private: - char type_; - - public: - FMT_CONSTEXPR char_specs_checker(char type, ErrorHandler eh) - : ErrorHandler(eh), type_(type) {} - - FMT_CONSTEXPR void on_int() { check_int_type_spec(type_, *this); } - FMT_CONSTEXPR void on_char() {} -}; - -template -FMT_CONSTEXPR void handle_char_specs(const basic_format_specs& specs, - Handler&& handler) { - if (specs.type && specs.type != 'c') return handler.on_int(); +// Checks char specs and returns true if the type spec is char (and not int). +template +FMT_CONSTEXPR bool check_char_specs(const basic_format_specs& specs, + ErrorHandler&& eh) { + if (specs.type && specs.type != 'c') { + check_int_type_spec(specs.type, eh); + return false; + } if (specs.align == align::numeric || specs.sign != sign::none || specs.alt) - handler.on_error("invalid format specifier for char"); - handler.on_char(); + eh.on_error("invalid format specifier for char"); + return true; } // A floating-point presentation format. @@ -2789,8 +2780,7 @@ struct formatter(specs_.type, eh)); + detail::check_char_specs(specs_, eh); break; case detail::type::float_type: if (detail::const_check(FMT_USE_FLOAT)) @@ -2919,10 +2909,13 @@ FMT_INLINE auto vformat( } #if FMT_COMPILE_TIME_CHECKS -template struct format_string { - string_view str; +template class basic_format_string { + private: + basic_string_view str; - template consteval format_string(const char (&s)[N]) : str(s) { + public: + template + consteval basic_format_string(const char (&s)[N]) : str(s) { if constexpr (detail::count_named_args() == 0) { using checker = detail::format_string_checker...>; @@ -2932,13 +2925,17 @@ template struct format_string { template )> - format_string(const T& s) : str(s) {} + basic_format_string(const T& s) : str(s) {} + + operator basic_string_view() const { return str; } }; template -FMT_INLINE std::string format( - format_string...> format_str, Args&&... args) { - return detail::vformat(format_str.str, make_format_args(args...)); +using format_string = basic_format_string...>; + +template +FMT_INLINE auto format(format_string str, T&&... args) -> std::string { + return detail::vformat(str, make_format_args(args...)); } #endif diff --git a/include/fmt/format.h b/include/fmt/format.h index f86afcf2..aefad1be 100644 --- a/include/fmt/format.h +++ b/include/fmt/format.h @@ -346,6 +346,8 @@ template using checked_ptr = T*; template inline T* make_checked(T* p, size_t) { return p; } #endif +// Attempts to reserve space for n extra characters in the output range. +// Returns a pointer to the reserved range or a reference to it. template ::value)> #if FMT_CLANG_VERSION >= 307 && !FMT_ICC_VERSION __attribute__((no_sanitize("undefined"))) @@ -1304,7 +1306,7 @@ template FMT_CONSTEXPR OutputIt write(OutputIt out, Char value, const basic_format_specs& specs, locale_ref loc = {}) { - return !specs.type || specs.type == 'c' + return check_char_specs(specs, error_handler()) ? write_char(out, value, specs) : write(out, static_cast(value), specs, loc); } @@ -1807,7 +1809,8 @@ inline OutputIt write(OutputIt out, T value) { } template -OutputIt write(OutputIt out, monostate) { +OutputIt write(OutputIt out, monostate, basic_format_specs = {}, + locale_ref = {}) { FMT_ASSERT(false, ""); return out; } @@ -1927,152 +1930,32 @@ template struct default_arg_formatter { basic_format_args args; locale_ref loc; - template OutputIt operator()(T value) { + template auto operator()(T value) -> OutputIt { return write(out, value); } - - OutputIt operator()(typename basic_format_arg::handle handle) { + auto operator()(typename basic_format_arg::handle h) -> OutputIt { basic_format_parse_context parse_ctx({}); basic_format_context format_ctx(out, args, loc); - handle.format(parse_ctx, format_ctx); + h.format(parse_ctx, format_ctx); return format_ctx.out(); } }; -template -class arg_formatter_base { - public: - using iterator = OutputIt; - using char_type = Char; - using format_specs = basic_format_specs; +template struct arg_formatter { + using context = basic_format_context; - private: - iterator out_; - const format_specs& specs_; - locale_ref locale_; + OutputIt out; + const basic_format_specs& specs; + locale_ref locale; - // Attempts to reserve space for n extra characters in the output range. - // Returns a pointer to the reserved range or a reference to out_. - auto reserve(size_t n) -> decltype(detail::reserve(out_, n)) { - return detail::reserve(out_, n); + template + FMT_CONSTEXPR FMT_INLINE auto operator()(T value) -> OutputIt { + return detail::write(out, value, specs, locale); } - - void write(char value) { - auto&& it = reserve(1); - *it++ = value; - } - - template ::value)> - void write(Ch value) { - out_ = detail::write(out_, value); - } - - void write(string_view value) { - auto&& it = reserve(value.size()); - it = copy_str(value.begin(), value.end(), it); - } - void write(wstring_view value) { - static_assert(std::is_same::value, ""); - auto&& it = reserve(value.size()); - it = copy_str(value.begin(), value.end(), it); - } - - template - void write(const Ch* s, size_t size, const format_specs& specs) { - auto width = - specs.width != 0 ? compute_width(basic_string_view(s, size)) : 0; - out_ = write_padded(out_, specs, size, width, - [=](reserve_iterator it) { - return copy_str(s, s + size, it); - }); - } - - template - FMT_CONSTEXPR void write(basic_string_view s, - const format_specs& specs = {}) { - out_ = detail::write(out_, s, specs); - } - - struct char_spec_handler : ErrorHandler { - arg_formatter_base& formatter; - Char value; - - constexpr char_spec_handler(arg_formatter_base& f, Char val) - : formatter(f), value(val) {} - - FMT_CONSTEXPR void on_int() { - // char is only formatted as int if there are specs. - formatter.out_ = detail::write(formatter.out_, static_cast(value), - formatter.specs_, formatter.locale_); - } - FMT_CONSTEXPR void on_char() { - formatter.out_ = - detail::write(formatter.out_, value, formatter.specs_); - } - }; - - protected: - iterator out() { return out_; } - const format_specs& specs() { return specs_; } - - FMT_CONSTEXPR void write(bool value) { - write(string_view(value ? "true" : "false"), specs_); - } - - void write(const Char* value) { - if (value) - write(basic_string_view(value), specs_); - else - FMT_THROW(format_error("string pointer is null")); - } - - public: - constexpr arg_formatter_base(OutputIt out, const format_specs& s, - locale_ref loc) - : out_(out), specs_(s), locale_(loc) {} - - iterator operator()(monostate) { - FMT_ASSERT(false, "invalid argument type"); - return out_; - } - - template ::value)> - FMT_CONSTEXPR FMT_INLINE iterator operator()(T value) { - return out_ = detail::write(out_, value, specs_, locale_); - } - - FMT_CONSTEXPR iterator operator()(Char value) { - handle_char_specs(specs_, - char_spec_handler(*this, static_cast(value))); - return out_; - } - - FMT_CONSTEXPR iterator operator()(bool value) { - if (specs_.type && specs_.type != 's') return (*this)(value ? 1 : 0); - write(value != 0); - return out_; - } - - template ::value)> - iterator operator()(T value) { - if (const_check(is_supported_floating_point(value))) - out_ = detail::write(out_, value, specs_, locale_); - else - FMT_ASSERT(false, "unsupported float argument type"); - return out_; - } - - iterator operator()(const Char* value) { - return out_ = detail::write(out_, value, specs_, locale_); - } - - FMT_CONSTEXPR iterator operator()(basic_string_view value) { - return out_ = detail::write(out_, value, specs_, locale_); - } - - iterator operator()(const void* value) { - return out_ = detail::write(out_, value, specs_, locale_); + auto operator()(typename basic_format_arg::handle) -> OutputIt { + // User-defined types are handled separately because they require access + // to the parse context. + return out; } }; @@ -2247,25 +2130,9 @@ struct format_handler : detail::error_handler { arg.type()); begin = parse_format_specs(begin, end, handler); if (begin == end || *begin != '}') on_error("missing '}' in format string"); - - using arg_formatter_base = detail::arg_formatter_base; - struct arg_formatter : arg_formatter_base { - Context& ctx_; - - FMT_CONSTEXPR explicit arg_formatter( - Context& ctx, const typename arg_formatter_base::format_specs& specs) - : arg_formatter_base(ctx.out(), specs, ctx.locale()), ctx_(ctx) {} - - using arg_formatter_base::operator(); - - auto operator()(typename basic_format_arg::handle) -> - typename arg_formatter_base::iterator { - // User-defined types are handled separately because they require access - // to the parse context. - return ctx_.out(); - } - }; - context.advance_to(visit_format_arg(arg_formatter(context, specs), arg)); + auto f = detail::arg_formatter{context.out(), specs, + context.locale()}; + context.advance_to(visit_format_arg(f, arg)); return begin; } }; @@ -2631,8 +2498,8 @@ struct formatter, Char> { }; /** - Returns an object that formats the iterator range `[begin, end)` with elements - separated by `sep`. + Returns an object that formats the iterator range `[begin, end)` with + elements separated by `sep`. */ template arg_join join(It begin, Sentinel end, string_view sep) { @@ -2692,8 +2559,8 @@ inline std::string to_string(const T& value) { template ::value)> inline std::string to_string(T value) { - // The buffer should be large enough to store the number including the sign or - // "false" for bool. + // The buffer should be large enough to store the number including the sign + // or "false" for bool. constexpr int max_size = detail::digits10() + 2; char buffer[max_size > 5 ? static_cast(max_size) : 5]; char* begin = buffer; diff --git a/include/fmt/printf.h b/include/fmt/printf.h index e16d0b4f..f08a02c9 100644 --- a/include/fmt/printf.h +++ b/include/fmt/printf.h @@ -191,33 +191,32 @@ template class printf_width_handler { // The ``printf`` argument formatter. template -class printf_arg_formatter : public detail::arg_formatter_base { +class printf_arg_formatter : public arg_formatter { private: - using base = detail::arg_formatter_base; + using base = arg_formatter; using context_type = basic_printf_context; - using format_specs = typename base::format_specs; + using format_specs = basic_format_specs; context_type& context_; OutputIt write_null_pointer(bool is_string = false) { - auto s = this->specs(); + auto s = this->specs; s.type = 0; - return detail::write(this->out(), - string_view(is_string ? "(null)" : "(nil)"), s); + return write(this->out, string_view(is_string ? "(null)" : "(nil)"), s); } public: - printf_arg_formatter(OutputIt iter, format_specs& specs, context_type& ctx) - : base(iter, specs, detail::locale_ref()), context_(ctx) {} + printf_arg_formatter(OutputIt iter, format_specs& s, context_type& ctx) + : base{iter, s, locale_ref()}, context_(ctx) {} OutputIt operator()(monostate value) { return base::operator()(value); } - template ::value)> + template ::value)> OutputIt operator()(T value) { // MSVC2013 fails to compile separate overloads for bool and Char so use // std::is_same instead. if (std::is_same::value) { - format_specs fmt_specs = this->specs(); + format_specs fmt_specs = this->specs; if (fmt_specs.type && fmt_specs.type != 'c') return (*this)(static_cast(value)); fmt_specs.sign = sign::none; @@ -227,8 +226,7 @@ class printf_arg_formatter : public detail::arg_formatter_base { // ignored for non-numeric types if (fmt_specs.align == align::none || fmt_specs.align == align::numeric) fmt_specs.align = align::right; - return detail::write(this->out(), static_cast(value), - fmt_specs); + return write(this->out, static_cast(value), fmt_specs); } return base::operator()(value); } @@ -241,13 +239,13 @@ class printf_arg_formatter : public detail::arg_formatter_base { /** Formats a null-terminated C string. */ OutputIt operator()(const char* value) { if (value) return base::operator()(value); - return write_null_pointer(this->specs().type != 'p'); + return write_null_pointer(this->specs.type != 'p'); } /** Formats a null-terminated wide C string. */ OutputIt operator()(const wchar_t* value) { if (value) return base::operator()(value); - return write_null_pointer(this->specs().type != 'p'); + return write_null_pointer(this->specs.type != 'p'); } OutputIt operator()(basic_string_view value) { @@ -262,7 +260,7 @@ class printf_arg_formatter : public detail::arg_formatter_base { /** Formats an argument of a custom (user-defined) type. */ OutputIt operator()(typename basic_format_arg::handle handle) { handle.format(context_.parse_context(), context_); - return this->out(); + return this->out; } };