diff --git a/fmt/format.h b/fmt/format.h index 3cf25848..370b6fc3 100644 --- a/fmt/format.h +++ b/fmt/format.h @@ -365,7 +365,7 @@ class basic_string_view { the size with ``std::char_traits::length``. \endrst */ - constexpr basic_string_view(const Char *s) + basic_string_view(const Char *s) : data_(s), size_(std::char_traits::length(s)) {} /** @@ -389,7 +389,7 @@ class basic_string_view { const Char *data() const { return data_; } /** Returns the string size. */ - std::size_t size() const { return size_; } + constexpr std::size_t size() const { return size_; } const Char *begin() const { return data_; } const Char *end() const { return data_ + size_; } @@ -762,7 +762,7 @@ template class null_terminating_iterator; template -const Char *pointer_from(null_terminating_iterator it); +constexpr 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 @@ -840,10 +840,10 @@ class null_terminating_iterator { }; template -const T *pointer_from(const T *p) { return p; } +constexpr const T *pointer_from(const T *p) { return p; } template -const Char *pointer_from(null_terminating_iterator it) { +constexpr const Char *pointer_from(null_terminating_iterator it) { return it.ptr_; } @@ -3012,7 +3012,7 @@ void arg(wstring_view, const internal::named_arg&) namespace fmt { namespace internal { template -inline bool is_name_start(Char c) { +constexpr bool is_name_start(Char c) { return ('a' <= c && c <= 'z') || ('A' <= c && c <= 'Z') || '_' == c; } @@ -3020,7 +3020,7 @@ inline bool is_name_start(Char c) { // This function assumes that the first character of it is a digit and a // presence of a non-digit character at the end. template -unsigned parse_nonnegative_int(Iterator &it) { +constexpr unsigned parse_nonnegative_int(Iterator &it) { assert('0' <= *it && *it <= '9'); unsigned value = 0; do { @@ -3316,8 +3316,14 @@ class dynamic_specs_handler : ParseContext &context_; }; +struct error_handler { + void on_error(const char *message) { + FMT_THROW(format_error(message)); + } +}; + template -Iterator parse_arg_id(Iterator it, Handler handler) { +constexpr Iterator parse_arg_id(Iterator it, Handler& handler) { using char_type = typename std::iterator_traits::value_type; char_type c = *it; if (c == '}' || c == ':') { @@ -3326,13 +3332,17 @@ Iterator parse_arg_id(Iterator it, Handler handler) { } if (c >= '0' && c <= '9') { unsigned index = parse_nonnegative_int(it); - if (*it != '}' && *it != ':') - FMT_THROW(format_error("invalid format string")); + if (*it != '}' && *it != ':') { + handler.on_error("invalid format string"); + return it; + } handler(index); return it; } - if (!is_name_start(c)) - FMT_THROW(format_error("invalid format string")); + if (!is_name_start(c)) { + handler.on_error("invalid format string"); + return it; + } auto start = it; do { c = *++it; @@ -3413,7 +3423,7 @@ Iterator parse_format_specs(Iterator it, Handler &handler) { if ('0' <= *it && *it <= '9') { handler.on_width(parse_nonnegative_int(it)); } else if (*it == '{') { - struct width_handler { + struct width_handler : error_handler { explicit width_handler(Handler &h) : handler(h) {} void operator()() { handler.on_dynamic_width(auto_id()); } @@ -3423,8 +3433,8 @@ Iterator parse_format_specs(Iterator it, Handler &handler) { } Handler &handler; - }; - it = parse_arg_id(it + 1, width_handler(handler)); + } wh(handler); + it = parse_arg_id(it + 1, wh); if (*it++ != '}') FMT_THROW(format_error("invalid format string")); } @@ -3435,7 +3445,7 @@ Iterator parse_format_specs(Iterator it, Handler &handler) { if ('0' <= *it && *it <= '9') { handler.on_precision(parse_nonnegative_int(it)); } else if (*it == '{') { - struct precision_handler { + struct precision_handler : error_handler { explicit precision_handler(Handler &h) : handler(h) {} void operator()() { handler.on_dynamic_precision(auto_id()); } @@ -3445,8 +3455,8 @@ Iterator parse_format_specs(Iterator it, Handler &handler) { } Handler &handler; - }; - it = parse_arg_id(it + 1, precision_handler(handler)); + } ph(handler); + it = parse_arg_id(it + 1, ph); if (*it++ != '}') FMT_THROW(format_error("invalid format string")); } else { @@ -3652,7 +3662,7 @@ void vformat_to(basic_buffer &buffer, basic_string_view format_str, buffer.append(pointer_from(start), pointer_from(it) - 1); basic_arg arg; - struct id_handler { + struct id_handler : internal::error_handler { id_handler(Context &c, basic_arg &a): context(c), arg(a) {} void operator()() { arg = context.next_arg(); } diff --git a/test/format-test.cc b/test/format-test.cc index b1156108..a857f7ed 100644 --- a/test/format-test.cc +++ b/test/format-test.cc @@ -1581,3 +1581,40 @@ TEST(FormatTest, DynamicFormatter) { EXPECT_THROW_MSG(format("{:.2}", num), format_error, "precision not allowed in integer format specifier"); } + +struct TestHandler { + enum Result { NONE, EMPTY, INDEX, NAME, ERROR }; + Result result = NONE; + unsigned index = 0; + string_view name; + + constexpr void operator()() { result = EMPTY; } + + constexpr void operator()(unsigned index) { + result = INDEX; + this->index = index; + } + + constexpr void operator()(string_view name) { + result = NAME; + this->name = name; + } + + constexpr void on_error(const char *) { result = ERROR; } +}; + +constexpr TestHandler parse_arg_id(const char* id) { + TestHandler h; + fmt::internal::parse_arg_id(id, h); + return h; +} + +TEST(FormatTest, ConstexprParseArgId) { + static_assert(parse_arg_id(":").result == TestHandler::EMPTY, ""); + static_assert(parse_arg_id("}").result == TestHandler::EMPTY, ""); + static_assert(parse_arg_id("42:").result == TestHandler::INDEX, ""); + static_assert(parse_arg_id("42:").index == 42, ""); + static_assert(parse_arg_id("foo:").result == TestHandler::NAME, ""); + static_assert(parse_arg_id("foo:").name.size() == 3, ""); + static_assert(parse_arg_id("!").result == TestHandler::ERROR, ""); +}