From aeff88fc63c942855dd902d900667aa919ef8d10 Mon Sep 17 00:00:00 2001 From: Oliver Lee Date: Sat, 23 Dec 2023 23:17:34 -0800 Subject: [PATCH] define printer for ranges, types without stream insertion (#6) Change-Id: I60bf6c70b610d38da41bc1bd55a9c3f4fac42b2d --- BUILD.bazel | 3 +- src/default_printer.hpp | 5 +- src/detail/arg_fmt.hpp | 107 ++++++++++++++++++++++++++++++++++++ src/detail/is_range.hpp | 23 ++++++++ src/detail/relation.hpp | 10 ++-- src/detail/tuple_fmt.hpp | 35 ------------ src/rope.hpp | 1 + src/test_param.hpp | 16 +----- test/BUILD.bazel | 11 ++++ test/arg_fmt_test.cpp | 54 ++++++++++++++++++ test/pred_notation_test.cpp | 42 +++----------- 11 files changed, 215 insertions(+), 92 deletions(-) create mode 100644 src/detail/arg_fmt.hpp create mode 100644 src/detail/is_range.hpp delete mode 100644 src/detail/tuple_fmt.hpp create mode 100644 test/arg_fmt_test.cpp diff --git a/BUILD.bazel b/BUILD.bazel index 4b8f49b..7b03b37 100644 --- a/BUILD.bazel +++ b/BUILD.bazel @@ -18,14 +18,15 @@ cc_library( "src/aborts.hpp", "src/cfg.hpp", "src/default_printer.hpp", + "src/detail/arg_fmt.hpp", "src/detail/is_defined.hpp", + "src/detail/is_range.hpp", "src/detail/is_specialization_of.hpp", "src/detail/predicate.hpp", "src/detail/priority.hpp", "src/detail/relation.hpp", "src/detail/remove_cvref.hpp", "src/detail/trim_substring.hpp", - "src/detail/tuple_fmt.hpp", "src/detail/type_name.hpp", "src/expect.hpp", "src/pred.hpp", diff --git a/src/default_printer.hpp b/src/default_printer.hpp index 426d1c7..6ba3e2c 100644 --- a/src/default_printer.hpp +++ b/src/default_printer.hpp @@ -1,5 +1,6 @@ #pragma once +#include "src/detail/arg_fmt.hpp" #include "src/detail/priority.hpp" #include "src/result.hpp" #include "src/utility.hpp" @@ -47,10 +48,10 @@ class default_printer }; os() << "("; - (*this) << std::get<0>(r.args); + (*this) << detail::arg_fmt(std::get<0>(r.args)); os() << " " << Relation::predicate_type::symbol << " "; - (*this) << std::get<1>(r.args); + (*this) << detail::arg_fmt(std::get<1>(r.args)); os() << ")" << colors::none; diff --git a/src/detail/arg_fmt.hpp b/src/detail/arg_fmt.hpp new file mode 100644 index 0000000..43a2b26 --- /dev/null +++ b/src/detail/arg_fmt.hpp @@ -0,0 +1,107 @@ +#pragma once + +#include "src/detail/is_range.hpp" +#include "src/detail/priority.hpp" +#include "src/detail/type_name.hpp" +#include "src/rope.hpp" + +#include +#include +#include +#include + +namespace skytest::detail { +template +struct range_fmt; +template +struct tuple_fmt; + +class arg_fmt_fn +{ + struct ostream_inserter + { + template + auto operator()(const T& value) + -> decltype(std::declval() << value); + }; + + template + static constexpr auto is_ostreamable_v = + std::is_invocable_r_v; + + template < + class R, + std::enable_if_t and not is_ostreamable_v, bool> = true> + static constexpr auto impl(priority<3>, const R& arg) + { + return range_fmt{arg}; + } + + template < + class... Ts, + std::enable_if_t>, bool> = true> + static constexpr auto impl(priority<2>, const std::tuple& arg) + { + return tuple_fmt{arg}; + } + + template , bool> = true> + static constexpr auto& impl(priority<1>, const T& arg) + { + return arg; + } + + template + static constexpr auto impl(priority<0>, const T&) + { + return rope<2>{type_name, "{...}"}; + } + +public: + template + constexpr decltype(auto) operator()(const T & arg) const + { + return impl(priority<3>{}, arg); + } +}; +inline constexpr auto arg_fmt = arg_fmt_fn{}; + +template +struct range_fmt +{ + const R& range; + + friend auto& operator<<(std::ostream& os, const range_fmt& f) + { + auto first = true; + + os << "["; + for (const auto& value : f.range) { + os << (std::exchange(first, false) ? "" : ", ") << value; + } + os << "]"; + return os; + } +}; + +template +struct tuple_fmt +{ + const std::tuple& value; + + template + auto& stream_insert(std::index_sequence, std::ostream& os) const + { + std::ignore = + ((os << (Is == 0 ? "" : ", ") << arg_fmt(std::get(value)), true) and + ...); + return os; + } + + friend auto& operator<<(std::ostream& os, const tuple_fmt& tup) + { + return tup.stream_insert(std::index_sequence_for{}, os); + } +}; + +} // namespace skytest::detail diff --git a/src/detail/is_range.hpp b/src/detail/is_range.hpp new file mode 100644 index 0000000..6fc8542 --- /dev/null +++ b/src/detail/is_range.hpp @@ -0,0 +1,23 @@ +#pragma once + +#include +#include + +namespace skytest::detail { + +template +struct is_range : std::false_type +{}; +template +struct is_range< + T, + std::enable_if_t< + decltype(std::declval().begin(), + std::declval().end(), + std::true_type{})::value>> : std::true_type +{}; + +template +inline constexpr auto is_range_v = is_range::value; + +} // namespace skytest::detail diff --git a/src/detail/relation.hpp b/src/detail/relation.hpp index 1c94bad..ce94425 100644 --- a/src/detail/relation.hpp +++ b/src/detail/relation.hpp @@ -1,6 +1,6 @@ #pragma once -#include "src/detail/tuple_fmt.hpp" +#include "src/detail/arg_fmt.hpp" #include "src/detail/type_name.hpp" #include "src/utility.hpp" @@ -53,22 +53,22 @@ struct relation os << predicate_type::name << "{}"; } - os << "(" << fmt(r.args) << ")"; + os << "(" << arg_fmt(r.args) << ")"; return os; } static auto& print(notation::prefix, std::ostream& os, const relation& r) { static_assert(sizeof...(Ts) != 0); - os << "(" << predicate_type::symbol << " " << fmt(r.args) << ")"; + os << "(" << predicate_type::symbol << " " << arg_fmt(r.args) << ")"; return os; } static auto& print(notation::infix, std::ostream& os, const relation& r) { static_assert(sizeof...(Ts) == 2); - os << "(" << std::get<0>(r.args) << " " << predicate_type::symbol << " " - << std::get<1>(r.args) << ")"; + os << "(" << arg_fmt(std::get<0>(r.args)) << " " << predicate_type::symbol + << " " << arg_fmt(std::get<1>(r.args)) << ")"; return os; } diff --git a/src/detail/tuple_fmt.hpp b/src/detail/tuple_fmt.hpp deleted file mode 100644 index 114e78f..0000000 --- a/src/detail/tuple_fmt.hpp +++ /dev/null @@ -1,35 +0,0 @@ -#pragma once - -#include -#include -#include -#include - -namespace skytest::detail { - -template -struct tuple_fmt -{ - const std::tuple& value; - - template - auto& stream_insert(std::index_sequence, std::ostream& os) const - { - std::ignore = std::array{ - (os << (Is == 0 ? "" : ", ") << std::get(value), true)...}; - return os; - } - - friend auto& operator<<(std::ostream& os, const tuple_fmt& tup) - { - return tup.stream_insert(std::index_sequence_for{}, os); - } -}; - -template -constexpr auto fmt(const std::tuple& tup) -{ - return tuple_fmt{tup}; -} - -} // namespace skytest::detail diff --git a/src/rope.hpp b/src/rope.hpp index 6064960..d2d288b 100644 --- a/src/rope.hpp +++ b/src/rope.hpp @@ -49,6 +49,7 @@ struct rope friend auto& operator<<(std::ostream& os, const rope& r) { os << rope_ref{r}; + return os; } }; diff --git a/src/test_param.hpp b/src/test_param.hpp index 1a44a4a..a1f13eb 100644 --- a/src/test_param.hpp +++ b/src/test_param.hpp @@ -1,6 +1,7 @@ #pragma once #include "src/detail/is_defined.hpp" +#include "src/detail/is_range.hpp" #include "src/detail/priority.hpp" #include "src/detail/remove_cvref.hpp" #include "src/detail/trim_substring.hpp" @@ -32,21 +33,6 @@ namespace detail { template using constant = std::integral_constant; -template -struct is_range : std::false_type -{}; -template -struct is_range< - T, - std::enable_if_t< - decltype(std::declval().begin(), - std::declval().end(), - std::true_type{})::value>> : std::true_type -{}; - -template -inline constexpr auto is_range_v = is_range::value; - template struct has_static_value : std::false_type {}; diff --git a/test/BUILD.bazel b/test/BUILD.bazel index 473ca83..809cb14 100644 --- a/test/BUILD.bazel +++ b/test/BUILD.bazel @@ -123,6 +123,17 @@ skytest_test( ], ) +skytest_test( + name = "arg_fmt_test", + srcs = ["arg_fmt_test.cpp"], + return_code = 1, + stdout = [ + "empty(\\[1, 2, 3\\])", + "empty({4}{5})", + "wrapped{...} == wrapped{...}", + ], +) + skytest_test( name = "constexpr_test", srcs = ["constexpr_test.cpp"], diff --git a/test/arg_fmt_test.cpp b/test/arg_fmt_test.cpp new file mode 100644 index 0000000..8f8c96a --- /dev/null +++ b/test/arg_fmt_test.cpp @@ -0,0 +1,54 @@ +#include "skytest/skytest.hpp" + +#include +#include +#include +#include +#include + +struct empty_desc +{ + using notation_type = skytest::notation::function; + static constexpr auto symbol = std::string_view{"empty"}; +}; +constexpr auto empty = skytest::pred(empty_desc{}, [](const auto& rng) { + return std::empty(rng); +}); + +template +struct array : std::array +{ + friend auto& operator<<(std::ostream& os, const array& arr) + { + for (const auto& x : arr) { + os << "{" << x << "}"; + } + return os; + } +}; + +struct wrapped +{ + int value; + + friend constexpr auto operator==(wrapped, wrapped) { return false; } +}; + +auto main() -> int +{ + using namespace ::skytest::literals; + using ::skytest::eq; + using ::skytest::expect; + + "array uses default range arg fmt"_test = [] { + return expect(empty(std::array{1, 2, 3})); + }; + + "custom array uses custom fmt"_test = [] { + return expect(empty(array{4, 5})); + }; + + "type without operator<< is displayed"_test = [] { + return expect(eq(wrapped{1}, wrapped{2})); + }; +} diff --git a/test/pred_notation_test.cpp b/test/pred_notation_test.cpp index 4afe655..cd2a395 100644 --- a/test/pred_notation_test.cpp +++ b/test/pred_notation_test.cpp @@ -1,44 +1,18 @@ +#include "skytest/skytest.hpp" + #include +#include #include -#include #include #include -// TODO: default printer for ranges - -template -auto& operator<<(std::ostream& os, const std::array& arr) -{ - auto first = true; - - os << "["; - for (auto value : arr) { - os << (std::exchange(first, false) ? "" : ", ") << value; - } - os << "]"; - return os; -} - -#include "skytest/skytest.hpp" - -using ::skytest::function; -using ::skytest::pred; - -static constexpr auto empty = // - pred(function<"∅">, [](const auto& range) { - using T = std::conditional_t< - std::is_convertible_v, - std::string_view, - decltype(range)>; - - return std::empty(static_cast(range)); - }); - auto main() -> int { using namespace ::skytest::literals; using ::skytest::expect; + using ::skytest::function; using ::skytest::infix; + using ::skytest::pred; "infix notation"_test = [] { return expect(pred(infix<"@">, [](auto a, auto b) { @@ -47,11 +21,11 @@ auto main() -> int }; "function notation"_test = [] { - return expect(pred(function<"f">, [](auto a, auto b) { - return a == b; - })(1, 2)); + return expect(pred(function<"f">, std::equal_to<>{})(1, 2)); }; + static constexpr auto empty = pred(function<"∅">, std::ranges::empty); + "empty array"_test = [] { return expect(empty(std::array{1, 23})); }; "empty string"_test = [] { return expect(empty("hello")); }; }