mirror of
https://github.com/fmtlib/fmt.git
synced 2025-07-30 10:47:35 +02:00
Refactor format string compilation
This commit is contained in:
@ -14,117 +14,156 @@
|
|||||||
FMT_BEGIN_NAMESPACE
|
FMT_BEGIN_NAMESPACE
|
||||||
namespace internal {
|
namespace internal {
|
||||||
|
|
||||||
|
// Part of a compiled format string. It can be either literal text or a
|
||||||
|
// replacement field.
|
||||||
template <typename Char> struct format_part {
|
template <typename Char> 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<Char> arg_id;
|
|
||||||
dynamic_format_specs<Char> parsed_specs;
|
|
||||||
};
|
|
||||||
|
|
||||||
enum class kind { arg_index, arg_name, text, replacement };
|
enum class kind { arg_index, arg_name, text, replacement };
|
||||||
|
|
||||||
|
struct replacement {
|
||||||
|
arg_ref<Char> arg_id;
|
||||||
|
dynamic_format_specs<Char> specs;
|
||||||
|
};
|
||||||
|
|
||||||
kind part_kind;
|
kind part_kind;
|
||||||
std::size_t end_of_argument_id;
|
|
||||||
union value {
|
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;
|
unsigned arg_index;
|
||||||
string_view_metadata arg_name;
|
string_view_metadata str;
|
||||||
string_view_metadata text;
|
|
||||||
replacement repl;
|
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;
|
} val;
|
||||||
|
std::size_t arg_id_end = 0; // Position past the end of the argument id.
|
||||||
|
|
||||||
FMT_CONSTEXPR format_part()
|
FMT_CONSTEXPR format_part(kind k = kind::arg_index, value v = {})
|
||||||
: part_kind(kind::arg_index), end_of_argument_id(0u), val(0u) {}
|
: part_kind(k), val(v) {}
|
||||||
|
|
||||||
FMT_CONSTEXPR format_part(unsigned arg_index)
|
static FMT_CONSTEXPR format_part make_arg_index(unsigned index) {
|
||||||
: part_kind(kind::arg_index), end_of_argument_id(0u), val(arg_index) {}
|
return format_part(kind::arg_index, index);
|
||||||
|
}
|
||||||
FMT_CONSTEXPR format_part(named_arg_id arg_name)
|
static FMT_CONSTEXPR format_part make_arg_name(string_view_metadata name) {
|
||||||
: part_kind(kind::arg_name), end_of_argument_id(0u), val(arg_name) {}
|
return format_part(kind::arg_name, name);
|
||||||
|
}
|
||||||
FMT_CONSTEXPR format_part(string_view_metadata text)
|
static FMT_CONSTEXPR format_part make_text(string_view_metadata text) {
|
||||||
: part_kind(kind::text), end_of_argument_id(0u), val(text) {}
|
return format_part(kind::text, text);
|
||||||
|
}
|
||||||
FMT_CONSTEXPR format_part(replacement repl)
|
static FMT_CONSTEXPR format_part make_replacement(replacement repl) {
|
||||||
: part_kind(kind::replacement), end_of_argument_id(0u), val(repl) {}
|
return format_part(kind::replacement, repl);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
template <typename Char, typename PartsContainer>
|
template <typename Char> struct part_counter {
|
||||||
class format_preparation_handler : public internal::error_handler {
|
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<Char>) { ++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 <typename Char>
|
||||||
|
FMT_CONSTEXPR unsigned count_parts(basic_string_view<Char> format_str) {
|
||||||
|
part_counter<Char> counter;
|
||||||
|
parse_format_string<true>(format_str, counter);
|
||||||
|
return counter.num_parts;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename Char, typename PartHandler>
|
||||||
|
class format_string_compiler : public error_handler {
|
||||||
private:
|
private:
|
||||||
using part = format_part<Char>;
|
using part = format_part<Char>;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
FMT_CONSTEXPR format_preparation_handler(basic_string_view<Char> format,
|
FMT_CONSTEXPR format_string_compiler(basic_string_view<Char> format_str,
|
||||||
PartsContainer& parts)
|
PartHandler handler)
|
||||||
: parts_(parts), format_(format), parse_context_(format) {}
|
: handler_(handler),
|
||||||
|
format_str_(format_str),
|
||||||
|
parse_context_(format_str) {}
|
||||||
|
|
||||||
FMT_CONSTEXPR void on_text(const Char* begin, const Char* end) {
|
FMT_CONSTEXPR void on_text(const Char* begin, const Char* end) {
|
||||||
if (begin == end) return;
|
if (begin == end) return;
|
||||||
const auto offset = begin - format_.data();
|
const auto offset = begin - format_str_.data();
|
||||||
const auto size = end - begin;
|
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() {
|
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) {
|
FMT_CONSTEXPR void on_arg_id(unsigned id) {
|
||||||
parse_context_.check_arg_id(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<Char> id) {
|
FMT_CONSTEXPR void on_arg_id(basic_string_view<Char> id) {
|
||||||
const auto view = string_view_metadata(format_, id);
|
part_ = part::make_arg_name(string_view_metadata(format_str_, id));
|
||||||
const auto arg_id = typename part::named_arg_id(view);
|
|
||||||
parts_.push_back(part(arg_id));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
FMT_CONSTEXPR void on_replacement_field(const Char* ptr) {
|
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,
|
FMT_CONSTEXPR const Char* on_format_specs(const Char* begin,
|
||||||
const Char* end) {
|
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<Char>;
|
auto repl = typename part::replacement();
|
||||||
internal::dynamic_format_specs<Char> parsed_specs;
|
dynamic_specs_handler<basic_parse_context<Char>> handler(repl.specs,
|
||||||
dynamic_specs_handler<parse_context> handler(parsed_specs, parse_context_);
|
parse_context_);
|
||||||
begin = parse_format_specs(begin, end, handler);
|
begin = parse_format_specs(begin, end, handler);
|
||||||
|
|
||||||
if (*begin != '}') on_error("missing '}' in format string");
|
if (*begin != '}') on_error("missing '}' in format string");
|
||||||
|
|
||||||
auto& last_part = parts_.back();
|
repl.arg_id = part_.part_kind == part::kind::arg_index
|
||||||
auto specs = last_part.part_kind == part::kind::arg_index
|
? arg_ref<Char>(part_.val.arg_index)
|
||||||
? typename part::replacement(last_part.val.arg_index)
|
: arg_ref<Char>(part_.val.str);
|
||||||
: typename part::replacement(last_part.val.arg_name);
|
auto part = part::make_replacement(repl);
|
||||||
specs.parsed_specs = parsed_specs;
|
part.arg_id_end = specs_offset;
|
||||||
last_part = part(specs);
|
handler_(part);
|
||||||
last_part.end_of_argument_id = specs_offset;
|
|
||||||
return begin;
|
return begin;
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
PartsContainer& parts_;
|
PartHandler handler_;
|
||||||
basic_string_view<Char> format_;
|
part part_;
|
||||||
|
basic_string_view<Char> format_str_;
|
||||||
basic_parse_context<Char> parse_context_;
|
basic_parse_context<Char> parse_context_;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Compiles a format string and invokes handler(part) for each parsed part.
|
||||||
|
template <bool IS_CONSTEXPR, typename Char, typename PartHandler>
|
||||||
|
FMT_CONSTEXPR void compile_format_string(basic_string_view<Char> format_str,
|
||||||
|
PartHandler handler) {
|
||||||
|
parse_format_string<IS_CONSTEXPR>(
|
||||||
|
format_str,
|
||||||
|
format_string_compiler<Char, PartHandler>(format_str, handler));
|
||||||
|
}
|
||||||
|
|
||||||
template <typename Format, typename PreparedPartsProvider, typename... Args>
|
template <typename Format, typename PreparedPartsProvider, typename... Args>
|
||||||
class compiled_format {
|
class compiled_format {
|
||||||
public:
|
public:
|
||||||
@ -152,7 +191,7 @@ class compiled_format {
|
|||||||
|
|
||||||
switch (part.part_kind) {
|
switch (part.part_kind) {
|
||||||
case format_part_t::kind::text: {
|
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 output = ctx.out();
|
||||||
auto&& it = internal::reserve(output, text.size());
|
auto&& it = internal::reserve(output, text.size());
|
||||||
it = std::copy_n(text.begin(), text.size(), it);
|
it = std::copy_n(text.begin(), text.size(), it);
|
||||||
@ -166,7 +205,7 @@ class compiled_format {
|
|||||||
|
|
||||||
case format_part_t::kind::arg_name: {
|
case format_part_t::kind::arg_name: {
|
||||||
advance_parse_context_to_specification(parse_ctx, part);
|
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<Range>(parse_ctx, ctx, named_arg_id);
|
format_arg<Range>(parse_ctx, ctx, named_arg_id);
|
||||||
} break;
|
} break;
|
||||||
case format_part_t::kind::replacement: {
|
case format_part_t::kind::replacement: {
|
||||||
@ -176,7 +215,7 @@ class compiled_format {
|
|||||||
: ctx.arg(arg_id_value.name.to_view(
|
: ctx.arg(arg_id_value.name.to_view(
|
||||||
to_string_view(format_).data()));
|
to_string_view(format_).data()));
|
||||||
|
|
||||||
auto specs = value.repl.parsed_specs;
|
auto specs = value.repl.specs;
|
||||||
|
|
||||||
handle_dynamic_spec<internal::width_checker>(
|
handle_dynamic_spec<internal::width_checker>(
|
||||||
specs.width, specs.width_ref, ctx, format_view.begin());
|
specs.width, specs.width_ref, ctx, format_view.begin());
|
||||||
@ -199,7 +238,7 @@ class compiled_format {
|
|||||||
basic_parse_context<char_type>& parse_ctx,
|
basic_parse_context<char_type>& parse_ctx,
|
||||||
const format_part_t& part) const {
|
const format_part_t& part) const {
|
||||||
const auto view = to_string_view(format_);
|
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);
|
advance_to(parse_ctx, specification_begin);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -228,53 +267,15 @@ class compiled_format {
|
|||||||
PreparedPartsProvider parts_provider_;
|
PreparedPartsProvider parts_provider_;
|
||||||
};
|
};
|
||||||
|
|
||||||
template <typename Char> 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<Char>) { ++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 <typename Format> class compiletime_prepared_parts_type_provider {
|
template <typename Format> class compiletime_prepared_parts_type_provider {
|
||||||
private:
|
private:
|
||||||
using char_type = char_t<Format>;
|
using char_type = char_t<Format>;
|
||||||
|
|
||||||
static FMT_CONSTEXPR unsigned count_parts() {
|
|
||||||
FMT_CONSTEXPR_DECL const auto text = to_string_view(Format{});
|
|
||||||
part_counter<char_type> counter;
|
|
||||||
internal::parse_format_string</*IS_CONSTEXPR=*/true>(text, counter);
|
|
||||||
return counter.num_parts;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Workaround for old compilers. Compiletime parts preparation will not be
|
// Workaround for old compilers. Compiletime parts preparation will not be
|
||||||
// performed with them anyway.
|
// performed with them anyway.
|
||||||
#if FMT_USE_CONSTEXPR
|
#if FMT_USE_CONSTEXPR
|
||||||
static FMT_CONSTEXPR_DECL const unsigned number_of_format_parts =
|
static FMT_CONSTEXPR_DECL const unsigned number_of_format_parts =
|
||||||
compiletime_prepared_parts_type_provider::count_parts();
|
count_parts(to_string_view(Format()));
|
||||||
#else
|
#else
|
||||||
static const unsigned number_of_format_parts = 0u;
|
static const unsigned number_of_format_parts = 0u;
|
||||||
#endif
|
#endif
|
||||||
@ -303,32 +304,19 @@ template <typename Format> class compiletime_prepared_parts_type_provider {
|
|||||||
format_parts_array<number_of_format_parts>, empty>;
|
format_parts_array<number_of_format_parts>, empty>;
|
||||||
};
|
};
|
||||||
|
|
||||||
template <typename Parts> 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 <typename PartsContainer, typename Char>
|
template <typename PartsContainer, typename Char>
|
||||||
FMT_CONSTEXPR PartsContainer
|
FMT_CONSTEXPR PartsContainer
|
||||||
prepare_compiletime_parts(basic_string_view<Char> format) {
|
prepare_compiletime_parts(basic_string_view<Char> format_str) {
|
||||||
using collector = compiletime_prepared_parts_collector<PartsContainer>;
|
// This is not a lambda for compatibility with older compilers.
|
||||||
|
struct collector {
|
||||||
|
PartsContainer& parts;
|
||||||
|
unsigned counter = 0;
|
||||||
|
FMT_CONSTEXPR void operator()(const format_part<Char>& part) {
|
||||||
|
parts[counter++] = part;
|
||||||
|
}
|
||||||
|
};
|
||||||
PartsContainer parts;
|
PartsContainer parts;
|
||||||
collector c(parts);
|
compile_format_string<true>(format_str, collector{parts});
|
||||||
internal::parse_format_string</*IS_CONSTEXPR=*/true>(
|
|
||||||
format, format_preparation_handler<Char, collector>(format, c));
|
|
||||||
return parts;
|
return parts;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -336,10 +324,10 @@ template <typename Char> class runtime_parts_provider {
|
|||||||
public:
|
public:
|
||||||
using parts_container = std::vector<internal::format_part<Char>>;
|
using parts_container = std::vector<internal::format_part<Char>>;
|
||||||
|
|
||||||
runtime_parts_provider(basic_string_view<Char> format) {
|
runtime_parts_provider(basic_string_view<Char> format_str) {
|
||||||
internal::parse_format_string<false>(
|
compile_format_string<false>(
|
||||||
format,
|
format_str,
|
||||||
format_preparation_handler<Char, parts_container>(format, parts_));
|
[this](const format_part<Char>& part) { parts_.push_back(part); });
|
||||||
}
|
}
|
||||||
|
|
||||||
const parts_container& parts() const { return parts_; }
|
const parts_container& parts() const { return parts_; }
|
||||||
|
Reference in New Issue
Block a user