diff --git a/include/fmt/format.h b/include/fmt/format.h index 2f8d2b86..8b77f132 100644 --- a/include/fmt/format.h +++ b/include/fmt/format.h @@ -610,98 +610,6 @@ class null_terminating_iterator; template FMT_CONSTEXPR_DECL 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 std::ptrdiff_t difference_type; - typedef Char value_type; - typedef const Char* pointer; - typedef const Char& reference; - typedef std::random_access_iterator_tag iterator_category; - - null_terminating_iterator() : ptr_(0), end_(0) {} - - FMT_CONSTEXPR null_terminating_iterator(const Char *ptr, const Char *end) - : ptr_(ptr), end_(end) {} - - template - FMT_CONSTEXPR explicit null_terminating_iterator(const Range &r) - : ptr_(r.begin()), end_(r.end()) {} - - FMT_CONSTEXPR null_terminating_iterator &operator=(const Char *ptr) { - assert(ptr <= end_); - ptr_ = ptr; - return *this; - } - - FMT_CONSTEXPR Char operator*() const { - return ptr_ != end_ ? *ptr_ : Char(); - } - - FMT_CONSTEXPR null_terminating_iterator operator++() { - ++ptr_; - return *this; - } - - FMT_CONSTEXPR null_terminating_iterator operator++(int) { - null_terminating_iterator result(*this); - ++ptr_; - return result; - } - - FMT_CONSTEXPR null_terminating_iterator operator--() { - --ptr_; - return *this; - } - - FMT_CONSTEXPR null_terminating_iterator operator+(difference_type n) { - return null_terminating_iterator(ptr_ + n, end_); - } - - FMT_CONSTEXPR null_terminating_iterator operator-(difference_type n) { - return null_terminating_iterator(ptr_ - n, end_); - } - - FMT_CONSTEXPR null_terminating_iterator operator+=(difference_type n) { - ptr_ += n; - return *this; - } - - FMT_CONSTEXPR difference_type operator-( - null_terminating_iterator other) const { - return ptr_ - other.ptr_; - } - - FMT_CONSTEXPR bool operator!=(null_terminating_iterator other) const { - return ptr_ != other.ptr_; - } - - bool operator>=(null_terminating_iterator other) const { - return ptr_ >= other.ptr_; - } - - // This should be a friend specialization pointer_from but the latter - // doesn't compile by gcc 5.1 due to a compiler bug. - template - friend FMT_CONSTEXPR_DECL const CharT *pointer_from( - null_terminating_iterator it); - - private: - const Char *ptr_; - const Char *end_; -}; - -template -FMT_CONSTEXPR const T *pointer_from(const T *p) { return p; } - -template -FMT_CONSTEXPR const Char *pointer_from(null_terminating_iterator it) { - return it.ptr_; -} - // An output iterator that counts the number of objects written to it and // discards them. template @@ -1538,38 +1446,6 @@ FMT_CONSTEXPR bool is_name_start(Char c) { return ('a' <= c && c <= 'z') || ('A' <= c && c <= 'Z') || '_' == c; } -// DEPRECATED: Parses the input as an unsigned integer. This function assumes -// that the first character is a digit and presence of a non-digit character at -// the end. -// it: an iterator pointing to the beginning of the input range. -template -FMT_CONSTEXPR unsigned parse_nonnegative_int(Iterator &it, ErrorHandler &&eh) { - assert('0' <= *it && *it <= '9'); - if (*it == '0') { - ++it; - return 0; - } - unsigned value = 0; - // Convert to unsigned to prevent a warning. - unsigned max_int = (std::numeric_limits::max)(); - unsigned big = max_int / 10; - do { - // Check for overflow. - if (value > big) { - value = max_int + 1; - break; - } - value = value * 10 + unsigned(*it - '0'); - // Workaround for MSVC "setup_exception stack overflow" error: - auto next = it; - ++next; - it = next; - } while ('0' <= *it && *it <= '9'); - if (value > max_int) - eh.on_error("number is too big"); - return value; -} - // Parses the range [begin, end) as an unsigned integer. This function assumes // that the range is non-empty and the first character is a digit. template @@ -1904,36 +1780,6 @@ class dynamic_specs_handler : ParseContext &context_; }; -template -FMT_CONSTEXPR Iterator parse_arg_id(Iterator it, IDHandler &&handler) { - typedef typename std::iterator_traits::value_type char_type; - char_type c = *it; - if (c == '}' || c == ':') { - handler(); - return it; - } - if (c >= '0' && c <= '9') { - unsigned index = parse_nonnegative_int(it, handler); - if (*it != '}' && *it != ':') { - handler.on_error("invalid format string"); - return it; - } - handler(index); - return it; - } - if (!is_name_start(c)) { - handler.on_error("invalid format string"); - return it; - } - auto start = it; - do { - c = *++it; - } while (is_name_start(c) || ('0' <= c && c <= '9')); - handler(basic_string_view( - pointer_from(start), to_unsigned(it - start))); - return it; -} - template FMT_CONSTEXPR const Char *parse_arg_id( const Char *begin, const Char *end, IDHandler &&handler) { @@ -1996,22 +1842,18 @@ struct precision_adapter { // 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 -FMT_CONSTEXPR Iterator parse_format_specs(Iterator it, SpecHandler &&handler) { - typedef typename std::iterator_traits::value_type char_type; - char_type c = *it; - if (c == '}' || !c) - return it; +template +FMT_CONSTEXPR const Char *parse_format_specs( + const Char *begin, const Char *end, SpecHandler &&handler) { + if (begin == end || *begin == '}') + return begin; // Parse fill and alignment. alignment align = ALIGN_DEFAULT; - int i = 1; + int i = 0; + if (begin + 1 != end) ++i; do { - auto p = it + i; - switch (static_cast(*p)) { + switch (static_cast(begin[i])) { case '<': align = ALIGN_LEFT; break; @@ -2026,80 +1868,86 @@ FMT_CONSTEXPR Iterator parse_format_specs(Iterator it, SpecHandler &&handler) { break; } if (align != ALIGN_DEFAULT) { - if (p != it) { - if (c == '{') { - handler.on_error("invalid fill character '{'"); - return it; - } - it += 2; + if (i > 0) { + auto c = *begin; + if (c == '{') + return handler.on_error("invalid fill character '{'"), begin; + begin += 2; handler.on_fill(c); - } else ++it; + } else ++begin; handler.on_align(align); + if (begin == end) return begin; break; } - } while (--i >= 0); + } while (i-- > 0); // Parse sign. - switch (static_cast(*it)) { + switch (static_cast(*begin)) { case '+': handler.on_plus(); - ++it; + ++begin; break; case '-': handler.on_minus(); - ++it; + ++begin; break; case ' ': handler.on_space(); - ++it; + ++begin; break; } + if (begin == end) return begin; - if (*it == '#') { + if (*begin == '#') { handler.on_hash(); - ++it; + if (++begin == end) return begin; } // Parse zero flag. - if (*it == '0') { + if (*begin == '0') { handler.on_zero(); - ++it; + if (++begin == end) return begin; } // Parse width. - if ('0' <= *it && *it <= '9') { - handler.on_width(parse_nonnegative_int(it, handler)); - } else if (*it == '{') { - it = parse_arg_id(it + 1, width_adapter(handler)); - if (*it++ != '}') { - handler.on_error("invalid format string"); - return it; + if ('0' <= *begin && *begin <= '9') { + handler.on_width(parse_nonnegative_int(begin, end, handler)); + if (begin == end) return begin; + } else if (*begin == '{') { + ++begin; + if (begin != end) { + begin = parse_arg_id( + begin, end, width_adapter(handler)); } + if (begin == end || *begin != '}') + return handler.on_error("invalid format string"), begin; + if (++begin == end) return begin; } // Parse precision. - if (*it == '.') { - ++it; - if ('0' <= *it && *it <= '9') { - handler.on_precision(parse_nonnegative_int(it, handler)); - } else if (*it == '{') { - it = parse_arg_id( - it + 1, precision_adapter(handler)); - if (*it++ != '}') { - handler.on_error("invalid format string"); - return it; + if (*begin == '.') { + ++begin; + auto c = begin != end ? *begin : 0; + if ('0' <= c && c <= '9') { + handler.on_precision(parse_nonnegative_int(begin, end, handler)); + } else if (c == '{') { + ++begin; + if (begin != end) { + begin = parse_arg_id( + begin, end, precision_adapter(handler)); } + if (begin == end || *begin++ != '}') + return handler.on_error("invalid format string"), begin; } else { - handler.on_error("missing precision specifier"); - return it; + return handler.on_error("missing precision specifier"), begin; } handler.end_precision(); } // Parse type. - if (*it != '}' && *it) - handler.on_type(*it++); - return it; + if (begin != end && *begin != '}') + handler.on_type(*begin++); + return begin; } // Return the result via the out param to workaround gcc bug 77539. @@ -2174,11 +2022,9 @@ FMT_CONSTEXPR void parse_format_string( if (c == '}') { handler.on_replacement_field(p); } else if (c == ':') { - internal::null_terminating_iterator it(p + 1, end); - it = handler.on_format_specs(it); - if (*it != '}') + p = handler.on_format_specs(p + 1, end); + if (p == end || *p != '}') return handler.on_error("unknown format specifier"); - p = pointer_from(it); } else { return handler.on_error("missing '}' in format string"); } @@ -2203,8 +2049,6 @@ class format_string_checker { : arg_id_(-1), context_(format_str, eh), parse_funcs_{&parse_format_specs...} {} - typedef internal::null_terminating_iterator iterator; - FMT_CONSTEXPR void on_text(const Char *, const Char *) {} FMT_CONSTEXPR void on_arg_id() { @@ -2220,11 +2064,10 @@ class format_string_checker { FMT_CONSTEXPR void on_replacement_field(const Char *) {} - FMT_CONSTEXPR const Char *on_format_specs(iterator it) { - auto p = pointer_from(it); - context_.advance_to(p); + FMT_CONSTEXPR const Char *on_format_specs(const Char *begin, const Char *) { + context_.advance_to(begin); return to_unsigned(arg_id_) < NUM_ARGS ? - parse_funcs_[arg_id_](context_) : p; + parse_funcs_[arg_id_](context_) : begin; } FMT_CONSTEXPR void on_error(const char *message) { @@ -3080,13 +2923,12 @@ struct formatter< // terminating '}'. template FMT_CONSTEXPR typename ParseContext::iterator parse(ParseContext &ctx) { - auto it = internal::null_terminating_iterator(ctx); typedef internal::dynamic_specs_handler handler_type; auto type = internal::get_type< typename buffer_context::type, T>::value; internal::specs_checker handler(handler_type(specs_, ctx), type); - it = parse_format_specs(it, handler); + auto it = parse_format_specs(ctx.begin(), ctx.end(), handler); auto type_spec = specs_.type; auto eh = ctx.error_handler(); switch (type) { @@ -3127,7 +2969,7 @@ struct formatter< // formatter specializations. break; } - return pointer_from(it); + return it; } template @@ -3170,11 +3012,9 @@ class dynamic_formatter { public: template auto parse(ParseContext &ctx) -> decltype(ctx.begin()) { - auto it = internal::null_terminating_iterator(ctx); // Checks are deferred to formatting time when the argument type is known. internal::dynamic_specs_handler handler(specs_, ctx); - it = parse_format_specs(it, handler); - return pointer_from(it); + return parse_format_specs(ctx.begin(), ctx.end(), handler); } template @@ -3224,7 +3064,6 @@ typename basic_format_context::format_arg template struct format_handler : internal::error_handler { - typedef internal::null_terminating_iterator iterator; typedef typename ArgFormatter::range range; format_handler(range r, basic_string_view str, @@ -3256,22 +3095,22 @@ struct format_handler : internal::error_handler { context.advance_to(visit_format_arg(ArgFormatter(context), arg)); } - iterator on_format_specs(iterator it) { + const Char *on_format_specs(const Char *begin, const Char *end) { auto &parse_ctx = context.parse_context(); - parse_ctx.advance_to(pointer_from(it)); + parse_ctx.advance_to(begin); internal::custom_formatter f(context); if (visit_format_arg(f, arg)) - return iterator(parse_ctx); + return parse_ctx.begin(); basic_format_specs specs; using internal::specs_handler; internal::specs_checker> handler(specs_handler(specs, context), arg.type()); - it = parse_format_specs(it, handler); - if (*it != '}') + begin = parse_format_specs(begin, end, handler); + if (begin == end || *begin != '}') on_error("missing '}' in format string"); - parse_ctx.advance_to(pointer_from(it)); + parse_ctx.advance_to(begin); context.advance_to(visit_format_arg(ArgFormatter(context, &specs), arg)); - return it; + return begin; } Context context; diff --git a/include/fmt/printf.h b/include/fmt/printf.h index 2e8d800f..606cea48 100644 --- a/include/fmt/printf.h +++ b/include/fmt/printf.h @@ -16,6 +16,130 @@ FMT_BEGIN_NAMESPACE namespace internal { +// 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 std::ptrdiff_t difference_type; + typedef Char value_type; + typedef const Char* pointer; + typedef const Char& reference; + typedef std::random_access_iterator_tag iterator_category; + + null_terminating_iterator() : ptr_(0), end_(0) {} + + FMT_CONSTEXPR null_terminating_iterator(const Char *ptr, const Char *end) + : ptr_(ptr), end_(end) {} + + template + FMT_CONSTEXPR explicit null_terminating_iterator(const Range &r) + : ptr_(r.begin()), end_(r.end()) {} + + FMT_CONSTEXPR null_terminating_iterator &operator=(const Char *ptr) { + assert(ptr <= end_); + ptr_ = ptr; + return *this; + } + + FMT_CONSTEXPR Char operator*() const { + return ptr_ != end_ ? *ptr_ : Char(); + } + + FMT_CONSTEXPR null_terminating_iterator operator++() { + ++ptr_; + return *this; + } + + FMT_CONSTEXPR null_terminating_iterator operator++(int) { + null_terminating_iterator result(*this); + ++ptr_; + return result; + } + + FMT_CONSTEXPR null_terminating_iterator operator--() { + --ptr_; + return *this; + } + + FMT_CONSTEXPR null_terminating_iterator operator+(difference_type n) { + return null_terminating_iterator(ptr_ + n, end_); + } + + FMT_CONSTEXPR null_terminating_iterator operator-(difference_type n) { + return null_terminating_iterator(ptr_ - n, end_); + } + + FMT_CONSTEXPR null_terminating_iterator operator+=(difference_type n) { + ptr_ += n; + return *this; + } + + FMT_CONSTEXPR difference_type operator-( + null_terminating_iterator other) const { + return ptr_ - other.ptr_; + } + + FMT_CONSTEXPR bool operator!=(null_terminating_iterator other) const { + return ptr_ != other.ptr_; + } + + bool operator>=(null_terminating_iterator other) const { + return ptr_ >= other.ptr_; + } + + // This should be a friend specialization pointer_from but the latter + // doesn't compile by gcc 5.1 due to a compiler bug. + template + friend FMT_CONSTEXPR_DECL const CharT *pointer_from( + null_terminating_iterator it); + + private: + const Char *ptr_; + const Char *end_; +}; + +template +FMT_CONSTEXPR const T *pointer_from(const T *p) { return p; } + +template +FMT_CONSTEXPR const Char *pointer_from(null_terminating_iterator it) { + return it.ptr_; +} + +// DEPRECATED: Parses the input as an unsigned integer. This function assumes +// that the first character is a digit and presence of a non-digit character at +// the end. +// it: an iterator pointing to the beginning of the input range. +template +FMT_CONSTEXPR unsigned parse_nonnegative_int(Iterator &it, ErrorHandler &&eh) { + assert('0' <= *it && *it <= '9'); + if (*it == '0') { + ++it; + return 0; + } + unsigned value = 0; + // Convert to unsigned to prevent a warning. + unsigned max_int = (std::numeric_limits::max)(); + unsigned big = max_int / 10; + do { + // Check for overflow. + if (value > big) { + value = max_int + 1; + break; + } + value = value * 10 + unsigned(*it - '0'); + // Workaround for MSVC "setup_exception stack overflow" error: + auto next = it; + ++next; + it = next; + } while ('0' <= *it && *it <= '9'); + if (value > max_int) + eh.on_error("number is too big"); + return value; +} + // Checks if a value fits in int - used to avoid warnings about comparing // signed and unsigned integers. template diff --git a/include/fmt/time.h b/include/fmt/time.h index b269eda1..fbd3c60a 100644 --- a/include/fmt/time.h +++ b/include/fmt/time.h @@ -116,17 +116,16 @@ template struct formatter { template auto parse(ParseContext &ctx) -> decltype(ctx.begin()) { - auto it = internal::null_terminating_iterator(ctx); - if (*it == ':') + auto it = ctx.begin(); + if (it != ctx.end() && *it == ':') ++it; auto end = it; - while (*end && *end != '}') + while (end != ctx.end() && *end != '}') ++end; tm_format.reserve(end - it + 1); - using internal::pointer_from; - tm_format.append(pointer_from(it), pointer_from(end)); + tm_format.append(it, end); tm_format.push_back('\0'); - return pointer_from(end); + return end; } template diff --git a/test/format-test.cc b/test/format-test.cc index aa69a1a2..8fb6a792 100644 --- a/test/format-test.cc +++ b/test/format-test.cc @@ -193,13 +193,16 @@ TEST(UtilTest, ParseNonnegativeInt) { fmt::print("Skipping parse_nonnegative_int test\n"); return; } - const char *s = "10000000000"; + fmt::string_view s = "10000000000"; + auto begin = s.begin(), end = s.end(); EXPECT_THROW_MSG( - parse_nonnegative_int(s, fmt::internal::error_handler()), + parse_nonnegative_int(begin, end, fmt::internal::error_handler()), fmt::format_error, "number is too big"); s = "2147483649"; + begin = s.begin(); + end = s.end(); EXPECT_THROW_MSG( - parse_nonnegative_int(s, fmt::internal::error_handler()), + parse_nonnegative_int(begin, end, fmt::internal::error_handler()), fmt::format_error, "number is too big"); } @@ -941,6 +944,7 @@ TEST(FormatterTest, Fill) { EXPECT_EQ("abc**", format("{0:*<5}", "abc")); EXPECT_EQ("**0xface", format("{0:*>8}", reinterpret_cast(0xface))); EXPECT_EQ("foo=", format("{:}=", "foo")); + EXPECT_EQ(std::string("\0\0\0*", 4), format(string_view("{:\0>4}", 6), '*')); } TEST(FormatterTest, PlusSign) { @@ -2087,9 +2091,10 @@ struct test_arg_id_handler { FMT_CONSTEXPR void on_error(const char *) { res = ERROR; } }; -FMT_CONSTEXPR test_arg_id_handler parse_arg_id(const char* s) { +template +FMT_CONSTEXPR test_arg_id_handler parse_arg_id(const char (&s)[N]) { test_arg_id_handler h; - fmt::internal::parse_arg_id(s, h); + fmt::internal::parse_arg_id(s, s + N, h); return h; } @@ -2149,9 +2154,10 @@ struct test_format_specs_handler { FMT_CONSTEXPR void on_error(const char *) { res = ERROR; } }; -FMT_CONSTEXPR test_format_specs_handler parse_test_specs(const char *s) { +template +FMT_CONSTEXPR test_format_specs_handler parse_test_specs(const char (&s)[N]) { test_format_specs_handler h; - fmt::internal::parse_format_specs(s, h); + fmt::internal::parse_format_specs(s, s + N, h); return h; } @@ -2195,11 +2201,12 @@ struct test_context { FMT_CONSTEXPR test_context error_handler() { return *this; } }; -FMT_CONSTEXPR fmt::format_specs parse_specs(const char *s) { +template +FMT_CONSTEXPR fmt::format_specs parse_specs(const char (&s)[N]) { fmt::format_specs specs; test_context ctx{}; fmt::internal::specs_handler h(specs, ctx); - parse_format_specs(s, h); + parse_format_specs(s, s + N, h); return specs; } @@ -2220,12 +2227,13 @@ TEST(FormatTest, ConstexprSpecsHandler) { static_assert(parse_specs("d").type == 'd', ""); } +template FMT_CONSTEXPR fmt::internal::dynamic_format_specs - parse_dynamic_specs(const char *s) { + parse_dynamic_specs(const char (&s)[N]) { fmt::internal::dynamic_format_specs specs; test_context ctx{}; fmt::internal::dynamic_specs_handler h(specs, ctx); - parse_format_specs(s, h); + parse_format_specs(s, s + N, h); return specs; } @@ -2246,10 +2254,11 @@ TEST(FormatTest, ConstexprDynamicSpecsHandler) { static_assert(parse_dynamic_specs("d").type == 'd', ""); } -FMT_CONSTEXPR test_format_specs_handler check_specs(const char *s) { +template +FMT_CONSTEXPR test_format_specs_handler check_specs(const char (&s)[N]) { fmt::internal::specs_checker checker(test_format_specs_handler(), fmt::internal::double_type); - parse_format_specs(s, checker); + parse_format_specs(s, s + N, checker); return checker; } @@ -2278,11 +2287,11 @@ struct test_format_string_handler { template FMT_CONSTEXPR void on_arg_id(T) {} - template - FMT_CONSTEXPR void on_replacement_field(Iterator) {} + FMT_CONSTEXPR void on_replacement_field(const char *) {} - template - FMT_CONSTEXPR Iterator on_format_specs(Iterator it) { return it; } + FMT_CONSTEXPR const char *on_format_specs(const char *begin, const char*) { + return begin; + } FMT_CONSTEXPR void on_error(const char *) { error = true; }