diff --git a/inc/__helpers.hpp b/inc/__helpers.hpp new file mode 100644 index 0000000..456c7c7 --- /dev/null +++ b/inc/__helpers.hpp @@ -0,0 +1,12 @@ +#ifndef _FPGEN___HELPERS +#define _FPGEN___HELPERS + +#ifdef __clang__ +#include +#define _FPGEN_USE_CONCEPTS 0 +#else +#include +#define _FPGEN_USE_CONCEPTS 1 +#endif + +#endif diff --git a/inc/fpgen.hpp b/inc/fpgen.hpp index c3f2d15..19e99cc 100644 --- a/inc/fpgen.hpp +++ b/inc/fpgen.hpp @@ -2,5 +2,6 @@ #define _FPGEN_MAIN #include "generator.hpp" +#include "sources.hpp" #endif diff --git a/inc/generator.hpp b/inc/generator.hpp index fb38854..17e8ae5 100644 --- a/inc/generator.hpp +++ b/inc/generator.hpp @@ -2,29 +2,22 @@ #define _FPGEN_GENERATOR #ifdef __clang__ -#include #include namespace std { using namespace experimental; } #else -#include #include #endif +#include "__helpers.hpp" #include /** - * @brief The namespace containing all of fpgen's code. + * \brief The namespace containing all of fpgen's code. */ namespace fpgen { -#ifdef __clang__ -template -using enabler = - typename std::enable_if::value, bool>::type; - -template = true> class generator { -#else +#if _FPGEN_USE_CONCEPTS /** * \brief The main generator type. * @@ -39,6 +32,12 @@ template = true> class generator { * `std::copyable`, or on (older) CLang versions, `std::is_copy_assignable`. */ template class generator { +#else +template +using enabler = + typename std::enable_if::value, bool>::type; + +template = true> class generator { #endif public: /** @@ -202,6 +201,8 @@ template class generator { if (!source) is_finished = true; value = source(); + if (!source) + is_finished = true; return {source, is_finished, value}; } diff --git a/inc/sources.hpp b/inc/sources.hpp new file mode 100644 index 0000000..ac7eeb0 --- /dev/null +++ b/inc/sources.hpp @@ -0,0 +1,106 @@ +#ifndef _FPGEN_SOURCES +#define _FPGEN_SOURCES + +#include "generator.hpp" +#include +#include + +/** + * \brief The namespace containing all of fpgen's code. + */ +namespace fpgen { +/** + * \brief Creates a generator over a data source. + * + * The data source should have an iterator (using `std::begin` and `std::end`). + * For most builtin containers (`std::vector`, ...) this is already satisfied. + * For `std::map`, see fpgen::from_tup. + * + * \tparam T The type contained in the container. + * \tparam TArgs Any other template parameters passed to the container. + * \tparam Container The container type. + * \param[in] cont The container to iterate over. + * \returns A new generator which will iterate over the container. + * \see fpgen::from_tup, fpgen::enumerate + */ +template typename Container> +generator from(const Container &cont) { + for (auto it = std::begin(cont); it != std::end(cont); ++it) { + co_yield *it; + } +} + +/** + * \brief Creates a generator over a data source, with indexing. + * + * The data source does not have to allow indexing, but it is recommended for + * repeatable behaviour. If the index is not needed, use fpgen::from. The data + * source should support iterating (using `std::begin` and `std::end`). + * + * \tparam T The type contained in the container. + * \tparam TArgs Any other template parameters passed to the container. + * \tparam Container The container type. + * \param[in] cont The container to iterate over. + * \returns A new generator which will iterate over the container using index + * and value. + * \see fpgen::from_tup, fpgen::enumerate + */ +template typename Container> +generator> enumerate(const Container &cont) { + size_t i = 0; + for (auto it = std::begin(cont); it != std::end(cont); ++it) { + co_yield {i, *it}; + i++; + } +} + +/** + * \brief Creates a generator over an associative data source. + * + * The data source should have an iterator (using `std::begin` and `std::end`). + * For most builtin containers (`std::map`, ...) this is already satisfied. + * The container should have two type arguments. For single-type containers, see + * fpgen::from. + * + * \tparam TKey The first type contained in the container (key type). + * \tparam TValue The second type contained in the container (value type). + * \tparam TArgs Any other template parameters passed to the container. + * \tparam Container The container type. + * \param[in] cont The container to iterate over. + * \returns A new generator which will iterate over the container. + * \see fpgen::from + */ +template typename Container> +generator> +from_tup(const Container &cont) { + for (auto it = cont.begin(); it != cont.end(); ++it) { + co_yield *it; + } +} + +/** + * \brief Creates an infinitely incrementing generator. + * + * The generator is contstructed by continuously incrementing (a copy of) the + * given value. While mainly meant for integral types, any type supporting + * operator++() (the prefix increment operator) can be used. The first value + * returned is the start value itself. + * + * \tparam T The type to increment. + * \param[in] start The initial value. + * \returns An infinite generator which increments a value. + */ +template generator inc(T start) { + T value = start; + while (true) { + co_yield value; + ++value; + } +} + +} // namespace fpgen + +#endif diff --git a/test/Makefile b/test/Makefile index eeeab60..010828e 100644 --- a/test/Makefile +++ b/test/Makefile @@ -1,8 +1,12 @@ SOURCES=$(shell find $(SRCD) -name '*.cpp') DEPS=$(SOURCES:$(SRCD)/%.cpp=$(OBJD)/%.d) -TESTS=generator +TESTS=generator sources +TESTOBJ=$(TESTS:%=$(OBJD)/test_%.o) -all: dirs conan/conanbuildinfo.mak $(BIND)/generator +CONAN_PKG_OVERRIDE=gtest +CONAN_MODIFY=$(CONAN_PKG_OVERRIDE:%=-s %:compiler.version=11.2) + +all: dirs conan/conanbuildinfo.mak $(BIND)/_test -include $(DEPS) -include conan/conanbuildinfo.mak @@ -11,21 +15,22 @@ LDXTRA=$(CONAN_LIB_DIRS:%=-L%) $(CONAN_LIBS:%=-l%) $(CONAN_SYSTEM_LIBS:%=-l%) dirs: @([ ! -d $(OBJD)/generator ] && mkdir -p $(OBJD)/generator) || true + @([ ! -d $(OBJD)/sources ] && mkdir -p $(OBJD)/sources) || true -$(BIND)/generator: $(OBJD)/generator/test_generator.o - $(CC) $< $(LDXTRA) $(LDARGS) -o $@ - $(BIND)/generator --gtest_output="xml:$(BIND)/" +$(BIND)/_test: $(TESTOBJ) $(OBJD)/test_main.o + $(CC) $(LDXTRA) $(LDARGS) $^ -o $@ + $(BIND)/_test $(OBJD)/%.o: $(SRCD)/%.cpp Makefile $(CC) $(CXXARGS) $(CXXXTRA) $< -o $@ conan/conanbuildinfo.mak: conanfile.txt @([ ! -d conan/ ] && mkdir -p conan/) || true - cd conan && CC=gcc CXX=$(CC) conan install .. --build=gtest + cd conan && CC=gcc CXX=$(CC) conan install .. $(CONAN_MODIFY) --build=gtest clean: find ./bin/ -type f | grep -v '.gitkeep' | xargs rm -rf find ./obj/ -type f | grep -v '.gitkeep' | xargs rm -rf find ./conan/ -type f | grep -v '.gitkeep' | xargs rm -rf -.PHONY: all dirs clean +.PHONY: all dirs clean $(TESTS:%=$(BIND)/%) diff --git a/test/src/generator/test_generator.cpp b/test/src/test_generator.cpp similarity index 92% rename from test/src/generator/test_generator.cpp rename to test/src/test_generator.cpp index 5396599..231f29a 100644 --- a/test/src/generator/test_generator.cpp +++ b/test/src/test_generator.cpp @@ -63,8 +63,3 @@ TEST(generator, iterator) { expect++; } } - -int main(int argc, char **argv) { - testing::InitGoogleTest(&argc, argv); - return RUN_ALL_TESTS(); -} diff --git a/test/src/test_main.cpp b/test/src/test_main.cpp new file mode 100644 index 0000000..4e26ef4 --- /dev/null +++ b/test/src/test_main.cpp @@ -0,0 +1,3 @@ +#include "generator.hpp" +#include "gmock/gmock.h" +#include "gtest/gtest.h" diff --git a/test/src/test_sources.cpp b/test/src/test_sources.cpp new file mode 100644 index 0000000..d27fb1d --- /dev/null +++ b/test/src/test_sources.cpp @@ -0,0 +1,81 @@ +#include "generator.hpp" +#include "sources.hpp" +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +#include +#include +#include +#include + +TEST(sources, from_vector) { + std::vector values = {0, 5, 1, 4, 2, 3}; + size_t idx = 0; + for (auto v : fpgen::from(values)) { + EXPECT_EQ(values[idx], v); + idx++; + } +} + +TEST(sources, from_set) { + std::set srcs = {"key 1", "key 2", "key 3", "something"}; + std::set todo = {"key 1", "key 2", "key 3", "something"}; + + for (auto v : fpgen::from(srcs)) { + EXPECT_NE(todo.find(v), todo.end()); + todo.erase(v); + } + EXPECT_TRUE(todo.empty()); +} + +TEST(sources, enumerate_vector) { + std::vector values = { 'a', 'c', 'e', 'k', 'j', 't' }; + size_t prev = 0; + for(auto v : fpgen::enumerate(values)) { + EXPECT_EQ(std::get<0>(v), prev); + EXPECT_EQ(values[prev], std::get<1>(v)); + prev++; + } +} + +TEST(sources, from_map_tup) { + std::map map = {{"key 1", "value 1"}, + {"key 2", "value 2"}, + {"key 3", "value 3"}, + {"something", "else"}}; + std::set todo = {"key 1", "key 2", "key 3", "something"}; + for (auto v : fpgen::from_tup(map)) { + EXPECT_NE(todo.find(std::get<0>(v)), todo.end()); + EXPECT_EQ(map[std::get<0>(v)], std::get<1>(v)); + todo.erase(std::get<0>(v)); + } + EXPECT_TRUE(todo.empty()); +} + +TEST(sources, incrementable) { + auto gen = fpgen::inc(0); + for (int i = 0; i < 25; i++) { + EXPECT_EQ(gen(), i); + } +} + +TEST(sources, incrementable_struct) { + struct inc_struct { + int value; + inline inc_struct operator++() { + value++; + return {value}; + } + inline inc_struct operator++(int) { + inc_struct next = {value}; + value++; + return next; + } + } v1; + v1.value = 0; + + auto gen = fpgen::inc(v1); + for (int i = 0; i < 25; i++) { + EXPECT_EQ(gen().value, i); + } +}