aboutsummaryrefslogtreecommitdiffstats
path: root/nihil.core
diff options
context:
space:
mode:
authorLexi Winter <lexi@le-fay.org>2025-07-02 05:49:47 +0100
committerLexi Winter <lexi@le-fay.org>2025-07-02 05:49:47 +0100
commitebe4cb0bdeabd06a31072547af47cacaab7f78c0 (patch)
tree65a81c2c86260b595107ee6c5505583f9afaf39d /nihil.core
parent5adeb648f74c1771164c0686d6e0fc584cf36d9e (diff)
downloadnihil-ebe4cb0bdeabd06a31072547af47cacaab7f78c0.tar.gz
nihil-ebe4cb0bdeabd06a31072547af47cacaab7f78c0.tar.bz2
replace nihil::generator
the new implementation is much simpler and PD-licensed. the only downside is it doesn't support elements_of. while here, move it to nihil.core.
Diffstat (limited to 'nihil.core')
-rw-r--r--nihil.core/CMakeLists.txt29
-rw-r--r--nihil.core/error.test.cc1
-rw-r--r--nihil.core/generator.ccm240
-rw-r--r--nihil.core/generator.test.cc109
-rw-r--r--nihil.core/nihil.core.ccm1
5 files changed, 379 insertions, 1 deletions
diff --git a/nihil.core/CMakeLists.txt b/nihil.core/CMakeLists.txt
index e9998a3..d0713ce 100644
--- a/nihil.core/CMakeLists.txt
+++ b/nihil.core/CMakeLists.txt
@@ -14,6 +14,7 @@ target_sources(nihil.core
error.ccm
features.ccm
flagset.ccm
+ generator.ccm
guard.ccm
match.ccm
monad.ccm
@@ -25,3 +26,31 @@ target_sources(nihil.core
tabulate.ccm
uuid.ccm
)
+
+if (NIHIL_TESTS)
+ add_executable(nihil.core.test
+ capture_stream.test.cc
+ ctype.test.cc
+ error.test.cc
+ flagset.test.cc
+ generator.test.cc
+ guard.test.cc
+ match.test.cc
+ monad.test.cc
+ next_word.test.cc
+ parse_size.test.cc
+ skipws.test.cc
+ tabulate.test.cc
+ )
+
+ target_link_libraries(nihil.core.test PRIVATE
+ nihil.std
+ nihil.core
+ Catch2::Catch2WithMain)
+
+ include(CTest)
+ include(Catch)
+ catch_discover_tests(nihil.core.test)
+
+ enable_testing()
+endif ()
diff --git a/nihil.core/error.test.cc b/nihil.core/error.test.cc
index f4ec1ee..7a923e4 100644
--- a/nihil.core/error.test.cc
+++ b/nihil.core/error.test.cc
@@ -4,7 +4,6 @@
import nihil.std;
import nihil.core;
-import nihil.util;
namespace {
inline constexpr auto *test_tags = "[nihil][nihil.error]";
diff --git a/nihil.core/generator.ccm b/nihil.core/generator.ccm
new file mode 100644
index 0000000..4d2bfe3
--- /dev/null
+++ b/nihil.core/generator.ccm
@@ -0,0 +1,240 @@
+// generator - Single-header, ranges-compatible generator type built
+// on C++20 coroutines
+// Written in 2021 by Sy Brand (tartanllama@gmail.com, @TartanLlama)
+//
+// Documentation available at https://tl.tartanllama.xyz/
+//
+// To the extent possible under law, the author(s) have dedicated all
+// copyright and related and neighboring rights to this software to the
+// public domain worldwide. This software is distributed without any warranty.
+//
+// You should have received a copy of the CC0 Public Domain Dedication
+// along with this software. If not, see
+// <http://creativecommons.org/publicdomain/zero/1.0/>.
+export module nihil.core:generator;
+
+import nihil.std;
+
+namespace nihil {
+
+export template <typename T>
+struct generator
+{
+private:
+ struct promise
+ {
+ using value_type = std::remove_reference_t<T>;
+ using reference_type =
+ std::conditional_t<std::is_pointer_v<value_type>, value_type, value_type &>;
+ using pointer_type =
+ std::conditional_t<std::is_pointer_v<value_type>, value_type, value_type *>;
+
+ promise() = default;
+
+ [[nodiscard]] auto get_return_object() -> generator
+ {
+ return generator(std::coroutine_handle<promise>::from_promise(*this));
+ }
+
+ [[nodiscard]] auto initial_suspend() const -> std::suspend_always
+ {
+ return {};
+ }
+
+ [[nodiscard]] auto final_suspend() const noexcept -> std::suspend_always
+ {
+ return {};
+ }
+
+ void return_void() const noexcept
+ {
+ }
+
+ void unhandled_exception() noexcept
+ {
+ exception_ = std::current_exception();
+ }
+
+ void rethrow_if_exception()
+ {
+ if (exception_) {
+ std::rethrow_exception(exception_);
+ }
+ }
+
+ std::suspend_always yield_value(value_type &&v) noexcept
+ {
+ value_ = std::move(v);
+ return {};
+ }
+
+ std::suspend_always yield_value(value_type const &v) noexcept
+ requires(!std::is_reference_v<T>)
+ {
+ value_ = v;
+ return {};
+ }
+
+ std::suspend_always yield_value(value_type &v) noexcept
+ requires(std::is_reference_v<T>)
+ {
+ value_ = &v;
+ return {};
+ }
+
+ std::exception_ptr exception_;
+ std::variant<std::monostate, value_type, value_type *> value_;
+ };
+
+public:
+ using promise_type = promise;
+ class sentinel
+ {
+ };
+
+ class iterator
+ {
+ using handle_type = std::coroutine_handle<promise_type>;
+
+ public:
+ using value_type = typename promise_type::value_type;
+ using reference_type = typename promise_type::reference_type;
+ using pointer_type = typename promise_type::pointer_type;
+ using difference_type = std::ptrdiff_t;
+
+ iterator() = default;
+ ~iterator()
+ {
+ if (handle_)
+ handle_.destroy();
+ }
+
+ // Non-copyable because coroutine handles point to a unique resource
+ iterator(iterator const &) = delete;
+ iterator(iterator &&rhs) noexcept
+ : handle_(std::exchange(rhs.handle_, nullptr))
+ {
+ }
+
+ auto operator=(iterator const &) -> iterator & = delete;
+
+ auto operator=(iterator &&rhs) noexcept -> iterator &
+ {
+ handle_ = std::exchange(rhs.handle_, nullptr);
+ return *this;
+ }
+
+ friend auto operator==(iterator const &it, sentinel) noexcept -> bool
+ {
+ return (!it.handle_ || it.handle_.done());
+ }
+
+ auto operator++() -> iterator &
+ {
+ handle_.resume();
+ if (handle_.done()) {
+ handle_.promise().rethrow_if_exception();
+ }
+ return *this;
+ }
+
+ void operator++(int)
+ {
+ (void)this->operator++();
+ }
+
+ reference_type operator*() const
+ noexcept(noexcept(std::is_nothrow_copy_constructible_v<reference_type>))
+ requires(std::is_reference_v<T>)
+ {
+ return *std::get<value_type *>(handle_.promise().value_);
+ }
+
+ reference_type operator*() const
+ noexcept(noexcept(std::is_nothrow_copy_constructible_v<reference_type>))
+ requires(!std::is_reference_v<T>)
+ {
+ return std::get<value_type>(handle_.promise().value_);
+ }
+
+ value_type *operator->() const
+ noexcept(noexcept(std::is_nothrow_copy_constructible_v<reference_type>))
+ requires(std::is_reference_v<T>)
+ {
+ return std::get<value_type *>(handle_.promise().value_);
+ }
+
+ value_type *operator->() const
+ noexcept(noexcept(std::is_nothrow_copy_constructible_v<reference_type>))
+ requires(!std::is_reference_v<T>)
+ {
+ return &std::get<value_type>(handle_.promise().value_);
+ }
+
+ private:
+ friend struct generator;
+
+ explicit iterator(handle_type handle)
+ : handle_(handle)
+ {
+ }
+
+ handle_type handle_;
+ };
+
+ using handle_type = std::coroutine_handle<promise_type>;
+
+ generator() noexcept = default;
+ ~generator()
+ {
+ if (handle_)
+ handle_.destroy();
+ }
+
+ generator(generator const &) = delete;
+
+ generator(generator &&rhs) noexcept
+ : handle_(std::exchange(rhs.handle_, nullptr))
+ {
+ }
+
+ auto operator=(generator const &) -> generator & = delete;
+
+ auto operator=(generator &&rhs) noexcept -> generator &
+ {
+ swap(rhs);
+ return *this;
+ }
+
+ auto begin() -> iterator
+ {
+ handle_.resume();
+ if (handle_.done()) {
+ handle_.promise().rethrow_if_exception();
+ }
+ return iterator(std::exchange(handle_, nullptr));
+ }
+
+ auto end() const noexcept -> sentinel
+ {
+ return {};
+ }
+
+ void swap(generator &other) noexcept
+ {
+ std::swap(handle_, other.handle_);
+ }
+
+private:
+ friend class iterator;
+ explicit generator(handle_type handle) noexcept
+ : handle_(handle)
+ {
+ }
+
+ handle_type handle_ = nullptr;
+};
+} // namespace nihil
+
+export template <typename T>
+inline constexpr bool std::ranges::enable_view<nihil::generator<T>> = true;
diff --git a/nihil.core/generator.test.cc b/nihil.core/generator.test.cc
new file mode 100644
index 0000000..1fc0f22
--- /dev/null
+++ b/nihil.core/generator.test.cc
@@ -0,0 +1,109 @@
+// This source code is released into the public domain.
+
+#include <catch2/catch_test_macros.hpp>
+
+import nihil.std;
+import nihil.core;
+
+namespace {
+inline auto constexpr test_tags = "[nihil][nihil.generator]";
+
+SCENARIO("A generator that yields values", test_tags)
+{
+ GIVEN ("A generator that yields values") {
+ auto fn = [&]() -> nihil::generator<int> {
+ co_yield 1;
+ co_yield 2;
+ };
+
+ THEN ("The generator yields the original values") {
+ REQUIRE(std::ranges::equal(fn(), std::vector{1, 2}));
+ }
+ }
+}
+
+SCENARIO("A generator that yields references", test_tags)
+{
+ GIVEN ("A generator that yields references") {
+ auto one = 1, two = 2;
+ auto fn = [&]() -> nihil::generator<int &> {
+ co_yield one;
+ co_yield two;
+ };
+ auto range = fn();
+
+ THEN ("The references refer to the original values") {
+ auto it = std::ranges::begin(range);
+ REQUIRE(&*it == &one);
+ ++it;
+ REQUIRE(&*it == &two);
+ ++it;
+ REQUIRE(it == std::ranges::end(range));
+ }
+ }
+}
+
+SCENARIO("A generator that yields pointers", test_tags)
+{
+ GIVEN ("A generator that yields pointers") {
+ auto one = 1, two = 2;
+ auto fn = [&]() -> nihil::generator<int *> {
+ co_yield &one;
+ co_yield &two;
+ };
+
+ THEN ("The pointers point to the original values") {
+ REQUIRE(std::ranges::equal(fn(), std::vector{&one, &two}));
+ }
+ }
+}
+
+SCENARIO("A generator that yields lvalues", test_tags)
+{
+ GIVEN ("A generator that yields pointers") {
+ auto one = 1, two = 2;
+ auto fn = [&]() -> nihil::generator<int> {
+ co_yield one;
+ co_yield two;
+ };
+
+ THEN ("The pointers point to the original values") {
+ REQUIRE(std::ranges::equal(fn(), std::vector{1, 2}));
+ }
+ }
+}
+
+TEST_CASE("generator: exceptions", "[generator]")
+{
+ auto fn = []() -> nihil::generator<int> {
+ co_yield 1;
+ throw std::runtime_error("test");
+ };
+
+ auto range = fn();
+ auto it = std::ranges::begin(range);
+ REQUIRE(*it == 1);
+ REQUIRE_THROWS_AS(it++, std::runtime_error);
+}
+
+#if 0
+// TODO: Re-enable this test once we have a standard-compliant generator.
+TEST_CASE("generator: elements_of", "[generator]")
+{
+ auto fn1 = [] -> nihil::generator<int> {
+ co_yield 1;
+ co_yield 2;
+ co_yield 3;
+ };
+
+ auto fn2 = [&fn1] -> nihil::generator<int> {
+ co_yield nihil::elements_of(fn1());
+ };
+
+ auto values = std::vector<int>();
+ std::ranges::copy(fn2(), std::back_inserter(values));
+
+ REQUIRE(values == std::vector{1, 2, 3});
+}
+#endif
+} // anonymous namespace
diff --git a/nihil.core/nihil.core.ccm b/nihil.core/nihil.core.ccm
index 37ad032..7634026 100644
--- a/nihil.core/nihil.core.ccm
+++ b/nihil.core/nihil.core.ccm
@@ -8,6 +8,7 @@ export import :errc;
export import :error;
export import :features;
export import :flagset;
+export import :generator;
export import :guard;
export import :match;
export import :monad;