From ccf4ccde23c2209e065db36ad53c63f4fb494ec6 Mon Sep 17 00:00:00 2001 From: Victor Zverovich Date: Thu, 29 Apr 2021 17:17:59 -0700 Subject: [PATCH] Cleanup tests and format string compilation --- include/fmt/compile.h | 345 +----------------------------------------- test/compile-test.cc | 104 ++++--------- 2 files changed, 32 insertions(+), 417 deletions(-) diff --git a/include/fmt/compile.h b/include/fmt/compile.h index fac475be..da3f054f 100644 --- a/include/fmt/compile.h +++ b/include/fmt/compile.h @@ -135,333 +135,6 @@ const T& first(const T& value, const Tail&...) { return value; } -// Part of a compiled format string. It can be either literal text or a -// replacement field. -template struct format_part { - enum class kind { arg_index, arg_name, text, replacement }; - - struct replacement { - arg_ref arg_id; - dynamic_format_specs specs; - }; - - kind part_kind; - union value { - int arg_index; - basic_string_view str; - replacement repl; - - FMT_CONSTEXPR value(int index = 0) : arg_index(index) {} - FMT_CONSTEXPR value(basic_string_view s) : str(s) {} - FMT_CONSTEXPR value(replacement r) : repl(r) {} - } val; - // Position past the end of the argument id. - const Char* arg_id_end = nullptr; - - FMT_CONSTEXPR format_part(kind k = kind::arg_index, value v = {}) - : part_kind(k), val(v) {} - - static FMT_CONSTEXPR format_part make_arg_index(int index) { - return format_part(kind::arg_index, index); - } - static FMT_CONSTEXPR format_part make_arg_name(basic_string_view name) { - return format_part(kind::arg_name, name); - } - static FMT_CONSTEXPR format_part make_text(basic_string_view text) { - return format_part(kind::text, text); - } - static FMT_CONSTEXPR format_part make_replacement(replacement repl) { - return format_part(kind::replacement, repl); - } -}; - -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 int on_arg_id() { return ++num_parts, 0; } - FMT_CONSTEXPR int on_arg_id(int) { return ++num_parts, 0; } - FMT_CONSTEXPR int on_arg_id(basic_string_view) { - return ++num_parts, 0; - } - - FMT_CONSTEXPR void on_replacement_field(int, const Char*) {} - - FMT_CONSTEXPR const Char* on_format_specs(int, 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; - - PartHandler handler_; - part part_; - basic_string_view format_str_; - basic_format_parse_context parse_context_; - - public: - 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) - handler_(part::make_text({begin, to_unsigned(end - begin)})); - } - - FMT_CONSTEXPR int on_arg_id() { - part_ = part::make_arg_index(parse_context_.next_arg_id()); - return 0; - } - - FMT_CONSTEXPR int on_arg_id(int id) { - parse_context_.check_arg_id(id); - part_ = part::make_arg_index(id); - return 0; - } - - FMT_CONSTEXPR int on_arg_id(basic_string_view id) { - part_ = part::make_arg_name(id); - return 0; - } - - FMT_CONSTEXPR void on_replacement_field(int, const Char* ptr) { - part_.arg_id_end = ptr; - handler_(part_); - } - - FMT_CONSTEXPR const Char* on_format_specs(int, const Char* begin, - const Char* end) { - auto repl = typename part::replacement(); - dynamic_specs_handler> handler( - repl.specs, parse_context_); - auto it = parse_format_specs(begin, end, handler); - if (*it != '}') on_error("missing '}' in format string"); - repl.arg_id = part_.part_kind == part::kind::arg_index - ? arg_ref(part_.val.arg_index) - : arg_ref(part_.val.str); - auto replacement_part = part::make_replacement(repl); - replacement_part.arg_id_end = begin; - handler_(replacement_part); - return it; - } -}; - -// 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 -void format_arg( - basic_format_parse_context& parse_ctx, - Context& ctx, Id arg_id) { - auto arg = ctx.arg(arg_id); - if (arg.type() == type::custom_type) { - visit_format_arg(custom_formatter(parse_ctx, ctx), arg); - } else { - ctx.advance_to(visit_format_arg( - default_arg_formatter{ - ctx.out(), ctx.args(), ctx.locale()}, - arg)); - } -} - -// vformat_to is defined in a subnamespace to prevent ADL. -namespace cf { -template -auto vformat_to(OutputIt out, CompiledFormat& cf, - basic_format_args args) -> typename Context::iterator { - using char_type = typename Context::char_type; - basic_format_parse_context parse_ctx( - to_string_view(cf.format_str_)); - Context ctx(out, args); - - const auto& parts = cf.parts(); - for (auto part_it = std::begin(parts); part_it != std::end(parts); - ++part_it) { - const auto& part = *part_it; - const auto& value = part.val; - - using format_part_t = format_part; - switch (part.part_kind) { - case format_part_t::kind::text: { - const auto text = value.str; - auto output = ctx.out(); - auto&& it = reserve(output, text.size()); - it = std::copy_n(text.begin(), text.size(), it); - ctx.advance_to(output); - break; - } - - case format_part_t::kind::arg_index: - advance_to(parse_ctx, part.arg_id_end); - detail::format_arg(parse_ctx, ctx, value.arg_index); - break; - - case format_part_t::kind::arg_name: - advance_to(parse_ctx, part.arg_id_end); - detail::format_arg(parse_ctx, ctx, value.str); - break; - - case format_part_t::kind::replacement: { - const auto& arg_id_value = value.repl.arg_id.val; - const auto arg = value.repl.arg_id.kind == arg_id_kind::index - ? ctx.arg(arg_id_value.index) - : ctx.arg(arg_id_value.name); - - auto specs = value.repl.specs; - - handle_dynamic_spec(specs.width, specs.width_ref, ctx); - handle_dynamic_spec(specs.precision, - specs.precision_ref, ctx); - - error_handler h; - numeric_specs_checker checker(h, arg.type()); - if (specs.align == align::numeric) checker.require_numeric_argument(); - if (specs.sign != sign::none) checker.check_sign(); - if (specs.alt) checker.require_numeric_argument(); - if (specs.precision >= 0) checker.check_precision(); - - advance_to(parse_ctx, part.arg_id_end); - ctx.advance_to(visit_format_arg( - arg_formatter(ctx, specs), - arg)); - break; - } - } - } - return ctx.out(); -} -} // namespace cf - -struct basic_compiled_format {}; - -template -struct compiled_format_base : basic_compiled_format { - using char_type = char_t; - using parts_container = std::vector>; - - parts_container compiled_parts; - - explicit compiled_format_base(basic_string_view format_str) { - compile_format_string(format_str, - [this](const format_part& part) { - compiled_parts.push_back(part); - }); - } - - const parts_container& parts() const { return compiled_parts; } -}; - -template struct format_part_array { - format_part data[N] = {}; - FMT_CONSTEXPR format_part_array() = default; -}; - -template -FMT_CONSTEXPR format_part_array compile_to_parts( - basic_string_view format_str) { - format_part_array parts; - unsigned counter = 0; - // This is not a lambda for compatibility with older compilers. - struct { - format_part* parts; - unsigned* counter; - FMT_CONSTEXPR void operator()(const format_part& part) { - parts[(*counter)++] = part; - } - } collector{parts.data, &counter}; - compile_format_string(format_str, collector); - if (counter < N) { - parts.data[counter] = - format_part::make_text(basic_string_view()); - } - return parts; -} - -template constexpr const T& constexpr_max(const T& a, const T& b) { - return (a < b) ? b : a; -} - -template -struct compiled_format_base::value>> - : basic_compiled_format { - using char_type = char_t; - - FMT_CONSTEXPR explicit compiled_format_base(basic_string_view) {} - -// Workaround for old compilers. Format string compilation will not be -// performed there anyway. -#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 - - using parts_container = format_part[num_format_parts]; - - const parts_container& parts() const { - static FMT_CONSTEXPR_DECL const auto compiled_parts = - compile_to_parts( - detail::to_string_view(S())); - return compiled_parts.data; - } -}; - -template -class compiled_format : private compiled_format_base { - public: - using typename compiled_format_base::char_type; - - private: - basic_string_view format_str_; - - template - friend auto cf::vformat_to(OutputIt out, CompiledFormat& cf, - basic_format_args args) -> - typename Context::iterator; - - public: - compiled_format() = delete; - explicit constexpr compiled_format(basic_string_view format_str) - : compiled_format_base(format_str), format_str_(format_str) {} -}; - #ifdef __cpp_if_constexpr template struct type_list {}; @@ -826,12 +499,6 @@ constexpr auto compile(S format_str) { return result; } } -#else -template ::value)> -constexpr auto compile(S format_str) -> detail::compiled_format { - return detail::compiled_format(to_string_view(format_str)); -} #endif // __cpp_if_constexpr } // namespace detail @@ -855,13 +522,11 @@ constexpr OutputIt format_to(OutputIt out, const CompiledFormat& cf, const Args&... args) { return cf.format(out, args...); } -#endif // __cpp_if_constexpr template ::value)> FMT_INLINE std::basic_string format(const S&, Args&&... args) { -#ifdef __cpp_if_constexpr if constexpr (std::is_same::value) { constexpr basic_string_view str = S(); if constexpr (str.size() == 2 && str[0] == '{' && str[1] == '}') { @@ -874,9 +539,7 @@ FMT_INLINE std::basic_string format(const S&, } } } -#endif constexpr auto compiled = detail::compile(S()); -#ifdef __cpp_if_constexpr if constexpr (std::is_same, detail::unknown_format>()) { return format(static_cast>(S()), @@ -884,16 +547,12 @@ FMT_INLINE std::basic_string format(const S&, } else { return format(compiled, std::forward(args)...); } -#else - return format(compiled, std::forward(args)...); -#endif } template ::value)> FMT_CONSTEXPR OutputIt format_to(OutputIt out, const S&, Args&&... args) { constexpr auto compiled = detail::compile(S()); -#ifdef __cpp_if_constexpr if constexpr (std::is_same, detail::unknown_format>()) { return format_to(out, @@ -902,10 +561,8 @@ FMT_CONSTEXPR OutputIt format_to(OutputIt out, const S&, Args&&... args) { } else { return format_to(out, compiled, std::forward(args)...); } -#else - return format_to(out, compiled, std::forward(args)...); -#endif } +#endif template ::value)> diff --git a/test/compile-test.cc b/test/compile-test.cc index cbdc4613..4ff6d5f2 100644 --- a/test/compile-test.cc +++ b/test/compile-test.cc @@ -5,49 +5,38 @@ // // For the license information refer to format.h. -#include +#include "fmt/compile.h" + #include -// Check that fmt/compile.h compiles with windows.h included before it. -#ifdef _WIN32 -# include -#endif - #include "fmt/chrono.h" -#include "fmt/compile.h" #include "gmock/gmock.h" -#include "gtest-extra.h" -#include "util.h" -TEST(IteratorTest, TruncatingIterator) { +TEST(iterator_test, truncating_iterator) { char* p = nullptr; - fmt::detail::truncating_iterator it(p, 3); + auto it = fmt::detail::truncating_iterator(p, 3); auto prev = it++; EXPECT_EQ(prev.base(), p); EXPECT_EQ(it.base(), p + 1); } -TEST(IteratorTest, TruncatingIteratorDefaultConstruct) { - static_assert(std::is_default_constructible< - fmt::detail::truncating_iterator>::value, - ""); - - fmt::detail::truncating_iterator it; +TEST(iterator_test, truncating_iterator_default_construct) { + auto it = fmt::detail::truncating_iterator(); EXPECT_EQ(nullptr, it.base()); EXPECT_EQ(std::size_t{0}, it.count()); } #ifdef __cpp_lib_ranges -TEST(IteratorTest, TruncatingIteratorOutputIterator) { +TEST(iterator_test, truncating_iterator_is_output_iterator) { static_assert( std::output_iterator, char>); } #endif -TEST(IteratorTest, TruncatingBackInserter) { - std::string buffer; +TEST(iterator_test, truncating_back_inserter) { + auto buffer = std::string(); auto bi = std::back_inserter(buffer); - fmt::detail::truncating_iterator it(bi, 2); + auto it = fmt::detail::truncating_iterator(bi, 2); *it++ = '4'; *it++ = '2'; *it++ = '1'; @@ -55,37 +44,6 @@ TEST(IteratorTest, TruncatingBackInserter) { EXPECT_EQ(buffer, "42"); } -// compiletime_prepared_parts_type_provider is useful only with relaxed -// constexpr. -#if FMT_USE_CONSTEXPR -template -void check_prepared_parts_type(Format format) { - typedef fmt::detail::compiled_format_base provider; - typedef fmt::detail::format_part - expected_parts_type[EXPECTED_PARTS_COUNT]; - static_assert(std::is_same::value, - "CompileTimePreparedPartsTypeProvider test failed"); -} - -TEST(CompileTest, CompileTimePreparedPartsTypeProvider) { - check_prepared_parts_type<1u>(FMT_STRING("text")); - check_prepared_parts_type<1u>(FMT_STRING("{}")); - check_prepared_parts_type<2u>(FMT_STRING("text{}")); - check_prepared_parts_type<2u>(FMT_STRING("{}text")); - check_prepared_parts_type<3u>(FMT_STRING("text{}text")); - check_prepared_parts_type<3u>(FMT_STRING("{:{}.{}} {:{}}")); - - check_prepared_parts_type<3u>(FMT_STRING("{{{}}}")); // '{', 'argument', '}' - check_prepared_parts_type<2u>(FMT_STRING("text{{")); // 'text', '{' - check_prepared_parts_type<3u>(FMT_STRING("text{{ ")); // 'text', '{', ' ' - check_prepared_parts_type<2u>(FMT_STRING("}}text")); // '}', text - check_prepared_parts_type<2u>(FMT_STRING("text}}text")); // 'text}', 'text' - check_prepared_parts_type<4u>( - FMT_STRING("text{{}}text")); // 'text', '{', '}', 'text' -} -#endif - struct test_formattable {}; FMT_BEGIN_NAMESPACE @@ -97,14 +55,14 @@ template <> struct formatter : formatter { }; FMT_END_NAMESPACE -TEST(CompileTest, CompileFallback) { +TEST(compile_test, compile_fallback) { // FMT_COMPILE should fallback on runtime formatting when `if constexpr` is // not available. EXPECT_EQ("42", fmt::format(FMT_COMPILE("{}"), 42)); } #ifdef __cpp_if_constexpr -TEST(CompileTest, FormatDefault) { +TEST(compile_test, format_default) { EXPECT_EQ("42", fmt::format(FMT_COMPILE("{}"), 42)); EXPECT_EQ("42", fmt::format(FMT_COMPILE("{}"), 42u)); EXPECT_EQ("42", fmt::format(FMT_COMPILE("{}"), 42ll)); @@ -120,18 +78,18 @@ TEST(CompileTest, FormatDefault) { # endif } -TEST(CompileTest, FormatWideString) { +TEST(compile_test, format_wide_string) { EXPECT_EQ(L"42", fmt::format(FMT_COMPILE(L"{}"), 42)); } -TEST(CompileTest, FormatSpecs) { +TEST(compile_test, format_specs) { EXPECT_EQ("42", fmt::format(FMT_COMPILE("{:x}"), 0x42)); EXPECT_EQ("1.2 ms ", fmt::format(FMT_COMPILE("{:7.1%Q %q}"), std::chrono::duration(1.234))); } -TEST(CompileTest, DynamicFormatSpecs) { +TEST(compile_test, dynamic_format_specs) { EXPECT_EQ("foo ", fmt::format(FMT_COMPILE("{:{}}"), "foo", 5)); EXPECT_EQ(" 3.14", fmt::format(FMT_COMPILE("{:{}.{}f}"), 3.141592, 6, 2)); EXPECT_EQ( @@ -140,7 +98,7 @@ TEST(CompileTest, DynamicFormatSpecs) { std::chrono::duration(1.234), 9, 3)); } -TEST(CompileTest, ManualOrdering) { +TEST(compile_test, manual_ordering) { EXPECT_EQ("42", fmt::format(FMT_COMPILE("{0}"), 42)); EXPECT_EQ(" -42", fmt::format(FMT_COMPILE("{0:4}"), -42)); EXPECT_EQ("41 43", fmt::format(FMT_COMPILE("{0} {1}"), 41, 43)); @@ -157,7 +115,7 @@ TEST(CompileTest, ManualOrdering) { EXPECT_EQ(L"42", fmt::format(FMT_COMPILE(L"{0}"), 42)); } -TEST(CompileTest, Named) { +TEST(compile_test, named) { auto runtime_named_field_compiled = fmt::detail::compile(FMT_COMPILE("{arg}")); static_assert(std::is_same_v>>42<<<", fmt::format(FMT_COMPILE(">>>{}<<<"), 42)); EXPECT_EQ("42!", fmt::format(FMT_COMPILE("{}!"), 42)); } -TEST(CompileTest, UnknownFormatFallback) { +TEST(compile_test, unknown_format_fallback) { EXPECT_EQ(" 42 ", fmt::format(FMT_COMPILE("{name:^4}"), fmt::arg("name", 42))); @@ -253,7 +211,7 @@ TEST(CompileTest, UnknownFormatFallback) { EXPECT_EQ(" 42 ", fmt::string_view(buffer, 4)); } -TEST(CompileTest, Empty) { EXPECT_EQ("", fmt::format(FMT_COMPILE(""))); } +TEST(compile_test, empty) { EXPECT_EQ("", fmt::format(FMT_COMPILE(""))); } struct to_stringable { friend fmt::string_view to_string_view(to_stringable) { return {}; } @@ -272,13 +230,13 @@ template <> struct formatter { }; FMT_END_NAMESPACE -TEST(CompileTest, ToStringAndFormatter) { +TEST(compile_test, to_string_and_formatter) { fmt::format(FMT_COMPILE("{}"), to_stringable()); } #endif #if FMT_USE_NONTYPE_TEMPLATE_PARAMETERS -TEST(CompileTest, CompileFormatStringLiteral) { +TEST(compile_test, compile_format_string_literal) { using namespace fmt::literals; EXPECT_EQ("", fmt::format(""_cf)); EXPECT_EQ("42", fmt::format("{}"_cf, 42)); @@ -302,14 +260,14 @@ consteval auto test_format(auto format, const Args&... args) { return string; } -TEST(CompileTimeFormattingTest, Bool) { +TEST(compile_time_formatting_test, bool) { EXPECT_EQ("true", test_format<5>(FMT_COMPILE("{}"), true)); EXPECT_EQ("false", test_format<6>(FMT_COMPILE("{}"), false)); EXPECT_EQ("true ", test_format<6>(FMT_COMPILE("{:5}"), true)); EXPECT_EQ("1", test_format<2>(FMT_COMPILE("{:d}"), true)); } -TEST(CompileTimeFormattingTest, Integer) { +TEST(compile_time_formatting_test, integer) { EXPECT_EQ("42", test_format<3>(FMT_COMPILE("{}"), 42)); EXPECT_EQ("420", test_format<4>(FMT_COMPILE("{}"), 420)); EXPECT_EQ("42 42", test_format<6>(FMT_COMPILE("{} {}"), 42, 42)); @@ -339,14 +297,14 @@ TEST(CompileTimeFormattingTest, Integer) { EXPECT_EQ("**-42", test_format<6>(FMT_COMPILE("{:*>5}"), -42)); } -TEST(CompileTimeFormattingTest, Char) { +TEST(compile_time_formatting_test, char) { EXPECT_EQ("c", test_format<2>(FMT_COMPILE("{}"), 'c')); EXPECT_EQ("c ", test_format<4>(FMT_COMPILE("{:3}"), 'c')); EXPECT_EQ("99", test_format<3>(FMT_COMPILE("{:d}"), 'c')); } -TEST(CompileTimeFormattingTest, String) { +TEST(compile_time_formatting_test, string) { EXPECT_EQ("42", test_format<3>(FMT_COMPILE("{}"), "42")); EXPECT_EQ("The answer is 42", test_format<17>(FMT_COMPILE("{} is {}"), "The answer", "42")); @@ -355,14 +313,14 @@ TEST(CompileTimeFormattingTest, String) { EXPECT_EQ("**🤡**", test_format<9>(FMT_COMPILE("{:*^6}"), "🤡")); } -TEST(CompileTimeFormattingTest, Combination) { +TEST(compile_time_formatting_test, combination) { EXPECT_EQ("420, true, answer", test_format<18>(FMT_COMPILE("{}, {}, {}"), 420, true, "answer")); EXPECT_EQ(" -42", test_format<5>(FMT_COMPILE("{:{}}"), -42, 4)); } -TEST(CompileTimeFormattingTest, MultiByteFill) { +TEST(compile_time_formatting_test, multibyte_fill) { EXPECT_EQ("жж42", test_format<8>(FMT_COMPILE("{:ж>4}"), 42)); } #endif