commit a1208121a09689b2ea0905273ed0a5c3d759615e
parent 1c262923cbf5f8b63c1b096145a8e85e25d24813
Author: Henry Wilson <henry@henryandlizzy.uk>
Date: Fri, 22 Sep 2023 21:33:37 +0100
coro-generator2: Add alternative example of coroutine generators
Diffstat:
1 file changed, 301 insertions(+), 0 deletions(-)
diff --git a/src/coro-generator2.cpp b/src/coro-generator2.cpp
@@ -0,0 +1,301 @@
+#include <coroutine>
+#include <exception>
+#include <iostream>
+#include <optional>
+#include <utility>
+#include <cstdio>
+#include <unistd.h>
+
+auto exchange_default(auto& x)
+{
+ return std::exchange(x, {});
+}
+
+struct resume_null_coroutine : std::exception
+{
+ ~resume_null_coroutine() override = default;
+ char const* what() const noexcept override
+ {
+ return "resume_null_coroutine";
+ }
+};
+
+struct resume_finished_coroutine : std::exception
+{
+ ~resume_finished_coroutine() override = default;
+ char const* what() const noexcept override
+ {
+ return "resume_finished_coroutine";
+ }
+};
+
+template <typename T = void>
+struct [[nodiscard]] coroutine_owner
+{
+ coroutine_owner() = default;
+
+ template <typename U>
+ explicit coroutine_owner(std::coroutine_handle<U> h) noexcept
+ : h{h}
+ {}
+
+ coroutine_owner(coroutine_owner const&) = delete;
+ coroutine_owner& operator =(coroutine_owner const&) = delete;
+
+ template <typename U>
+ coroutine_owner(coroutine_owner<U>&& old) noexcept
+ : h{old.release()}
+ {}
+
+ coroutine_owner& operator =(coroutine_owner&& old)
+ {
+ cancel();
+ h = old.release();
+ return *this;
+ }
+
+ ~coroutine_owner() noexcept
+ try {
+ cancel();
+ } catch (...)
+ {}
+
+ void cancel()
+ {
+ if (auto x = release())
+ x.destroy();
+ }
+
+ [[nodiscard]]
+ std::coroutine_handle<T> get() noexcept
+ {
+ return h;
+ }
+
+ [[nodiscard]]
+ std::coroutine_handle<T> release() noexcept
+ {
+ return exchange_default(h);
+ }
+
+ [[nodiscard]]
+ std::coroutine_handle<T>const* operator->() const noexcept
+ {
+ return &h;
+ }
+
+ [[nodiscard]]
+ std::coroutine_handle<T>* operator->() noexcept
+ {
+ return &h;
+ }
+
+ void resume()
+ {
+ if (not h)
+ throw resume_null_coroutine{};
+ if (h.done())
+ throw resume_finished_coroutine{};
+ h.resume();
+ }
+
+ void operator() ()
+ {
+ resume();
+ }
+
+private:
+ std::coroutine_handle<T> h;
+};
+
+template <typename T, typename U = T>
+coroutine_owner<U> make_coroutine_owner(T& promise) noexcept
+{
+ return coroutine_owner<U>{std::coroutine_handle<T>::from_promise(promise)};
+}
+
+struct yield_blocked : std::exception
+{
+ ~yield_blocked() override = default;
+ char const* what() const noexcept override
+ {
+ return "yield_blocked";
+ }
+};
+
+struct symmetric_transfer : std::suspend_always
+{
+ std::coroutine_handle<> await_suspend(std::coroutine_handle<>) noexcept
+ {
+ return std::exchange(waiting, std::noop_coroutine());
+ }
+ std::coroutine_handle<>& waiting;
+};
+
+template <typename T>
+struct [[nodiscard]] generator
+{
+ struct promise_type
+ {
+ promise_type() = default;
+ ~promise_type() noexcept
+ try {
+ if (ep)
+ std::rethrow_exception(ep);
+ } catch (std::exception const& e)
+ {
+ std::cerr << "Dropped exception '" << e.what() << "'\n";
+ } catch (...)
+ {
+ std::cerr << "Dropped unknown exception type\n";
+ }
+
+ auto get_return_object() { return generator{*this}; }
+ auto initial_suspend() const noexcept { return std::suspend_always{}; }
+ auto yield_value(T v)
+ {
+ return_value(std::move(v));
+ return symmetric_transfer{{}, waiting};
+ }
+ void return_value(T v)
+ {
+ if (yielded_value)
+ throw yield_blocked{};
+ yielded_value = std::move(v);
+ }
+ void unhandled_exception() noexcept { ep = std::current_exception(); }
+ auto final_suspend() noexcept { return symmetric_transfer{{}, waiting}; }
+
+ friend struct generator;
+ private:
+ std::optional<T> yielded_value;
+ std::exception_ptr ep;
+ std::coroutine_handle<> waiting = std::noop_coroutine();
+ };
+
+ struct [[nodiscard]] awaitable
+ {
+ bool await_ready()
+ {
+ return !!coro->promise().yielded_value;
+ }
+ std::coroutine_handle<> await_suspend(std::coroutine_handle<> h)
+ {
+ coro->promise().waiting = h;
+ return coro.get();
+ }
+ T await_resume()
+ {
+ if (auto ep = exchange_default(coro->promise().ep))
+ std::rethrow_exception(ep);
+ return exchange_default(coro->promise().yielded_value).value();
+ }
+ coroutine_owner<promise_type>& coro;
+ };
+
+ awaitable get_awaitable() { return {coro}; }
+
+ T generate()
+ {
+ auto& promise = coro->promise();
+ if (not promise.yielded_value)
+ {
+ coro.resume();
+ if (auto ep = exchange_default(promise.ep))
+ std::rethrow_exception(ep);
+ }
+ return exchange_default(promise.yielded_value).value();
+ }
+
+ T operator() () { return generate(); }
+
+private:
+ explicit generator(promise_type& p)
+ : coro{make_coroutine_owner<promise_type>(p)}
+ {}
+
+ coroutine_owner<promise_type> coro;
+};
+
+generator<unsigned char> fd_charize(int fd)
+{
+ for (;;)
+ {
+ unsigned char buf[1024];
+ ssize_t n = ::read(fd, buf, sizeof buf);
+
+ if (n < 0)
+ throw std::runtime_error{"read() failed"};
+ else if (not n)
+ co_return '\0';
+
+ for (ssize_t i = 0; i < n; ++i)
+ if (buf[i])
+ co_yield buf[i];
+ else
+ throw std::runtime_error{"null in char stream"};
+ }
+}
+
+generator<std::string> tokenize(generator<unsigned char>& chars, int(*is_delimiter)(int))
+{
+ std::string rval;
+
+ while (auto c = chars.generate())
+ {
+ while (is_delimiter(c))
+ if (not (c = chars.generate()))
+ goto empty;
+
+ while (not is_delimiter(c))
+ {
+ rval.push_back(c);
+ if (not (c = chars.generate()))
+ goto empty;
+ }
+
+ co_yield std::move(rval);
+ rval.clear();
+ }
+empty:
+ if (not rval.empty())
+ co_yield std::move(rval);
+ co_return {};
+}
+
+generator<std::string> tokenize2(generator<unsigned char>& chars, int(*is_delimiter)(int))
+{
+ std::string rval;
+
+ auto a = chars.get_awaitable();
+
+ while (auto c = co_await a)
+ {
+ while (is_delimiter(c))
+ if (not (c = co_await a))
+ goto empty;
+
+ while (not is_delimiter(c))
+ {
+ rval.push_back(c);
+ if (not (c = co_await a))
+ goto empty;
+ }
+
+ co_yield std::move(rval);
+ rval.clear();
+ }
+empty:
+ if (not rval.empty())
+ co_yield std::move(rval);
+ co_return {};
+}
+
+int main()
+{
+ std::cerr << std::boolalpha;
+ auto chars = fd_charize(0);
+ auto strings = tokenize2(chars, std::isspace);
+ for (auto str = strings.generate(); not str.empty(); str = strings.generate())
+ std::cout << '[' << str << "]\n";
+}