mirror of https://github.com/rollbear/trompeloeil
You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
577 lines
10 KiB
C++
577 lines
10 KiB
C++
/*
|
|
* Trompeloeil C++ mocking framework
|
|
*
|
|
* Copyright (C) Björn Fahller
|
|
*
|
|
* Use, modification and distribution is subject to the
|
|
* Boost Software License, Version 1.0. (See accompanying
|
|
* file LICENSE_1_0.txt or copy atl
|
|
* http://www.boost.org/LICENSE_1_0.txt)
|
|
*
|
|
* Project home: https://github.com/rollbear/trompeloeil
|
|
*/
|
|
|
|
#ifndef TROMPELOEIL_MICRO_CORO_HPP
|
|
#define TROMPELOEIL_MICRO_CORO_HPP
|
|
|
|
#include <coroutine>
|
|
#include <variant>
|
|
#include <exception>
|
|
#include <cstdlib>
|
|
|
|
namespace coro
|
|
{
|
|
template<typename>
|
|
struct task;
|
|
|
|
template<typename T>
|
|
struct promise
|
|
{
|
|
promise() = default;
|
|
|
|
promise &operator=(promise &&) = delete;
|
|
|
|
std::suspend_never
|
|
initial_suspend()
|
|
const
|
|
noexcept
|
|
{
|
|
return {};
|
|
}
|
|
|
|
std::suspend_always
|
|
final_suspend()
|
|
const
|
|
noexcept
|
|
{
|
|
return {};
|
|
}
|
|
|
|
template<typename P>
|
|
std::coroutine_handle<>
|
|
await_suspend(std::coroutine_handle <P> p)
|
|
noexcept
|
|
{
|
|
if (p.promise().continuation_)
|
|
{
|
|
return p.promise().continuation_;
|
|
}
|
|
return std::noop_coroutine();
|
|
}
|
|
|
|
task<T>
|
|
get_return_object()
|
|
noexcept;
|
|
|
|
bool has_data() const
|
|
{
|
|
return !std::holds_alternative<std::monostate>(data_);
|
|
}
|
|
|
|
void
|
|
return_value(T t)
|
|
{
|
|
data_.template emplace<data>(std::move(t));
|
|
if (continuation_)
|
|
{
|
|
continuation_.resume();
|
|
}
|
|
}
|
|
|
|
auto
|
|
yield_value(T t)
|
|
{
|
|
data_.template emplace<data>(std::move(t));
|
|
if (continuation_)
|
|
{
|
|
continuation_.resume();
|
|
}
|
|
return std::suspend_always{};
|
|
}
|
|
|
|
void
|
|
unhandled_exception()
|
|
noexcept
|
|
{
|
|
data_.template emplace<std::exception_ptr>(std::current_exception());
|
|
}
|
|
|
|
T
|
|
result()
|
|
{
|
|
struct cleaner_ {
|
|
promise* p;
|
|
~cleaner_() { p->data_.template emplace<std::monostate>();}
|
|
};
|
|
cleaner_ c{this};
|
|
if (auto v = std::get_if<data>(&data_))
|
|
{
|
|
return std::move(v->value);
|
|
}
|
|
else if (auto e = std::get_if<std::exception_ptr>(&data_))
|
|
{
|
|
std::rethrow_exception(*e);
|
|
}
|
|
else
|
|
{
|
|
abort();
|
|
}
|
|
}
|
|
|
|
void
|
|
continuation(std::coroutine_handle<> next)
|
|
noexcept
|
|
{
|
|
continuation_ = next;
|
|
}
|
|
private:
|
|
struct data
|
|
{
|
|
data(T&& t) : value(std::move(t)) {}
|
|
T value;
|
|
};
|
|
std::variant <std::monostate, data, std::exception_ptr> data_;
|
|
std::coroutine_handle<> continuation_{nullptr};
|
|
};
|
|
|
|
template<>
|
|
struct promise<void>
|
|
{
|
|
promise() = default;
|
|
promise &operator=(promise &&) = delete;
|
|
|
|
std::suspend_never
|
|
initial_suspend()
|
|
const
|
|
noexcept
|
|
{
|
|
return {};
|
|
}
|
|
|
|
std::suspend_always
|
|
final_suspend()
|
|
const
|
|
noexcept
|
|
{
|
|
return {};
|
|
}
|
|
|
|
template<typename P>
|
|
std::coroutine_handle<>
|
|
await_suspend(std::coroutine_handle <P> p)
|
|
noexcept
|
|
{
|
|
if (p.promise().continuation_)
|
|
{
|
|
return p.promise().continuation_;
|
|
}
|
|
return std::noop_coroutine();
|
|
}
|
|
|
|
task<void>
|
|
get_return_object()
|
|
noexcept;
|
|
|
|
bool
|
|
has_data()
|
|
const
|
|
{
|
|
return has_data_;
|
|
}
|
|
void
|
|
return_void()
|
|
{
|
|
has_data_ = true;
|
|
exception_ = nullptr;
|
|
}
|
|
|
|
void
|
|
unhandled_exception()
|
|
noexcept
|
|
{
|
|
has_data_ = true;
|
|
exception_ = std::current_exception();
|
|
}
|
|
|
|
void
|
|
result()
|
|
{
|
|
has_data_ = false;
|
|
if (exception_)
|
|
{
|
|
std::rethrow_exception(exception_);
|
|
}
|
|
}
|
|
|
|
void
|
|
continuation(std::coroutine_handle<> next)
|
|
noexcept
|
|
{
|
|
continuation_ = next;
|
|
}
|
|
private:
|
|
std::exception_ptr exception_ = nullptr;
|
|
std::coroutine_handle<> continuation_{nullptr};
|
|
bool has_data_ = false;
|
|
};
|
|
|
|
template<typename T>
|
|
struct task {
|
|
using promise_type = coro::promise<T>;
|
|
|
|
explicit task(std::coroutine_handle <promise_type> h = nullptr) : coroutine_(h) {}
|
|
|
|
task(task &&h) : coroutine_(std::exchange(h.coroutine_, nullptr)) {}
|
|
|
|
~task() {
|
|
if (coroutine_) {
|
|
coroutine_.destroy();
|
|
}
|
|
}
|
|
|
|
bool
|
|
is_ready()
|
|
const
|
|
noexcept
|
|
{
|
|
return !coroutine_ || coroutine_.done();
|
|
}
|
|
|
|
bool
|
|
resume()
|
|
{
|
|
if (!coroutine_.done())
|
|
{
|
|
coroutine_.resume();
|
|
}
|
|
return !coroutine_.done();
|
|
}
|
|
|
|
bool
|
|
destroy()
|
|
{
|
|
if (coroutine_)
|
|
{
|
|
coroutine_.destroy();
|
|
coroutine_ = nullptr;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
struct awaitable_base
|
|
{
|
|
bool
|
|
await_ready()
|
|
const
|
|
noexcept
|
|
{
|
|
return coroutine_.promise().has_data();
|
|
}
|
|
|
|
std::coroutine_handle<>
|
|
await_suspend(std::coroutine_handle<> next)
|
|
noexcept
|
|
{
|
|
coroutine_.promise().continuation(next);
|
|
return coroutine_;
|
|
}
|
|
|
|
|
|
std::coroutine_handle<promise_type> coroutine_;
|
|
};
|
|
|
|
auto
|
|
operator co_await()
|
|
const &
|
|
noexcept
|
|
|
|
{
|
|
struct awaitable : awaitable_base {
|
|
decltype(auto)
|
|
await_resume()
|
|
{
|
|
return awaitable_base::coroutine_.promise().result();
|
|
}
|
|
|
|
};
|
|
return awaitable{coroutine_};
|
|
}
|
|
|
|
auto
|
|
operator co_await()
|
|
const &&
|
|
noexcept
|
|
|
|
{
|
|
struct awaitable : awaitable_base {
|
|
decltype(auto)
|
|
await_resume()
|
|
{
|
|
return std::move(awaitable_base::coroutine_.promise()).result();
|
|
}
|
|
|
|
};
|
|
return awaitable{coroutine_};
|
|
}
|
|
|
|
promise_type&
|
|
promise()
|
|
const
|
|
{
|
|
return coroutine_.promise();
|
|
}
|
|
|
|
private:
|
|
std::coroutine_handle <promise_type> coroutine_;
|
|
};
|
|
|
|
template<typename T>
|
|
task<T>
|
|
promise<T>::get_return_object()
|
|
noexcept
|
|
{
|
|
return task<T>{std::coroutine_handle<promise>::from_promise(*this)};
|
|
}
|
|
|
|
task<void>
|
|
promise<void>::get_return_object()
|
|
noexcept
|
|
{
|
|
return task<void>{std::coroutine_handle<promise>::from_promise(*this)};
|
|
}
|
|
|
|
template <typename>
|
|
struct generator;
|
|
|
|
template<typename T>
|
|
struct generator_promise
|
|
{
|
|
generator_promise() = default;
|
|
|
|
generator_promise &operator=(generator_promise &&) = delete;
|
|
|
|
std::suspend_never
|
|
initial_suspend()
|
|
const
|
|
noexcept
|
|
{
|
|
return {};
|
|
}
|
|
|
|
std::suspend_always
|
|
final_suspend()
|
|
const
|
|
noexcept
|
|
{
|
|
return {};
|
|
}
|
|
|
|
template<typename P>
|
|
std::coroutine_handle<>
|
|
await_suspend(std::coroutine_handle<P> p)
|
|
noexcept
|
|
{
|
|
if (p.promise().continuation_)
|
|
{
|
|
return p.promise().continuation_;
|
|
}
|
|
return std::noop_coroutine();
|
|
}
|
|
|
|
generator<T>
|
|
get_return_object()
|
|
noexcept;
|
|
|
|
bool has_data() const
|
|
{
|
|
return !std::holds_alternative<std::monostate>(data_);
|
|
}
|
|
|
|
void
|
|
return_void()
|
|
{
|
|
if (continuation_)
|
|
{
|
|
continuation_.resume();
|
|
}
|
|
}
|
|
|
|
auto
|
|
yield_value(T t)
|
|
{
|
|
data_.template emplace<data>(std::move(t));
|
|
if (continuation_)
|
|
{
|
|
continuation_.resume();
|
|
}
|
|
return std::suspend_always{};
|
|
}
|
|
|
|
void
|
|
unhandled_exception()
|
|
noexcept
|
|
{
|
|
data_.template emplace<std::exception_ptr>(std::current_exception());
|
|
}
|
|
|
|
T
|
|
result()
|
|
{
|
|
struct cleaner_
|
|
{
|
|
generator_promise *p;
|
|
|
|
~cleaner_()
|
|
{ p->data_.template emplace<std::monostate>(); }
|
|
};
|
|
cleaner_ c{this};
|
|
if (auto v = std::get_if<data>(&data_))
|
|
{
|
|
return std::move(v->value);
|
|
} else if (auto e = std::get_if<std::exception_ptr>(&data_))
|
|
{
|
|
std::rethrow_exception(*e);
|
|
} else
|
|
{
|
|
abort();
|
|
}
|
|
}
|
|
void
|
|
continuation(std::coroutine_handle<> next)
|
|
noexcept
|
|
{
|
|
continuation_ = next;
|
|
}
|
|
private:
|
|
struct data
|
|
{
|
|
data(T&& t) : value(std::move(t)) {}
|
|
T value;
|
|
};
|
|
std::variant <std::monostate, data, std::exception_ptr> data_;
|
|
std::coroutine_handle<> continuation_{nullptr};
|
|
};
|
|
|
|
template <typename T>
|
|
struct generator
|
|
{
|
|
using promise_type = coro::generator_promise<T>;
|
|
|
|
explicit generator(std::coroutine_handle <promise_type> h = nullptr) : coroutine_(h) {}
|
|
|
|
generator(generator &&h) : coroutine_(std::exchange(h.coroutine_, nullptr)) {}
|
|
|
|
~generator() {
|
|
if (coroutine_) {
|
|
coroutine_.destroy();
|
|
}
|
|
}
|
|
|
|
bool
|
|
is_ready()
|
|
const
|
|
noexcept
|
|
{
|
|
return !coroutine_ || coroutine_.done();
|
|
}
|
|
|
|
bool
|
|
resume()
|
|
{
|
|
if (!coroutine_.done())
|
|
{
|
|
coroutine_.resume();
|
|
}
|
|
return !coroutine_.done();
|
|
}
|
|
|
|
bool
|
|
destroy()
|
|
{
|
|
if (coroutine_)
|
|
{
|
|
coroutine_.destroy();
|
|
coroutine_ = nullptr;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
struct awaitable_base
|
|
{
|
|
bool
|
|
await_ready()
|
|
const
|
|
noexcept
|
|
{
|
|
return coroutine_.promise().has_data();
|
|
}
|
|
|
|
std::coroutine_handle<>
|
|
await_suspend(std::coroutine_handle<> next)
|
|
noexcept
|
|
{
|
|
coroutine_.promise().continuation(next);
|
|
return coroutine_;
|
|
}
|
|
|
|
|
|
std::coroutine_handle<promise_type> coroutine_;
|
|
};
|
|
|
|
auto
|
|
operator co_await()
|
|
const &
|
|
noexcept
|
|
|
|
{
|
|
struct awaitable : awaitable_base {
|
|
decltype(auto)
|
|
await_resume()
|
|
{
|
|
return awaitable_base::coroutine_.promise().result();
|
|
}
|
|
|
|
};
|
|
return awaitable{coroutine_};
|
|
}
|
|
|
|
auto
|
|
operator co_await()
|
|
const &&
|
|
noexcept
|
|
|
|
{
|
|
struct awaitable : awaitable_base {
|
|
decltype(auto)
|
|
await_resume()
|
|
{
|
|
return std::move(awaitable_base::coroutine_.promise()).result();
|
|
}
|
|
|
|
};
|
|
return awaitable{coroutine_};
|
|
}
|
|
|
|
promise_type&
|
|
promise()
|
|
const
|
|
{
|
|
return coroutine_.promise();
|
|
}
|
|
|
|
private:
|
|
std::coroutine_handle <promise_type> coroutine_;
|
|
|
|
};
|
|
|
|
template <typename T>
|
|
generator<T> generator_promise<T>::get_return_object() noexcept
|
|
{
|
|
return generator<T>{std::coroutine_handle<generator_promise>::from_promise(*this)};
|
|
}
|
|
} // namespace coro
|
|
|
|
#endif //TROMPELOEIL_MICRO_CORO_HPP
|