From 68d10eafb1aa93875b361c5102e2071b884286e2 Mon Sep 17 00:00:00 2001 From: jay-tux Date: Mon, 14 Feb 2022 18:32:55 +0100 Subject: [PATCH] Generator, promise type, iterator and related tests --- Makefile | 13 ++-- inc/fpgen.hpp | 6 ++ inc/generator.hpp | 101 ++++++++++++++++++++++++++ test/Makefile | 31 ++++++++ test/conanfile.txt | 5 ++ test/src/generator/test_generator.cpp | 70 ++++++++++++++++++ 6 files changed, 218 insertions(+), 8 deletions(-) create mode 100644 inc/fpgen.hpp create mode 100644 inc/generator.hpp create mode 100644 test/src/generator/test_generator.cpp diff --git a/Makefile b/Makefile index 72bad1c..22b2739 100644 --- a/Makefile +++ b/Makefile @@ -7,10 +7,8 @@ INSTALL_DIR=/usr/include/fpgen INCL_PATH=$(abspath ./inc) DOC_DIR=$(abspath ./docs/) -JOB_COUNT=4 - CC=g++ -CXXARGS=-I$(abspath ./inc) +CXXARGS=-I$(abspath ./inc) -g -c -std=c++20 -MMD LDARGS= all: @@ -33,10 +31,9 @@ all: @echo " -> INSTALL_DIR (for install and uninstall): sets the installation directory" @echo " -> INCL_PATH (for install, docs and test): the path to the directory with the headers, if you run make from another directory" @echo " -> DOC_DIR (for docs): the path to the build directory for the documentation" - @echo " -> JOB_COUNT (for test): the amount of threads to start" @echo " Current/default arguments: " @echo " CC=$(CC) CXXARGS=$(CXXARGS) LDARGS=$(LDARGS) EXTRA_CXX=$(EXTRA_CXX) EXTRA_LD=$(EXTRA_LD)" - @echo " BUILD_DIR=$(BUILD_DIR) BIN_DIR=$(BIN_DIR) TEST_DIR=$(TEST_DIR) INSTALL_DIR=$(INSTALL_DIR) INCL_PATH=$(INCL_PATH) DOC_DIR=$(DOC_DIR) JOB_COUNT=$(JOB_COUNT)" + @echo " BUILD_DIR=$(BUILD_DIR) BIN_DIR=$(BIN_DIR) TEST_DIR=$(TEST_DIR) INSTALL_DIR=$(INSTALL_DIR) INCL_PATH=$(INCL_PATH) DOC_DIR=$(DOC_DIR)" install: [ ! -d $(INSTALL_DIR) ] && mkdir -p $(INSTALL_DIR) @@ -50,10 +47,10 @@ docs: OUTDIR=$(DOC_DIR) INDIR=$(INCL_PATH) doxygen doxyfile.mk test: - make CC="$(CC)" OBJD="$(BUILD_DIR)" BIND="$(BIN_DIR)" SRCD="$(TEST_DIR)" CXXARGS="$(CXXARGS) $(EXTRA_CXX) -I$(INCL_PATH)" LDARGS="$(LDARGS) $(EXTRA_LD)" -C $(TEST_DIR)/.. -j $(JOB_COUNT) + make CC="$(CC)" OBJD="$(BUILD_DIR)" BIND="$(BIN_DIR)" SRCD="$(TEST_DIR)" CXXARGS="$(CXXARGS) $(EXTRA_CXX) -I$(INCL_PATH)" LDARGS="$(LDARGS) $(EXTRA_LD)" -C $(TEST_DIR)/.. clean: - rm -rf $(DOC_DIR)/* $(BUILD_DIR)/* $(BIN_DIR)/* - + rm -rf $(DOC_DIR)/* + cd $(TEST_DIR)/.. && make clean OBJD="$(BUILD_DIR)" BIND="$(BIN_DIR)" SRCD="$(TEST_DIR)" .PHONY: install uninstall test clean diff --git a/inc/fpgen.hpp b/inc/fpgen.hpp new file mode 100644 index 0000000..c3f2d15 --- /dev/null +++ b/inc/fpgen.hpp @@ -0,0 +1,6 @@ +#ifndef _FPGEN_MAIN +#define _FPGEN_MAIN + +#include "generator.hpp" + +#endif diff --git a/inc/generator.hpp b/inc/generator.hpp new file mode 100644 index 0000000..00d788a --- /dev/null +++ b/inc/generator.hpp @@ -0,0 +1,101 @@ +#ifndef _FPGEN_GENERATOR +#define _FPGEN_GENERATOR + +#ifdef __clang__ +#include +#include +namespace std { +using namespace experimental; +} +#else +#include +#include +#endif + +#include + +namespace fpgen { +#ifdef __clang__ +template +using enabler = + typename std::enable_if::value, bool>::type; + +template = true> class generator { +#else +template class generator { +#endif +public: + struct promise_type { + using value_type = T; + using except_type = std::exception_ptr; + using gen_type = generator; + using suspend_type = std::suspend_always; + + value_type value; + except_type ex; + + gen_type get_return_object() { return gen_type(*this); } + suspend_type initial_suspend() { return {}; } + suspend_type final_suspend() noexcept { return {}; } + void return_value(value_type v) { value = v; } + suspend_type yield_value(value_type v) { + value = v; + return {}; + } + + void unhandled_exception() { ex = std::current_exception(); } + }; + struct iterator_type { + using gen_t = generator; + using iter_t = iterator_type; + using value_t = T; + + gen_t &source; + bool is_finished; + value_t value; + + iterator_type(gen_t &source, bool is_finised) + : source{source}, is_finished{is_finised} { + if (!is_finised) + ++(*this); + } + iterator_type(gen_t &source, bool is_finished, value_t value) + : source{source}, is_finished{is_finished}, value{value} {} + + iter_t operator++() { + if (!source) + is_finished = true; + value = source(); + return {source, is_finished, value}; + } + operator bool() { return static_cast(source); } + bool operator!=(iter_t &other) { return is_finished != other.is_finished; } + value_t operator*() { return value; } + operator value_t() { return value; } + }; + + using handle_type = std::coroutine_handle; + using value_type = T; + + generator(promise_type &p) : _h{handle_type::from_promise(p)} {} + operator handle_type() const { return _h; } + operator bool() const { return !_h.done(); } + + iterator_type begin() { return {*this, false}; } + iterator_type end() { return {*this, true}; } + + value_type operator()() { + if (*this) + _h(); + if (_h.promise().ex) + std::rethrow_exception(_h.promise().ex); + return _h.promise().value; + } + +private: + handle_type _h; +}; + +} // namespace fpgen + +#endif diff --git a/test/Makefile b/test/Makefile index e69de29..4f1388c 100644 --- a/test/Makefile +++ b/test/Makefile @@ -0,0 +1,31 @@ +SOURCES=$(shell find $(SRCD) -name '*.cpp') +DEPS=$(SOURCES:$(SRCD)/%.cpp=$(OBJD)/%.d) +TESTS=generator + +all: dirs conan/conanbuildinfo.mak $(BIND)/generator + +-include $(DEPS) +-include conan/conanbuildinfo.mak +CXXXTRA=$(CONAN_INCLUDE_DIRS:%=-I%) $(CONAN_CXXFLAGS) +LDXTRA=$(CONAN_LIB_DIRS:%=-L%) $(CONAN_LIBS:%=-l%) $(CONAN_SYSTEM_LIBS:%=-l%) + +dirs: + @([ ! -d $(OBJD)/generator ] && mkdir -p $(OBJD)/generator) || true + +$(BIND)/generator: $(OBJD)/generator/test_generator.o + $(CC) $< $(LDXTRA) $(LDARGS) -o $@ + $(BIND)/generator + +$(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 + +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 diff --git a/test/conanfile.txt b/test/conanfile.txt index e69de29..021b071 100644 --- a/test/conanfile.txt +++ b/test/conanfile.txt @@ -0,0 +1,5 @@ +[requires] +gtest/cci.20210126 + +[generators] +make diff --git a/test/src/generator/test_generator.cpp b/test/src/generator/test_generator.cpp new file mode 100644 index 0000000..5396599 --- /dev/null +++ b/test/src/generator/test_generator.cpp @@ -0,0 +1,70 @@ +#include "generator.hpp" +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include + +fpgen::generator empty() { co_return 0; } + +fpgen::generator infinite() { + int value = 0; + while (true) { + co_yield value; + value++; + } +} + +fpgen::generator finite_squares(int min, int max) { + for (int val = min; val < max; val++) { + co_yield val *val; + } + co_return max *max; +} + +TEST(generator, accept_empty_gen) { + auto emptygen = empty(); + SUCCEED(); +} + +TEST(generator, iterator_empty_gen) { + auto gen = empty(); + gen(); + for (auto v : gen) { + FAIL(); + } + SUCCEED(); +} + +TEST(generator, can_call_and_continue) { + auto intgen = infinite(); + int value = intgen(); + EXPECT_EQ(0, value); + value = intgen(); + EXPECT_EQ(1, value); +} + +TEST(generator, can_while_over) { + int value; + int expect = 0; + auto intgen2 = finite_squares(0, 12); + while (intgen2) { + value = intgen2(); + EXPECT_THAT(expect, testing::AllOf(testing::Le(12), testing::Ge(0))); + EXPECT_EQ(expect * expect, value); + expect++; + } +} + +TEST(generator, iterator) { + int expect = -4; + auto intgen3 = finite_squares(-4, 8); + for (auto value : intgen3) { + EXPECT_THAT(expect, testing::AllOf(testing::Le(8), testing::Ge(-4))); + EXPECT_EQ(expect * expect, value); + expect++; + } +} + +int main(int argc, char **argv) { + testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +}