From dada9667bb30ad40d2f6db935c14d992ac545eeb Mon Sep 17 00:00:00 2001 From: jay-tux Date: Fri, 18 Feb 2022 10:57:07 +0100 Subject: [PATCH 1/9] Update README --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 797dfe0..30533ed 100644 --- a/README.md +++ b/README.md @@ -17,11 +17,11 @@ Currently supported features: - Lazy `map`ping over generators. - Lazy `zip`ping of generators. - Lazy `filter`ing of generators. - -Planned/In progress features: - Commonly used aggregators: - Lazy `fold`ing of generators. - Lazy `sum`ming of generators. + +Planned/In progress features: - Ergonomic improvements: - Enable streaming `<<` of generators. - Simple structure which allows generator construction, manipulation and aggregation using member functions. From 8a73538ac679967dad897b23bb4cef6f32fdc49c Mon Sep 17 00:00:00 2001 From: jay-tux Date: Fri, 18 Feb 2022 21:51:30 +0100 Subject: [PATCH 2/9] Stream should support << for manipulators --- inc/fpgen.hpp | 2 ++ inc/stream.hpp | 30 ++++++++++++++++++++++++++++++ test/Makefile | 2 +- test/src/test_stream.cpp | 18 ++++++++++++++++++ 4 files changed, 51 insertions(+), 1 deletion(-) create mode 100644 inc/stream.hpp create mode 100644 test/src/test_stream.cpp diff --git a/inc/fpgen.hpp b/inc/fpgen.hpp index 2aa2d20..6afedfe 100644 --- a/inc/fpgen.hpp +++ b/inc/fpgen.hpp @@ -1,8 +1,10 @@ #ifndef _FPGEN_MAIN #define _FPGEN_MAIN +#include "aggregators.hpp" #include "generator.hpp" #include "manipulators.hpp" #include "sources.hpp" +#include "stream.hpp" #endif diff --git a/inc/stream.hpp b/inc/stream.hpp new file mode 100644 index 0000000..48b3e6a --- /dev/null +++ b/inc/stream.hpp @@ -0,0 +1,30 @@ +#ifndef _FPGEN_STREAM +#define _FPGEN_STREAM + +#include "generator.hpp" +#include "sources.hpp" + +namespace fpgen { +template struct remove_generator {}; +template struct remove_generator> { using type = T; }; + +template class stream { +public: + stream(generator gen) : source{std::move(gen)} {} + stream(const stream &other) = delete; + stream(stream &&other) { *this = std::move(other); } + + stream &operator=(const stream &other) = delete; + stream &operator=(stream &&other) { source = std::move(other); } + + template auto operator<<(Func f) { + return stream>>::type>{f(std::move(source))}; + } + +private: + generator source; +}; +} // namespace fpgen + +#endif diff --git a/test/Makefile b/test/Makefile index 1e76973..decc87a 100644 --- a/test/Makefile +++ b/test/Makefile @@ -1,6 +1,6 @@ SOURCES=$(shell find $(SRCD) -name '*.cpp') DEPS=$(SOURCES:$(SRCD)/%.cpp=$(OBJD)/%.d) -TESTS=generator sources manip aggreg +TESTS=generator sources manip aggreg stream TESTOBJ=$(TESTS:%=$(OBJD)/test_%.o) CONAN_PKG_OVERRIDE=gtest diff --git a/test/src/test_stream.cpp b/test/src/test_stream.cpp new file mode 100644 index 0000000..ea9c594 --- /dev/null +++ b/test/src/test_stream.cpp @@ -0,0 +1,18 @@ +#include "aggregators.hpp" +#include "generator.hpp" +#include "manipulators.hpp" +#include "sources.hpp" +#include "stream.hpp" +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +char mapper(int i) { return 'a' + i; } +fpgen::generator map(fpgen::generator in) { return map(in, mapper); } + +TEST(stream, operator) { + fpgen::stream strm(fpgen::from(std::vector{0, 1, 2, 3, 4})); + // fpgen::stream strm2 = strm << map; + fpgen::stream strm2 = strm << [](fpgen::generator in) { + return map(in, [](int i) { return (char)('a' + i); }); + }; +} From e823723d1ce16f958b67890e3ac530e27b9bfa68 Mon Sep 17 00:00:00 2001 From: jay-tux Date: Sat, 19 Feb 2022 14:28:10 +0100 Subject: [PATCH 3/9] Removed co_return ; in favor of co_return; - fixed some things in generator - started on stream --- inc/generator.hpp | 66 ++++++++++++++++++++++--------------- inc/manipulators.hpp | 3 ++ inc/sources.hpp | 4 ++- inc/stream.hpp | 17 +++++----- test/src/test_aggreg.cpp | 29 ++++++++++------ test/src/test_generator.cpp | 8 ++--- test/src/test_manip.cpp | 22 ++++++------- test/src/test_stream.cpp | 24 ++++++++++---- 8 files changed, 105 insertions(+), 68 deletions(-) diff --git a/inc/generator.hpp b/inc/generator.hpp index 17e8ae5..c2f8efe 100644 --- a/inc/generator.hpp +++ b/inc/generator.hpp @@ -100,7 +100,10 @@ template = true> class generator { * coroutine calls `co_return`, the environment will call this function. * \param[in] v The value to set. */ - void return_value(value_type v) { value = v; } + // void return_value(value_type v) { value = v; } + + void return_void() {} + /** * \brief Sets an intermediate value from the coroutine. * @@ -155,12 +158,12 @@ template = true> class generator { * This value should only be true when the underlying generator is * finished. Iterators are compared based on this value. */ - bool is_finished; + // bool is_finished; /** * \brief The last value from the coroutine (and thus, the value currently * held by the generator's promise). */ - value_t value; + // value_t value; /** * \brief Constructs a new iterator from a source generator. @@ -173,8 +176,7 @@ template = true> class generator { * modified). * \param[in] is_finised Is this an `end` iterator? */ - iterator_type(gen_t &source, bool is_finised) - : source{source}, is_finished{is_finised} { + iterator_type(gen_t &source, bool is_finised) : source{source} { if (!is_finised) ++(*this); } @@ -190,20 +192,15 @@ template = true> class generator { * \param[in] is_finised Is this iterator finished? * \param[in] value The value this iterator should hold. */ - iterator_type(gen_t &source, bool is_finished, value_t value) - : source{source}, is_finished{is_finished}, value{value} {} + iterator_type(gen_t &source) : source{source} {} /** * \brief Steps the generator to the next value. * \returns A new iterator for this generator, with the same state. */ iter_t operator++() { - if (!source) - is_finished = true; - value = source(); - if (!source) - is_finished = true; - return {source, is_finished, value}; + source.next(); + return {source}; } /** @@ -219,19 +216,20 @@ template = true> class generator { * \param[in] other The iterator to compare against. * \returns False if both are equal, otherwise true. */ - bool operator!=(const iter_t &other) { - return is_finished != other.is_finished; - } + bool operator!=(const iter_t &) { return static_cast(source); } /** * \brief Gets the current value from the iterator. * \returns The current value. */ - value_t operator*() { return value; } + value_t operator*() { + source.contains = false; + return source._h.promise().value; + } /** * \brief Converts this iterator to the current value. * \returns The current value. */ - operator value_t() { return value; } + operator value_t() { return source._h.promise().value; } }; /** @@ -250,12 +248,13 @@ template = true> class generator { * This method should only be called from the environment. * \param[in] p The promise to use. */ - generator(promise_type &p) : _h{handle_type::from_promise(p)} {} + generator(promise_type &p) + : _h{handle_type::from_promise(p)}, contains{false} {} /** * \brief Copy-constructing generators results in undefined behaviour. */ - generator(const generator &other) = delete; + generator(const generator &other) = default; /** * \brief Moves the data from the other generator into this one. * \param[in,out] other The other generator. @@ -265,7 +264,7 @@ template = true> class generator { /** * \brief Copy-assigning generators results in undefined behaviour. */ - generator &operator=(const generator &other) = delete; + generator &operator=(const generator &other) = default; /** * \brief Moves the data from the other generator into this one. * \param[in,out] other The other generator. @@ -287,7 +286,12 @@ template = true> class generator { * \brief Converts this generator to a bool. * \returns True if more values remain, otherwise false. */ - operator bool() const { return !_h.done(); } + operator bool() { + if (_h.done()) + return false; + next(); + return !_h.done(); + } /** * \brief Gets an iterator to the current coroutine state. @@ -312,15 +316,23 @@ template = true> class generator { * that can be thrown from the coroutine. */ value_type operator()() { - if (*this) - _h(); - if (_h.promise().ex) - std::rethrow_exception(_h.promise().ex); - return _h.promise().value; + next(); + contains = false; + return std::move(_h.promise().value); } private: handle_type _h; + bool contains; + + void next() { + if (!contains) { + _h(); + if (_h.promise().ex) + std::rethrow_exception(_h.promise().ex); + contains = true; + } + } }; } // namespace fpgen diff --git a/inc/manipulators.hpp b/inc/manipulators.hpp index 7f095c0..16f5ee8 100644 --- a/inc/manipulators.hpp +++ b/inc/manipulators.hpp @@ -30,6 +30,7 @@ auto map(generator &gen, Fun func) while (gen) { co_yield func(gen()); } + co_return; } /** @@ -50,6 +51,7 @@ generator> zip(generator &gen1, generator &gen2) { while (gen1 && gen2) { co_yield {gen1(), gen2()}; } + co_return; } /** @@ -74,6 +76,7 @@ generator filter(generator &gen, Pred p) { if (p(val)) co_yield val; } + co_return; } } // namespace fpgen diff --git a/inc/sources.hpp b/inc/sources.hpp index ac7eeb0..e3fb577 100644 --- a/inc/sources.hpp +++ b/inc/sources.hpp @@ -2,7 +2,6 @@ #define _FPGEN_SOURCES #include "generator.hpp" -#include #include /** @@ -29,6 +28,7 @@ generator from(const Container &cont) { for (auto it = std::begin(cont); it != std::end(cont); ++it) { co_yield *it; } + co_return; } /** @@ -54,6 +54,7 @@ generator> enumerate(const Container &cont) { co_yield {i, *it}; i++; } + co_return; } /** @@ -79,6 +80,7 @@ from_tup(const Container &cont) { for (auto it = cont.begin(); it != cont.end(); ++it) { co_yield *it; } + co_return; } /** diff --git a/inc/stream.hpp b/inc/stream.hpp index 48b3e6a..6f78339 100644 --- a/inc/stream.hpp +++ b/inc/stream.hpp @@ -3,6 +3,7 @@ #include "generator.hpp" #include "sources.hpp" +#include namespace fpgen { template struct remove_generator {}; @@ -10,18 +11,16 @@ template struct remove_generator> { using type = T; }; template class stream { public: - stream(generator gen) : source{std::move(gen)} {} - stream(const stream &other) = delete; - stream(stream &&other) { *this = std::move(other); } + stream(generator gen) : source{gen} {} - stream &operator=(const stream &other) = delete; - stream &operator=(stream &&other) { source = std::move(other); } - - template auto operator<<(Func f) { - return stream>>::type>{f(std::move(source))}; + template typename Container> + Container &operator>>(Container &cont) { + return aggregate_to(source, cont); } + operator generator &() { return source; } + generator &get_generator() { return source; } + private: generator source; }; diff --git a/test/src/test_aggreg.cpp b/test/src/test_aggreg.cpp index 7ea4304..259163e 100644 --- a/test/src/test_aggreg.cpp +++ b/test/src/test_aggreg.cpp @@ -1,13 +1,14 @@ #include "aggregators.hpp" #include "generator.hpp" #include "manipulators.hpp" +#include "sources.hpp" #include "gmock/gmock.h" #include "gtest/gtest.h" #include #include -fpgen::generator a_empty() { co_return 0; } +fpgen::generator a_empty() { co_return; } fpgen::generator values() { size_t i = 0; @@ -15,14 +16,14 @@ fpgen::generator values() { co_yield 0; co_yield 1; - while (j < 21) { + while (j <= 21) { size_t tmp = i + j; co_yield tmp; i = j; j = tmp; } - co_return i + j; + // co_return i + j; } size_t calc_sum() { @@ -46,7 +47,7 @@ size_t sum_ref(size_t &old, size_t in) { TEST(aggregate, empty) { auto gen = a_empty(); - gen(); + // gen(); std::vector res; EXPECT_EQ(0, fpgen::aggregate_to(gen, res).size()); } @@ -69,6 +70,14 @@ TEST(aggregate, vector) { EXPECT_EQ(res.size(), 10); } +TEST(aggregate, vec_to_vec) { + std::vector in = {0, 1, 2, 3, 4, 5, 6}; + std::vector out = {}; + auto gen = fpgen::from(in); + out = fpgen::aggregate_to(gen, out); + EXPECT_EQ(in, out); +} + TEST(aggregate, map) { fpgen::generator sources[2] = {values(), values()}; auto gen = fpgen::zip(sources[0], sources[1]); @@ -89,7 +98,7 @@ TEST(aggregate, map) { TEST(aggregate, count_empty) { auto gen = a_empty(); - gen(); + // gen(); EXPECT_EQ(0, fpgen::count(gen)); } @@ -100,7 +109,7 @@ TEST(aggregate, count) { TEST(fold, fold_noin_empty) { auto gen = a_empty(); - gen(); + // gen(); EXPECT_EQ(0, fpgen::fold(gen, sum)); } @@ -111,7 +120,7 @@ TEST(fold, fold_noin) { TEST(fold, fold_in_noref_empty) { auto gen = a_empty(); - gen(); + // gen(); EXPECT_EQ(7, fpgen::fold(gen, sum, 7)); } @@ -122,7 +131,7 @@ TEST(fold, fold_in_noref) { TEST(fold, fold_in_ref_empty) { auto gen = a_empty(); - gen(); + // gen(); size_t res = 7; EXPECT_EQ(7, fpgen::fold_ref(gen, sum, res)); EXPECT_EQ(7, res); @@ -137,7 +146,7 @@ TEST(fold, fold_in_ref) { TEST(sum, empty) { auto gen = a_empty(); - gen(); + // gen(); EXPECT_EQ(0, fpgen::sum(gen)); } @@ -148,7 +157,7 @@ TEST(sum, normal) { TEST(foreach, empty) { auto gen = a_empty(); - gen(); + // gen(); size_t res = 0; fpgen::foreach (gen, [&res](size_t val) { res += val; }); EXPECT_EQ(res, 0); diff --git a/test/src/test_generator.cpp b/test/src/test_generator.cpp index 231f29a..a63e552 100644 --- a/test/src/test_generator.cpp +++ b/test/src/test_generator.cpp @@ -3,7 +3,7 @@ #include "gtest/gtest.h" #include -fpgen::generator empty() { co_return 0; } +fpgen::generator empty() { co_return; } fpgen::generator infinite() { int value = 0; @@ -14,10 +14,10 @@ fpgen::generator infinite() { } fpgen::generator finite_squares(int min, int max) { - for (int val = min; val < max; val++) { + for (int val = min; val <= max; val++) { co_yield val *val; } - co_return max *max; + co_return; // max *max; } TEST(generator, accept_empty_gen) { @@ -27,7 +27,7 @@ TEST(generator, accept_empty_gen) { TEST(generator, iterator_empty_gen) { auto gen = empty(); - gen(); + // gen(); for (auto v : gen) { FAIL(); } diff --git a/test/src/test_manip.cpp b/test/src/test_manip.cpp index aeb1521..5576c93 100644 --- a/test/src/test_manip.cpp +++ b/test/src/test_manip.cpp @@ -9,22 +9,22 @@ #include #include -fpgen::generator manip_empty() { co_return 0; } +fpgen::generator manip_empty() { co_return; } fpgen::generator manip() { size_t i = 1; - while (i < 1024) { + while (i <= 1024) { co_yield i; i *= 2; } - co_return i; + co_return; // i; } fpgen::generator until12() { - for (int i = 0; i < 12; i++) { + for (int i = 0; i <= 12; i++) { co_yield i; } - co_return 12; + co_return; // 12; } size_t mapper(size_t v) { return v * v; } @@ -33,7 +33,7 @@ bool over_100(size_t v) { return (v > 100); } TEST(manipulators, map_empty) { auto gen = manip_empty(); - gen(); + // gen(); for (auto v : fpgen::map(gen, mapper)) { FAIL() << "Should not return a value"; @@ -54,8 +54,8 @@ TEST(manipulators, map) { TEST(manipulators, zip_both_empty) { auto gen = manip_empty(); auto gen2 = manip_empty(); - gen(); - gen2(); + // gen(); + // gen2(); for (auto v : fpgen::zip(gen, gen2)) { FAIL() << "Should not return a value"; @@ -66,7 +66,7 @@ TEST(manipulators, zip_both_empty) { TEST(manipulators, zip_first_empty) { auto gen = manip_empty(); auto gen2 = fpgen::inc((size_t)0); - gen(); + // gen(); for (auto v : fpgen::zip(gen, gen2)) { FAIL() << "Should not return a value"; @@ -77,7 +77,7 @@ TEST(manipulators, zip_first_empty) { TEST(manipulators, zip_second_empty) { auto gen = fpgen::inc((size_t)0); auto gen2 = manip_empty(); - gen2(); + // gen2(); for (auto v : fpgen::zip(gen, gen2)) { FAIL() << "Should not return a value"; @@ -105,7 +105,7 @@ TEST(manipulators, zip_none_empty) { TEST(manipulators, filter_empty) { auto gen = manip_empty(); - gen(); + // gen(); size_t i = 0; diff --git a/test/src/test_stream.cpp b/test/src/test_stream.cpp index ea9c594..d265778 100644 --- a/test/src/test_stream.cpp +++ b/test/src/test_stream.cpp @@ -6,13 +6,25 @@ #include "gmock/gmock.h" #include "gtest/gtest.h" +// temporary +#include + +char *demangle(const char *name) { + return abi::__cxa_demangle(name, nullptr, nullptr, nullptr); +} + char mapper(int i) { return 'a' + i; } fpgen::generator map(fpgen::generator in) { return map(in, mapper); } -TEST(stream, operator) { - fpgen::stream strm(fpgen::from(std::vector{0, 1, 2, 3, 4})); - // fpgen::stream strm2 = strm << map; - fpgen::stream strm2 = strm << [](fpgen::generator in) { - return map(in, [](int i) { return (char)('a' + i); }); - }; +TEST(stream, inout) { + std::vector in = {0, 1, 2, 3}; + fpgen::stream strm = fpgen::from(in); + std::vector output; + strm >> output; + + std::vector expected; + fpgen::generator gen = fpgen::from(in); + expected = fpgen::aggregate_to(gen, expected); + + EXPECT_EQ(output, expected); } From 2573ab82d1ee9e1d6be72f9ada4c639072cd672e Mon Sep 17 00:00:00 2001 From: jay-tux Date: Sat, 19 Feb 2022 14:34:01 +0100 Subject: [PATCH 4/9] Removed reference requirements, added test_chain to test chains --- inc/aggregators.hpp | 16 ++++++++-------- inc/manipulators.hpp | 6 +++--- test/src/test_chain.cpp | 6 ++++++ 3 files changed, 17 insertions(+), 11 deletions(-) create mode 100644 test/src/test_chain.cpp diff --git a/inc/aggregators.hpp b/inc/aggregators.hpp index 898d2c1..8297dd9 100644 --- a/inc/aggregators.hpp +++ b/inc/aggregators.hpp @@ -28,7 +28,7 @@ namespace fpgen { */ template typename Container> -Container &aggregate_to(generator &gen, +Container &aggregate_to(generator gen, Container &out) { while (gen) { out.push_back(gen()); @@ -55,7 +55,7 @@ Container &aggregate_to(generator &gen, template typename Container> Container & -tup_aggregate_to(generator> &gen, +tup_aggregate_to(generator> gen, Container &out) { while (gen) { std::tuple tup = gen(); @@ -75,7 +75,7 @@ tup_aggregate_to(generator> &gen, * \param[in,out] gen The generator to iterate over. * \returns The amount of elements in the generator. */ -template size_t count(generator &gen) { +template size_t count(generator gen) { size_t cnt = 0; while (gen) { gen(); @@ -102,7 +102,7 @@ template size_t count(generator &gen) { * \returns The final accumulator value. */ template -TOut fold(generator &gen, Fun folder) { +TOut fold(generator gen, Fun folder) { TOut value = {}; while (gen) { value = folder(value, gen()); @@ -129,7 +129,7 @@ TOut fold(generator &gen, Fun folder) { * \returns The final accumulator value. */ template -TOut fold(generator &gen, Fun folder, TOut initial) { +TOut fold(generator gen, Fun folder, TOut initial) { TOut value(initial); while (gen) { value = folder(value, gen()); @@ -157,7 +157,7 @@ TOut fold(generator &gen, Fun folder, TOut initial) { * now the output value. */ template -TOut &fold_ref(generator &gen, Fun folder, TOut &initial) { +TOut &fold_ref(generator gen, Fun folder, TOut &initial) { while (gen) { initial = folder(initial, gen()); } @@ -175,7 +175,7 @@ TOut &fold_ref(generator &gen, Fun folder, TOut &initial) { * \param[in,out] gen The generator to sum over. * \returns The sum of all elements. */ -template T sum(generator &gen) { +template T sum(generator gen) { T accum = {}; while (gen) { accum = accum + gen(); @@ -195,7 +195,7 @@ template T sum(generator &gen) { * \param[in,out] gen The generator to iterate over. * \param[in] func The function to use. */ -template void foreach (generator &gen, Fun func) { +template void foreach (generator gen, Fun func) { while (gen) { func(gen()); } diff --git a/inc/manipulators.hpp b/inc/manipulators.hpp index 16f5ee8..44e7cde 100644 --- a/inc/manipulators.hpp +++ b/inc/manipulators.hpp @@ -25,7 +25,7 @@ namespace fpgen { * mapping function. */ template -auto map(generator &gen, Fun func) +auto map(generator gen, Fun func) -> generator::type> { while (gen) { co_yield func(gen()); @@ -47,7 +47,7 @@ auto map(generator &gen, Fun func) * \returns A new generator containing tuples of values from both generators. */ template -generator> zip(generator &gen1, generator &gen2) { +generator> zip(generator gen1, generator gen2) { while (gen1 && gen2) { co_yield {gen1(), gen2()}; } @@ -70,7 +70,7 @@ generator> zip(generator &gen1, generator &gen2) { * except those not matching the predicate. */ template -generator filter(generator &gen, Pred p) { +generator filter(generator gen, Pred p) { while (gen) { T val(gen()); if (p(val)) diff --git a/test/src/test_chain.cpp b/test/src/test_chain.cpp new file mode 100644 index 0000000..c20d0a2 --- /dev/null +++ b/test/src/test_chain.cpp @@ -0,0 +1,6 @@ +#include "aggregators.hpp" +#include "generator.hpp" +#include "manipulators.hpp" +#include "sources.hpp" +#include "gmock/gmock.h" +#include "gtest/gtest.h" From f7a00331094a87f9299e928de257e56a8f37974c Mon Sep 17 00:00:00 2001 From: jay-tux Date: Sat, 19 Feb 2022 14:50:08 +0100 Subject: [PATCH 5/9] Added drop/take --- inc/manipulators.hpp | 18 +++++++++++++++ test/src/test_manip.cpp | 51 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 69 insertions(+) diff --git a/inc/manipulators.hpp b/inc/manipulators.hpp index 44e7cde..480c139 100644 --- a/inc/manipulators.hpp +++ b/inc/manipulators.hpp @@ -78,6 +78,24 @@ generator filter(generator gen, Pred p) { } co_return; } + +template generator drop(generator gen, size_t count) { + for (size_t i = 0; i < count && gen; i++) { + gen(); + } + + while (gen) { + co_yield gen(); + } + co_return; +} + +template generator take(generator gen, size_t count) { + for (size_t i = 0; i < count && gen; i++) { + co_yield gen(); + } + co_return; +} } // namespace fpgen #endif diff --git a/test/src/test_manip.cpp b/test/src/test_manip.cpp index 5576c93..3622b41 100644 --- a/test/src/test_manip.cpp +++ b/test/src/test_manip.cpp @@ -133,3 +133,54 @@ TEST(manipulators, filter_normal) { i += 2; } } + +TEST(manipulators, drop_empty) { + auto gen = drop(manip_empty(), 5); + for (auto v : gen) { + FAIL() << "should not return a value"; + } + SUCCEED(); +} + +TEST(manipulators, drop_normal) { + auto gen = drop(until12(), 5); + size_t exp = 5; + for (auto v : gen) { + EXPECT_EQ(v, exp); + EXPECT_TRUE(exp <= 12); + exp++; + } + EXPECT_EQ(exp, 13); +} + +TEST(manipulators, take_empty) { + auto gen = take(manip_empty(), 4); + for (auto v : gen) { + FAIL() << "should not return a value"; + } + SUCCEED(); +} + +TEST(manipulators, take_normal) { + auto gen = take(fpgen::inc((size_t)0), 8); + size_t exp = 0; + for (auto v : gen) { + EXPECT_EQ(v, exp); + EXPECT_TRUE(exp <= 8); + exp++; + } + EXPECT_EQ(exp, 8); +} + +TEST(manipulators, drop_take) { + auto gen = take(drop(fpgen::inc((size_t)0), 4), 9); + for (size_t exp = 4; exp < 13; exp++) { + EXPECT_TRUE(static_cast(gen)); + EXPECT_EQ(exp, gen()); + } + + for (auto v : gen) { + FAIL() << "should not return a value"; + } + SUCCEED(); +} From 8ae072a9cabe8165bff4b4b6d4dee95d4c7cdee0 Mon Sep 17 00:00:00 2001 From: jay-tux Date: Sat, 19 Feb 2022 15:01:36 +0100 Subject: [PATCH 6/9] Added drop_while/take_while --- README.md | 2 +- inc/manipulators.hpp | 28 ++++++++++++++++++++++++++++ test/src/test_manip.cpp | 38 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 67 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 30533ed..dd63e9c 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # fpgen *Functional programming in C++ using C++20 coroutines* -![](https://img.shields.io/badge/test_coverage-98%25-brightgreen) +![](https://img.shields.io/badge/test_coverage-97%25-brightgreen) ## Aim diff --git a/inc/manipulators.hpp b/inc/manipulators.hpp index 480c139..f31d69f 100644 --- a/inc/manipulators.hpp +++ b/inc/manipulators.hpp @@ -96,6 +96,34 @@ template generator take(generator gen, size_t count) { } co_return; } + +template +generator drop_while(generator gen, Pred p) { + while (gen) { + T temp = gen(); + if (!p(temp)) { + co_yield temp; + break; + } + } + + while (gen) { + co_yield gen(); + } + co_return; +} + +template +generator take_while(generator gen, Pred p) { + while (gen) { + T val = gen(); + if (!p(val)) { + break; + } + co_yield val; + } + co_return; +} } // namespace fpgen #endif diff --git a/test/src/test_manip.cpp b/test/src/test_manip.cpp index 3622b41..9df732a 100644 --- a/test/src/test_manip.cpp +++ b/test/src/test_manip.cpp @@ -184,3 +184,41 @@ TEST(manipulators, drop_take) { } SUCCEED(); } + +TEST(manipulators, drop_while_empty) { + auto gen = drop_while(manip_empty(), [](size_t v) { return v > 3; }); + for (auto v : gen) { + FAIL() << "should not return a value"; + } + SUCCEED(); +} + +TEST(manipulators, drop_while_normal) { + auto gen = drop_while(until12(), [](size_t v) { return v < 5; }); + size_t exp = 5; + for (auto v : gen) { + EXPECT_EQ(v, exp); + EXPECT_TRUE(exp <= 12); + exp++; + } + EXPECT_EQ(exp, 13); +} + +TEST(manipulators, take_while_empty) { + auto gen = take_while(manip_empty(), [](size_t v) { return v < 4; }); + for (auto v : gen) { + FAIL() << "should not return a value"; + } + SUCCEED(); +} + +TEST(manipulators, take_while_normal) { + auto gen = take_while(fpgen::inc((size_t)0), [](size_t v) { return v < 8; }); + size_t exp = 0; + for (auto v : gen) { + EXPECT_EQ(v, exp); + EXPECT_TRUE(exp <= 8); + exp++; + } + EXPECT_EQ(exp, 8); +} From d2c938c1ab0b163b82b2a0656b73988ad8c0f3d2 Mon Sep 17 00:00:00 2001 From: jay-tux Date: Sat, 19 Feb 2022 15:22:47 +0100 Subject: [PATCH 7/9] Added simple chain test --- test/Makefile | 2 +- test/src/test_chain.cpp | 30 ++++++++++++++++++++++++++++++ 2 files changed, 31 insertions(+), 1 deletion(-) diff --git a/test/Makefile b/test/Makefile index decc87a..731ae90 100644 --- a/test/Makefile +++ b/test/Makefile @@ -1,6 +1,6 @@ SOURCES=$(shell find $(SRCD) -name '*.cpp') DEPS=$(SOURCES:$(SRCD)/%.cpp=$(OBJD)/%.d) -TESTS=generator sources manip aggreg stream +TESTS=generator sources manip aggreg stream chain TESTOBJ=$(TESTS:%=$(OBJD)/test_%.o) CONAN_PKG_OVERRIDE=gtest diff --git a/test/src/test_chain.cpp b/test/src/test_chain.cpp index c20d0a2..f9a4158 100644 --- a/test/src/test_chain.cpp +++ b/test/src/test_chain.cpp @@ -4,3 +4,33 @@ #include "sources.hpp" #include "gmock/gmock.h" #include "gtest/gtest.h" + +using namespace fpgen; + +template +std::ostream &operator<<(std::ostream &in, std::tuple &other) { + return in << "{ " << std::get<0>(other) << ", " << std::get<1>(other) << " }"; +} + +TEST(chain, simple_chain) { + /* + Chain: + -> [0..] -> [5..] -> [5..13] -> [15...169] \ + -> {[15...169], ['h'...'p']} + -> [0..] -> [7..] -> [7..22] -> ['h'...'w'] / + */ + auto gen = + map(take(drop(inc((size_t)0), 5), 9), [](size_t in) { return in * in; }); + + auto second = map(take(drop(inc((size_t)0), 7), 22), + [](size_t in) { return (char)('a' + (in % 26)); }); + + size_t value = 5; + for (auto v : zip(gen, second)) { + EXPECT_EQ(value * value, std::get<0>(v)); + EXPECT_EQ('a' + 2 + value, std::get<1>(v)); + EXPECT_TRUE(value <= 13); + value++; + } + EXPECT_EQ(value, 14); +} From 6cb0dc5a6b6e655b26bd5bf9e9e5e1121f52eaf1 Mon Sep 17 00:00:00 2001 From: jay-tux Date: Mon, 21 Feb 2022 21:14:17 +0100 Subject: [PATCH 8/9] Stream did not prove to be practical; added documentation --- inc/manipulators.hpp | 63 ++++++++++++++++++++++++++++++++++++++++++-- inc/stream.hpp | 29 -------------------- 2 files changed, 61 insertions(+), 31 deletions(-) delete mode 100644 inc/stream.hpp diff --git a/inc/manipulators.hpp b/inc/manipulators.hpp index f31d69f..0a6ac60 100644 --- a/inc/manipulators.hpp +++ b/inc/manipulators.hpp @@ -64,8 +64,8 @@ generator> zip(generator gen1, generator gen2) { * \tparam T The type contained in the generator. * \tparam Pred The function type of the predicate function. Should return a * bool. - * \param gen The generator containing the original values. - * \param p The predicate. + * \param[in,out] gen The generator containing the original values. + * \param[in] p The predicate. * \returns A new generator which yields all values in the original generator * except those not matching the predicate. */ @@ -79,6 +79,19 @@ generator filter(generator gen, Pred p) { co_return; } +/** + * \brief Ignores the first n elements from a generator. + * + * Extracts the first `count` elements from the generator and throws those + * away. All subsequent elements in the generator are passed through as normal. + * If there aren't enough elements in the original generator, an empty generator + * is returned. + * + * \tparam T The type contained in the generator. + * \param[in,out] gen The generator to drop from. + * \param[in] count The amount of elements to ignore. + * \returns A new generator yielding all values except the first n. + */ template generator drop(generator gen, size_t count) { for (size_t i = 0; i < count && gen; i++) { gen(); @@ -90,6 +103,19 @@ template generator drop(generator gen, size_t count) { co_return; } +/** + * \brief Yields the first n elements from a generator. + * + * Extracts and yields exactly `count` elements (or less if the generator + * doesn't contain that much values). The iteration is then stopped and no + * further elements will be generated. If generation has side effects, the side + * effects of elements after the first n will not be observable. + * + * \tparam T The type contained in the generator. + * \param[in,out] gen The generator to take elements from. + * \param[in] count The amount of elements to yield. + * \returns A new generator yielding only the first n values. + */ template generator take(generator gen, size_t count) { for (size_t i = 0; i < count && gen; i++) { co_yield gen(); @@ -97,6 +123,22 @@ template generator take(generator gen, size_t count) { co_return; } +/** + * \brief Drops elements while they satisfy a certain predicate. + * + * Iterates over the elements in the generator and drops them until it + * encounters a value not satisfying the predicate (`!p(value)` is true). Then + * it starts yielding each remaining value in the generator. To remove all + * values satisfying `p`, use `fpgen::filter(gen, [&p](auto v) { return !p(v); + * });`. + * + * \tparam T The type contained in the generator. + * \tparam Pred The type of the predicate (should be a T -> bool function). + * \param gen The generator to drop elements from. + * \param p The predicate. + * \returns A new generator where the first element is guaranteed to not + * satisfy `p`. + */ template generator drop_while(generator gen, Pred p) { while (gen) { @@ -113,6 +155,23 @@ generator drop_while(generator gen, Pred p) { co_return; } +/** + * \brief Yields elements while they satisfy a certain predicate. + * + * Iterates over the elements in the generator and yields them until it + * encounters a value not satisfying the predicate (`!p(value)`). Then it stops + * generating (any side effects from the subsequent elements won't be + * observable). To obtain a generator with all elements satisfying `p`, use + * `fpgen::filter(gen, p);` instead. + * + * \tparam T The type contained in the generator. + * \tparam Pred The type of the predicate (should be a T -> bool function). + * \param gen The generator to take elements from. + * \param p The predicate. + * \returns A new generator where all elements are guaranteed to satisfy the + * predicate and were generated before any element which didn't satisfy the + * predicate. + */ template generator take_while(generator gen, Pred p) { while (gen) { diff --git a/inc/stream.hpp b/inc/stream.hpp deleted file mode 100644 index 6f78339..0000000 --- a/inc/stream.hpp +++ /dev/null @@ -1,29 +0,0 @@ -#ifndef _FPGEN_STREAM -#define _FPGEN_STREAM - -#include "generator.hpp" -#include "sources.hpp" -#include - -namespace fpgen { -template struct remove_generator {}; -template struct remove_generator> { using type = T; }; - -template class stream { -public: - stream(generator gen) : source{gen} {} - - template typename Container> - Container &operator>>(Container &cont) { - return aggregate_to(source, cont); - } - - operator generator &() { return source; } - generator &get_generator() { return source; } - -private: - generator source; -}; -} // namespace fpgen - -#endif From e8ff499d6c19a3e67f28311f41908714b706548d Mon Sep 17 00:00:00 2001 From: jay-tux Date: Mon, 21 Feb 2022 21:15:47 +0100 Subject: [PATCH 9/9] Removed some lines in README --- README.md | 5 ----- 1 file changed, 5 deletions(-) diff --git a/README.md b/README.md index dd63e9c..fa5090a 100644 --- a/README.md +++ b/README.md @@ -21,11 +21,6 @@ Currently supported features: - Lazy `fold`ing of generators. - Lazy `sum`ming of generators. -Planned/In progress features: - - Ergonomic improvements: - - Enable streaming `<<` of generators. - - Simple structure which allows generator construction, manipulation and aggregation using member functions. - Got another idea? Drop a feature request on the repo. ## Requirements