238 lines
		
	
	
		
			8.3 KiB
		
	
	
	
		
			C++
		
	
	
	
			
		
		
	
	
			238 lines
		
	
	
		
			8.3 KiB
		
	
	
	
		
			C++
		
	
	
	
| /*
 | |
|  *  Created by Martin on 23/2/2019.
 | |
|  *
 | |
|  *  Distributed under the Boost Software License, Version 1.0. (See accompanying
 | |
|  *  file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
 | |
|  */
 | |
| #ifndef TWOBLUECUBES_CATCH_GENERATORS_GENERIC_HPP_INCLUDED
 | |
| #define TWOBLUECUBES_CATCH_GENERATORS_GENERIC_HPP_INCLUDED
 | |
| 
 | |
| #include "catch_generators.hpp"
 | |
| #include "catch_meta.hpp"
 | |
| 
 | |
| namespace Catch {
 | |
| namespace Generators {
 | |
| 
 | |
|     template <typename T>
 | |
|     class TakeGenerator : public IGenerator<T> {
 | |
|         GeneratorWrapper<T> m_generator;
 | |
|         size_t m_returned = 0;
 | |
|         size_t m_target;
 | |
|     public:
 | |
|         TakeGenerator(size_t target, GeneratorWrapper<T>&& generator):
 | |
|             m_generator(std::move(generator)),
 | |
|             m_target(target)
 | |
|         {
 | |
|             assert(target != 0 && "Empty generators are not allowed");
 | |
|         }
 | |
|         T const& get() const override {
 | |
|             return m_generator.get();
 | |
|         }
 | |
|         bool next() override {
 | |
|             ++m_returned;
 | |
|             if (m_returned >= m_target) {
 | |
|                 return false;
 | |
|             }
 | |
| 
 | |
|             const auto success = m_generator.next();
 | |
|             // If the underlying generator does not contain enough values
 | |
|             // then we cut short as well
 | |
|             if (!success) {
 | |
|                 m_returned = m_target;
 | |
|             }
 | |
|             return success;
 | |
|         }
 | |
|     };
 | |
| 
 | |
|     template <typename T>
 | |
|     GeneratorWrapper<T> take(size_t target, GeneratorWrapper<T>&& generator) {
 | |
|         return GeneratorWrapper<T>(pf::make_unique<TakeGenerator<T>>(target, std::move(generator)));
 | |
|     }
 | |
| 
 | |
| 
 | |
|     template <typename T, typename Predicate>
 | |
|     class FilterGenerator : public IGenerator<T> {
 | |
|         GeneratorWrapper<T> m_generator;
 | |
|         Predicate m_predicate;
 | |
|     public:
 | |
|         template <typename P = Predicate>
 | |
|         FilterGenerator(P&& pred, GeneratorWrapper<T>&& generator):
 | |
|             m_generator(std::move(generator)),
 | |
|             m_predicate(std::forward<P>(pred))
 | |
|         {
 | |
|             if (!m_predicate(m_generator.get())) {
 | |
|                 // It might happen that there are no values that pass the
 | |
|                 // filter. In that case we throw an exception.
 | |
|                 auto has_initial_value = next();
 | |
|                 if (!has_initial_value) {
 | |
|                     Catch::throw_exception(GeneratorException("No valid value found in filtered generator"));
 | |
|                 }
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         T const& get() const override {
 | |
|             return m_generator.get();
 | |
|         }
 | |
| 
 | |
|         bool next() override {
 | |
|             bool success = m_generator.next();
 | |
|             if (!success) {
 | |
|                 return false;
 | |
|             }
 | |
|             while (!m_predicate(m_generator.get()) && (success = m_generator.next()) == true);
 | |
|             return success;
 | |
|         }
 | |
|     };
 | |
| 
 | |
| 
 | |
|     template <typename T, typename Predicate>
 | |
|     GeneratorWrapper<T> filter(Predicate&& pred, GeneratorWrapper<T>&& generator) {
 | |
|         return GeneratorWrapper<T>(std::unique_ptr<IGenerator<T>>(pf::make_unique<FilterGenerator<T, Predicate>>(std::forward<Predicate>(pred), std::move(generator))));
 | |
|     }
 | |
| 
 | |
|     template <typename T>
 | |
|     class RepeatGenerator : public IGenerator<T> {
 | |
|         static_assert(!std::is_same<T, bool>::value,
 | |
|             "RepeatGenerator currently does not support bools"
 | |
|             "because of std::vector<bool> specialization");
 | |
|         GeneratorWrapper<T> m_generator;
 | |
|         mutable std::vector<T> m_returned;
 | |
|         size_t m_target_repeats;
 | |
|         size_t m_current_repeat = 0;
 | |
|         size_t m_repeat_index = 0;
 | |
|     public:
 | |
|         RepeatGenerator(size_t repeats, GeneratorWrapper<T>&& generator):
 | |
|             m_generator(std::move(generator)),
 | |
|             m_target_repeats(repeats)
 | |
|         {
 | |
|             assert(m_target_repeats > 0 && "Repeat generator must repeat at least once");
 | |
|         }
 | |
| 
 | |
|         T const& get() const override {
 | |
|             if (m_current_repeat == 0) {
 | |
|                 m_returned.push_back(m_generator.get());
 | |
|                 return m_returned.back();
 | |
|             }
 | |
|             return m_returned[m_repeat_index];
 | |
|         }
 | |
| 
 | |
|         bool next() override {
 | |
|             // There are 2 basic cases:
 | |
|             // 1) We are still reading the generator
 | |
|             // 2) We are reading our own cache
 | |
| 
 | |
|             // In the first case, we need to poke the underlying generator.
 | |
|             // If it happily moves, we are left in that state, otherwise it is time to start reading from our cache
 | |
|             if (m_current_repeat == 0) {
 | |
|                 const auto success = m_generator.next();
 | |
|                 if (!success) {
 | |
|                     ++m_current_repeat;
 | |
|                 }
 | |
|                 return m_current_repeat < m_target_repeats;
 | |
|             }
 | |
| 
 | |
|             // In the second case, we need to move indices forward and check that we haven't run up against the end
 | |
|             ++m_repeat_index;
 | |
|             if (m_repeat_index == m_returned.size()) {
 | |
|                 m_repeat_index = 0;
 | |
|                 ++m_current_repeat;
 | |
|             }
 | |
|             return m_current_repeat < m_target_repeats;
 | |
|         }
 | |
|     };
 | |
| 
 | |
|     template <typename T>
 | |
|     GeneratorWrapper<T> repeat(size_t repeats, GeneratorWrapper<T>&& generator) {
 | |
|         return GeneratorWrapper<T>(pf::make_unique<RepeatGenerator<T>>(repeats, std::move(generator)));
 | |
|     }
 | |
| 
 | |
|     template <typename T, typename U, typename Func>
 | |
|     class MapGenerator : public IGenerator<T> {
 | |
|         // TBD: provide static assert for mapping function, for friendly error message
 | |
|         GeneratorWrapper<U> m_generator;
 | |
|         Func m_function;
 | |
|         // To avoid returning dangling reference, we have to save the values
 | |
|         T m_cache;
 | |
|     public:
 | |
|         template <typename F2 = Func>
 | |
|         MapGenerator(F2&& function, GeneratorWrapper<U>&& generator) :
 | |
|             m_generator(std::move(generator)),
 | |
|             m_function(std::forward<F2>(function)),
 | |
|             m_cache(m_function(m_generator.get()))
 | |
|         {}
 | |
| 
 | |
|         T const& get() const override {
 | |
|             return m_cache;
 | |
|         }
 | |
|         bool next() override {
 | |
|             const auto success = m_generator.next();
 | |
|             if (success) {
 | |
|                 m_cache = m_function(m_generator.get());
 | |
|             }
 | |
|             return success;
 | |
|         }
 | |
|     };
 | |
| 
 | |
|     template <typename Func, typename U, typename T = FunctionReturnType<Func, U>>
 | |
|     GeneratorWrapper<T> map(Func&& function, GeneratorWrapper<U>&& generator) {
 | |
|         return GeneratorWrapper<T>(
 | |
|             pf::make_unique<MapGenerator<T, U, Func>>(std::forward<Func>(function), std::move(generator))
 | |
|         );
 | |
|     }
 | |
| 
 | |
|     template <typename T, typename U, typename Func>
 | |
|     GeneratorWrapper<T> map(Func&& function, GeneratorWrapper<U>&& generator) {
 | |
|         return GeneratorWrapper<T>(
 | |
|             pf::make_unique<MapGenerator<T, U, Func>>(std::forward<Func>(function), std::move(generator))
 | |
|         );
 | |
|     }
 | |
| 
 | |
|     template <typename T>
 | |
|     class ChunkGenerator final : public IGenerator<std::vector<T>> {
 | |
|         std::vector<T> m_chunk;
 | |
|         size_t m_chunk_size;
 | |
|         GeneratorWrapper<T> m_generator;
 | |
|         bool m_used_up = false;
 | |
|     public:
 | |
|         ChunkGenerator(size_t size, GeneratorWrapper<T> generator) :
 | |
|             m_chunk_size(size), m_generator(std::move(generator))
 | |
|         {
 | |
|             m_chunk.reserve(m_chunk_size);
 | |
|             if (m_chunk_size != 0) {
 | |
|                 m_chunk.push_back(m_generator.get());
 | |
|                 for (size_t i = 1; i < m_chunk_size; ++i) {
 | |
|                     if (!m_generator.next()) {
 | |
|                         Catch::throw_exception(GeneratorException("Not enough values to initialize the first chunk"));
 | |
|                     }
 | |
|                     m_chunk.push_back(m_generator.get());
 | |
|                 }
 | |
|             }
 | |
|         }
 | |
|         std::vector<T> const& get() const override {
 | |
|             return m_chunk;
 | |
|         }
 | |
|         bool next() override {
 | |
|             m_chunk.clear();
 | |
|             for (size_t idx = 0; idx < m_chunk_size; ++idx) {
 | |
|                 if (!m_generator.next()) {
 | |
|                     return false;
 | |
|                 }
 | |
|                 m_chunk.push_back(m_generator.get());
 | |
|             }
 | |
|             return true;
 | |
|         }
 | |
|     };
 | |
| 
 | |
|     template <typename T>
 | |
|     GeneratorWrapper<std::vector<T>> chunk(size_t size, GeneratorWrapper<T>&& generator) {
 | |
|         return GeneratorWrapper<std::vector<T>>(
 | |
|             pf::make_unique<ChunkGenerator<T>>(size, std::move(generator))
 | |
|         );
 | |
|     }
 | |
| 
 | |
| } // namespace Generators
 | |
| } // namespace Catch
 | |
| 
 | |
| 
 | |
| #endif // TWOBLUECUBES_CATCH_GENERATORS_GENERIC_HPP_INCLUDED
 |