examples

Toy examples in single C files.
git clone git://henryandlizzy.uk/examples
Log | Files | Refs

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:
Asrc/coro-generator2.cpp | 301+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
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"; +}