Get rid of null_terminating_iterator in format

This commit is contained in:
Victor Zverovich 2018-12-13 07:50:50 -08:00
parent 93fd473b8f
commit 8668639ae2
4 changed files with 222 additions and 251 deletions

View File

@ -610,98 +610,6 @@ class null_terminating_iterator;
template <typename Char>
FMT_CONSTEXPR_DECL const Char *pointer_from(null_terminating_iterator<Char> 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 <typename Char>
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 <typename Range>
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<Char> but the latter
// doesn't compile by gcc 5.1 due to a compiler bug.
template <typename CharT>
friend FMT_CONSTEXPR_DECL const CharT *pointer_from(
null_terminating_iterator<CharT> it);
private:
const Char *ptr_;
const Char *end_;
};
template <typename T>
FMT_CONSTEXPR const T *pointer_from(const T *p) { return p; }
template <typename Char>
FMT_CONSTEXPR const Char *pointer_from(null_terminating_iterator<Char> it) {
return it.ptr_;
}
// An output iterator that counts the number of objects written to it and
// discards them.
template <typename T>
@ -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 <typename Iterator, typename ErrorHandler>
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<int>::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 <typename Char, typename ErrorHandler>
@ -1904,36 +1780,6 @@ class dynamic_specs_handler :
ParseContext &context_;
};
template <typename Iterator, typename IDHandler>
FMT_CONSTEXPR Iterator parse_arg_id(Iterator it, IDHandler &&handler) {
typedef typename std::iterator_traits<Iterator>::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<char_type>(
pointer_from(start), to_unsigned(it - start)));
return it;
}
template <typename Char, typename IDHandler>
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 <typename Iterator, typename SpecHandler>
FMT_CONSTEXPR Iterator parse_format_specs(Iterator it, SpecHandler &&handler) {
typedef typename std::iterator_traits<Iterator>::value_type char_type;
char_type c = *it;
if (c == '}' || !c)
return it;
template <typename Char, typename SpecHandler>
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<char>(*p)) {
switch (static_cast<char>(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<char>(*it)) {
switch (static_cast<char>(*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<SpecHandler, char_type>(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<SpecHandler, Char>(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<SpecHandler, char_type>(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<SpecHandler, Char>(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<Char> 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<Args, parse_context_type>...} {}
typedef internal::null_terminating_iterator<Char> 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 <typename ParseContext>
FMT_CONSTEXPR typename ParseContext::iterator parse(ParseContext &ctx) {
auto it = internal::null_terminating_iterator<Char>(ctx);
typedef internal::dynamic_specs_handler<ParseContext> handler_type;
auto type = internal::get_type<
typename buffer_context<Char>::type, T>::value;
internal::specs_checker<handler_type>
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 <typename FormatContext>
@ -3170,11 +3012,9 @@ class dynamic_formatter {
public:
template <typename ParseContext>
auto parse(ParseContext &ctx) -> decltype(ctx.begin()) {
auto it = internal::null_terminating_iterator<Char>(ctx);
// Checks are deferred to formatting time when the argument type is known.
internal::dynamic_specs_handler<ParseContext> handler(specs_, ctx);
it = parse_format_specs(it, handler);
return pointer_from(it);
return parse_format_specs(ctx.begin(), ctx.end(), handler);
}
template <typename T, typename FormatContext>
@ -3224,7 +3064,6 @@ typename basic_format_context<Range, Char>::format_arg
template <typename ArgFormatter, typename Char, typename Context>
struct format_handler : internal::error_handler {
typedef internal::null_terminating_iterator<Char> iterator;
typedef typename ArgFormatter::range range;
format_handler(range r, basic_string_view<Char> 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<Char, Context> f(context);
if (visit_format_arg(f, arg))
return iterator(parse_ctx);
return parse_ctx.begin();
basic_format_specs<Char> specs;
using internal::specs_handler;
internal::specs_checker<specs_handler<Context>>
handler(specs_handler<Context>(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;

View File

@ -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 <typename Char>
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 <typename Range>
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<Char> but the latter
// doesn't compile by gcc 5.1 due to a compiler bug.
template <typename CharT>
friend FMT_CONSTEXPR_DECL const CharT *pointer_from(
null_terminating_iterator<CharT> it);
private:
const Char *ptr_;
const Char *end_;
};
template <typename T>
FMT_CONSTEXPR const T *pointer_from(const T *p) { return p; }
template <typename Char>
FMT_CONSTEXPR const Char *pointer_from(null_terminating_iterator<Char> 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 <typename Iterator, typename ErrorHandler>
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<int>::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 <bool IsSigned>

View File

@ -116,17 +116,16 @@ template <typename Char>
struct formatter<std::tm, Char> {
template <typename ParseContext>
auto parse(ParseContext &ctx) -> decltype(ctx.begin()) {
auto it = internal::null_terminating_iterator<Char>(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 <typename FormatContext>

View File

@ -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<void*>(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 <size_t N>
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 <size_t N>
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 <size_t N>
FMT_CONSTEXPR fmt::format_specs parse_specs(const char (&s)[N]) {
fmt::format_specs specs;
test_context ctx{};
fmt::internal::specs_handler<test_context> 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 <size_t N>
FMT_CONSTEXPR fmt::internal::dynamic_format_specs<char>
parse_dynamic_specs(const char *s) {
parse_dynamic_specs(const char (&s)[N]) {
fmt::internal::dynamic_format_specs<char> specs;
test_context ctx{};
fmt::internal::dynamic_specs_handler<test_context> 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 <size_t N>
FMT_CONSTEXPR test_format_specs_handler check_specs(const char (&s)[N]) {
fmt::internal::specs_checker<test_format_specs_handler>
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 <typename T>
FMT_CONSTEXPR void on_arg_id(T) {}
template <typename Iterator>
FMT_CONSTEXPR void on_replacement_field(Iterator) {}
FMT_CONSTEXPR void on_replacement_field(const char *) {}
template <typename Iterator>
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; }