Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 5 additions & 8 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand 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)
Expand All @@ -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
6 changes: 6 additions & 0 deletions inc/fpgen.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
#ifndef _FPGEN_MAIN
#define _FPGEN_MAIN

#include "generator.hpp"

#endif
101 changes: 101 additions & 0 deletions inc/generator.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
#ifndef _FPGEN_GENERATOR
#define _FPGEN_GENERATOR

#ifdef __clang__
#include <concepts>
#include <experimental/coroutine>
namespace std {
using namespace experimental;
}
#else
#include <concepts>
#include <coroutine>
#endif

#include <exception>

namespace fpgen {
#ifdef __clang__
template <typename T>
using enabler =
typename std::enable_if<std::is_copy_assignable<T>::value, bool>::type;

template <typename T, enabler<T> = true> class generator {
#else
template <std::copyable T> class generator {
#endif
public:
struct promise_type {
using value_type = T;
using except_type = std::exception_ptr;
using gen_type = generator<T>;
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<T>;
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<bool>(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<promise_type>;
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
31 changes: 31 additions & 0 deletions test/Makefile
Original file line number Diff line number Diff line change
@@ -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
5 changes: 5 additions & 0 deletions test/conanfile.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
[requires]
gtest/cci.20210126

[generators]
make
70 changes: 70 additions & 0 deletions test/src/generator/test_generator.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
#include "generator.hpp"
#include "gmock/gmock.h"
#include "gtest/gtest.h"
#include <iostream>

fpgen::generator<float> empty() { co_return 0; }

fpgen::generator<int> infinite() {
int value = 0;
while (true) {
co_yield value;
value++;
}
}

fpgen::generator<long> 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();
}