aboutsummaryrefslogtreecommitdiffstats
path: root/modules
diff options
context:
space:
mode:
Diffstat (limited to 'modules')
-rw-r--r--modules/CMakeLists.txt13
-rw-r--r--modules/ctype.ccm86
-rw-r--r--modules/fd.ccm309
-rw-r--r--modules/generator.ccm691
-rw-r--r--modules/generic_error.ccm26
-rw-r--r--modules/getenv.ccm47
-rw-r--r--modules/guard.ccm50
-rw-r--r--modules/nihil.ccm15
-rw-r--r--modules/tabulate.ccm282
9 files changed, 1519 insertions, 0 deletions
diff --git a/modules/CMakeLists.txt b/modules/CMakeLists.txt
new file mode 100644
index 0000000..383dd0a
--- /dev/null
+++ b/modules/CMakeLists.txt
@@ -0,0 +1,13 @@
+# This source code is released into the public domain.
+
+add_library(nihil STATIC)
+target_sources(nihil PUBLIC
+ FILE_SET modules TYPE CXX_MODULES FILES
+ nihil.ccm
+ ctype.ccm
+ fd.ccm
+ generator.ccm
+ generic_error.ccm
+ getenv.ccm
+ guard.ccm
+ tabulate.ccm)
diff --git a/modules/ctype.ccm b/modules/ctype.ccm
new file mode 100644
index 0000000..cc058cd
--- /dev/null
+++ b/modules/ctype.ccm
@@ -0,0 +1,86 @@
+/*
+ * This source code is released into the public domain.
+ */
+
+module;
+
+#include <concepts>
+#include <locale>
+
+export module nihil:ctype;
+
+namespace nihil {
+
+/*
+ * ctype_is: wrap std::ctype<T>::is() in a form suitable for use as an algorithm
+ * predicate, i.e., ctype_is(m) will return a functor object that takes any char
+ * type as an argument and returns bool.
+ *
+ * If the locale is not specified, the current global locale is used by default.
+ *
+ * ctype_is copies the locale, so passing a temporary is fine.
+ */
+
+export struct ctype_is final {
+ ctype_is(std::ctype_base::mask mask_,
+ std::locale const &locale_ = std::locale())
+ : mask(mask_)
+ , locale(locale_)
+ {}
+
+ auto operator()(this ctype_is const &self, std::integral auto c)
+ {
+ using ctype = std::ctype<decltype(c)>;
+ auto &facet = std::use_facet<ctype>(self.locale);
+ return facet.is(self.mask, c);
+ }
+
+private:
+ std::ctype_base::mask mask;
+ std::locale locale;
+};
+
+// Predefined tests for the current global locale.
+
+export inline auto is_space = ctype_is(std::ctype_base::space);
+export inline auto is_print = ctype_is(std::ctype_base::print);
+export inline auto is_cntrl = ctype_is(std::ctype_base::cntrl);
+export inline auto is_upper = ctype_is(std::ctype_base::upper);
+export inline auto is_lower = ctype_is(std::ctype_base::lower);
+export inline auto is_alpha = ctype_is(std::ctype_base::alpha);
+export inline auto is_digit = ctype_is(std::ctype_base::digit);
+export inline auto is_punct = ctype_is(std::ctype_base::punct);
+export inline auto is_xdigit = ctype_is(std::ctype_base::xdigit);
+export inline auto is_blank = ctype_is(std::ctype_base::blank);
+export inline auto is_alnum = ctype_is(std::ctype_base::alnum);
+export inline auto is_graph = ctype_is(std::ctype_base::graph);
+
+// Predefined tests for the C locale. The C locale is guaranteed to always be
+// available, so this doesn't create lifetime issues.
+
+export inline auto is_c_space =
+ ctype_is(std::ctype_base::space, std::locale::classic());
+export inline auto is_c_print =
+ ctype_is(std::ctype_base::print, std::locale::classic());
+export inline auto is_c_cntrl =
+ ctype_is(std::ctype_base::cntrl, std::locale::classic());
+export inline auto is_c_upper =
+ ctype_is(std::ctype_base::upper, std::locale::classic());
+export inline auto is_c_lower =
+ ctype_is(std::ctype_base::lower, std::locale::classic());
+export inline auto is_c_alpha =
+ ctype_is(std::ctype_base::alpha, std::locale::classic());
+export inline auto is_c_digit =
+ ctype_is(std::ctype_base::digit, std::locale::classic());
+export inline auto is_c_punct =
+ ctype_is(std::ctype_base::punct, std::locale::classic());
+export inline auto is_c_xdigit =
+ ctype_is(std::ctype_base::xdigit, std::locale::classic());
+export inline auto is_c_blank =
+ ctype_is(std::ctype_base::blank, std::locale::classic());
+export inline auto is_c_alnum =
+ ctype_is(std::ctype_base::alnum, std::locale::classic());
+export inline auto is_c_graph =
+ ctype_is(std::ctype_base::graph, std::locale::classic());
+
+} // namespace nihil
diff --git a/modules/fd.ccm b/modules/fd.ccm
new file mode 100644
index 0000000..ad96ea7
--- /dev/null
+++ b/modules/fd.ccm
@@ -0,0 +1,309 @@
+/*
+ * This source code is released into the public domain.
+ */
+
+module;
+
+#include <fcntl.h>
+#include <unistd.h>
+
+#include <expected>
+#include <format>
+#include <stdexcept>
+#include <system_error>
+
+export module nihil:fd;
+
+import :generic_error;
+
+namespace nihil {
+
+/*
+ * Exception thrown when an internal fd error occurs. This is not supposed
+ * to be caught, since it indicates an internal logic error in the caller.
+ */
+export struct fd_logic_error final : std::logic_error {
+ fd_logic_error(std::string what)
+ : std::logic_error(std::move(what))
+ {}
+};
+
+/*
+ * fd: a file descriptor.
+ */
+
+export struct fd final {
+ // Construct an empty (invalid) fd.
+ fd() noexcept = default;
+
+ // Construct an fd from an exising file destrictor, taking ownership.
+ fd(int fd_) noexcept : _fd(fd_) {}
+
+ // Destructor. Close the fd, discarding any errors.
+ ~fd()
+ {
+ if (*this)
+ this->close();
+ }
+
+ // Move from another fd, leaving the moved-from fd in an invalid state.
+ fd(fd &&other) noexcept
+ : _fd(std::exchange(other._fd, _invalid_fd))
+ {}
+
+ // Move assign from another fd.
+ auto operator=(fd &&other) noexcept -> fd &
+ {
+ if (this != &other)
+ _fd = std::exchange(other._fd, _invalid_fd);
+ return *this;
+ }
+
+ // Not copyable.
+ fd(fd const &) = delete;
+ fd& operator=(fd const &) = delete;
+
+ // Return true if this fd is valid (open).
+ explicit operator bool(this fd const &self) noexcept
+ {
+ return self._fd != _invalid_fd;
+ }
+
+ // Close the wrapped fd.
+ auto close(this fd &self) -> std::expected<void, std::error_code>
+ {
+ auto const ret = ::close(self.get());
+ self._fd = _invalid_fd;
+
+ if (ret == 0)
+ return {};
+
+ return std::unexpected(std::make_error_code(std::errc(errno)));
+ }
+
+ // Return the stored fd.
+ auto get(this fd const &self) -> int {
+ if (self)
+ return self._fd;
+ throw fd_logic_error("Attempt to call get() on invalid fd");
+ }
+
+
+ // Release the stored fd and return it. The caller must close it.
+ auto release(this fd &&self) -> int {
+ if (self)
+ return std::exchange(self._fd, self._invalid_fd);
+ throw fd_logic_error("Attempt to release an invalid fd");
+ }
+
+private:
+ static constexpr int _invalid_fd = -1;
+
+ int _fd = _invalid_fd;
+};
+
+// Create a copy of this fd by calling dup().
+export auto dup(fd const &self) -> std::expected<fd, std::error_code>
+{
+ auto thisfd = self.get();
+
+ auto const newfd = ::dup(thisfd);
+ if (newfd != -1)
+ return {newfd};
+
+ return std::unexpected(std::make_error_code(std::errc(errno)));
+}
+
+// Create a copy of this fd by calling dup2(). Note that because this results
+// in the existing fd and the new fd both being managed by an fd instance,
+// there are two potential cases that can cause problems:
+//
+// - dup()ing an fd to itself (a no-op)
+// - dup()ing an fd to an fd which is already managed by an fd instance
+//
+// In both of these cases, either use raw_dup() instead, or immediately call
+// release() on the returned fd to prevent the fd instance from closing it.
+export auto dup(fd const &self, int newfd)
+ -> std::expected<fd, std::error_code>
+{
+ auto thisfd = self.get();
+
+ auto const ret = ::dup2(thisfd, newfd);
+ if (ret != -1)
+ return {newfd};
+
+ return std::unexpected(std::make_error_code(std::errc(errno)));
+}
+
+// Create a copy of this fd by calling dup().
+export auto raw_dup(fd const &self) -> std::expected<int, std::error_code>
+{
+ auto thisfd = self.get();
+
+ auto const newfd = ::dup(thisfd);
+ if (newfd != -1)
+ return {newfd};
+
+ return std::unexpected(std::make_error_code(std::errc(errno)));
+}
+
+// Create a copy of this fd by calling dup2().
+export auto raw_dup(fd const &self, int newfd)
+ -> std::expected<int, std::error_code>
+{
+ auto thisfd = self.get();
+
+ auto const ret = ::dup2(thisfd, newfd);
+ if (ret != -1)
+ return {newfd};
+
+ return std::unexpected(std::make_error_code(std::errc(errno)));
+}
+
+// Return the fnctl flags for this fd.
+export auto getflags(fd const &self) -> std::expected<int, std::error_code>
+{
+ auto const flags = ::fcntl(self.get(), F_GETFL);
+ if (flags != -1)
+ return {flags};
+
+ return std::unexpected(std::make_error_code(std::errc(errno)));
+}
+
+// Replace the fnctl flags for this fd.
+export auto replaceflags(fd &self, int newflags)
+ -> std::expected<void, std::error_code>
+{
+ auto const ret = ::fcntl(self.get(), F_SETFL, newflags);
+ if (ret == 0)
+ return {};
+
+ return std::unexpected(std::make_error_code(std::errc(errno)));
+}
+
+// Add bits to the fcntl flags for this fd. Returns the new flags.
+export auto setflags(fd &self, int newflags)
+ -> std::expected<int, std::error_code>
+{
+ auto flags = getflags(self);
+ if (!flags)
+ return flags;
+
+ *flags |= newflags;
+ auto const ret = replaceflags(self, *flags);
+ if (!ret)
+ return std::unexpected(ret.error());
+
+ return {*flags};
+}
+
+// Remove bits from the fcntl flags for this fd. Returns the new flags.
+export auto clearflags(fd &self, int clrflags)
+ -> std::expected<int, std::error_code>
+{
+ auto flags = getflags(self);
+ if (!flags)
+ return flags;
+
+ *flags &= ~clrflags;
+ auto const ret = replaceflags(self, *flags);
+ if (!ret)
+ return std::unexpected(ret.error());
+
+ return {*flags};
+}
+
+// Return the fd flags for this fd.
+export auto getfdflags(fd const &self) -> std::expected<int, std::error_code>
+{
+ auto const flags = ::fcntl(self.get(), F_GETFD);
+ if (flags != -1)
+ return {flags};
+
+ return std::unexpected(std::make_error_code(std::errc(errno)));
+}
+
+// Replace the fd flags for this fd.
+export auto replacefdflags(fd &self, int newflags)
+ -> std::expected<void, std::error_code>
+{
+ auto const ret = ::fcntl(self.get(), F_SETFD, newflags);
+ if (ret != -1)
+ return {};
+
+ return std::unexpected(std::make_error_code(std::errc(errno)));
+}
+
+// Add bits to the fd flags for this fd. Returns the new flags.
+export auto setfdflags(fd &self, int newflags)
+ -> std::expected<int, std::error_code>
+{
+ auto flags = getfdflags(self);
+ if (!flags)
+ return flags;
+
+ *flags |= newflags;
+ auto const ret = replacefdflags(self, *flags);
+ if (!ret)
+ return std::unexpected(ret.error());
+
+ return {*flags};
+}
+
+// Remove bits from the fd flags for this fd. Returns the new flags.
+export auto clearfdflags(fd &self, int clrflags)
+ -> std::expected<int, std::error_code>
+{
+ auto flags = getfdflags(self);
+ if (!flags)
+ return flags;
+
+ *flags &= ~clrflags;
+ auto ret = replacefdflags(self, *flags);
+ if (!ret)
+ return std::unexpected(ret.error());
+
+ return {*flags};
+}
+
+// Create two fds by calling pipe() and return them.
+export auto pipe() -> std::expected<std::pair<fd, fd>, std::error_code> {
+ auto fds = std::array<int, 2>{};
+
+ if (auto const ret = ::pipe(fds.data()); ret != 0)
+ return std::unexpected(std::make_error_code(std::errc(errno)));
+
+ return {{fd(fds[0]), fd(fds[1])}};
+}
+
+/*
+ * Write data to a file descriptor from the provided buffer. Returns the
+ * number of bytes (not objects) written. Incomplete writes may cause a
+ * partial object to be written.
+ */
+export auto write(fd &file, std::ranges::contiguous_range auto &&range)
+ -> std::expected<std::size_t, std::error_code>
+{
+ auto const ret = ::write(file.get(), std::ranges::data(range),
+ std::ranges::size(range));
+ if (ret >= 0)
+ return ret;
+ return std::unexpected(std::make_error_code(std::errc(errno)));
+}
+
+/*
+ * Read data from a file descriptor into the provided buffer. Returns the
+ * number of bytes (not objects) read. Incomplete reads may cause a partial
+ * object to be read.
+ */
+export auto read(fd &file, std::ranges::contiguous_range auto &&range)
+ -> std::expected<std::size_t, std::error_code>
+{
+ auto const ret = ::read(file.get(), std::ranges::data(range),
+ std::ranges::size(range));
+ if (ret >= 0)
+ return ret;
+ return std::unexpected(std::make_error_code(std::errc(errno)));
+}
+
+} // namespace nihil
diff --git a/modules/generator.ccm b/modules/generator.ccm
new file mode 100644
index 0000000..82bcb27
--- /dev/null
+++ b/modules/generator.ccm
@@ -0,0 +1,691 @@
+///////////////////////////////////////////////////////////////////////////////
+// Reference implementation of std::generator proposal P2168.
+//
+// See https://wg21.link/P2168 for details.
+//
+///////////////////////////////////////////////////////////////////////////////
+// Copyright Lewis Baker, Corentin Jabot
+//
+// Use, modification and distribution is subject to the Boost Software License,
+// Version 1.0.
+// (See accompanying file LICENSE or http://www.boost.org/LICENSE_1_0.txt)
+///////////////////////////////////////////////////////////////////////////////
+
+module;
+
+#include <cassert>
+#include <coroutine>
+#include <exception>
+#include <memory>
+#include <ranges>
+#include <type_traits>
+
+export module nihil:generator;
+
+namespace nihil {
+
+template <typename _T>
+class __manual_lifetime {
+ public:
+ __manual_lifetime() noexcept {}
+ ~__manual_lifetime() {}
+
+ template <typename... _Args>
+ _T& construct(_Args&&... __args) noexcept(std::is_nothrow_constructible_v<_T, _Args...>) {
+ return *::new (static_cast<void*>(std::addressof(__value_))) _T((_Args&&)__args...);
+ }
+
+ void destruct() noexcept(std::is_nothrow_destructible_v<_T>) {
+ __value_.~_T();
+ }
+
+ _T& get() & noexcept {
+ return __value_;
+ }
+ _T&& get() && noexcept {
+ return static_cast<_T&&>(__value_);
+ }
+ const _T& get() const & noexcept {
+ return __value_;
+ }
+ const _T&& get() const && noexcept {
+ return static_cast<const _T&&>(__value_);
+ }
+
+ private:
+ union {
+ std::remove_const_t<_T> __value_;
+ };
+};
+
+template <typename _T>
+class __manual_lifetime<_T&> {
+ public:
+ __manual_lifetime() noexcept : __value_(nullptr) {}
+ ~__manual_lifetime() {}
+
+ _T& construct(_T& __value) noexcept {
+ __value_ = std::addressof(__value);
+ return __value;
+ }
+
+ void destruct() noexcept {}
+
+ _T& get() const noexcept {
+ return *__value_;
+ }
+
+ private:
+ _T* __value_;
+};
+
+template <typename _T>
+class __manual_lifetime<_T&&> {
+ public:
+ __manual_lifetime() noexcept : __value_(nullptr) {}
+ ~__manual_lifetime() {}
+
+ _T&& construct(_T&& __value) noexcept {
+ __value_ = std::addressof(__value);
+ return static_cast<_T&&>(__value);
+ }
+
+ void destruct() noexcept {}
+
+ _T&& get() const noexcept {
+ return static_cast<_T&&>(*__value_);
+ }
+
+ private:
+ _T* __value_;
+};
+
+struct use_allocator_arg {};
+
+namespace ranges {
+
+export template <typename _Rng, typename _Allocator = use_allocator_arg>
+struct elements_of {
+ explicit constexpr elements_of(_Rng&& __rng) noexcept
+ requires std::is_default_constructible_v<_Allocator>
+ : __range(static_cast<_Rng&&>(__rng))
+ {}
+
+ constexpr elements_of(_Rng&& __rng, _Allocator&& __alloc) noexcept
+ : __range((_Rng&&)__rng), __alloc((_Allocator&&)__alloc) {}
+
+ constexpr elements_of(elements_of&&) noexcept = default;
+
+ constexpr elements_of(const elements_of &) = delete;
+ constexpr elements_of &operator=(const elements_of &) = delete;
+ constexpr elements_of &operator=(elements_of &&) = delete;
+
+ constexpr _Rng&& get() noexcept {
+ return static_cast<_Rng&&>(__range);
+ }
+
+ constexpr _Allocator get_allocator() const noexcept {
+ return __alloc;
+ }
+
+private:
+ [[no_unique_address]] _Allocator __alloc; // \expos
+ _Rng && __range; // \expos
+};
+
+export template <typename _Rng>
+elements_of(_Rng &&) -> elements_of<_Rng>;
+
+export template <typename _Rng, typename Allocator>
+elements_of(_Rng &&, Allocator&&) -> elements_of<_Rng, Allocator>;
+
+} // namespace ranges
+
+template <typename _Alloc>
+static constexpr bool __allocator_needs_to_be_stored =
+ !std::allocator_traits<_Alloc>::is_always_equal::value ||
+ !std::is_default_constructible_v<_Alloc>;
+
+// Round s up to next multiple of a.
+constexpr size_t __aligned_allocation_size(size_t s, size_t a) {
+ return (s + a - 1) & ~(a - 1);
+}
+
+
+export template <typename _Ref,
+ typename _Value = std::remove_cvref_t<_Ref>,
+ typename _Allocator = use_allocator_arg>
+class generator;
+
+template<typename _Alloc>
+class __promise_base_alloc {
+ static constexpr std::size_t __offset_of_allocator(std::size_t __frameSize) noexcept {
+ return __aligned_allocation_size(__frameSize, alignof(_Alloc));
+ }
+
+ static constexpr std::size_t __padded_frame_size(std::size_t __frameSize) noexcept {
+ return __offset_of_allocator(__frameSize) + sizeof(_Alloc);
+ }
+
+ static _Alloc& __get_allocator(void* __frame, std::size_t __frameSize) noexcept {
+ return *reinterpret_cast<_Alloc*>(
+ static_cast<char*>(__frame) + __offset_of_allocator(__frameSize));
+ }
+
+public:
+ template<typename... _Args>
+ static void* operator new(std::size_t __frameSize, std::allocator_arg_t, _Alloc __alloc, _Args&...) {
+ void* __frame = __alloc.allocate(__padded_frame_size(__frameSize));
+
+ // Store allocator at end of the coroutine frame.
+ // Assuming the allocator's move constructor is non-throwing (a requirement for allocators)
+ ::new (static_cast<void*>(std::addressof(__get_allocator(__frame, __frameSize)))) _Alloc(std::move(__alloc));
+
+ return __frame;
+ }
+
+ template<typename _This, typename... _Args>
+ static void* operator new(std::size_t __frameSize, _This&, std::allocator_arg_t, _Alloc __alloc, _Args&...) {
+ return __promise_base_alloc::operator new(__frameSize, std::allocator_arg, std::move(__alloc));
+ }
+
+ static void operator delete(void* __ptr, std::size_t __frameSize) noexcept {
+ _Alloc& __alloc = __get_allocator(__ptr, __frameSize);
+ _Alloc __localAlloc(std::move(__alloc));
+ __alloc.~Alloc();
+ __localAlloc.deallocate(static_cast<std::byte*>(__ptr), __padded_frame_size(__frameSize));
+ }
+};
+
+template<typename _Alloc>
+ requires (!__allocator_needs_to_be_stored<_Alloc>)
+class __promise_base_alloc<_Alloc> {
+public:
+ static void* operator new(std::size_t __size) {
+ _Alloc __alloc;
+ return __alloc.allocate(__size);
+ }
+
+ static void operator delete(void* __ptr, std::size_t __size) noexcept {
+ _Alloc __alloc;
+ __alloc.deallocate(static_cast<std::byte*>(__ptr), __size);
+ }
+};
+
+template<typename _Ref>
+struct __generator_promise_base
+{
+ template <typename _Ref2, typename _Value, typename _Alloc>
+ friend class generator;
+
+ __generator_promise_base* __root_;
+ std::coroutine_handle<> __parentOrLeaf_;
+ // Note: Using manual_lifetime here to avoid extra calls to exception_ptr
+ // constructor/destructor in cases where it is not needed (i.e. where this
+ // generator coroutine is not used as a nested coroutine).
+ // This member is lazily constructed by the __yield_sequence_awaiter::await_suspend()
+ // method if this generator is used as a nested generator.
+ __manual_lifetime<std::exception_ptr> __exception_;
+ __manual_lifetime<_Ref> __value_;
+
+ explicit __generator_promise_base(std::coroutine_handle<> thisCoro) noexcept
+ : __root_(this)
+ , __parentOrLeaf_(thisCoro)
+ {}
+
+ ~__generator_promise_base() {
+ if (__root_ != this) {
+ // This coroutine was used as a nested generator and so will
+ // have constructed its __exception_ member which needs to be
+ // destroyed here.
+ __exception_.destruct();
+ }
+ }
+
+ std::suspend_always initial_suspend() noexcept {
+ return {};
+ }
+
+ void return_void() noexcept {}
+
+ void unhandled_exception() {
+ if (__root_ != this) {
+ __exception_.get() = std::current_exception();
+ } else {
+ throw;
+ }
+ }
+
+ // Transfers control back to the parent of a nested coroutine
+ struct __final_awaiter {
+ bool await_ready() noexcept {
+ return false;
+ }
+
+ template <typename _Promise>
+ std::coroutine_handle<>
+ await_suspend(std::coroutine_handle<_Promise> __h) noexcept {
+ _Promise& __promise = __h.promise();
+ __generator_promise_base& __root = *__promise.__root_;
+ if (&__root != &__promise) {
+ auto __parent = __promise.__parentOrLeaf_;
+ __root.__parentOrLeaf_ = __parent;
+ return __parent;
+ }
+ return std::noop_coroutine();
+ }
+
+ void await_resume() noexcept {}
+ };
+
+ __final_awaiter final_suspend() noexcept {
+ return {};
+ }
+
+ std::suspend_always yield_value(_Ref&& __x)
+ noexcept(std::is_nothrow_move_constructible_v<_Ref>) {
+ __root_->__value_.construct((_Ref&&)__x);
+ return {};
+ }
+
+ template <typename _T>
+ requires
+ (!std::is_reference_v<_Ref>) &&
+ std::is_convertible_v<_T, _Ref>
+ std::suspend_always yield_value(_T&& __x)
+ noexcept(std::is_nothrow_constructible_v<_Ref, _T>) {
+ __root_->__value_.construct((_T&&)__x);
+ return {};
+ }
+
+ template <typename _Gen>
+ struct __yield_sequence_awaiter {
+ _Gen __gen_;
+
+ __yield_sequence_awaiter(_Gen&& __g) noexcept
+ // Taking ownership of the generator ensures frame are destroyed
+ // in the reverse order of their execution.
+ : __gen_((_Gen&&)__g) {
+ }
+
+ bool await_ready() noexcept {
+ return false;
+ }
+
+ // set the parent, root and exceptions pointer and
+ // resume the nested
+ template<typename _Promise>
+ std::coroutine_handle<>
+ await_suspend(std::coroutine_handle<_Promise> __h) noexcept {
+ __generator_promise_base& __current = __h.promise();
+ __generator_promise_base& __nested = *__gen_.__get_promise();
+ __generator_promise_base& __root = *__current.__root_;
+
+ __nested.__root_ = __current.__root_;
+ __nested.__parentOrLeaf_ = __h;
+
+ // Lazily construct the __exception_ member here now that we
+ // know it will be used as a nested generator. This will be
+ // destroyed by the promise destructor.
+ __nested.__exception_.construct();
+ __root.__parentOrLeaf_ = __gen_.__get_coro();
+
+ // Immediately resume the nested coroutine (nested generator)
+ return __gen_.__get_coro();
+ }
+
+ void await_resume() {
+ __generator_promise_base& __nestedPromise = *__gen_.__get_promise();
+ if (__nestedPromise.__exception_.get()) {
+ std::rethrow_exception(std::move(__nestedPromise.__exception_.get()));
+ }
+ }
+ };
+
+ template <typename _OValue, typename _OAlloc>
+ __yield_sequence_awaiter<generator<_Ref, _OValue, _OAlloc>>
+ yield_value(nihil::ranges::elements_of<generator<_Ref, _OValue, _OAlloc>> __g) noexcept {
+ return std::move(__g).get();
+ }
+
+ template <std::ranges::range _Rng, typename _Allocator>
+ __yield_sequence_awaiter<generator<_Ref, std::remove_cvref_t<_Ref>, _Allocator>>
+ yield_value(nihil::ranges::elements_of<_Rng, _Allocator> && __x) {
+ return [](std::allocator_arg_t, _Allocator, auto && __rng) -> generator<_Ref, std::remove_cvref_t<_Ref>, _Allocator> {
+ for(auto && e: __rng)
+ co_yield static_cast<decltype(e)>(e);
+ }(std::allocator_arg, __x.get_allocator(), std::forward<_Rng>(__x.get()));
+ }
+
+ void resume() {
+ __parentOrLeaf_.resume();
+ }
+
+ // Disable use of co_await within this coroutine.
+ void await_transform() = delete;
+};
+
+template<typename _Generator, typename _ByteAllocator, bool _ExplicitAllocator = false>
+struct __generator_promise;
+
+template<typename _Ref, typename _Value, typename _Alloc, typename _ByteAllocator, bool _ExplicitAllocator>
+struct __generator_promise<generator<_Ref, _Value, _Alloc>, _ByteAllocator, _ExplicitAllocator> final
+ : public __generator_promise_base<_Ref>
+ , public __promise_base_alloc<_ByteAllocator> {
+ __generator_promise() noexcept
+ : __generator_promise_base<_Ref>(std::coroutine_handle<__generator_promise>::from_promise(*this))
+ {}
+
+ generator<_Ref, _Value, _Alloc> get_return_object() noexcept {
+ return generator<_Ref, _Value, _Alloc>{
+ std::coroutine_handle<__generator_promise>::from_promise(*this)
+ };
+ }
+
+ using __generator_promise_base<_Ref>::yield_value;
+
+ template <std::ranges::range _Rng>
+ typename __generator_promise_base<_Ref>::template __yield_sequence_awaiter<generator<_Ref, _Value, _Alloc>>
+ yield_value(nihil::ranges::elements_of<_Rng> && __x) {
+ static_assert (!_ExplicitAllocator,
+ "This coroutine has an explicit allocator specified with std::allocator_arg so an allocator needs to be passed "
+ "explicitely to std::elements_of");
+ return [](auto && __rng) -> generator<_Ref, _Value, _Alloc> {
+ for(auto && e: __rng)
+ co_yield static_cast<decltype(e)>(e);
+ }(std::forward<_Rng>(__x.get()));
+ }
+};
+
+template<typename _Alloc>
+using __byte_allocator_t = typename std::allocator_traits<std::remove_cvref_t<_Alloc>>::template rebind_alloc<std::byte>;
+
+} // namespace nihil
+
+namespace std {
+
+// Type-erased allocator with default allocator behaviour.
+export template<typename _Ref, typename _Value, typename... _Args>
+struct coroutine_traits<nihil::generator<_Ref, _Value>, _Args...> {
+ using promise_type = nihil::__generator_promise<nihil::generator<_Ref, _Value>, std::allocator<std::byte>>;
+};
+
+// Type-erased allocator with std::allocator_arg parameter
+export template<typename _Ref, typename _Value, typename _Alloc, typename... _Args>
+struct coroutine_traits<nihil::generator<_Ref, _Value>, allocator_arg_t, _Alloc, _Args...> {
+private:
+ using __byte_allocator = nihil::__byte_allocator_t<_Alloc>;
+public:
+ using promise_type = nihil::__generator_promise<nihil::generator<_Ref, _Value>, __byte_allocator, true /*explicit Allocator*/>;
+};
+
+// Type-erased allocator with std::allocator_arg parameter (non-static member functions)
+export template<typename _Ref, typename _Value, typename _This, typename _Alloc, typename... _Args>
+struct coroutine_traits<nihil::generator<_Ref, _Value>, _This, allocator_arg_t, _Alloc, _Args...> {
+private:
+ using __byte_allocator = nihil::__byte_allocator_t<_Alloc>;
+public:
+ using promise_type = nihil::__generator_promise<nihil::generator<_Ref, _Value>, __byte_allocator, true /*explicit Allocator*/>;
+};
+
+// Generator with specified allocator type
+export template<typename _Ref, typename _Value, typename _Alloc, typename... _Args>
+struct coroutine_traits<nihil::generator<_Ref, _Value, _Alloc>, _Args...> {
+ using __byte_allocator = nihil::__byte_allocator_t<_Alloc>;
+public:
+ using promise_type = nihil::__generator_promise<nihil::generator<_Ref, _Value, _Alloc>, __byte_allocator>;
+};
+
+} // namespace std
+
+namespace nihil {
+
+// TODO : make layout compatible promise casts possible
+export template <typename _Ref, typename _Value, typename _Alloc>
+class generator {
+ using __byte_allocator = __byte_allocator_t<_Alloc>;
+public:
+ using promise_type = __generator_promise<generator<_Ref, _Value, _Alloc>, __byte_allocator>;
+ friend promise_type;
+private:
+ using __coroutine_handle = std::coroutine_handle<promise_type>;
+public:
+
+ generator() noexcept = default;
+
+ generator(generator&& __other) noexcept
+ : __coro_(std::exchange(__other.__coro_, {}))
+ , __started_(std::exchange(__other.__started_, false)) {
+ }
+
+ ~generator() noexcept {
+ if (__coro_) {
+ if (__started_ && !__coro_.done()) {
+ __coro_.promise().__value_.destruct();
+ }
+ __coro_.destroy();
+ }
+ }
+
+ generator& operator=(generator && g) noexcept {
+ swap(g);
+ return *this;
+ }
+
+ void swap(generator& __other) noexcept {
+ std::swap(__coro_, __other.__coro_);
+ std::swap(__started_, __other.__started_);
+ }
+
+ struct sentinel {};
+
+ class iterator {
+ public:
+ using iterator_category = std::input_iterator_tag;
+ using difference_type = std::ptrdiff_t;
+ using value_type = _Value;
+ using reference = _Ref;
+ using pointer = std::add_pointer_t<_Ref>;
+
+ iterator() noexcept = default;
+ iterator(const iterator &) = delete;
+
+ iterator(iterator&& __other) noexcept
+ : __coro_(std::exchange(__other.__coro_, {})) {
+ }
+
+ iterator& operator=(iterator&& __other) {
+ std::swap(__coro_, __other.__coro_);
+ return *this;
+ }
+
+ ~iterator() {
+ }
+
+ friend bool operator==(const iterator &it, sentinel) noexcept {
+ return it.__coro_.done();
+ }
+
+ iterator &operator++() {
+ __coro_.promise().__value_.destruct();
+ __coro_.promise().resume();
+ return *this;
+ }
+ void operator++(int) {
+ (void)operator++();
+ }
+
+ reference operator*() const noexcept {
+ return static_cast<reference>(__coro_.promise().__value_.get());
+ }
+
+ private:
+ friend generator;
+
+ explicit iterator(__coroutine_handle __coro) noexcept
+ : __coro_(__coro) {}
+
+ __coroutine_handle __coro_;
+ };
+
+ iterator begin() {
+ assert(__coro_);
+ assert(!__started_);
+ __started_ = true;
+ __coro_.resume();
+ return iterator{__coro_};
+ }
+
+ sentinel end() noexcept {
+ return {};
+ }
+
+private:
+ explicit generator(__coroutine_handle __coro) noexcept
+ : __coro_(__coro) {
+ }
+
+public: // to get around access restrictions for __yield_sequence_awaitable
+ std::coroutine_handle<> __get_coro() noexcept { return __coro_; }
+ promise_type* __get_promise() noexcept { return std::addressof(__coro_.promise()); }
+
+private:
+ __coroutine_handle __coro_;
+ bool __started_ = false;
+};
+
+// Specialisation for type-erased allocator implementation.
+export template <typename _Ref, typename _Value>
+class generator<_Ref, _Value, use_allocator_arg> {
+ using __promise_base = __generator_promise_base<_Ref>;
+public:
+
+ generator() noexcept
+ : __promise_(nullptr)
+ , __coro_()
+ , __started_(false)
+ {}
+
+ generator(generator&& __other) noexcept
+ : __promise_(std::exchange(__other.__promise_, nullptr))
+ , __coro_(std::exchange(__other.__coro_, {}))
+ , __started_(std::exchange(__other.__started_, false)) {
+ }
+
+ ~generator() noexcept {
+ if (__coro_) {
+ if (__started_ && !__coro_.done()) {
+ __promise_->__value_.destruct();
+ }
+ __coro_.destroy();
+ }
+ }
+
+ generator& operator=(generator g) noexcept {
+ swap(g);
+ return *this;
+ }
+
+ void swap(generator& __other) noexcept {
+ std::swap(__promise_, __other.__promise_);
+ std::swap(__coro_, __other.__coro_);
+ std::swap(__started_, __other.__started_);
+ }
+
+ struct sentinel {};
+
+ class iterator {
+ public:
+ using iterator_category = std::input_iterator_tag;
+ using difference_type = std::ptrdiff_t;
+ using value_type = _Value;
+ using reference = _Ref;
+ using pointer = std::add_pointer_t<_Ref>;
+
+ iterator() noexcept = default;
+ iterator(const iterator &) = delete;
+
+ iterator(iterator&& __other) noexcept
+ : __promise_(std::exchange(__other.__promise_, nullptr))
+ , __coro_(std::exchange(__other.__coro_, {}))
+ {}
+
+ iterator& operator=(iterator&& __other) {
+ __promise_ = std::exchange(__other.__promise_, nullptr);
+ __coro_ = std::exchange(__other.__coro_, {});
+ return *this;
+ }
+
+ ~iterator() = default;
+
+ friend bool operator==(const iterator &it, sentinel) noexcept {
+ return it.__coro_.done();
+ }
+
+ iterator& operator++() {
+ __promise_->__value_.destruct();
+ __promise_->resume();
+ return *this;
+ }
+
+ void operator++(int) {
+ (void)operator++();
+ }
+
+ reference operator*() const noexcept {
+ return static_cast<reference>(__promise_->__value_.get());
+ }
+
+ private:
+ friend generator;
+
+ explicit iterator(__promise_base* __promise, std::coroutine_handle<> __coro) noexcept
+ : __promise_(__promise)
+ , __coro_(__coro)
+ {}
+
+ __promise_base* __promise_;
+ std::coroutine_handle<> __coro_;
+ };
+
+ iterator begin() {
+ assert(__coro_);
+ assert(!__started_);
+ __started_ = true;
+ __coro_.resume();
+ return iterator{__promise_, __coro_};
+ }
+
+ sentinel end() noexcept {
+ return {};
+ }
+
+private:
+ template<typename _Generator, typename _ByteAllocator, bool _ExplicitAllocator>
+ friend struct __generator_promise;
+
+ template<typename _Promise>
+ explicit generator(std::coroutine_handle<_Promise> __coro) noexcept
+ : __promise_(std::addressof(__coro.promise()))
+ , __coro_(__coro)
+ {}
+
+public: // to get around access restrictions for __yield_sequence_awaitable
+ std::coroutine_handle<> __get_coro() noexcept { return __coro_; }
+ __promise_base* __get_promise() noexcept { return __promise_; }
+
+private:
+ __promise_base* __promise_;
+ std::coroutine_handle<> __coro_;
+ bool __started_ = false;
+};
+
+} // namespace nihil
+
+export namespace std::ranges {
+
+template <typename _T, typename _U, typename _Alloc>
+constexpr inline bool enable_view<nihil::generator<_T, _U, _Alloc>> = true;
+
+} // namespace std::ranges
+
diff --git a/modules/generic_error.ccm b/modules/generic_error.ccm
new file mode 100644
index 0000000..a582519
--- /dev/null
+++ b/modules/generic_error.ccm
@@ -0,0 +1,26 @@
+/*
+ * This source code is released into the public domain.
+ */
+
+module;
+
+#include <format>
+#include <stdexcept>
+
+export module nihil:generic_error;
+
+namespace nihil {
+
+/*
+ * generic_error is the base class that all other exceptions derive from.
+ * It is an std::runtime_error, and what() should always be informative.
+ */
+
+export struct generic_error : std::runtime_error {
+ template<typename... Args>
+ generic_error(std::format_string<Args...> fmt, Args &&...args)
+ : std::runtime_error(std::format(fmt, std::forward<Args>(args)...))
+ {}
+};
+
+} // namespace nihil
diff --git a/modules/getenv.ccm b/modules/getenv.ccm
new file mode 100644
index 0000000..7397b79
--- /dev/null
+++ b/modules/getenv.ccm
@@ -0,0 +1,47 @@
+/*
+ * This source code is released into the public domain.
+ */
+
+module;
+
+#include <cstdint>
+#include <expected>
+#include <string>
+#include <system_error>
+#include <vector>
+
+#include <unistd.h>
+
+export module nihil:getenv;
+
+namespace nihil {
+
+/*
+ * Find a variable by the given name in the environment by calling getenv_r().
+ */
+
+export auto getenv(std::string_view varname)
+ -> std::expected<std::string, std::error_code>
+{
+ // Start with a buffer of this size, and double it every iteration.
+ constexpr auto bufinc = std::size_t{1024};
+
+ auto cvarname = std::string(varname);
+ auto buf = std::vector<char>(bufinc);
+ for (;;) {
+ auto const ret = ::getenv_r(cvarname.c_str(),
+ buf.data(), buf.size());
+
+ if (ret == 0)
+ return {std::string(buf.data())};
+
+ if (ret == -1 && errno == ERANGE) {
+ buf.resize(buf.size() * 2);
+ continue;
+ }
+
+ return std::unexpected(std::make_error_code(std::errc(errno)));
+ }
+}
+
+} // namespace nihil
diff --git a/modules/guard.ccm b/modules/guard.ccm
new file mode 100644
index 0000000..18c6d70
--- /dev/null
+++ b/modules/guard.ccm
@@ -0,0 +1,50 @@
+/*
+ * This source code is released into the public domain.
+ */
+
+module;
+
+#include <concepts>
+#include <optional>
+#include <utility>
+
+export module nihil:guard;
+
+namespace nihil {
+
+/*
+ * guard: invoke a callable when this object is destroyed; this is similar to
+ * scope_exit from the library fundamentals TS, which LLVM doesn't implement.
+ */
+export template<std::invocable F>
+struct guard final {
+ // Initialise the guard with a callable we will invoke later.
+ guard(F func) : _func(std::move(func)) {}
+
+ /*
+ * We are being destroyed, so call the callable.
+ * If the callable throws, std::terminate() will be called.
+ */
+ ~guard() {
+ if (_func)
+ std::invoke(*_func);
+ }
+
+ // Release the guard. This turns the destructor into a no-op.
+ void release() noexcept {
+ _func.reset();
+ }
+
+ // Not default-constructible or copyable.
+ guard() = delete;
+ guard(guard const &) = delete;
+ guard(guard &&) noexcept = delete;
+ guard &operator=(guard const &) = delete;
+ guard &operator=(guard &&) noexcept = delete;
+
+private:
+ // The callable to be invoked when we are destroyed.
+ std::optional<F> _func;
+};
+
+} // namespace nihil
diff --git a/modules/nihil.ccm b/modules/nihil.ccm
new file mode 100644
index 0000000..69cc282
--- /dev/null
+++ b/modules/nihil.ccm
@@ -0,0 +1,15 @@
+/*
+ * This source code is released into the public domain.
+ */
+
+module;
+
+export module nihil;
+
+export import :ctype;
+export import :generator;
+export import :generic_error;
+export import :getenv;
+export import :guard;
+export import :fd;
+export import :tabulate;
diff --git a/modules/tabulate.ccm b/modules/tabulate.ccm
new file mode 100644
index 0000000..debb784
--- /dev/null
+++ b/modules/tabulate.ccm
@@ -0,0 +1,282 @@
+/*
+ * This source code is released into the public domain.
+ */
+
+module;
+
+#include <algorithm>
+#include <cstdlib>
+#include <format>
+#include <ranges>
+#include <iterator>
+#include <vector>
+
+export module nihil:tabulate;
+
+import :ctype;
+import :generic_error;
+
+namespace nihil {
+
+/*
+ * tabulate: format the given range in an ASCII table and write the output
+ * to the given output iterator. The range's values will be converted to
+ * strings as if by std::format.
+ *
+ * tabulate is implemented by copying the range; this allows it to work on
+ * input/forward ranges at the cost of slightly increased memory use.
+ *
+ * The table spec is a string consisting of zero or more field formats,
+ * formatted as {flags:fieldname}; both flags and fieldname are optional.
+ * If there are fewer field formats than fields, the remaining fields
+ * are formatted as if by {:}.
+ *
+ * The following flags are supported:
+ *
+ * < left-align this column (default)
+ * > right-align this column
+ */
+
+// Exception thrown when a table spec is invalid.
+export struct table_spec_error : generic_error {
+ template<typename... Args>
+ table_spec_error(std::format_string<Args...> fmt, Args &&...args)
+ : generic_error(fmt, std::forward<Args>(args)...)
+ {}
+};
+
+/*
+ * The specification for a single field.
+ */
+template<typename Char>
+struct field_spec {
+ std::basic_string_view<Char> name;
+ std::size_t width = 0;
+ enum { left, right } align = left;
+
+ // Ensure the length of this field is at least the given width.
+ auto ensure_width(std::size_t newwidth) -> void
+ {
+ width = std::max(width, newwidth);
+ }
+
+ // Format an object to a string based on our field spec.
+ auto format(auto &&obj) const -> std::basic_string<Char>
+ {
+ std::basic_string<Char> format_string{'{', '}'};
+ return std::format(std::runtime_format(format_string), obj);
+ }
+
+ // Print a column value to an output iterator according to our field
+ // spec. If is_last is true, this is the last field on the line, so
+ // we won't output any trailling padding.
+ auto print(std::basic_string_view<Char> value,
+ std::output_iterator<Char> auto &out,
+ bool is_last)
+ const
+ {
+ auto padding = width - value.size();
+
+ if (align == right)
+ for (std::size_t i = 0; i < padding; ++i)
+ *out++ = ' ';
+
+ std::ranges::copy(value, out);
+
+ if (!is_last && align == left)
+ for (std::size_t i = 0; i < padding; ++i)
+ *out++ = ' ';
+ }
+};
+
+/*
+ * The specification for an entire table.
+ */
+template<typename Char>
+struct table_spec {
+ // Add a new field spec to this table.
+ auto add(field_spec<Char> field)
+ {
+ _fields.emplace_back(std::move(field));
+ }
+
+ // Return the field spec for a given field. If the field doesn't
+ // exist, this field and any intermediate fields will be created.
+ auto field(std::size_t fieldnr) -> field_spec<Char>&
+ {
+ if (_fields.size() < fieldnr + 1)
+ _fields.resize(fieldnr + 1);
+ return _fields.at(fieldnr);
+ }
+
+ // The number of columns in this table.
+ auto columns() const -> std::size_t
+ {
+ return _fields.size();
+ }
+
+ // Return all the fields in this table.
+ auto fields() const
+ {
+ return _fields;
+ }
+
+private:
+ std::vector<field_spec<Char>> _fields;
+};
+
+// Parse the field flags, e.g. '<'.
+template<typename Char,
+ std::input_iterator Iterator, std::sentinel_for<Iterator> Sentinel>
+auto parse_field_flags(field_spec<Char> &field, Iterator &pos, Sentinel end)
+ -> void
+{
+ while (pos < end) {
+ switch (*pos) {
+ case '<':
+ field.align = field_spec<Char>::left;
+ break;
+ case '>':
+ field.align = field_spec<Char>::right;
+ break;
+ case ':':
+ ++pos;
+ /*FALLTHROUGH*/
+ case '}':
+ return;
+ default:
+ throw table_spec_error(
+ "Invalid table spec: unknown flag character");
+ }
+
+ if (++pos == end)
+ throw table_spec_error("Invalid table spec: "
+ "unterminated field");
+ }
+}
+
+// Parse a complete field spec, e.g. "{<:NAME}".
+template<typename Char,
+ std::input_iterator Iterator, std::sentinel_for<Iterator> Sentinel>
+auto parse_field(Iterator &pos, Sentinel end)
+ -> field_spec<Char>
+{
+ auto field = field_spec<Char>{};
+
+ if (pos == end)
+ throw table_spec_error("Invalid table spec: empty field");
+
+ // The field spec should start with a '{'.
+ if (*pos != '{')
+ throw table_spec_error("Invalid table spec: expected '{{'");
+
+ if (++pos == end)
+ throw table_spec_error("Invalid table spec: unterminated field");
+
+ // This consumes 'pos' up to and including the ':'.
+ parse_field_flags(field, pos, end);
+
+ auto brace = std::ranges::find(pos, end, '}');
+ if (brace == end)
+ throw table_spec_error("Invalid table spec: expected '}}'");
+
+ field.name = std::basic_string_view<Char>(pos, brace);
+ pos = std::next(brace);
+
+ // The field must be at least as wide as its header.
+ field.width = field.name.size();
+
+ return field;
+}
+
+template<typename Char>
+auto parse_table_spec(std::basic_string_view<Char> spec) -> table_spec<Char>
+{
+ auto table = table_spec<Char>();
+
+ auto pos = std::ranges::begin(spec);
+ auto end = std::ranges::end(spec);
+
+ for (;;) {
+ // Skip leading whitespace
+ while (pos < end && is_c_space(*pos))
+ ++pos;
+
+ if (pos == end)
+ break;
+
+ table.add(parse_field<Char>(pos, end));
+ }
+
+ return table;
+}
+
+export template<typename Char,
+ std::ranges::range Range,
+ std::output_iterator<Char> Iterator>
+auto basic_tabulate(std::basic_string_view<Char> table_spec,
+ Range &&range,
+ Iterator &&out)
+ -> void
+{
+ // Parse the table spec.
+ auto table = parse_table_spec(table_spec);
+
+ // Create our copy of the input data.
+ auto data = std::vector<std::vector<std::basic_string<Char>>>();
+ // Reserve the first row for the header.
+ data.resize(1);
+
+ // Find the required length of each field.
+ for (auto &&row : range) {
+ // LLVM doesn't have std::enumerate_view yet
+ auto i = std::size_t{0};
+ auto &this_row = data.emplace_back();
+
+ for (auto &&column : row) {
+ auto &field = table.field(i);
+ auto &str = this_row.emplace_back(field.format(column));
+ field.ensure_width(str.size());
+ ++i;
+ }
+ }
+
+ // Add the header row.
+ for (auto &&field : table.fields())
+ data.at(0).emplace_back(std::from_range, field.name);
+
+ // Print the values.
+ for (auto &&row : data) {
+ for (std::size_t i = 0; i < row.size(); ++i) {
+ auto &field = table.field(i);
+ bool is_last = (i == row.size() - 1);
+
+ field.print(row[i], out, is_last);
+
+ if (!is_last)
+ *out++ = ' ';
+ }
+
+ *out++ = '\n';
+ }
+}
+
+export auto tabulate(std::string_view table_spec,
+ std::ranges::range auto &&range,
+ std::output_iterator<char> auto &&out)
+{
+ return basic_tabulate<char>(table_spec,
+ std::forward<decltype(range)>(range),
+ std::forward<decltype(out)>(out));
+}
+
+export auto wtabulate(std::wstring_view table_spec,
+ std::ranges::range auto &&range,
+ std::output_iterator<wchar_t> auto &&out)
+{
+ return basic_tabulate<wchar_t>(table_spec,
+ std::forward<decltype(range)>(range),
+ std::forward<decltype(out)>(out));
+}
+
+} // namespace nihil