Commit a128b5b2 authored by Victor Zverovich's avatar Victor Zverovich

Simplify format string compilation

parent 466128de
...@@ -172,11 +172,12 @@ template <typename Context, typename Range, typename CompiledFormat> ...@@ -172,11 +172,12 @@ template <typename Context, typename Range, typename CompiledFormat>
auto vformat_to(Range out, CompiledFormat& cf, basic_format_args<Context> args) auto vformat_to(Range out, CompiledFormat& cf, basic_format_args<Context> args)
-> typename Context::iterator { -> typename Context::iterator {
using char_type = typename Context::char_type; using char_type = typename Context::char_type;
basic_parse_context<char_type> parse_ctx(to_string_view(cf.format_)); basic_parse_context<char_type> parse_ctx(to_string_view(cf.format_str_));
Context ctx(out.begin(), args); Context ctx(out.begin(), args);
const auto& parts = cf.parts_provider_.parts(); const auto& parts = cf.parts();
for (auto part_it = parts.begin(); part_it != parts.end(); ++part_it) { for (auto part_it = std::begin(parts); part_it != std::end(parts);
++part_it) {
const auto& part = *part_it; const auto& part = *part_it;
const auto& value = part.val; const auto& value = part.val;
...@@ -188,7 +189,8 @@ auto vformat_to(Range out, CompiledFormat& cf, basic_format_args<Context> args) ...@@ -188,7 +189,8 @@ auto vformat_to(Range out, CompiledFormat& cf, basic_format_args<Context> args)
auto&& it = reserve(output, text.size()); auto&& it = reserve(output, text.size());
it = std::copy_n(text.begin(), text.size(), it); it = std::copy_n(text.begin(), text.size(), it);
ctx.advance_to(output); ctx.advance_to(output);
} break; break;
}
case format_part_t::kind::arg_index: case format_part_t::kind::arg_index:
advance_to(parse_ctx, part.arg_id_end); advance_to(parse_ctx, part.arg_id_end);
...@@ -222,132 +224,120 @@ auto vformat_to(Range out, CompiledFormat& cf, basic_format_args<Context> args) ...@@ -222,132 +224,120 @@ auto vformat_to(Range out, CompiledFormat& cf, basic_format_args<Context> args)
advance_to(parse_ctx, part.arg_id_end); advance_to(parse_ctx, part.arg_id_end);
ctx.advance_to( ctx.advance_to(
visit_format_arg(arg_formatter<Range>(ctx, nullptr, &specs), arg)); visit_format_arg(arg_formatter<Range>(ctx, nullptr, &specs), arg));
} break; break;
}
} }
} }
return ctx.out(); return ctx.out();
} }
} // namespace cf } // namespace cf
template <typename S, typename PreparedPartsProvider, typename... Args> template <typename S, typename = void> struct compiled_format_base {
class compiled_format {
private:
S format_;
PreparedPartsProvider parts_provider_;
template <typename Context, typename Range, typename CompiledFormat>
friend auto cf::vformat_to(Range out, CompiledFormat& cf,
basic_format_args<Context> args) ->
typename Context::iterator;
public:
using char_type = char_t<S>; using char_type = char_t<S>;
using parts_container = std::vector<internal::format_part<char_type>>;
compiled_format() = delete; parts_container compiled_parts;
constexpr compiled_format(S f)
: format_(std::move(f)), parts_provider_(to_string_view(format_)) {}
};
template <typename Format> class compiletime_prepared_parts_type_provider { explicit compiled_format_base(basic_string_view<char_type> format_str) {
private: compile_format_string<false>(format_str,
using char_type = char_t<Format>; [this](const format_part<char_type>& part) {
compiled_parts.push_back(part);
// 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 =
count_parts(to_string_view(Format()));
#else
static const unsigned number_of_format_parts = 0u;
#endif
public:
template <unsigned N> struct format_parts_array {
using value_type = format_part<char_type>;
FMT_CONSTEXPR format_parts_array() : arr{} {}
FMT_CONSTEXPR value_type& operator[](unsigned ind) { return arr[ind]; }
FMT_CONSTEXPR const value_type* begin() const { return arr; }
FMT_CONSTEXPR const value_type* end() const { return begin() + N; }
private:
value_type arr[N];
};
struct empty { const parts_container& parts() const { return compiled_parts; }
// Parts preparator will search for it };
using value_type = format_part<char_type>;
};
using type = conditional_t<number_of_format_parts != 0, template <typename Char, unsigned N> struct format_part_array {
format_parts_array<number_of_format_parts>, empty>; format_part<Char> data[N] = {};
FMT_CONSTEXPR format_part_array() = default;
}; };
template <typename PartsContainer, typename Char> template <typename Char, unsigned N>
FMT_CONSTEXPR PartsContainer FMT_CONSTEXPR format_part_array<Char, N> compile_to_parts(
prepare_compiletime_parts(basic_string_view<Char> format_str) { basic_string_view<Char> format_str) {
format_part_array<Char, N> parts;
unsigned counter = 0;
// This is not a lambda for compatibility with older compilers. // This is not a lambda for compatibility with older compilers.
struct collector { struct {
PartsContainer& parts; format_part<Char>* parts;
unsigned counter = 0; unsigned* counter;
FMT_CONSTEXPR void operator()(const format_part<Char>& part) { FMT_CONSTEXPR void operator()(const format_part<Char>& part) {
parts[counter++] = part; parts[(*counter)++] = part;
} }
}; } collector{parts.data, &counter};
PartsContainer parts; compile_format_string<true>(format_str, collector);
compile_format_string<true>(format_str, collector{parts}); if (counter < N) {
parts.data[counter] =
format_part<Char>::make_text(basic_string_view<Char>());
}
return parts; return parts;
} }
template <typename Char> class runtime_parts_provider { template <typename T> constexpr const T& constexpr_max(const T& a, const T& b) {
public: return (a < b) ? b : a;
using parts_container = std::vector<internal::format_part<Char>>; }
runtime_parts_provider(basic_string_view<Char> format_str) {
compile_format_string<false>(
format_str,
[this](const format_part<Char>& part) { parts_.push_back(part); });
}
const parts_container& parts() const { return parts_; } template <typename S>
struct compiled_format_base<S, enable_if_t<is_compile_string<S>::value>> {
using char_type = char_t<S>;
private: FMT_CONSTEXPR explicit compiled_format_base(basic_string_view<char_type>) {}
parts_container parts_;
};
template <typename Format> struct compiletime_parts_provider { // Workaround for old compilers. Format string compilation will not be
using parts_container = // performed there anyway.
typename internal::compiletime_prepared_parts_type_provider<Format>::type; #if FMT_USE_CONSTEXPR
static FMT_CONSTEXPR_DECL const unsigned num_format_parts =
constexpr_max(count_parts(to_string_view(S())), 1u);
#else
static const unsigned num_format_parts = 1;
#endif
template <typename Char> using parts_container = format_part<char_type>[num_format_parts];
FMT_CONSTEXPR compiletime_parts_provider(basic_string_view<Char>) {}
const parts_container& parts() const { const parts_container& parts() const {
static FMT_CONSTEXPR_DECL const parts_container prepared_parts = static FMT_CONSTEXPR_DECL const auto compiled_parts =
prepare_compiletime_parts<parts_container>( compile_to_parts<char_type, num_format_parts>(
internal::to_string_view(Format())); internal::to_string_view(S()));
return prepared_parts; return compiled_parts.data;
} }
}; };
template <typename S, typename... Args>
class compiled_format : private compiled_format_base<S> {
public:
using typename compiled_format_base<S>::char_type;
private:
basic_string_view<char_type> format_str_;
template <typename Context, typename Range, typename CompiledFormat>
friend auto cf::vformat_to(Range out, CompiledFormat& cf,
basic_format_args<Context> args) ->
typename Context::iterator;
public:
compiled_format() = delete;
explicit constexpr compiled_format(basic_string_view<char_type> format_str)
: compiled_format_base<S>(format_str), format_str_(format_str) {}
};
} // namespace internal } // namespace internal
#if FMT_USE_CONSTEXPR #if FMT_USE_CONSTEXPR
template <typename... Args, typename S, template <typename... Args, typename S,
FMT_ENABLE_IF(is_compile_string<S>::value)> FMT_ENABLE_IF(is_compile_string<S>::value)>
FMT_CONSTEXPR auto compile(S format_str) FMT_CONSTEXPR auto compile(S format_str)
-> internal::compiled_format<S, internal::compiletime_parts_provider<S>, -> internal::compiled_format<S, Args...> {
Args...> { return internal::compiled_format<S, Args...>(to_string_view(format_str));
return format_str;
} }
#endif #endif
// Compiles the format string which must be a string literal. // Compiles the format string which must be a string literal.
template <typename... Args, typename Char, size_t N> template <typename... Args, typename Char, size_t N>
auto compile(const Char (&format_str)[N]) -> internal::compiled_format< auto compile(const Char (&format_str)[N])
std::basic_string<Char>, internal::runtime_parts_provider<Char>, Args...> { -> internal::compiled_format<const Char*, Args...> {
return std::basic_string<Char>(format_str, N - 1); return internal::compiled_format<const Char*, Args...>(
basic_string_view<Char>(format_str, N - 1));
} }
template <typename CompiledFormat, typename... Args, template <typename CompiledFormat, typename... Args,
...@@ -383,7 +373,7 @@ format_to_n_result<OutputIt> format_to_n(OutputIt out, size_t n, ...@@ -383,7 +373,7 @@ format_to_n_result<OutputIt> format_to_n(OutputIt out, size_t n,
template <typename CompiledFormat, typename... Args> template <typename CompiledFormat, typename... Args>
std::size_t formatted_size(const CompiledFormat& cf, const Args&... args) { std::size_t formatted_size(const CompiledFormat& cf, const Args&... args) {
return fmt::format_to( return format_to(
internal::counting_iterator<typename CompiledFormat::char_type>(), internal::counting_iterator<typename CompiledFormat::char_type>(),
cf, args...) cf, args...)
.count(); .count();
......
...@@ -39,14 +39,12 @@ using testing::StrictMock; ...@@ -39,14 +39,12 @@ using testing::StrictMock;
#if FMT_USE_CONSTEXPR #if FMT_USE_CONSTEXPR
template <unsigned EXPECTED_PARTS_COUNT, typename Format> template <unsigned EXPECTED_PARTS_COUNT, typename Format>
void check_prepared_parts_type(Format format) { void check_prepared_parts_type(Format format) {
typedef fmt::internal::compiletime_prepared_parts_type_provider<decltype( typedef fmt::internal::compiled_format_base<decltype(format)> provider;
format)> typedef fmt::internal::format_part<char>
provider; expected_parts_type[EXPECTED_PARTS_COUNT];
typedef typename provider::template format_parts_array<EXPECTED_PARTS_COUNT> static_assert(std::is_same<typename provider::parts_container,
expected_parts_type; expected_parts_type>::value,
static_assert( "CompileTimePreparedPartsTypeProvider test failed");
std::is_same<typename provider::type, expected_parts_type>::value,
"CompileTimePreparedPartsTypeProvider test failed");
} }
TEST(CompileTest, CompileTimePreparedPartsTypeProvider) { TEST(CompileTest, CompileTimePreparedPartsTypeProvider) {
...@@ -122,8 +120,7 @@ TEST(CompileTest, FormattedSize) { ...@@ -122,8 +120,7 @@ TEST(CompileTest, FormattedSize) {
struct formattable {}; struct formattable {};
FMT_BEGIN_NAMESPACE FMT_BEGIN_NAMESPACE
template <> template <> struct formatter<formattable> : formatter<const char*> {
struct formatter<formattable> : formatter<const char*> {
auto format(formattable, format_context& ctx) -> decltype(ctx.out()) { auto format(formattable, format_context& ctx) -> decltype(ctx.out()) {
return formatter<const char*>::format("foo", ctx); return formatter<const char*>::format("foo", ctx);
} }
...@@ -134,3 +131,8 @@ TEST(CompileTest, FormatUserDefinedType) { ...@@ -134,3 +131,8 @@ TEST(CompileTest, FormatUserDefinedType) {
auto f = fmt::compile<formattable>("{}"); auto f = fmt::compile<formattable>("{}");
EXPECT_EQ(fmt::format(f, formattable()), "foo"); EXPECT_EQ(fmt::format(f, formattable()), "foo");
} }
TEST(CompileTest, EmptyFormatString) {
auto f = fmt::compile<>("");
EXPECT_EQ(fmt::format(f), "");
}
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment