diff --git a/include/fmt/format.h b/include/fmt/format.h index 05b7f0ce..657924c1 100644 --- a/include/fmt/format.h +++ b/include/fmt/format.h @@ -1584,8 +1584,9 @@ FMT_CONSTEXPR bool is_name_start(Char c) { return ('a' <= c && c <= 'z') || ('A' <= c && c <= 'Z') || '_' == c; } -// 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. +// 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) { @@ -1611,6 +1612,29 @@ FMT_CONSTEXPR unsigned parse_nonnegative_int(Iterator &it, ErrorHandler &&eh) { 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 +FMT_CONSTEXPR unsigned parse_nonnegative_int( + const Char *&begin, const Char *end, ErrorHandler &&eh) { + assert(begin != end && '0' <= *begin && *begin <= '9'); + 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(*begin++ - '0'); + } while (begin != end && '0' <= *begin && *begin <= '9'); + if (value > max_int) + eh.on_error("number is too big"); + return value; +} + template class custom_formatter: public function { private: @@ -1941,6 +1965,30 @@ FMT_CONSTEXPR Iterator parse_arg_id(Iterator it, IDHandler &&handler) { return it; } +template +FMT_CONSTEXPR const Char *parse_arg_id( + const Char *begin, const Char *end, IDHandler &&handler) { + assert(begin != end); + Char c = *begin; + if (c == '}' || c == ':') + return handler(), begin; + if (c >= '0' && c <= '9') { + unsigned index = parse_nonnegative_int(begin, end, handler); + if (begin == end || (*begin != '}' && *begin != ':')) + return handler.on_error("invalid format string"), begin; + handler(index); + return begin; + } + if (!is_name_start(c)) + return handler.on_error("invalid format string"), begin; + auto it = begin; + do { + c = *++it; + } while (it != end && (is_name_start(c) || ('0' <= c && c <= '9'))); + handler(basic_string_view(begin, to_unsigned(it - begin))); + return it; +} + // Adapts SpecHandler to IDHandler API for dynamic width. template struct width_adapter { @@ -2085,23 +2133,6 @@ FMT_CONSTEXPR Iterator parse_format_specs(Iterator it, SpecHandler &&handler) { return it; } -template -struct id_adapter { - FMT_CONSTEXPR explicit id_adapter(Handler &h): handler(h) {} - - FMT_CONSTEXPR void operator()() { handler.on_arg_id(); } - FMT_CONSTEXPR void operator()(unsigned id) { handler.on_arg_id(id); } - FMT_CONSTEXPR void operator()(basic_string_view id) { - handler.on_arg_id(id); - } - - FMT_CONSTEXPR void on_error(const char *message) { - handler.on_error(message); - } - - Handler &handler; -}; - // Return the result via the out param to workaround gcc bug 77539. template FMT_CONSTEXPR bool find(Ptr first, Ptr last, T value, Ptr &out) { @@ -2119,6 +2150,19 @@ inline bool find( return out != FMT_NULL; } +template +struct id_adapter { + FMT_CONSTEXPR void operator()() { handler.on_arg_id(); } + FMT_CONSTEXPR void operator()(unsigned id) { handler.on_arg_id(id); } + FMT_CONSTEXPR void operator()(basic_string_view id) { + handler.on_arg_id(id); + } + FMT_CONSTEXPR void on_error(const char *message) { + handler.on_error(message); + } + Handler &handler; +}; + template FMT_CONSTEXPR void parse_format_string( basic_string_view format_str, Handler &&handler) { @@ -2147,24 +2191,29 @@ FMT_CONSTEXPR void parse_format_string( return write(begin, end); write(begin, p); ++p; - if (p != end && *p == '{') { + if (p == end) + return handler.on_error("invalid format string"); + if (*p == '}') { + handler.on_arg_id(); + handler.on_replacement_field(p); + } else if (*p == '{') { handler.on_text(p, p + 1); - begin = p + 1; - continue; - } - internal::null_terminating_iterator it(p, end); - it = parse_arg_id(it, id_adapter(handler)); - if (*it == '}') { - handler.on_replacement_field(it); - } else if (*it == ':') { - ++it; - it = handler.on_format_specs(it); - if (*it != '}') - return handler.on_error("unknown format specifier"); } else { - return handler.on_error("missing '}' in format string"); + p = parse_arg_id(p, end, id_adapter{handler}); + Char c = p != end ? *p : 0; + 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 != '}') + return handler.on_error("unknown format specifier"); + p = pointer_from(it); + } else { + return handler.on_error("missing '}' in format string"); + } } - begin = pointer_from(it) + 1; + begin = p + 1; } } @@ -2199,7 +2248,7 @@ class format_string_checker { } FMT_CONSTEXPR void on_arg_id(basic_string_view) {} - FMT_CONSTEXPR void on_replacement_field(iterator) {} + FMT_CONSTEXPR void on_replacement_field(const Char *) {} FMT_CONSTEXPR const Char *on_format_specs(iterator it) { auto p = pointer_from(it); @@ -3277,8 +3326,8 @@ struct format_handler : internal::error_handler { arg = context.get_arg(id); } - void on_replacement_field(iterator it) { - context.parse_context().advance_to(pointer_from(it)); + void on_replacement_field(const Char *p) { + context.parse_context().advance_to(p); if (visit(internal::custom_formatter(context), arg)) return; basic_format_specs specs;