mirror of
				https://github.com/catchorg/Catch2.git
				synced 2025-10-31 12:17:11 +01:00 
			
		
		
		
	Add generic generator modifiers
This means mutiple generic generators and some inference helper * take(n, generator) * filter(predicate, generator) * map(func, generator) * repeat(generator, repeats)
This commit is contained in:
		| @@ -16,6 +16,10 @@ namespace Catch { | ||||
|  | ||||
| IGeneratorTracker::~IGeneratorTracker() {} | ||||
|  | ||||
| const char* GeneratorException::what() const noexcept { | ||||
|     return m_msg; | ||||
| } | ||||
|  | ||||
| namespace Generators { | ||||
|  | ||||
|     GeneratorUntypedBase::~GeneratorUntypedBase() {} | ||||
|   | ||||
| @@ -16,8 +16,21 @@ | ||||
| #include <cassert> | ||||
|  | ||||
| #include <utility> | ||||
| #include <exception> | ||||
|  | ||||
| namespace Catch { | ||||
|  | ||||
| class GeneratorException : public std::exception { | ||||
|     const char* const m_msg = ""; | ||||
|  | ||||
| public: | ||||
|     GeneratorException(const char* msg): | ||||
|         m_msg(msg) | ||||
|     {} | ||||
|  | ||||
|     const char* what() const noexcept override final; | ||||
| }; | ||||
|  | ||||
| namespace Generators { | ||||
|  | ||||
|     // !TBD move this into its own location? | ||||
| @@ -166,6 +179,175 @@ namespace Generators { | ||||
|         return makeGenerators( value( T( std::forward<U>( val ) ) ), std::forward<Gs>( moreGenerators )... ); | ||||
|     } | ||||
|  | ||||
|     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> { | ||||
|         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 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, typename Func> | ||||
|     GeneratorWrapper<T> map(Func&& function, GeneratorWrapper<T>&& generator) { | ||||
|         return GeneratorWrapper<T>( | ||||
|             pf::make_unique<MapGenerator<T, T, Func>>(std::forward<Func>(function), std::move(generator)) | ||||
|         ); | ||||
|     } | ||||
|  | ||||
|     auto acquireGeneratorTracker( SourceLineInfo const& lineInfo ) -> IGeneratorTracker&; | ||||
|  | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 Martin Hořeňovský
					Martin Hořeňovský