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 template <typename T, typename U, typename Func> map(Func && function,GeneratorWrapper<U> && generator)173 GeneratorWrapper<T> map(Func&& function, GeneratorWrapper<U>&& generator) { 174 return GeneratorWrapper<T>( 175 pf::make_unique<MapGenerator<T, U, Func>>(std::forward<Func>(function), std::move(generator)) 176 ); 177 } 178 template <typename T, typename Func> map(Func && function,GeneratorWrapper<T> && generator)179 GeneratorWrapper<T> map(Func&& function, GeneratorWrapper<T>&& generator) { 180 return GeneratorWrapper<T>( 181 pf::make_unique<MapGenerator<T, T, Func>>(std::forward<Func>(function), std::move(generator)) 182 ); 183 } 184 185 template <typename T> 186 class ChunkGenerator final : public IGenerator<std::vector<T>> { 187 std::vector<T> m_chunk; 188 size_t m_chunk_size; 189 GeneratorWrapper<T> m_generator; 190 bool m_used_up = false; 191 public: ChunkGenerator(size_t size,GeneratorWrapper<T> generator)192 ChunkGenerator(size_t size, GeneratorWrapper<T> generator) : 193 m_chunk_size(size), m_generator(std::move(generator)) 194 { 195 m_chunk.reserve(m_chunk_size); 196 m_chunk.push_back(m_generator.get()); 197 for (size_t i = 1; i < m_chunk_size; ++i) { 198 if (!m_generator.next()) { 199 Catch::throw_exception(GeneratorException("Not enough values to initialize the first chunk")); 200 } 201 m_chunk.push_back(m_generator.get()); 202 } 203 } get() const204 std::vector<T> const& get() const override { 205 return m_chunk; 206 } next()207 bool next() override { 208 m_chunk.clear(); 209 for (size_t idx = 0; idx < m_chunk_size; ++idx) { 210 if (!m_generator.next()) { 211 return false; 212 } 213 m_chunk.push_back(m_generator.get()); 214 } 215 return true; 216 } 217 }; 218 219 template <typename T> chunk(size_t size,GeneratorWrapper<T> && generator)220 GeneratorWrapper<std::vector<T>> chunk(size_t size, GeneratorWrapper<T>&& generator) { 221 return GeneratorWrapper<std::vector<T>>( 222 pf::make_unique<ChunkGenerator<T>>(size, std::move(generator)) 223 ); 224 } 225 226 } // namespace Generators 227 } // namespace Catch 228 229 230 #endif // TWOBLUECUBES_CATCH_GENERATORS_GENERIC_HPP_INCLUDED 231