From dfb857ebef2ec9e43fb5a91ba561036feb3d7c17 Mon Sep 17 00:00:00 2001 From: Victor Zverovich Date: Sat, 24 Dec 2022 19:22:39 -0800 Subject: [PATCH] Refactor format spec parsing --- doc/build.py | 3 +- include/fmt/core.h | 285 +++++++++++++++---------------------------- include/fmt/format.h | 149 +++------------------- test/core-test.cc | 140 ++------------------- test/format-test.cc | 78 +++--------- test/xchar-test.cc | 10 +- 6 files changed, 150 insertions(+), 515 deletions(-) diff --git a/doc/build.py b/doc/build.py index 56142da2..1c993d51 100755 --- a/doc/build.py +++ b/doc/build.py @@ -83,8 +83,7 @@ def build_docs(version='dev', **kwargs): internal_symbols = [ 'fmt::detail::.*', 'basic_data<>', - 'fmt::type_identity', - 'fmt::dynamic_formatter' + 'fmt::type_identity' ] noisy_warnings = [ 'warning: (Compound|Member .* of class) (' + '|'.join(internal_symbols) + \ diff --git a/include/fmt/core.h b/include/fmt/core.h index cc5f443f..1f20044f 100644 --- a/include/fmt/core.h +++ b/include/fmt/core.h @@ -1919,9 +1919,9 @@ class format_arg_store See `~fmt::arg` for lifetime considerations. \endrst */ -template -constexpr auto make_format_args(Args&&... args) - -> format_arg_store...> { +template +constexpr auto make_format_args(T&&... args) + -> format_arg_store...> { return {FMT_FORWARD(args)...}; } @@ -2206,90 +2206,6 @@ template struct dynamic_spec { }; }; -// A format specifier handler that sets fields in format_specs. -template class specs_setter { - protected: - format_specs& specs_; - - public: - explicit FMT_CONSTEXPR specs_setter(format_specs& specs) - : specs_(specs) {} - - FMT_CONSTEXPR void on_align(align_t align) { specs_.align = align; } - FMT_CONSTEXPR void on_fill(basic_string_view fill) { - specs_.fill = fill; - } - FMT_CONSTEXPR void on_sign(sign_t s) { specs_.sign = s; } - FMT_CONSTEXPR void on_hash() { specs_.alt = true; } - FMT_CONSTEXPR void on_localized() { specs_.localized = true; } - - FMT_CONSTEXPR void on_zero() { - // Ignore 0 if align is specified for compatibility with std::format. - if (specs_.align != align::none) return; - specs_.align = align::numeric; - specs_.fill[0] = Char('0'); - } - - FMT_CONSTEXPR void end_precision() {} - - FMT_CONSTEXPR void on_type(presentation_type type) { specs_.type = type; } -}; - -// Format spec handler that saves references to arguments representing dynamic -// width and precision to be resolved at formatting time. -template -class dynamic_specs_handler - : public specs_setter { - public: - using char_type = typename ParseContext::char_type; - - FMT_CONSTEXPR dynamic_specs_handler(dynamic_format_specs& specs, - ParseContext& ctx) - : specs_setter(specs), specs_(specs), context_(ctx) {} - - FMT_CONSTEXPR auto parse_context() -> ParseContext& { return context_; } - - FMT_CONSTEXPR void on_width(const dynamic_spec& spec) { - switch (spec.kind) { - case dynamic_spec_kind::none: - break; - case dynamic_spec_kind::value: - specs_.width = spec.value; - break; - case dynamic_spec_kind::index: - specs_.width_ref = arg_ref(spec.value); - break; - case dynamic_spec_kind::name: - specs_.width_ref = arg_ref(spec.name); - break; - } - } - - FMT_CONSTEXPR void on_precision(const dynamic_spec& spec) { - switch (spec.kind) { - case dynamic_spec_kind::none: - break; - case dynamic_spec_kind::value: - specs_.precision = spec.value; - break; - case dynamic_spec_kind::index: - specs_.precision_ref = arg_ref(spec.value); - break; - case dynamic_spec_kind::name: - specs_.precision_ref = arg_ref(spec.name); - break; - } - } - - FMT_CONSTEXPR void on_error(const char* message) { - throw_format_error(message); - } - - private: - dynamic_format_specs& specs_; - ParseContext& context_; -}; - template constexpr bool is_ascii_letter(Char c) { return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z'); } @@ -2558,90 +2474,136 @@ FMT_CONSTEXPR auto parse_presentation_type(Char type) -> presentation_type { } } -// Parses standard format specifiers and sends notifications about parsed -// components to handler. -template -FMT_CONSTEXPR FMT_INLINE auto parse_format_specs(const Char* begin, - const Char* end, - SpecHandler&& handler) - -> const Char* { +FMT_CONSTEXPR inline void require_numeric_argument(type arg_type) { + if (!is_arithmetic_type(arg_type)) + throw_format_error("format specifier requires numeric argument"); +} + +template struct parse_format_specs_result { + const Char* end; + dynamic_format_specs specs; +}; + +// Parses standard format specifiers. +template +FMT_CONSTEXPR FMT_INLINE auto parse_format_specs( + const Char* begin, const Char* end, basic_format_parse_context& ctx, + type arg_type) -> parse_format_specs_result { + auto specs = dynamic_format_specs(); if (1 < end - begin && begin[1] == '}' && is_ascii_letter(*begin) && *begin != 'L') { - presentation_type type = parse_presentation_type(*begin++); - if (type == presentation_type::none) - handler.on_error("invalid type specifier"); - handler.on_type(type); - return begin; + specs.type = parse_presentation_type(*begin++); + if (specs.type == presentation_type::none) + throw_format_error("invalid type specifier"); + return {begin, specs}; } + if (begin == end) return {begin, specs}; - if (begin == end) return begin; - - auto align_result = parse_align(begin, end); - if (align_result.align != align::none) { - auto fill_size = align_result.end - begin - 1; - if (fill_size > 0) handler.on_fill({begin, to_unsigned(fill_size)}); - handler.on_align(align_result.align); + auto align = parse_align(begin, end); + if (align.align != align::none) { + specs.align = align.align; + auto fill_size = align.end - begin - 1; + if (fill_size > 0) specs.fill = {begin, to_unsigned(fill_size)}; } - begin = align_result.end; - if (begin == end) return begin; + begin = align.end; + if (begin == end) return {begin, specs}; // Parse sign. switch (to_ascii(*begin)) { case '+': - handler.on_sign(sign::plus); + specs.sign = sign::plus; ++begin; break; case '-': - handler.on_sign(sign::minus); + specs.sign = sign::minus; ++begin; break; case ' ': - handler.on_sign(sign::space); + specs.sign = sign::space; ++begin; break; default: break; } - if (begin == end) return begin; + if (specs.sign != sign::none) { + require_numeric_argument(arg_type); + if (is_integral_type(arg_type) && arg_type != type::int_type && + arg_type != type::long_long_type && arg_type != type::int128_type && + arg_type != type::char_type) { + throw_format_error("format specifier requires signed argument"); + } + } + if (begin == end) return {begin, specs}; if (*begin == '#') { - handler.on_hash(); - if (++begin == end) return begin; + require_numeric_argument(arg_type); + specs.alt = true; + if (++begin == end) return {begin, specs}; } // Parse zero flag. if (*begin == '0') { - handler.on_zero(); - if (++begin == end) return begin; + require_numeric_argument(arg_type); + if (specs.align == align::none) { + // Ignore 0 if align is specified for compatibility with std::format. + specs.align = align::numeric; + specs.fill[0] = Char('0'); + } + if (++begin == end) return {begin, specs}; } - auto width = parse_dynamic_spec(begin, end, handler.parse_context()); - handler.on_width(width.spec); + auto width = parse_dynamic_spec(begin, end, ctx); + switch (width.spec.kind) { + case dynamic_spec_kind::none: + break; + case dynamic_spec_kind::value: + specs.width = width.spec.value; + break; + case dynamic_spec_kind::index: + specs.width_ref = arg_ref(width.spec.value); + break; + case dynamic_spec_kind::name: + specs.width_ref = arg_ref(width.spec.name); + break; + } begin = width.end; - if (begin == end) return begin; + if (begin == end) return {begin, specs}; // Parse precision. if (*begin == '.') { - auto precision = parse_precision(begin, end, handler.parse_context()); - handler.on_precision(precision.spec); - if (precision.spec.kind != dynamic_spec_kind::none) handler.end_precision(); + auto precision = parse_precision(begin, end, ctx); + switch (precision.spec.kind) { + case dynamic_spec_kind::none: + break; + case dynamic_spec_kind::value: + specs.precision = precision.spec.value; + break; + case dynamic_spec_kind::index: + specs.precision_ref = arg_ref(precision.spec.value); + break; + case dynamic_spec_kind::name: + specs.precision_ref = arg_ref(precision.spec.name); + break; + } + if (is_integral_type(arg_type) || arg_type == type::pointer_type) + throw_format_error("precision not allowed for this argument type"); begin = precision.end; - if (begin == end) return begin; + if (begin == end) return {begin, specs}; } if (*begin == 'L') { - handler.on_localized(); + require_numeric_argument(arg_type); + specs.localized = true; ++begin; } // Parse type. if (begin != end && *begin != '}') { - presentation_type type = parse_presentation_type(*begin++); - if (type == presentation_type::none) - handler.on_error("invalid type specifier"); - handler.on_type(type); + specs.type = parse_presentation_type(*begin++); + if (specs.type == presentation_type::none) + throw_format_error("invalid type specifier"); } - return begin; + return {begin, specs}; } template @@ -2866,57 +2828,6 @@ FMT_CONSTEXPR void check_pointer_type_spec(presentation_type type, eh.on_error("invalid type specifier"); } -// A parse_format_specs handler that checks if specifiers are consistent with -// the argument type. -template class specs_checker : public Handler { - private: - detail::type arg_type_; - - FMT_CONSTEXPR void require_numeric_argument() { - if (!is_arithmetic_type(arg_type_)) - this->on_error("format specifier requires numeric argument"); - } - - public: - FMT_CONSTEXPR specs_checker(const Handler& handler, detail::type arg_type) - : Handler(handler), arg_type_(arg_type) {} - - FMT_CONSTEXPR void on_align(align_t align) { - if (align == align::numeric) require_numeric_argument(); - Handler::on_align(align); - } - - FMT_CONSTEXPR void on_sign(sign_t s) { - require_numeric_argument(); - if (is_integral_type(arg_type_) && arg_type_ != type::int_type && - arg_type_ != type::long_long_type && arg_type_ != type::int128_type && - arg_type_ != type::char_type) { - this->on_error("format specifier requires signed argument"); - } - Handler::on_sign(s); - } - - FMT_CONSTEXPR void on_hash() { - require_numeric_argument(); - Handler::on_hash(); - } - - FMT_CONSTEXPR void on_localized() { - require_numeric_argument(); - Handler::on_localized(); - } - - FMT_CONSTEXPR void on_zero() { - require_numeric_argument(); - Handler::on_zero(); - } - - FMT_CONSTEXPR void end_precision() { - if (is_integral_type(arg_type_) || arg_type_ == type::pointer_type) - this->on_error("precision not allowed for this argument type"); - } -}; - constexpr int invalid_arg_index = -1; #if FMT_USE_NONTYPE_TEMPLATE_ARGS @@ -3047,11 +2958,9 @@ struct formatter decltype(ctx.begin()) { auto begin = ctx.begin(), end = ctx.end(); if (begin == end) return begin; - using handler_type = detail::dynamic_specs_handler; auto type = detail::type_constant::value; - auto checker = - detail::specs_checker(handler_type(specs_, ctx), type); - auto it = detail::parse_format_specs(begin, end, checker); + auto result = detail::parse_format_specs(begin, end, ctx, type); + specs_ = result.specs; auto eh = detail::error_handler(); switch (type) { case detail::type::none_type: @@ -3076,19 +2985,19 @@ struct formatter::value, diff --git a/include/fmt/format.h b/include/fmt/format.h index f3219b75..8dbe3253 100644 --- a/include/fmt/format.h +++ b/include/fmt/format.h @@ -3588,66 +3588,6 @@ FMT_CONSTEXPR auto get_arg(Context& ctx, ID id) -> return arg; } -// The standard format specifier handler with checking. -template class specs_handler : public specs_setter { - private: - basic_format_parse_context& parse_context_; - buffer_context& context_; - - // This is only needed for compatibility with gcc 4.4. - using format_arg = basic_format_arg>; - - public: - FMT_CONSTEXPR specs_handler(format_specs& specs, - basic_format_parse_context& parse_ctx, - buffer_context& ctx) - : specs_setter(specs), parse_context_(parse_ctx), context_(ctx) {} - - FMT_CONSTEXPR auto parse_context() -> basic_format_parse_context& { - return parse_context_; - } - - FMT_CONSTEXPR void on_width(const dynamic_spec& spec) { - auto arg = format_arg(); - switch (spec.kind) { - case dynamic_spec_kind::none: - return; - case dynamic_spec_kind::value: - this->specs_.width = spec.value; - return; - case dynamic_spec_kind::index: - arg = detail::get_arg(context_, spec.value); - break; - case dynamic_spec_kind::name: - arg = detail::get_arg(context_, spec.name); - break; - } - this->specs_.width = - get_dynamic_spec(arg, context_.error_handler()); - } - - FMT_CONSTEXPR void on_precision(const dynamic_spec& spec) { - auto arg = format_arg(); - switch (spec.kind) { - case dynamic_spec_kind::none: - return; - case dynamic_spec_kind::value: - this->specs_.precision = spec.value; - return; - case dynamic_spec_kind::index: - arg = detail::get_arg(context_, spec.value); - break; - case dynamic_spec_kind::name: - arg = detail::get_arg(context_, spec.name); - break; - } - this->specs_.precision = - get_dynamic_spec(arg, context_.error_handler()); - } - - void on_error(const char* message) { context_.on_error(message); } -}; - template