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
12 changes: 12 additions & 0 deletions inc/__helpers.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
#ifndef _FPGEN___HELPERS
#define _FPGEN___HELPERS

#ifdef __clang__
#include <type_traits>
#define _FPGEN_USE_CONCEPTS 0
#else
#include <concepts>
#define _FPGEN_USE_CONCEPTS 1
#endif

#endif
1 change: 1 addition & 0 deletions inc/fpgen.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,6 @@
#define _FPGEN_MAIN

#include "generator.hpp"
#include "sources.hpp"

#endif
21 changes: 11 additions & 10 deletions inc/generator.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,29 +2,22 @@
#define _FPGEN_GENERATOR

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

#include "__helpers.hpp"
#include <exception>

/**
* @brief The namespace containing all of fpgen's code.
* \brief The namespace containing all of fpgen's code.
*/
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
#if _FPGEN_USE_CONCEPTS
/**
* \brief The main generator type.
*
Expand All @@ -39,6 +32,12 @@ template <typename T, enabler<T> = true> class generator {
* `std::copyable`, or on (older) CLang versions, `std::is_copy_assignable<T>`.
*/
template <std::copyable T> class generator {
#else
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 {
#endif
public:
/**
Expand Down Expand Up @@ -202,6 +201,8 @@ template <std::copyable T> class generator {
if (!source)
is_finished = true;
value = source();
if (!source)
is_finished = true;
return {source, is_finished, value};
}

Expand Down
106 changes: 106 additions & 0 deletions inc/sources.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
#ifndef _FPGEN_SOURCES
#define _FPGEN_SOURCES

#include "generator.hpp"
#include <iostream>
#include <iterator>

/**
* \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 T, typename... TArgs,
template <typename...> typename Container>
generator<T> from(const Container<T, TArgs...> &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 T, typename... TArgs,
template <typename...> typename Container>
generator<std::tuple<size_t, T>> enumerate(const Container<T, TArgs...> &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 TKey, typename TVal, typename... TArgs,
template <typename...> typename Container>
generator<std::tuple<TKey, TVal>>
from_tup(const Container<TKey, TVal, TArgs...> &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 <typename T> generator<T> inc(T start) {
T value = start;
while (true) {
co_yield value;
++value;
}
}

} // namespace fpgen

#endif
19 changes: 12 additions & 7 deletions test/Makefile
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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)/%)
Original file line number Diff line number Diff line change
Expand Up @@ -63,8 +63,3 @@ TEST(generator, iterator) {
expect++;
}
}

int main(int argc, char **argv) {
testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}
3 changes: 3 additions & 0 deletions test/src/test_main.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
#include "generator.hpp"
#include "gmock/gmock.h"
#include "gtest/gtest.h"
81 changes: 81 additions & 0 deletions test/src/test_sources.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
#include "generator.hpp"
#include "sources.hpp"
#include "gmock/gmock.h"
#include "gtest/gtest.h"

#include <map>
#include <set>
#include <string>
#include <vector>

TEST(sources, from_vector) {
std::vector<int> 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<std::string> srcs = {"key 1", "key 2", "key 3", "something"};
std::set<std::string> 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<char> 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<std::string, std::string> map = {{"key 1", "value 1"},
{"key 2", "value 2"},
{"key 3", "value 3"},
{"something", "else"}};
std::set<std::string> 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<int>(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<inc_struct>(v1);
for (int i = 0; i < 25; i++) {
EXPECT_EQ(gen().value, i);
}
}