diff --git a/README.md b/README.md index 8441496..3c58236 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-97%25-brightgreen) +![](https://img.shields.io/badge/test_coverage-96.9%25-brightgreen) ## Aim diff --git a/inc/aggregators.hpp b/inc/aggregators.hpp index 8297dd9..8f2b4ad 100644 --- a/inc/aggregators.hpp +++ b/inc/aggregators.hpp @@ -3,6 +3,7 @@ #include "generator.hpp" #include +#include #include #include @@ -200,6 +201,94 @@ template void foreach (generator gen, Fun func) { func(gen()); } } + +/** + * \brief Sends each value to the stream. + * + * Calls `stream << value` for each value in the generator, then returns the + * resulting (modified) stream. + * + * \tparam T The type of values in the stream. + * \param[in,out] gen The generator supplying the values. + * \param[in,out] stream The stream to output to. + * \returns The resulting stream. + */ +template +std::ostream &to_stream(generator gen, std::ostream &stream) { + while (gen) { + stream << gen(); + } + return stream; +} + +/** + * \brief Sends each value to the stream. Values are separated by the given + * separator. + * + * Calls `stream << value` for each value in the generator, then returns the + * resulting (modified) stream. Between each pair of values in the generator, + * the separator is added. + * + * \tparam T The type of values in the stream. + * \tparam T2 The type of the separator. + * \param[in,out] gen The generator supplying the values. + * \param[in,out] stream The stream to output to. + * \param[in] separator The separator to use. + * \returns The resulting stream. + */ +template +std::ostream &to_stream(generator gen, std::ostream &stream, T2 separator) { + if (gen) { + stream << gen(); + } + while (gen) { + stream << separator << gen(); + } + return stream; +} + +/** + * \brief Sends each value to the stream on a separate line. + * + * Calls `stream << value << std::endl` for each value in the generator, then + * returns the resulting (modified) stream. To avoid a trailing newline, see + * `fpgen::to_lines_no_trail` + * + * \tparam T The type of values in the stream. + * \param[in,out] gen The generator supplying the values. + * \param[in,out] stream The stream to output to. + * \returns The resulting stream. + */ +template +std::ostream &to_lines(generator gen, std::ostream &stream) { + while (gen) { + stream << gen() << std::endl; + } + return stream; +} + +/** + * \brief Sends each value to the stream on a separate line, without trailing + * newline. + * + * Calls `stream << std::endl << value` for each value in the generator (except + * the first), then returns the resulting (modified) stream. + * + * \tparam T The type of values in the stream. + * \param[in,out] gen The generator supplying the values. + * \param[in,out] stream The stream to output to. + * \returns The resulting stream. + */ +template +std::ostream &to_lines_no_trail(generator gen, std::ostream &stream) { + if (gen) { + stream << gen(); + } + while (gen) { + stream << std::endl << gen(); + } + return stream; +} } // namespace fpgen #endif diff --git a/inc/sources.hpp b/inc/sources.hpp index e3fb577..fa68ba1 100644 --- a/inc/sources.hpp +++ b/inc/sources.hpp @@ -2,7 +2,9 @@ #define _FPGEN_SOURCES #include "generator.hpp" +#include #include +#include /** * \brief The namespace containing all of fpgen's code. @@ -103,6 +105,49 @@ template generator inc(T start) { } } +/** + * \brief Creates a generator from an input stream. + * + * The generator will apply the given function on the stream each time the + * generator is invoked. The function should return a value of the type you want + * in the generator. Once the stream fails (`!stream.good()`) or the stream + * reaches EOF, the generator stops. Trailing whitespace may result in + * unpredictable behaviour. Since the stream isn't copied, using the generator + * after the stream goes out of scope is undefined behaviour. + * + * \tparam Fun The type of the function. Should have the signature + * (std::istream &) -> T. + * \param[in,out] stream The input stream. + * \param[in] func The function to extract data from the stream. + * \returns A new generator which will iterate over the given stream, each time + * applying the given function to retrieve the next value. + */ +template +generator::type> +from_stream(std::istream &stream, Fun func) { + while (stream.good() && !stream.eof()) { + co_yield func(stream); + } + co_return; +} + +/** + * \brief Creates a generator supplying each line from a file. + * + * This generator is formed by combining `fpgen::from_stream<>` with + * `std::getline` on the stream. + * + * \param[in,out] stream The stream to extract lines from. + * \returns A new generator which yields each line in the stream. + */ +inline generator from_lines(std::istream &stream) { + return from_stream(stream, [](std::istream &stream) { + std::string value; + std::getline(stream, value); + return value; + }); +} + } // namespace fpgen #endif diff --git a/test/Makefile b/test/Makefile index 731ae90..691197b 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 chain +TESTS=generator sources manip aggreg chain TESTOBJ=$(TESTS:%=$(OBJD)/test_%.o) CONAN_PKG_OVERRIDE=gtest diff --git a/test/src/test_aggreg.cpp b/test/src/test_aggreg.cpp index 259163e..a5a9954 100644 --- a/test/src/test_aggreg.cpp +++ b/test/src/test_aggreg.cpp @@ -6,6 +6,7 @@ #include "gtest/gtest.h" #include +#include #include fpgen::generator a_empty() { co_return; } @@ -170,3 +171,40 @@ TEST(foreach, normal) { fpgen::foreach (gen, [&res](size_t val) { res += val; }); EXPECT_EQ(res, fpgen::sum(gen2)); } + +TEST(stream, nosep) { + std::vector vals = {1, 2, 3, 4, 5, 6}; + auto gen = fpgen::from(vals); + std::stringstream strm; + fpgen::to_stream(gen, strm); + EXPECT_EQ(strm.str(), "123456"); +} + +TEST(stream, sep) { + std::vector vals = {1, 2, 3, 4, 5, 6, 7}; + auto gen = fpgen::from(vals); + std::stringstream strm; + fpgen::to_stream(gen, strm, " "); + EXPECT_EQ(strm.str(), "1 2 3 4 5 6 7"); +} + +TEST(stream, lines_trail) { + std::vector vals = {1, 2, 3, 4}; + auto gen = fpgen::from(vals); + std::stringstream strm; + std::stringstream expect; + for (auto v : vals) + expect << v << std::endl; + fpgen::to_lines(gen, strm); + EXPECT_EQ(strm.str(), expect.str()); +} + +TEST(stream, lines_no_trail) { + std::vector vals = {1, 2, 3, 4}; + auto gen = fpgen::from(vals); + std::stringstream strm; + std::stringstream expect; + expect << 1 << std::endl << 2 << std::endl << 3 << std::endl << 4; + fpgen::to_lines_no_trail(gen, strm); + EXPECT_EQ(strm.str(), expect.str()); +} diff --git a/test/src/test_sources.cpp b/test/src/test_sources.cpp index 0322130..f7ac4d0 100644 --- a/test/src/test_sources.cpp +++ b/test/src/test_sources.cpp @@ -5,6 +5,7 @@ #include #include +#include #include #include @@ -79,3 +80,74 @@ TEST(sources, incrementable_struct) { EXPECT_EQ(gen().value, i); } } + +TEST(sources, instream) { + std::vector numbers = {1, 2, 3, 4, 5}; + std::stringstream str; + for (auto v : numbers) + str << " " << v; + auto func = [](std::istream &strm) { + int i; + strm >> i; + return i; + }; + + auto gen = fpgen::from_stream(str, func); + for (int i = 0; i < 5; i++) { + bool genstate = gen; + EXPECT_TRUE(genstate); + auto tmp = gen(); + EXPECT_EQ(tmp, numbers[i]); + std::cout << i << "," << tmp << std::endl; + } + + bool genstate = gen; + EXPECT_FALSE(genstate); +} + +TEST(sources, lipsum_lines) { + std::string lipsum = R"( +Lorem ipsum dolor sit amet, consectetur adipiscing elit. Quisque diam magna, +laoreet non dictum eget, scelerisque eu nibh. Cras luctus purus sit amet +sodales aliquet. Proin vulputate risus quam. Curabitur ultricies, elit nec +pharetra accumsan, leo eros mollis nibh, pulvinar lobortis dolor diam non quam. +Vivamus odio arcu, aliquet ornare leo quis, mollis porta nisl. Mauris malesuada +semper efficitur. Vestibulum nulla diam, hendrerit in diam a, tempor dignissim +turpis. Maecenas eleifend laoreet velit id semper. Aliquam quis mattis enim. +Cras gravida, felis vitae porta auctor, magna purus aliquet lorem, ut maximus +tortor tortor sit amet mauris. Mauris eleifend enim eget arcu blandit auctor. +Etiam vel porta augue. Maecenas volutpat odio in lacus sagittis fermentum. +)"; + std::string lines[] = {"Lorem ipsum dolor sit amet, consectetur adipiscing " + "elit. Quisque diam magna,", + "laoreet non dictum eget, scelerisque eu nibh. Cras " + "luctus purus sit amet", + "sodales aliquet. Proin vulputate risus quam. " + "Curabitur ultricies, elit nec", + "pharetra accumsan, leo eros mollis nibh, pulvinar " + "lobortis dolor diam non quam.", + "Vivamus odio arcu, aliquet ornare leo quis, mollis " + "porta nisl. Mauris malesuada", + "semper efficitur. Vestibulum nulla diam, hendrerit " + "in diam a, tempor dignissim", + "turpis. Maecenas eleifend laoreet velit id semper. " + "Aliquam quis mattis enim.", + "Cras gravida, felis vitae porta auctor, magna purus " + "aliquet lorem, ut maximus", + "tortor tortor sit amet mauris. Mauris eleifend enim " + "eget arcu blandit auctor.", + "Etiam vel porta augue. Maecenas volutpat odio in " + "lacus sagittis fermentum."}; + std::stringstream strm; + strm << lipsum; + auto gen = fpgen::from_lines(strm); + EXPECT_EQ("", gen()); + for (size_t i = 0; i < 10; i++) { + bool gens = gen; + EXPECT_TRUE(gens); + EXPECT_EQ(gen(), lines[i]); + } + EXPECT_EQ(gen(), ""); + bool gens = gen; + EXPECT_FALSE(gens); +} diff --git a/test/src/test_stream.cpp b/test/src/test_stream.cpp deleted file mode 100644 index d265778..0000000 --- a/test/src/test_stream.cpp +++ /dev/null @@ -1,30 +0,0 @@ -#include "aggregators.hpp" -#include "generator.hpp" -#include "manipulators.hpp" -#include "sources.hpp" -#include "stream.hpp" -#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, 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); -}