diff --git a/include/fmt/compile.h b/include/fmt/compile.h index 486fe475..294a5084 100644 --- a/include/fmt/compile.h +++ b/include/fmt/compile.h @@ -14,117 +14,156 @@ FMT_BEGIN_NAMESPACE namespace internal { +// Part of a compiled format string. It can be either literal text or a +// replacement field. template struct format_part { - struct named_arg_id { - FMT_CONSTEXPR named_arg_id(string_view_metadata id) : id(id) {} - string_view_metadata id; - }; - - struct replacement { - FMT_CONSTEXPR replacement() : arg_id(0u) {} - FMT_CONSTEXPR replacement(unsigned id) : arg_id(id) {} - - FMT_CONSTEXPR replacement(string_view_metadata id) : arg_id(id) {} - - arg_ref arg_id; - dynamic_format_specs parsed_specs; - }; - enum class kind { arg_index, arg_name, text, replacement }; + struct replacement { + arg_ref arg_id; + dynamic_format_specs specs; + }; + kind part_kind; - std::size_t end_of_argument_id; union value { - FMT_CONSTEXPR value() : arg_index(0u) {} - FMT_CONSTEXPR value(unsigned id) : arg_index(id) {} - FMT_CONSTEXPR value(named_arg_id named_id) : arg_name(named_id.id) {} - FMT_CONSTEXPR value(string_view_metadata t) : text(t) {} - FMT_CONSTEXPR value(replacement r) : repl(r) {} unsigned arg_index; - string_view_metadata arg_name; - string_view_metadata text; + string_view_metadata str; replacement repl; + + FMT_CONSTEXPR value(unsigned index = 0) : arg_index(index) {} + FMT_CONSTEXPR value(string_view_metadata s) : str(s) {} + FMT_CONSTEXPR value(replacement r) : repl(r) {} } val; + std::size_t arg_id_end = 0; // Position past the end of the argument id. - FMT_CONSTEXPR format_part() - : part_kind(kind::arg_index), end_of_argument_id(0u), val(0u) {} + FMT_CONSTEXPR format_part(kind k = kind::arg_index, value v = {}) + : part_kind(k), val(v) {} - FMT_CONSTEXPR format_part(unsigned arg_index) - : part_kind(kind::arg_index), end_of_argument_id(0u), val(arg_index) {} - - FMT_CONSTEXPR format_part(named_arg_id arg_name) - : part_kind(kind::arg_name), end_of_argument_id(0u), val(arg_name) {} - - FMT_CONSTEXPR format_part(string_view_metadata text) - : part_kind(kind::text), end_of_argument_id(0u), val(text) {} - - FMT_CONSTEXPR format_part(replacement repl) - : part_kind(kind::replacement), end_of_argument_id(0u), val(repl) {} + static FMT_CONSTEXPR format_part make_arg_index(unsigned index) { + return format_part(kind::arg_index, index); + } + static FMT_CONSTEXPR format_part make_arg_name(string_view_metadata name) { + return format_part(kind::arg_name, name); + } + static FMT_CONSTEXPR format_part make_text(string_view_metadata text) { + return format_part(kind::text, text); + } + static FMT_CONSTEXPR format_part make_replacement(replacement repl) { + return format_part(kind::replacement, repl); + } }; -template -class format_preparation_handler : public internal::error_handler { +template struct part_counter { + unsigned num_parts = 0; + + FMT_CONSTEXPR void on_text(const Char* begin, const Char* end) { + if (begin != end) ++num_parts; + } + + FMT_CONSTEXPR void on_arg_id() { ++num_parts; } + FMT_CONSTEXPR void on_arg_id(unsigned) { ++num_parts; } + FMT_CONSTEXPR void on_arg_id(basic_string_view) { ++num_parts; } + + FMT_CONSTEXPR void on_replacement_field(const Char*) {} + + FMT_CONSTEXPR const Char* on_format_specs(const Char* begin, + const Char* end) { + // Find the matching brace. + unsigned brace_counter = 0; + for (; begin != end; ++begin) { + if (*begin == '{') { + ++brace_counter; + } else if (*begin == '}') { + if (brace_counter == 0u) break; + --brace_counter; + } + } + return begin; + } + + FMT_CONSTEXPR void on_error(const char*) {} +}; + +// Counts the number of parts in a format string. +template +FMT_CONSTEXPR unsigned count_parts(basic_string_view format_str) { + part_counter counter; + parse_format_string(format_str, counter); + return counter.num_parts; +} + +template +class format_string_compiler : public error_handler { private: using part = format_part; public: - FMT_CONSTEXPR format_preparation_handler(basic_string_view format, - PartsContainer& parts) - : parts_(parts), format_(format), parse_context_(format) {} + FMT_CONSTEXPR format_string_compiler(basic_string_view format_str, + PartHandler handler) + : handler_(handler), + format_str_(format_str), + parse_context_(format_str) {} FMT_CONSTEXPR void on_text(const Char* begin, const Char* end) { if (begin == end) return; - const auto offset = begin - format_.data(); + const auto offset = begin - format_str_.data(); const auto size = end - begin; - parts_.push_back(part(string_view_metadata(offset, size))); + handler_(part::make_text(string_view_metadata(offset, size))); } FMT_CONSTEXPR void on_arg_id() { - parts_.push_back(part(parse_context_.next_arg_id())); + part_ = part::make_arg_index(parse_context_.next_arg_id()); } FMT_CONSTEXPR void on_arg_id(unsigned id) { parse_context_.check_arg_id(id); - parts_.push_back(part(id)); + part_ = part::make_arg_index(id); } FMT_CONSTEXPR void on_arg_id(basic_string_view id) { - const auto view = string_view_metadata(format_, id); - const auto arg_id = typename part::named_arg_id(view); - parts_.push_back(part(arg_id)); + part_ = part::make_arg_name(string_view_metadata(format_str_, id)); } FMT_CONSTEXPR void on_replacement_field(const Char* ptr) { - parts_.back().end_of_argument_id = ptr - format_.begin(); + part_.arg_id_end = ptr - format_str_.begin(); + handler_(part_); } FMT_CONSTEXPR const Char* on_format_specs(const Char* begin, const Char* end) { - const auto specs_offset = to_unsigned(begin - format_.begin()); + const auto specs_offset = to_unsigned(begin - format_str_.begin()); - using parse_context = basic_parse_context; - internal::dynamic_format_specs parsed_specs; - dynamic_specs_handler handler(parsed_specs, parse_context_); + auto repl = typename part::replacement(); + dynamic_specs_handler> handler(repl.specs, + parse_context_); begin = parse_format_specs(begin, end, handler); - if (*begin != '}') on_error("missing '}' in format string"); - auto& last_part = parts_.back(); - auto specs = last_part.part_kind == part::kind::arg_index - ? typename part::replacement(last_part.val.arg_index) - : typename part::replacement(last_part.val.arg_name); - specs.parsed_specs = parsed_specs; - last_part = part(specs); - last_part.end_of_argument_id = specs_offset; + repl.arg_id = part_.part_kind == part::kind::arg_index + ? arg_ref(part_.val.arg_index) + : arg_ref(part_.val.str); + auto part = part::make_replacement(repl); + part.arg_id_end = specs_offset; + handler_(part); return begin; } private: - PartsContainer& parts_; - basic_string_view format_; + PartHandler handler_; + part part_; + basic_string_view format_str_; basic_parse_context parse_context_; }; +// Compiles a format string and invokes handler(part) for each parsed part. +template +FMT_CONSTEXPR void compile_format_string(basic_string_view format_str, + PartHandler handler) { + parse_format_string( + format_str, + format_string_compiler(format_str, handler)); +} + template class compiled_format { public: @@ -152,7 +191,7 @@ class compiled_format { switch (part.part_kind) { case format_part_t::kind::text: { - const auto text = value.text.to_view(format_view.data()); + const auto text = value.str.to_view(format_view.data()); auto output = ctx.out(); auto&& it = internal::reserve(output, text.size()); it = std::copy_n(text.begin(), text.size(), it); @@ -166,7 +205,7 @@ class compiled_format { case format_part_t::kind::arg_name: { advance_parse_context_to_specification(parse_ctx, part); - const auto named_arg_id = value.arg_name.to_view(format_view.data()); + const auto named_arg_id = value.str.to_view(format_view.data()); format_arg(parse_ctx, ctx, named_arg_id); } break; case format_part_t::kind::replacement: { @@ -176,7 +215,7 @@ class compiled_format { : ctx.arg(arg_id_value.name.to_view( to_string_view(format_).data())); - auto specs = value.repl.parsed_specs; + auto specs = value.repl.specs; handle_dynamic_spec( specs.width, specs.width_ref, ctx, format_view.begin()); @@ -199,7 +238,7 @@ class compiled_format { basic_parse_context& parse_ctx, const format_part_t& part) const { const auto view = to_string_view(format_); - const auto specification_begin = view.data() + part.end_of_argument_id; + const auto specification_begin = view.data() + part.arg_id_end; advance_to(parse_ctx, specification_begin); } @@ -228,53 +267,15 @@ class compiled_format { PreparedPartsProvider parts_provider_; }; -template struct part_counter { - unsigned num_parts = 0; - - FMT_CONSTEXPR void on_text(const Char* begin, const Char* end) { - if (begin != end) ++num_parts; - } - - FMT_CONSTEXPR void on_arg_id() { ++num_parts; } - FMT_CONSTEXPR void on_arg_id(unsigned) { ++num_parts; } - FMT_CONSTEXPR void on_arg_id(basic_string_view) { ++num_parts; } - - FMT_CONSTEXPR void on_replacement_field(const Char*) {} - - FMT_CONSTEXPR const Char* on_format_specs(const Char* begin, - const Char* end) { - // Find the matching brace. - unsigned braces_counter = 0; - for (; begin != end; ++begin) { - if (*begin == '{') { - ++braces_counter; - } else if (*begin == '}') { - if (braces_counter == 0u) break; - --braces_counter; - } - } - return begin; - } - - FMT_CONSTEXPR void on_error(const char*) {} -}; - template class compiletime_prepared_parts_type_provider { private: using char_type = char_t; - static FMT_CONSTEXPR unsigned count_parts() { - FMT_CONSTEXPR_DECL const auto text = to_string_view(Format{}); - part_counter counter; - internal::parse_format_string(text, counter); - return counter.num_parts; - } - // Workaround for old compilers. Compiletime parts preparation will not be // performed with them anyway. #if FMT_USE_CONSTEXPR static FMT_CONSTEXPR_DECL const unsigned number_of_format_parts = - compiletime_prepared_parts_type_provider::count_parts(); + count_parts(to_string_view(Format())); #else static const unsigned number_of_format_parts = 0u; #endif @@ -303,32 +304,19 @@ template class compiletime_prepared_parts_type_provider { format_parts_array, empty>; }; -template class compiletime_prepared_parts_collector { - private: - using format_part = typename Parts::value_type; - - public: - FMT_CONSTEXPR explicit compiletime_prepared_parts_collector(Parts& parts) - : parts_{parts}, counter_{0u} {} - - FMT_CONSTEXPR void push_back(format_part part) { parts_[counter_++] = part; } - - FMT_CONSTEXPR format_part& back() { return parts_[counter_ - 1]; } - - private: - Parts& parts_; - unsigned counter_; -}; - template FMT_CONSTEXPR PartsContainer -prepare_compiletime_parts(basic_string_view format) { - using collector = compiletime_prepared_parts_collector; - +prepare_compiletime_parts(basic_string_view format_str) { + // This is not a lambda for compatibility with older compilers. + struct collector { + PartsContainer& parts; + unsigned counter = 0; + FMT_CONSTEXPR void operator()(const format_part& part) { + parts[counter++] = part; + } + }; PartsContainer parts; - collector c(parts); - internal::parse_format_string( - format, format_preparation_handler(format, c)); + compile_format_string(format_str, collector{parts}); return parts; } @@ -336,10 +324,10 @@ template class runtime_parts_provider { public: using parts_container = std::vector>; - runtime_parts_provider(basic_string_view format) { - internal::parse_format_string( - format, - format_preparation_handler(format, parts_)); + runtime_parts_provider(basic_string_view format_str) { + compile_format_string( + format_str, + [this](const format_part& part) { parts_.push_back(part); }); } const parts_container& parts() const { return parts_; }