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 #include "catch_meta.hpp" 12 13 namespace Catch { 14 namespace Generators { 15 16 template <typename T> 17 class TakeGenerator : public IGenerator<T> { 18 GeneratorWrapper<T> m_generator; 19 size_t m_returned = 0; 20 size_t m_target; 21 public: TakeGenerator(size_t target,GeneratorWrapper<T> && generator)22 TakeGenerator(size_t target, GeneratorWrapper<T>&& generator): 23 m_generator(std::move(generator)), 24 m_target(target) 25 { 26 assert(target != 0 && "Empty generators are not allowed"); 27 } get() const28 T const& get() const override { 29 return m_generator.get(); 30 } next()31 bool next() override { 32 ++m_returned; 33 if (m_returned >= m_target) { 34 return false; 35 } 36 37 const auto success = m_generator.next(); 38 // If the underlying generator does not contain enough values 39 // then we cut short as well 40 if (!success) { 41 m_returned = m_target; 42 } 43 return success; 44 } 45 }; 46 47 template <typename T> take(size_t target,GeneratorWrapper<T> && generator)48 GeneratorWrapper<T> take(size_t target, GeneratorWrapper<T>&& generator) { 49 return GeneratorWrapper<T>(pf::make_unique<TakeGenerator<T>>(target, std::move(generator))); 50 } 51 52 53 template <typename T, typename Predicate> 54 class FilterGenerator : public IGenerator<T> { 55 GeneratorWrapper<T> m_generator; 56 Predicate m_predicate; 57 public: 58 template <typename P = Predicate> FilterGenerator(P && pred,GeneratorWrapper<T> && generator)59 FilterGenerator(P&& pred, GeneratorWrapper<T>&& generator): 60 m_generator(std::move(generator)), 61 m_predicate(std::forward<P>(pred)) 62 { 63 if (!m_predicate(m_generator.get())) { 64 // It might happen that there are no values that pass the 65 // filter. In that case we throw an exception. 66 auto has_initial_value = next(); 67 if (!has_initial_value) { 68 Catch::throw_exception(GeneratorException("No valid value found in filtered generator")); 69 } 70 } 71 } 72 get() const73 T const& get() const override { 74 return m_generator.get(); 75 } 76 next()77 bool next() override { 78 bool success = m_generator.next(); 79 if (!success) { 80 return false; 81 } 82 while (!m_predicate(m_generator.get()) && (success = m_generator.next()) == true); 83 return success; 84 } 85 }; 86 87 88 template <typename T, typename Predicate> filter(Predicate && pred,GeneratorWrapper<T> && generator)89 GeneratorWrapper<T> filter(Predicate&& pred, GeneratorWrapper<T>&& generator) { 90 return GeneratorWrapper<T>(std::unique_ptr<IGenerator<T>>(pf::make_unique<FilterGenerator<T, Predicate>>(std::forward<Predicate>(pred), std::move(generator)))); 91 } 92 93 template <typename T> 94 class RepeatGenerator : public IGenerator<T> { 95 static_assert(!std::is_same<T, bool>::value, 96 "RepeatGenerator currently does not support bools" 97 "because of std::vector<bool> specialization"); 98 GeneratorWrapper<T> m_generator; 99 mutable std::vector<T> m_returned; 100 size_t m_target_repeats; 101 size_t m_current_repeat = 0; 102 size_t m_repeat_index = 0; 103 public: RepeatGenerator(size_t repeats,GeneratorWrapper<T> && generator)104 RepeatGenerator(size_t repeats, GeneratorWrapper<T>&& generator): 105 m_generator(std::move(generator)), 106 m_target_repeats(repeats) 107 { 108 assert(m_target_repeats > 0 && "Repeat generator must repeat at least once"); 109 } 110 get() const111 T const& get() const override { 112 if (m_current_repeat == 0) { 113 m_returned.push_back(m_generator.get()); 114 return m_returned.back(); 115 } 116 return m_returned[m_repeat_index]; 117 } 118 next()119 bool next() override { 120 // There are 2 basic cases: 121 // 1) We are still reading the generator 122 // 2) We are reading our own cache 123 124 // In the first case, we need to poke the underlying generator. 125 // If it happily moves, we are left in that state, otherwise it is time to start reading from our cache 126 if (m_current_repeat == 0) { 127 const auto success = m_generator.next(); 128 if (!success) { 129 ++m_current_repeat; 130 } 131 return m_current_repeat < m_target_repeats; 132 } 133 134 // In the second case, we need to move indices forward and check that we haven't run up against the end 135 ++m_repeat_index; 136 if (m_repeat_index == m_returned.size()) { 137 m_repeat_index = 0; 138 ++m_current_repeat; 139 } 140 return m_current_repeat < m_target_repeats; 141 } 142 }; 143 144 template <typename T> repeat(size_t repeats,GeneratorWrapper<T> && generator)145 GeneratorWrapper<T> repeat(size_t repeats, GeneratorWrapper<T>&& generator) { 146 return GeneratorWrapper<T>(pf::make_unique<RepeatGenerator<T>>(repeats, std::move(generator))); 147 } 148 149 template <typename T, typename U, typename Func> 150 class MapGenerator : public IGenerator<T> { 151 // TBD: provide static assert for mapping function, for friendly error message 152 GeneratorWrapper<U> m_generator; 153 Func m_function; 154 // To avoid returning dangling reference, we have to save the values 155 T m_cache; 156 public: 157 template <typename F2 = Func> MapGenerator(F2 && function,GeneratorWrapper<U> && generator)158 MapGenerator(F2&& function, GeneratorWrapper<U>&& generator) : 159 m_generator(std::move(generator)), 160 m_function(std::forward<F2>(function)), 161 m_cache(m_function(m_generator.get())) 162 {} 163 get() const164 T const& get() const override { 165 return m_cache; 166 } next()167 bool next() override { 168 const auto success = m_generator.next(); 169 if (success) { 170 m_cache = m_function(m_generator.get()); 171 } 172 return success; 173 } 174 }; 175 176 template <typename Func, typename U, typename T = FunctionReturnType<Func, U>> map(Func && function,GeneratorWrapper<U> && generator)177 GeneratorWrapper<T> map(Func&& function, GeneratorWrapper<U>&& generator) { 178 return GeneratorWrapper<T>( 179 pf::make_unique<MapGenerator<T, U, Func>>(std::forward<Func>(function), std::move(generator)) 180 ); 181 } 182 183 template <typename T, typename U, typename Func> 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> 191 class ChunkGenerator final : public IGenerator<std::vector<T>> { 192 std::vector<T> m_chunk; 193 size_t m_chunk_size; 194 GeneratorWrapper<T> m_generator; 195 bool m_used_up = false; 196 public: ChunkGenerator(size_t size,GeneratorWrapper<T> generator)197 ChunkGenerator(size_t size, GeneratorWrapper<T> generator) : 198 m_chunk_size(size), m_generator(std::move(generator)) 199 { 200 m_chunk.reserve(m_chunk_size); 201 if (m_chunk_size != 0) { 202 m_chunk.push_back(m_generator.get()); 203 for (size_t i = 1; i < m_chunk_size; ++i) { 204 if (!m_generator.next()) { 205 Catch::throw_exception(GeneratorException("Not enough values to initialize the first chunk")); 206 } 207 m_chunk.push_back(m_generator.get()); 208 } 209 } 210 } get() const211 std::vector<T> const& get() const override { 212 return m_chunk; 213 } next()214 bool next() override { 215 m_chunk.clear(); 216 for (size_t idx = 0; idx < m_chunk_size; ++idx) { 217 if (!m_generator.next()) { 218 return false; 219 } 220 m_chunk.push_back(m_generator.get()); 221 } 222 return true; 223 } 224 }; 225 226 template <typename T> chunk(size_t size,GeneratorWrapper<T> && generator)227 GeneratorWrapper<std::vector<T>> chunk(size_t size, GeneratorWrapper<T>&& generator) { 228 return GeneratorWrapper<std::vector<T>>( 229 pf::make_unique<ChunkGenerator<T>>(size, std::move(generator)) 230 ); 231 } 232 233 } // namespace Generators 234 } // namespace Catch 235 236 237 #endif // TWOBLUECUBES_CATCH_GENERATORS_GENERIC_HPP_INCLUDED 238