From 5e0562ab51f1d5fd75ed7e38aa47524bb23b4df4 Mon Sep 17 00:00:00 2001 From: Victor Zverovich Date: Sun, 13 Aug 2017 13:09:02 -0700 Subject: [PATCH] Separate parsing and formatting --- fmt/format.h | 476 +++++++++++++++++++++------------- fmt/ostream.h | 30 ++- fmt/printf.cc | 4 +- fmt/printf.h | 53 ++-- fmt/time.h | 72 ++--- test/custom-formatter-test.cc | 4 +- test/format-test.cc | 24 +- test/ostream-test.cc | 2 +- test/util-test.cc | 54 ++-- 9 files changed, 449 insertions(+), 270 deletions(-) diff --git a/fmt/format.h b/fmt/format.h index d7dbf00c..907911fe 100644 --- a/fmt/format.h +++ b/fmt/format.h @@ -451,6 +451,11 @@ class basic_string_view { /** Returns the string size. */ std::size_t size() const { return size_; } + void remove_prefix(size_t n) { + data_ += n; + size_ -= n; + } + // Lexicographically compare this string reference to other. int compare(basic_string_view other) const { std::size_t size = size_ < other.size_ ? size_ : other.size_; @@ -483,6 +488,9 @@ class basic_string_view { typedef basic_string_view string_view; typedef basic_string_view wstring_view; +template +inline const Char *begin(basic_string_view s) { return s.data(); } + /** A formatting error such as invalid format string. */ class format_error : public std::runtime_error { public: @@ -496,8 +504,8 @@ class format_error : public std::runtime_error { }; // A formatter for objects of type T. -template -class formatter; +template +struct formatter; namespace internal { @@ -847,6 +855,109 @@ struct conditional { typedef T type; }; template struct conditional { typedef F type; }; +template +class null_terminating_iterator; + +template +const Char *pointer_from(null_terminating_iterator it); + +// An iterator that produces a null terminator on *end. This simplifies parsing +// and allows comparing the performance of processing a null-terminated string +// vs string_view. +template +class null_terminating_iterator { + public: + typedef Char value_type; + typedef std::ptrdiff_t difference_type; + + null_terminating_iterator() : ptr_(0), end_(0) {} + + null_terminating_iterator(const Char *ptr, const Char *end) + : ptr_(ptr), end_(end) {} + + explicit null_terminating_iterator(basic_string_view s) + : ptr_(s.data()), end_(s.data() + s.size()) {} + + null_terminating_iterator &operator=(const Char *ptr) { + assert(ptr <= end_); + ptr_ = ptr; + return *this; + } + + Char operator*() const { + return ptr_ != end_ ? *ptr_ : 0; + } + + null_terminating_iterator operator++() { + ++ptr_; + return *this; + } + + null_terminating_iterator operator++(int) { + null_terminating_iterator result(*this); + ++ptr_; + return result; + } + + null_terminating_iterator operator--() { + --ptr_; + return *this; + } + + null_terminating_iterator operator+(difference_type n) { + return null_terminating_iterator(ptr_ + n, end_); + } + + null_terminating_iterator operator+=(difference_type n) { + ptr_ += n; + return *this; + } + + difference_type operator-(null_terminating_iterator other) const { + return ptr_ - other.ptr_; + } + + bool operator!=(null_terminating_iterator other) const { + return ptr_ != other.ptr_; + } + + bool operator>=(null_terminating_iterator other) const { + return ptr_ >= other.ptr_; + } + + friend const Char *pointer_from(null_terminating_iterator it); + + private: + const Char *ptr_; + const Char *end_; +}; + +template < + typename T, + typename Char, + typename std::enable_if< + std::is_same>::value, int>::type = 0> +null_terminating_iterator to_iterator(basic_string_view v) { + const Char *s = v.data(); + return null_terminating_iterator(s, s + v.size()); +} + +template < + typename T, + typename Char, + typename std::enable_if::value, int>::type = 0> +const Char *to_iterator(basic_string_view v) { + return v.data(); +} + +template +const T *pointer_from(const T *p) { return p; } + +template +const Char *pointer_from(null_terminating_iterator it) { + return it.ptr_; +} + // Returns true if value is negative, false otherwise. // Same as (value < 0) but doesn't produce warnings if T is an unsigned type. template @@ -1122,7 +1233,8 @@ struct string_value { template struct custom_value { typedef void (*FormatFunc)( - basic_buffer &buffer, const void *arg, void *ctx); + basic_buffer &buffer, const void *arg, + basic_string_view& format, void *ctx); const void *value; FormatFunc format; @@ -1243,9 +1355,16 @@ class value { // Formats an argument of a custom type, such as a user-defined class. template static void format_custom_arg( - basic_buffer &buffer, const void *arg, void *context) { - format_value(buffer, *static_cast(arg), - *static_cast(context)); + basic_buffer &buffer, const void *arg, + basic_string_view &format, void *context) { + Context &ctx = *static_cast(context); + // Get the formatter type through the context to allow different contexts + // have different extension points, e.g. `formatter` for `format` and + // `printf_formatter` for `printf`. + typename Context::template formatter_type f; + auto it = f.parse(format); + format.remove_prefix(it - begin(format)); + f.format(buffer, *static_cast(arg), ctx); } public: @@ -1475,14 +1594,6 @@ basic_arg make_arg(const T &value) { typedef int FMT_CONCAT_(Assert, __LINE__)[(cond) ? 1 : -1] FMT_UNUSED #endif -template -void format_value(basic_buffer &, const T &, Formatter &, const Char *) { - FMT_STATIC_ASSERT(sizeof(T) < 0, - "Cannot format argument. To enable the use of ostream " - "operator<< include fmt/ostream.h. Otherwise provide " - "an overload of format_value."); -} - template struct named_arg : basic_arg { typedef typename Context::char_type Char; @@ -1947,115 +2058,17 @@ class arg_formatter_base { } }; -template -class null_terminating_iterator; - -template -const Char *pointer_from(null_terminating_iterator it); - -// An iterator that produces a null terminator on *end. This simplifies parsing -// and allows comparing the performance of processing a null-terminated string -// vs string_view. -template -class null_terminating_iterator { - public: - typedef Char value_type; - typedef std::ptrdiff_t difference_type; - - null_terminating_iterator() : ptr_(0), end_(0) {} - - null_terminating_iterator(const Char *ptr, const Char *end) - : ptr_(ptr), end_(end) {} - - Char operator*() const { - return ptr_ != end_ ? *ptr_ : 0; - } - - null_terminating_iterator operator++() { - ++ptr_; - return *this; - } - - null_terminating_iterator operator++(int) { - null_terminating_iterator result(*this); - ++ptr_; - return result; - } - - null_terminating_iterator operator--() { - --ptr_; - return *this; - } - - null_terminating_iterator operator+(difference_type n) { - return null_terminating_iterator(ptr_ + n, end_); - } - - null_terminating_iterator operator+=(difference_type n) { - ptr_ += n; - return *this; - } - - difference_type operator-(null_terminating_iterator other) const { - return ptr_ - other.ptr_; - } - - bool operator!=(null_terminating_iterator other) const { - return ptr_ != other.ptr_; - } - - bool operator>=(null_terminating_iterator other) const { - return ptr_ >= other.ptr_; - } - - friend const Char *pointer_from(null_terminating_iterator it); - - private: - const Char *ptr_; - const Char *end_; -}; - -template < - typename T, - typename Char, - typename std::enable_if< - std::is_same>::value, int>::type = 0> -null_terminating_iterator to_iterator(basic_string_view v) { - const Char *s = v.data(); - return null_terminating_iterator(s, s + v.size()); -} - -template < - typename T, - typename Char, - typename std::enable_if::value, int>::type = 0> -const Char *to_iterator(const basic_string_view v) { - return v.data(); -} - -template -const T *pointer_from(const T *p) { return p; } - -template -const Char *pointer_from(null_terminating_iterator it) { - return it.ptr_; -} - template class context_base { - public: - typedef null_terminating_iterator iterator; - private: - iterator pos_; basic_args args_; int next_arg_index_; protected: typedef basic_arg format_arg; - context_base(basic_string_view format_str, basic_args args) - : pos_(to_iterator(format_str)), args_(args), next_arg_index_(0) {} + explicit context_base(basic_args args) + : args_(args), next_arg_index_(0) {} ~context_base() {} basic_args args() const { return args_; } @@ -2091,10 +2104,6 @@ class context_base { next_arg_index_ = -1; return true; } - - public: - // Returns an iterator to the current position in the format string. - iterator &pos() { return pos_; } }; } // namespace internal @@ -2125,7 +2134,8 @@ class arg_formatter : public internal::arg_formatter_base { /** Formats an argument of a custom (user-defined) type. */ void operator()(internal::custom_value c) { - c.format(this->writer().buffer(), c.value, &ctx_); + basic_string_view format_str; + c.format(this->writer().buffer(), c.value, format_str, &ctx_); } }; @@ -2134,7 +2144,10 @@ class basic_context : public internal::context_base> { public: /** The character type for the output. */ - typedef Char char_type; + using char_type = Char; + + template + using formatter_type = formatter; private: internal::arg_map> map_; @@ -2153,9 +2166,7 @@ class basic_context : stored in the object so make sure they have appropriate lifetimes. \endrst */ - basic_context( - basic_string_view format_str, basic_args args) - : Base(format_str, args) {} + basic_context(basic_args args): Base(args) {} format_arg next_arg() { const char *error = 0; @@ -2177,8 +2188,6 @@ class basic_context : // Checks if manual indexing is used and returns the argument with // specified name. format_arg get_arg(basic_string_view name); - - using Base::pos; }; /** @@ -3210,14 +3219,16 @@ template class custom_formatter { private: basic_buffer &buffer_; + basic_string_view &format_; Context &ctx_; public: - custom_formatter(basic_buffer &buffer, Context &ctx) - : buffer_(buffer), ctx_(ctx) {} + custom_formatter(basic_buffer &buffer, basic_string_view &format, + Context &ctx) + : buffer_(buffer), format_(format), ctx_(ctx) {} bool operator()(internal::custom_value custom) { - custom.format(buffer_, custom.value, &ctx_); + custom.format(buffer_, custom.value, format_, &ctx_); return true; } @@ -3267,16 +3278,14 @@ struct precision_handler { } }; -template -class specs_handler { +template +class specs_handler_base { public: - typedef typename Context::char_type char_type; - - specs_handler(basic_format_specs &specs, Context &ctx) - : specs_(specs), context_(ctx) {} + explicit specs_handler_base(basic_format_specs &specs) + : specs_(specs) {} void on_align(alignment align) { specs_.align_ = align; } - void on_fill(char_type fill) { specs_.fill_ = fill; } + void on_fill(Char fill) { specs_.fill_ = fill; } void on_plus() { specs_.flags_ |= SIGN_FLAG | PLUS_FLAG; } void on_minus() { specs_.flags_ |= MINUS_FLAG; } void on_space() { specs_.flags_ |= SIGN_FLAG; } @@ -3288,46 +3297,107 @@ class specs_handler { } void on_width(unsigned width) { specs_.width_ = width; } + void on_precision(unsigned precision) { specs_.precision_ = precision; } + void on_type(char type) { specs_.type_ = type; } + + protected: + ~specs_handler_base() {} + + basic_format_specs &specs_; +}; + +template +inline void set_dynamic_spec(T &value, basic_arg arg) { + ulong_long big_value = visit(Handler(), arg); + if (big_value > (std::numeric_limits::max)()) + FMT_THROW(format_error("number is too big")); + value = static_cast(big_value); +} + +template +class specs_handler : public specs_handler_base { + public: + typedef typename Context::char_type char_type; + + specs_handler(basic_format_specs &specs, Context &ctx) + : specs_handler_base(specs), context_(ctx) {} template void on_dynamic_width(Id arg_id) { - auto width_arg = get_arg(arg_id); - ulong_long width = visit(internal::width_handler(), width_arg); - if (width > (std::numeric_limits::max)()) - FMT_THROW(format_error("number is too big")); - specs_.width_ = static_cast(width); + set_dynamic_spec( + this->specs_.width_, get_arg(arg_id)); } - void on_precision(unsigned precision) { specs_.precision_ = precision; } - template void on_dynamic_precision(Id arg_id) { - auto precision_arg = get_arg(arg_id); - ulong_long precision = visit(internal::precision_handler(), precision_arg); - if (precision > (std::numeric_limits::max)()) - FMT_THROW(format_error("number is too big")); - specs_.precision_ = static_cast(precision); + set_dynamic_spec( + this->specs_.precision_, get_arg(arg_id)); } - void on_type(char type) { specs_.type_ = type; } - private: basic_arg get_arg(monostate) { return context_.next_arg(); } - basic_arg get_arg(unsigned index) { - return context_.get_arg(index); + template + basic_arg get_arg(Id arg_id) { + return context_.get_arg(arg_id); } - basic_arg get_arg(basic_string_view name) { - return context_.get_arg(name); - } - - basic_format_specs &specs_; Context &context_; }; +// An argument reference. +template +struct arg_ref { + enum Kind { NONE, INDEX, NAME }; + + arg_ref() : kind(NONE) {} + explicit arg_ref(unsigned index) : kind(INDEX), index(index) {} + explicit arg_ref(basic_string_view name) : kind(NAME), name(name) {} + + Kind kind; + union { + unsigned index; + basic_string_view name; + }; +}; + +template +struct dynamic_format_specs : basic_format_specs { + arg_ref width_ref; + arg_ref precision_ref; +}; + +template +class dynamic_specs_handler : public specs_handler_base { + public: + explicit dynamic_specs_handler(dynamic_format_specs &specs) + : specs_handler_base(specs), specs_(specs) {} + + template + void on_dynamic_width(Id arg_id) { + set(specs_.width_ref, arg_id); + } + + template + void on_dynamic_precision(Id arg_id) { + set(specs_.precision_ref, arg_id); + } + + private: + template + void set(arg_ref &ref, Id arg_id) { + ref = arg_ref(arg_id); + } + + void set(arg_ref &ref, monostate) { + ref.kind = arg_ref::NONE; + } + + dynamic_format_specs &specs_; +}; + template Iterator parse_arg_id(Iterator it, Handler handler) { typedef typename Iterator::value_type char_type; @@ -3338,15 +3408,13 @@ Iterator parse_arg_id(Iterator it, Handler handler) { } if (c >= '0' && c <= '9') { unsigned index = parse_nonnegative_int(it); - if (*it != '}' && *it != ':') { + if (*it != '}' && *it != ':') FMT_THROW(format_error("invalid format string")); - } handler(index); return it; } - if (!is_name_start(c)) { + if (!is_name_start(c)) FMT_THROW(format_error("invalid format string")); - } auto start = it; do { c = *++it; @@ -3357,6 +3425,9 @@ Iterator parse_arg_id(Iterator it, Handler handler) { // Parses standard format specifiers and sends notifications about parsed // components to handler. +// it: an iterator pointing to the beginning of a null-terminated range of +// characters, possibly emulated via null_terminating_iterator, representing +// format specifiers. template Iterator parse_format_specs(Iterator it, Type arg_type, Handler &handler) { typedef typename Iterator::value_type char_type; @@ -3482,13 +3553,16 @@ Iterator parse_format_specs(Iterator it, Type arg_type, Handler &handler) { // Formats a single argument. template -void do_format_arg(basic_buffer &buffer, const basic_arg &arg, - Context &ctx) { - auto &it = ctx.pos(); +const Char *do_format_arg(basic_buffer &buffer, + const basic_arg &arg, + basic_string_view format, + Context &ctx) { + auto it = null_terminating_iterator(format); basic_format_specs specs; if (*it == ':') { - if (visit(custom_formatter(buffer, ctx), arg)) - return; + 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); } @@ -3498,28 +3572,69 @@ void do_format_arg(basic_buffer &buffer, const basic_arg &arg, // Format argument. visit(ArgFormatter(buffer, ctx, specs), arg); + return pointer_from(it); } + +// Specifies whether to format enums. +template +struct format_enum : std::integral_constant::value> {}; } // namespace internal -template -class formatter { - public: - explicit formatter(basic_context &ctx) { - auto &it = ctx.pos(); - if (*it == ':') { - ++it; - internal::specs_handler> handler(specs_, ctx); - it = parse_format_specs(it, internal::gettype(), handler); - } +// Formatter of objects of type T. +template +struct formatter() != internal::CUSTOM>::type> { + + // Parses format specifiers stopping either at the end of the range or at the + // terminating '}'. + 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); + return pointer_from(it); } void format(basic_buffer &buf, const T &val, basic_context &ctx) { + handle_dynamic_spec( + specs_.width_, specs_.width_ref, ctx); + handle_dynamic_spec( + specs_.precision_, specs_.precision_ref, ctx); visit(arg_formatter(buf, ctx, specs_), internal::make_arg>(val)); } private: - basic_format_specs specs_; + 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_; +}; + +template +struct formatter::value>::type> + : public formatter { + template + auto parse(Range format) -> decltype(begin(format)) { + return begin(format); + } }; template @@ -3541,9 +3656,9 @@ inline typename basic_context::format_arg template void vformat_to(basic_buffer &buffer, basic_string_view format_str, basic_args args) { - basic_context ctx(format_str, args); - auto &it = ctx.pos(); - auto start = it; + basic_context ctx(args); + auto start = internal::null_terminating_iterator(format_str); + auto it = start; using internal::pointer_from; while (*it) { Char c = *it++; @@ -3573,7 +3688,8 @@ void vformat_to(basic_buffer &buffer, basic_string_view format_str, } handler(ctx, arg); it = parse_arg_id(it, handler); - internal::do_format_arg(buffer, arg, ctx); + format_str.remove_prefix(pointer_from(it) - format_str.data()); + it = internal::do_format_arg(buffer, arg, format_str, ctx); if (*it != '}') FMT_THROW(format_error(fmt::format("unknown format specifier"))); start = ++it; diff --git a/fmt/ostream.h b/fmt/ostream.h index 3780b360..f52e03d5 100644 --- a/fmt/ostream.h +++ b/fmt/ostream.h @@ -77,18 +77,28 @@ void format_value(basic_buffer &buffer, const T &value) { output << value; buffer.resize(format_buf.size()); } + +// Disable builtin formatting of enums and use operator<< instead. +template +struct format_enum::value>::type> : std::false_type {}; } // namespace internal -// Formats a value. -template -void format_value(basic_buffer &buf, const T &value, - basic_context &ctx) { - basic_memory_buffer buffer; - internal::format_value(buffer, value); - basic_string_view str(buffer.data(), buffer.size()); - internal::do_format_arg< arg_formatter >( - buf, internal::make_arg< basic_context >(str), ctx); -} +// Formats an object of type T that has an overloaded ostream operator<<. +template +struct formatter() == internal::CUSTOM>::type> + : formatter, Char> { + + void format(basic_buffer &buf, const T &value, + basic_context &ctx) { + basic_memory_buffer buffer; + internal::format_value(buffer, value); + basic_string_view str(buffer.data(), buffer.size()); + formatter, Char>::format(buf, str, ctx); + } +}; FMT_API void vprint(std::ostream &os, string_view format_str, args args); diff --git a/fmt/printf.cc b/fmt/printf.cc index 53d4c0b9..c107c46b 100644 --- a/fmt/printf.cc +++ b/fmt/printf.cc @@ -14,7 +14,7 @@ FMT_FUNC int vfprintf(std::FILE *f, string_view format, printf_args args) { } #ifndef FMT_HEADER_ONLY -template void printf_context::format(buffer &); -template void printf_context::format(wbuffer &); +template void printf_context::format(string_view, buffer &); +template void printf_context::format(wstring_view, wbuffer &); #endif } diff --git a/fmt/printf.h b/fmt/printf.h index c44ac414..f02f8e41 100644 --- a/fmt/printf.h +++ b/fmt/printf.h @@ -286,28 +286,46 @@ class printf_arg_formatter : public internal::arg_formatter_base { /** Formats an argument of a custom (user-defined) type. */ void operator()(internal::custom_value c) { - const Char format_str[] = {'}', '\0'}; + const Char format_str_data[] = {'}', '\0'}; + basic_string_view format_str = format_str_data; auto args = basic_args>(); - basic_context ctx(format_str, args); - c.format(this->writer().buffer(), c.value, &ctx); + basic_context ctx(args); + c.format(this->writer().buffer(), c.value, format_str, &ctx); + } +}; + +template > +class printf_context; + +template +struct printf_formatter { + const Char *parse(basic_string_view s) { + return s.data(); + } + + void format(basic_buffer &buf, const T &value, printf_context &) { + internal::format_value(buf, value); } }; /** This template formats data and writes the output to a writer. */ -template > +template class printf_context : private internal::context_base< Char, printf_context> { public: /** The character type for the output. */ - typedef Char char_type; + using char_type = Char; + + template + using formatter_type = printf_formatter; private: typedef internal::context_base Base; typedef typename Base::format_arg format_arg; typedef basic_format_specs format_specs; - typedef typename Base::iterator iterator; + typedef internal::null_terminating_iterator iterator; void parse_flags(format_specs &spec, iterator &it); @@ -328,12 +346,11 @@ class printf_context : appropriate lifetimes. \endrst */ - explicit printf_context(basic_string_view format_str, - basic_args args) - : Base(format_str, args) {} + explicit printf_context(basic_args args): Base(args) {} /** Formats stored arguments and writes the output to the buffer. */ - FMT_API void format(basic_buffer &buffer); + FMT_API void format( + basic_string_view format_str, basic_buffer &buffer); }; template @@ -415,8 +432,9 @@ unsigned printf_context::parse_header( } template -void printf_context::format(basic_buffer &buffer) { - auto start = this->pos(); +void printf_context::format( + basic_string_view format_str, basic_buffer &buffer) { + auto start = iterator(format_str); auto it = start; using internal::pointer_from; while (*it) { @@ -515,17 +533,10 @@ void printf_context::format(basic_buffer &buffer) { buffer.append(pointer_from(start), pointer_from(it)); } -// Formats a value. -template -void format_value(basic_buffer &buf, const T &value, - printf_context& ctx) { - internal::format_value(buf, value); -} - template void printf(basic_buffer &buf, basic_string_view format, basic_args> args) { - printf_context(format, args).format(buf); + printf_context(args).format(format, buf); } typedef basic_args> printf_args; diff --git a/fmt/time.h b/fmt/time.h index 02d84c8d..648c60ef 100644 --- a/fmt/time.h +++ b/fmt/time.h @@ -15,40 +15,46 @@ namespace fmt { -void format_value(buffer &buf, const std::tm &tm, context &ctx) { - auto &it = ctx.pos(); - if (*it == ':') - ++it; - auto end = it; - while (*end && *end != '}') - ++end; - if (*end != '}') - FMT_THROW(format_error("missing '}' in format string")); - memory_buffer format; - format.reserve(end - it + 1); - using internal::pointer_from; - format.append(pointer_from(it), pointer_from(end)); - format.push_back('\0'); - std::size_t start = buf.size(); - for (;;) { - std::size_t size = buf.capacity() - start; - std::size_t count = std::strftime(&buf[start], size, &format[0], &tm); - if (count != 0) { - buf.resize(start + count); - break; - } - if (size >= format.size() * 256) { - // If the buffer is 256 times larger than the format string, assume - // that `strftime` gives an empty result. There doesn't seem to be a - // better way to distinguish the two cases: - // https://github.com/fmtlib/fmt/issues/367 - break; - } - const std::size_t MIN_GROWTH = 10; - buf.reserve(buf.capacity() + (size > MIN_GROWTH ? size : MIN_GROWTH)); +template <> +struct formatter { + template + auto parse(Range format) -> decltype(begin(format)) { + auto it = internal::null_terminating_iterator(format); + if (*it == ':') + ++it; + auto end = it; + while (*end && *end != '}') + ++end; + tm_format.reserve(end - it + 1); + using internal::pointer_from; + tm_format.append(pointer_from(it), pointer_from(end)); + tm_format.push_back('\0'); + return pointer_from(end); } - it = end; -} + + void format(buffer &buf, const std::tm &tm, context &ctx) { + std::size_t start = buf.size(); + for (;;) { + std::size_t size = buf.capacity() - start; + std::size_t count = std::strftime(&buf[start], size, &tm_format[0], &tm); + if (count != 0) { + buf.resize(start + count); + break; + } + if (size >= tm_format.size() * 256) { + // If the buffer is 256 times larger than the format string, assume + // that `strftime` gives an empty result. There doesn't seem to be a + // better way to distinguish the two cases: + // https://github.com/fmtlib/fmt/issues/367 + break; + } + const std::size_t MIN_GROWTH = 10; + buf.reserve(buf.capacity() + (size > MIN_GROWTH ? size : MIN_GROWTH)); + } + } + + memory_buffer tm_format; +}; } #endif // FMT_TIME_H_ diff --git a/test/custom-formatter-test.cc b/test/custom-formatter-test.cc index e189fa4d..b84db61e 100644 --- a/test/custom-formatter-test.cc +++ b/test/custom-formatter-test.cc @@ -65,8 +65,8 @@ std::string custom_vsprintf( const char* format_str, fmt::basic_args args) { fmt::memory_buffer buffer; - CustomPrintfFormatter formatter(format_str, args); - formatter.format(buffer); + CustomPrintfFormatter formatter(args); + formatter.format(format_str, buffer); return std::string(buffer.data(), buffer.size()); } diff --git a/test/format-test.cc b/test/format-test.cc index 25b36423..88d30b72 100644 --- a/test/format-test.cc +++ b/test/format-test.cc @@ -1227,8 +1227,18 @@ TEST(FormatterTest, FormatStringView) { EXPECT_EQ("test", format("{0}", string_view("test"))); } -void format_value(fmt::buffer &buf, const Date &d, fmt::context &) { - fmt::format_to(buf, "{}-{}-{}", d.year(), d.month(), d.day()); +namespace fmt { +template <> +struct formatter { + template + auto parse(Range format) -> decltype(begin(format)) { + return begin(format); + } + + void format(buffer &buf, const Date &d, context &) { + format_to(buf, "{}-{}-{}", d.year(), d.month(), d.day()); + } +}; } TEST(FormatterTest, FormatCustom) { @@ -1239,9 +1249,13 @@ TEST(FormatterTest, FormatCustom) { class Answer {}; -void format_value(fmt::buffer &buf, Answer, fmt::context &ctx) { - fmt::formatter f(ctx); - f.format(buf, 42, ctx); +namespace fmt { +template <> +struct formatter : formatter { + void format(fmt::buffer &buf, Answer, fmt::context &ctx) { + formatter::format(buf, 42, ctx); + } +}; } TEST(FormatterTest, CustomFormat) { diff --git a/test/ostream-test.cc b/test/ostream-test.cc index c0bc4b0d..1b3119c3 100644 --- a/test/ostream-test.cc +++ b/test/ostream-test.cc @@ -65,7 +65,7 @@ struct TestArgFormatter : fmt::arg_formatter { TEST(OStreamTest, CustomArg) { fmt::memory_buffer buffer; - fmt::context ctx("}", fmt::args()); + fmt::context ctx((fmt::args())); fmt::format_specs spec; TestArgFormatter af(buffer, ctx, spec); visit(af, fmt::internal::make_arg(TestEnum())); diff --git a/test/util-test.cc b/test/util-test.cc index 1ca29817..6bae0439 100644 --- a/test/util-test.cc +++ b/test/util-test.cc @@ -66,19 +66,27 @@ namespace { struct Test {}; -template -void format_value(fmt::basic_buffer &b, Test, - fmt::basic_context &) { - const Char *test = "test"; - b.append(test, test + std::strlen(test)); -} - template basic_arg make_arg(const T &value) { return fmt::internal::make_arg(value); } } // namespace +namespace fmt { +template +struct formatter { + template + auto parse(Range format) -> decltype(begin(format)) { + return begin(format); + } + + void format(basic_buffer &b, Test, basic_context &) { + const Char *test = "test"; + b.append(test, test + std::strlen(test)); + } +}; +} + void CheckForwarding( MockAllocator &alloc, AllocatorRef< MockAllocator > &ref) { int mem; @@ -424,20 +432,33 @@ TEST(UtilTest, FormatArgs) { } struct CustomContext { - typedef char char_type; - bool called; -}; + using char_type = char; -void format_value(fmt::buffer &, const Test &, CustomContext &ctx) { - ctx.called = true; -} + template + struct formatter_type { + template + auto parse(Range range) -> decltype(begin(range)) { + return begin(range); + } + + void format(fmt::buffer &, const T &, CustomContext& ctx) { + ctx.called = true; + } + }; + + bool called; + + fmt::string_view format() { return ""; } + void advance_to(const char *) {} +}; TEST(UtilTest, MakeValueWithCustomFormatter) { ::Test t; fmt::internal::value arg(t); CustomContext ctx = {false}; fmt::memory_buffer buffer; - arg.custom.format(buffer, &t, &ctx); + fmt::string_view format_str; + arg.custom.format(buffer, &t, format_str, &ctx); EXPECT_TRUE(ctx.called); } @@ -581,8 +602,9 @@ TEST(UtilTest, CustomArg) { testing::Invoke([&](fmt::internal::custom_value custom) { EXPECT_EQ(&test, custom.value); fmt::memory_buffer buffer; - fmt::context ctx("}", fmt::args()); - custom.format(buffer, &test, &ctx); + fmt::context ctx((fmt::args())); + fmt::string_view format_str; + custom.format(buffer, &test, format_str, &ctx); EXPECT_EQ("test", std::string(buffer.data(), buffer.size())); return Visitor::Result(); }));