examples

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

coro-generator2.cpp (5882B)


      1 #include <coroutine>
      2 #include <exception>
      3 #include <iostream>
      4 #include <optional>
      5 #include <utility>
      6 #include <cstdio>
      7 #include <unistd.h>
      8 
      9 auto exchange_default(auto& x)
     10 {
     11 	return std::exchange(x, {});
     12 }
     13 
     14 struct resume_null_coroutine : std::exception
     15 {
     16 	~resume_null_coroutine() override = default;
     17 	char const* what() const noexcept override
     18 	{
     19 		return "resume_null_coroutine";
     20 	}
     21 };
     22 
     23 struct resume_finished_coroutine : std::exception
     24 {
     25 	~resume_finished_coroutine() override = default;
     26 	char const* what() const noexcept override
     27 	{
     28 		return "resume_finished_coroutine";
     29 	}
     30 };
     31 
     32 template <typename T = void>
     33 struct [[nodiscard]] coroutine_owner
     34 {
     35 	coroutine_owner() = default;
     36 
     37 	template <typename U>
     38 	explicit coroutine_owner(std::coroutine_handle<U> h) noexcept
     39 	:   h{h}
     40 	{}
     41 
     42 	coroutine_owner(coroutine_owner const&) = delete;
     43 	coroutine_owner& operator =(coroutine_owner const&) = delete;
     44 
     45 	template <typename U>
     46 	coroutine_owner(coroutine_owner<U>&& old) noexcept
     47 	:   h{old.release()}
     48 	{}
     49 
     50 	coroutine_owner& operator =(coroutine_owner&& old)
     51 	{
     52 		cancel();
     53 		h = old.release();
     54 		return *this;
     55 	}
     56 
     57 	~coroutine_owner() noexcept
     58 	try {
     59 		cancel();
     60 	} catch (...)
     61 	{}
     62 
     63 	void cancel()
     64 	{
     65 		if (auto x = release())
     66 			x.destroy();
     67 	}
     68 
     69 	[[nodiscard]]
     70 	std::coroutine_handle<T> get() noexcept
     71 	{
     72 		return h;
     73 	}
     74 
     75 	[[nodiscard]]
     76 	std::coroutine_handle<T> release() noexcept
     77 	{
     78 		return exchange_default(h);
     79 	}
     80 
     81 	[[nodiscard]]
     82 	std::coroutine_handle<T>const* operator->() const noexcept
     83 	{
     84 		return &h;
     85 	}
     86 
     87 	[[nodiscard]]
     88 	std::coroutine_handle<T>* operator->() noexcept
     89 	{
     90 		return &h;
     91 	}
     92 
     93 	void resume()
     94 	{
     95 		if (not h)
     96 			throw resume_null_coroutine{};
     97 		if (h.done())
     98 			throw resume_finished_coroutine{};
     99 		h.resume();
    100 	}
    101 
    102 	void operator() ()
    103 	{
    104 		resume();
    105 	}
    106 
    107 private:
    108 	std::coroutine_handle<T> h;
    109 };
    110 
    111 template <typename T, typename U = T>
    112 coroutine_owner<U> make_coroutine_owner(T& promise) noexcept
    113 {
    114  	return coroutine_owner<U>{std::coroutine_handle<T>::from_promise(promise)};
    115 }
    116 
    117 struct yield_blocked : std::exception
    118 {
    119 	~yield_blocked() override = default;
    120 	char const* what() const noexcept override
    121 	{
    122 		return "yield_blocked";
    123 	}
    124 };
    125 
    126 struct symmetric_transfer : std::suspend_always
    127 {
    128 	std::coroutine_handle<> await_suspend(std::coroutine_handle<>) noexcept
    129 	{
    130 		return std::exchange(waiting, std::noop_coroutine());
    131 	}
    132 	std::coroutine_handle<>& waiting;
    133 };
    134 
    135 template <typename T>
    136 struct [[nodiscard]] generator
    137 {
    138 	struct promise_type
    139 	{
    140 		promise_type() = default;
    141 		~promise_type() noexcept
    142 		try {
    143 			if (ep)
    144 				std::rethrow_exception(ep);
    145 		} catch (std::exception const& e)
    146 		{
    147 			std::cerr << "Dropped exception '" << e.what() << "'\n";
    148 		} catch (...)
    149 		{
    150 			std::cerr << "Dropped unknown exception type\n";
    151 		}
    152 
    153 		auto get_return_object() { return generator{*this}; }
    154 		auto initial_suspend() const noexcept { return std::suspend_always{}; }
    155 		auto yield_value(T v)
    156 		{
    157 			return_value(std::move(v));
    158 			return symmetric_transfer{{}, waiting};
    159 		}
    160 		void return_value(T v)
    161 		{
    162 			if (yielded_value)
    163 				throw yield_blocked{};
    164 			yielded_value = std::move(v);
    165 		}
    166 		void unhandled_exception() noexcept { ep = std::current_exception(); }
    167 		auto final_suspend() noexcept { return symmetric_transfer{{}, waiting}; }
    168 
    169 		friend struct generator;
    170 	private:
    171 		std::optional<T> yielded_value;
    172 		std::exception_ptr ep;
    173 		std::coroutine_handle<> waiting = std::noop_coroutine();
    174 	};
    175 
    176 	struct [[nodiscard]] awaitable
    177 	{
    178 		bool await_ready()
    179 		{
    180 			return !!coro->promise().yielded_value;
    181 		}
    182 		std::coroutine_handle<> await_suspend(std::coroutine_handle<> h)
    183 		{
    184 			coro->promise().waiting = h;
    185 			return coro.get();
    186 		}
    187 		T await_resume()
    188 		{
    189 			if (auto ep = exchange_default(coro->promise().ep))
    190 				std::rethrow_exception(ep);
    191 			return exchange_default(coro->promise().yielded_value).value();
    192 		}
    193 		coroutine_owner<promise_type>& coro;
    194 	};
    195 
    196 	awaitable get_awaitable() { return {coro}; }
    197 
    198 	T generate()
    199 	{
    200 		auto& promise = coro->promise();
    201 		if (not promise.yielded_value)
    202 		{
    203 			coro.resume();
    204 			if (auto ep = exchange_default(promise.ep))
    205 				std::rethrow_exception(ep);
    206 		}
    207 		return exchange_default(promise.yielded_value).value();
    208 	}
    209 
    210 	T operator() () { return generate(); }
    211 
    212 private:
    213 	explicit generator(promise_type& p)
    214 	:   coro{make_coroutine_owner<promise_type>(p)}
    215 	{}
    216 
    217 	coroutine_owner<promise_type> coro;
    218 };
    219 
    220 generator<unsigned char> fd_charize(int fd)
    221 {
    222 	for (;;)
    223 	{
    224 		unsigned char buf[1024];
    225 		ssize_t n = ::read(fd, buf, sizeof buf);
    226 
    227 		if (n < 0)
    228 			throw std::runtime_error{"read() failed"};
    229 		else if (not n)
    230 			co_return '\0';
    231 
    232 		for (ssize_t i = 0; i < n; ++i)
    233 			if (buf[i])
    234 				co_yield buf[i];
    235 			else
    236 				throw std::runtime_error{"null in char stream"};
    237 	}
    238 }
    239 
    240 generator<std::string> tokenize(generator<unsigned char>& chars, int(*is_delimiter)(int))
    241 {
    242 	std::string rval;
    243 
    244 	while (auto c = chars.generate())
    245 	{
    246 		while (is_delimiter(c))
    247 			if (not (c = chars.generate()))
    248 				goto empty;
    249 
    250 		while (not is_delimiter(c))
    251 		{
    252 			rval.push_back(c);
    253 			if (not (c = chars.generate()))
    254 				goto empty;
    255 		}
    256 
    257 		co_yield std::move(rval);
    258 		rval.clear();
    259 	}
    260 empty:
    261 	if (not rval.empty())
    262 		co_yield std::move(rval);
    263 	co_return {};
    264 }
    265 
    266 generator<std::string> tokenize2(generator<unsigned char>& chars, int(*is_delimiter)(int))
    267 {
    268 	std::string rval;
    269 
    270 	auto a = chars.get_awaitable();
    271 
    272 	while (auto c = co_await a)
    273 	{
    274 		while (is_delimiter(c))
    275 			if (not (c = co_await a))
    276 				goto empty;
    277 
    278 		while (not is_delimiter(c))
    279 		{
    280 			rval.push_back(c);
    281 			if (not (c = co_await a))
    282 				goto empty;
    283 		}
    284 
    285 		co_yield std::move(rval);
    286 		rval.clear();
    287 	}
    288 empty:
    289 	if (not rval.empty())
    290 		co_yield std::move(rval);
    291 	co_return {};
    292 }
    293 
    294 int main()
    295 {
    296 	std::cerr << std::boolalpha;
    297 	auto chars = fd_charize(0);
    298 	auto strings = tokenize2(chars, std::isspace);
    299 	for (auto str = strings.generate(); not str.empty(); str = strings.generate())
    300 		std::cout << '[' << str << "]\n";
    301 }