1 /* 2 * Created by Martin on 23/2/2019. 3 * 4 * Distributed under the Boost Software License, Version 1.0. (See accompanying 5 * file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 6 */ 7 #ifndef TWOBLUECUBES_CATCH_GENERATORS_GENERIC_HPP_INCLUDED 8 #define TWOBLUECUBES_CATCH_GENERATORS_GENERIC_HPP_INCLUDED 9 10 #include "catch_generators.hpp" 11 12 namespace Catch { 13 namespace Generators { 14 15 template <typename T> 16 class TakeGenerator : public IGenerator<T> { 17 GeneratorWrapper<T> m_generator; 18 size_t m_returned = 0; 19 size_t m_target; 20 public: TakeGenerator(size_t target,GeneratorWrapper<T> && generator)21 TakeGenerator(size_t target, GeneratorWrapper<T>&& generator): 22 m_generator(std::move(generator)), 23 m_target(target) 24 { 25 assert(target != 0 && "Empty generators are not allowed"); 26 } get() const27 T const& get() const override { 28 return m_generator.get(); 29 } next()30 bool next() override { 31 ++m_returned; 32 if (m_returned >= m_target) { 33 return false; 34 } 35 36 const auto success = m_generator.next(); 37 // If the underlying generator does not contain enough values 38 // then we cut short as well 39 if (!success) { 40 m_returned = m_target; 41 } 42 return success; 43 } 44 }; 45 46 template <typename T> take(size_t target,GeneratorWrapper<T> && generator)47 GeneratorWrapper<T> take(size_t target, GeneratorWrapper<T>&& generator) { 48 return GeneratorWrapper<T>(pf::make_unique<TakeGenerator<T>>(target, std::move(generator))); 49 } 50 51 52 template <typename T, typename Predicate> 53 class FilterGenerator : public IGenerator<T> { 54 GeneratorWrapper<T> m_generator; 55 Predicate m_predicate; 56 public: 57 template <typename P = Predicate> FilterGenerator(P && pred,GeneratorWrapper<T> && generator)58 FilterGenerator(P&& pred, GeneratorWrapper<T>&& generator): 59 m_generator(std::move(generator)), 60 m_predicate(std::forward<P>(pred)) 61 { 62 if (!m_predicate(m_generator.get())) { 63 // It might happen that there are no values that pass the 64 // filter. In that case we throw an exception. 65 auto has_initial_value = next(); 66 if (!has_initial_value) { 67 Catch::throw_exception(GeneratorException("No valid value found in filtered generator")); 68 } 69 } 70 } 71 get() const72 T const& get() const override { 73 return m_generator.get(); 74 } 75 next()76 bool next() override { 77 bool success = m_generator.next(); 78 if (!success) { 79 return false; 80 } 81 while (!m_predicate(m_generator.get()) && (success = m_generator.next()) == true); 82 return success; 83 } 84 }; 85 86 87 template <typename T, typename Predicate> filter(Predicate && pred,GeneratorWrapper<T> && generator)88 GeneratorWrapper<T> filter(Predicate&& pred, GeneratorWrapper<T>&& generator) { 89 return GeneratorWrapper<T>(std::unique_ptr<IGenerator<T>>(pf::make_unique<FilterGenerator<T, Predicate>>(std::forward<Predicate>(pred), std::move(generator)))); 90 } 91 92 template <typename T> 93 class RepeatGenerator : public IGenerator<T> { 94 GeneratorWrapper<T> m_generator; 95 mutable std::vector<T> m_returned; 96 size_t m_target_repeats; 97 size_t m_current_repeat = 0; 98 size_t m_repeat_index = 0; 99 public: RepeatGenerator(size_t repeats,GeneratorWrapper<T> && generator)100 RepeatGenerator(size_t repeats, GeneratorWrapper<T>&& generator): 101 m_generator(std::move(generator)), 102 m_target_repeats(repeats) 103 { 104 assert(m_target_repeats > 0 && "Repeat generator must repeat at least once"); 105 } 106 get() const107 T const& get() const override { 108 if (m_current_repeat == 0) { 109 m_returned.push_back(m_generator.get()); 110 return m_returned.back(); 111 } 112 return m_returned[m_repeat_index]; 113 } 114 next()115 bool next() override { 116 // There are 2 basic cases: 117 // 1) We are still reading the generator 118 // 2) We are reading our own cache 119 120 // In the first case, we need to poke the underlying generator. 121 // If it happily moves, we are left in that state, otherwise it is time to start reading from our cache 122 if (m_current_repeat == 0) { 123 const auto success = m_generator.next(); 124 if (!success) { 125 ++m_current_repeat; 126 } 127 return m_current_repeat < m_target_repeats; 128 } 129 130 // In the second case, we need to move indices forward and check that we haven't run up against the end 131 ++m_repeat_index; 132 if (m_repeat_index == m_returned.size()) { 133 m_repeat_index = 0; 134 ++m_current_repeat; 135 } 136 return m_current_repeat < m_target_repeats; 137 } 138 }; 139 140 template <typename T> repeat(size_t repeats,GeneratorWrapper<T> && generator)141 GeneratorWrapper<T> repeat(size_t repeats, GeneratorWrapper<T>&& generator) { 142 return GeneratorWrapper<T>(pf::make_unique<RepeatGenerator<T>>(repeats, std::move(generator))); 143 } 144 145 template <typename T, typename U, typename Func> 146 class MapGenerator : public IGenerator<T> { 147 // TBD: provide static assert for mapping function, for friendly error message 148 GeneratorWrapper<U> m_generator; 149 Func m_function; 150 // To avoid returning dangling reference, we have to save the values 151 T m_cache; 152 public: 153 template <typename F2 = Func> MapGenerator(F2 && function,GeneratorWrapper<U> && generator)154 MapGenerator(F2&& function, GeneratorWrapper<U>&& generator) : 155 m_generator(std::move(generator)), 156 m_function(std::forward<F2>(function)), 157 m_cache(m_function(m_generator.get())) 158 {} 159 get() const160 T const& get() const override { 161 return m_cache; 162 } next()163 bool next() override { 164 const auto success = m_generator.next(); 165 if (success) { 166 m_cache = m_function(m_generator.get()); 167 } 168 return success; 169 } 170 }; 171 172 #if defined(__cpp_lib_is_invocable) && __cpp_lib_is_invocable >= 201703 173 // std::result_of is deprecated in C++17 and removed in C++20. Hence, it is 174 // replaced with std::invoke_result here. Also *_t format is preferred over 175 // typename *::type format. 176 template <typename Func, typename U> 177 using MapFunctionReturnType = std::remove_reference_t<std::remove_cv_t<std::invoke_result_t<Func, U>>>; 178 #else 179 template <typename Func, typename U> 180 using MapFunctionReturnType = typename std::remove_reference<typename std::remove_cv<typename std::result_of<Func(U)>::type>::type>::type; 181 #endif 182 183 template <typename Func, typename U, typename T = MapFunctionReturnType<Func, U>> map(Func && function,GeneratorWrapper<U> && generator)184 GeneratorWrapper<T> map(Func&& function, GeneratorWrapper<U>&& generator) { 185 return GeneratorWrapper<T>( 186 pf::make_unique<MapGenerator<T, U, Func>>(std::forward<Func>(function), std::move(generator)) 187 ); 188 } 189 190 template <typename T, typename U, typename Func> map(Func && function,GeneratorWrapper<U> && generator)191 GeneratorWrapper<T> map(Func&& function, GeneratorWrapper<U>&& generator) { 192 return GeneratorWrapper<T>( 193 pf::make_unique<MapGenerator<T, U, Func>>(std::forward<Func>(function), std::move(generator)) 194 ); 195 } 196 197 template <typename T> 198 class ChunkGenerator final : public IGenerator<std::vector<T>> { 199 std::vector<T> m_chunk; 200 size_t m_chunk_size; 201 GeneratorWrapper<T> m_generator; 202 bool m_used_up = false; 203 public: ChunkGenerator(size_t size,GeneratorWrapper<T> generator)204 ChunkGenerator(size_t size, GeneratorWrapper<T> generator) : 205 m_chunk_size(size), m_generator(std::move(generator)) 206 { 207 m_chunk.reserve(m_chunk_size); 208 m_chunk.push_back(m_generator.get()); 209 for (size_t i = 1; i < m_chunk_size; ++i) { 210 if (!m_generator.next()) { 211 Catch::throw_exception(GeneratorException("Not enough values to initialize the first chunk")); 212 } 213 m_chunk.push_back(m_generator.get()); 214 } 215 } get() const216 std::vector<T> const& get() const override { 217 return m_chunk; 218 } next()219 bool next() override { 220 m_chunk.clear(); 221 for (size_t idx = 0; idx < m_chunk_size; ++idx) { 222 if (!m_generator.next()) { 223 return false; 224 } 225 m_chunk.push_back(m_generator.get()); 226 } 227 return true; 228 } 229 }; 230 231 template <typename T> chunk(size_t size,GeneratorWrapper<T> && generator)232 GeneratorWrapper<std::vector<T>> chunk(size_t size, GeneratorWrapper<T>&& generator) { 233 return GeneratorWrapper<std::vector<T>>( 234 pf::make_unique<ChunkGenerator<T>>(size, std::move(generator)) 235 ); 236 } 237 238 } // namespace Generators 239 } // namespace Catch 240 241 242 #endif // TWOBLUECUBES_CATCH_GENERATORS_GENERIC_HPP_INCLUDED 243