diff options
| author | Lexi Winter <lexi@le-fay.org> | 2025-07-02 03:25:28 +0100 |
|---|---|---|
| committer | Lexi Winter <lexi@le-fay.org> | 2025-07-02 03:25:28 +0100 |
| commit | a4607e29540a9352c35afff17193ceeab137cc9d (patch) | |
| tree | 0e9c2ea9c94f17b81f222fd6ebf1ccd75bb1f7f8 /nihil.util | |
| parent | bde0492644845de63cf95b8997c5e613a9247826 (diff) | |
| download | nihil-a4607e29540a9352c35afff17193ceeab137cc9d.tar.gz nihil-a4607e29540a9352c35afff17193ceeab137cc9d.tar.bz2 | |
move monad to util
Diffstat (limited to 'nihil.util')
| -rw-r--r-- | nihil.util/CMakeLists.txt | 5 | ||||
| -rw-r--r-- | nihil.util/monad.ccm | 282 | ||||
| -rw-r--r-- | nihil.util/monad.test.cc | 66 | ||||
| -rw-r--r-- | nihil.util/nihil.util.ccm | 1 | ||||
| -rw-r--r-- | nihil.util/parse_size.ccm | 2 |
5 files changed, 353 insertions, 3 deletions
diff --git a/nihil.util/CMakeLists.txt b/nihil.util/CMakeLists.txt index 2755103..4c832c8 100644 --- a/nihil.util/CMakeLists.txt +++ b/nihil.util/CMakeLists.txt @@ -5,7 +5,6 @@ target_link_libraries(nihil.util PRIVATE nihil.std nihil.core nihil.error - nihil.monad ) target_sources(nihil.util PUBLIC FILE_SET modules TYPE CXX_MODULES FILES @@ -16,6 +15,7 @@ target_sources(nihil.util ctype.ccm flagset.ccm guard.ccm + monad.ccm parse_size.ccm next_word.ccm save_errno.ccm @@ -31,13 +31,14 @@ if(NIHIL_TESTS) ctype.test.cc flagset.test.cc guard.test.cc + monad.test.cc parse_size.test.cc next_word.test.cc skipws.test.cc tabulate.test.cc ) target_link_libraries(nihil.util.test PRIVATE - nihil.util + nihil.std nihil.util Catch2::Catch2WithMain ) diff --git a/nihil.util/monad.ccm b/nihil.util/monad.ccm new file mode 100644 index 0000000..eefa1fe --- /dev/null +++ b/nihil.util/monad.ccm @@ -0,0 +1,282 @@ +/* + * From https://github.com/toby-allsopp/coroutine_monad + * + * Copyright (c) 2017 Toby Allsopp + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + +export module nihil.util:monad; + +import nihil.std; + +namespace nihil { + +/********************************************************************** + * return_object_holder + */ + +// An object that starts out unitialized. Initialized by a call to emplace. +export template <typename T> +using deferred = std::optional<T>; + +export template <typename T> +struct return_object_holder { + // The staging object that is returned (by copy/move) to the caller of + // the coroutine. + deferred<T> stage; + return_object_holder*& p; + + // When constructed, we assign a pointer to ourselves to the supplied + // reference to pointer. + return_object_holder(return_object_holder*& p) + : stage{} + , p(p) + { + p = this; + } + + // Copying doesn't make any sense (which copy should the pointer refer + // to?). + return_object_holder(return_object_holder const&) = delete; + + // To move, we just update the pointer to point at the new object. + return_object_holder(return_object_holder&& other) + : stage(std::move(other.stage)) + , p(other.p) + { + p = this; + } + + // Assignment doesn't make sense. + void operator=(return_object_holder const&) = delete; + void operator=(return_object_holder&&) = delete; + + // A non-trivial destructor is required until + // https://bugs.llvm.org//show_bug.cgi?id=28593 is fixed. + ~return_object_holder() {} + + // Construct the staging value; arguments are perfect forwarded to T's + // constructor. + template <typename... Args> + void emplace(Args&&... args) + { + stage.emplace(std::forward<Args>(args)...); + } + + // We assume that we will be converted only once, so we can move from + // the staging object. We also assume that `emplace` has been called + // at least once. + operator T() + { + return std::move(*stage); + } +}; + +export template <typename T> +auto make_return_object_holder(return_object_holder<T>*& p) +{ + return return_object_holder<T>{p}; +} + +/********************************************************************** + * std::optional + */ + +template <typename T> +struct optional_promise { + return_object_holder<std::optional<T>>* data; + + auto get_return_object() + { + return make_return_object_holder(data); + } + + auto initial_suspend() noexcept -> std::suspend_never + { + return {}; + } + + auto final_suspend() noexcept -> std::suspend_never + { + return {}; + } + + void return_value(T x) + { + data->emplace(std::move(x)); + } + + void unhandled_exception() + { + std::rethrow_exception(std::current_exception()); + } +}; + +} // namespace nihil + +export template <typename T, typename... Args> +struct std::coroutine_traits<std::optional<T>, Args...> { + using promise_type = nihil::optional_promise<T>; +}; + +namespace nihil { + +template <typename T> +struct optional_awaitable { + std::optional<T> o; + + auto await_ready() + { + return o.has_value(); + } + + auto await_resume() + { + return *o; + } + + template <typename U> + void await_suspend(std::coroutine_handle<optional_promise<U>> h) + { + h.promise().data->emplace(std::nullopt); + h.destroy(); + } +}; + +} // namespace nihil + +namespace std { + +export template <typename T> +auto operator co_await(std::optional<T> o) { + return nihil::optional_awaitable<T>{std::move(o)}; +} + +} // namespace std + +/********************************************************************** + * std::expected + */ + +namespace nihil { + +export template <typename T, typename E> +struct expected_promise_base { + return_object_holder<std::expected<T, E>>* data; + + auto get_return_object() + { + return make_return_object_holder(data); + } + + auto initial_suspend() noexcept -> std::suspend_never + { + return {}; + } + + auto final_suspend() noexcept -> std::suspend_never + { + return {}; + } + + void unhandled_exception() + { + std::rethrow_exception(std::current_exception()); + } +}; + +export template <typename T, typename E> +struct expected_promise : expected_promise_base<T, E> { + void return_value(this expected_promise &self, std::unexpected<E> err) + { + self.data->emplace(std::move(err)); + } + + void return_value(this expected_promise &self, T o) + { + self.data->emplace(std::move(o)); + } +}; + +export template <typename E> +struct expected_promise<void, E> : expected_promise_base<void, E> { + void return_value(this expected_promise &self, std::unexpected<E> err) + { + self.data->emplace(std::move(err)); + } + + void return_value(this expected_promise &self, std::expected<void, E> o) + { + self.data->emplace(std::move(o)); + } +}; + +} // namespace nihil + +export template <typename T, typename E, typename... Args> +struct std::coroutine_traits<std::expected<T, E>, Args...> { + using promise_type = nihil::expected_promise<T, E>; +}; + +namespace nihil { + +export template<typename T, typename E> +struct expected_awaitable_base { + std::expected<T, E> o; + + auto await_ready() + { + return o.has_value(); + } + + template <typename P> + void await_suspend(std::coroutine_handle<P> h) + { + h.promise().data->emplace(std::unexpected(o.error())); + h.destroy(); + } +}; + +export template <typename T, typename E> +struct expected_awaitable : expected_awaitable_base<T, E> { + auto await_resume(this expected_awaitable &self) + { + return std::move(*self.o); + } +}; + +export template <typename E> +struct expected_awaitable<void, E> : expected_awaitable_base<void, E> { + auto await_resume(this expected_awaitable &) + { + return std::expected<void, E>(); + } +}; + +} // namespace nihil + +namespace std { + +export template <typename T, typename E> +auto operator co_await(std::expected<T, E> o) { + return nihil::expected_awaitable<T, E>{std::move(o)}; +} + +} // namespace std diff --git a/nihil.util/monad.test.cc b/nihil.util/monad.test.cc new file mode 100644 index 0000000..bc9e406 --- /dev/null +++ b/nihil.util/monad.test.cc @@ -0,0 +1,66 @@ +// This source code is released into the public domain. + +#include <catch2/catch_test_macros.hpp> + +import nihil.std; +import nihil.error; +import nihil.util; + +namespace { +TEST_CASE("monad: co_await std::optional<> with value", "[nihil]") +{ + auto get_value = [] -> std::optional<int> { + return 42; + }; + + auto try_get_value = [&get_value] -> std::optional<int> { + co_return co_await get_value(); + }; + + auto o = try_get_value(); + REQUIRE(o == 42); +} + +TEST_CASE("monad: co_await std::optional<> without value", "[nihil]") +{ + auto get_value = [] -> std::optional<int> { + return {}; + }; + + auto try_get_value = [&get_value] -> std::optional<int> { + co_return co_await get_value(); + }; + + auto o = try_get_value(); + REQUIRE(!o.has_value()); +} + +TEST_CASE("monad: co_await std::expected<> with value", "[nihil]") +{ + auto get_value = [] -> std::expected<int, std::string> { + return 42; + }; + + auto try_get_value = [&get_value] -> std::expected<int, std::string> { + co_return co_await get_value(); + }; + + auto o = try_get_value(); + REQUIRE(o == 42); +} + +TEST_CASE("monad: co_await std::expected<> with error", "[nihil]") +{ + auto get_value = [] -> std::expected<int, std::string> { + return std::unexpected("error"); + }; + + auto try_get_value = [&get_value] -> std::expected<int, std::string> { + co_return co_await get_value(); + }; + + auto o = try_get_value(); + REQUIRE(!o); + REQUIRE(o.error() == "error"); +} +} // anonymous namespace diff --git a/nihil.util/nihil.util.ccm b/nihil.util/nihil.util.ccm index 6b4bc30..de88f8c 100644 --- a/nihil.util/nihil.util.ccm +++ b/nihil.util/nihil.util.ccm @@ -6,6 +6,7 @@ export import :construct; export import :ctype; export import :flagset; export import :guard; +export import :monad; export import :parse_size; export import :next_word; export import :save_errno; diff --git a/nihil.util/parse_size.ccm b/nihil.util/parse_size.ccm index 7fc3fa4..6219323 100644 --- a/nihil.util/parse_size.ccm +++ b/nihil.util/parse_size.ccm @@ -4,9 +4,9 @@ export module nihil.util:parse_size; import nihil.std; import nihil.core; import nihil.error; -import nihil.monad; import :ctype; +import :monad; namespace nihil { |
