From 2e2d1bd3b6c7776b77c33b94f30ead89367a71e6 Mon Sep 17 00:00:00 2001 From: Lexi Winter Date: Tue, 1 Jul 2025 17:07:04 +0100 Subject: add nihil.std --- nihil.error/CMakeLists.txt | 6 +- nihil.error/error.cc | 160 -------------------- nihil.error/error.ccm | 361 +++++++++++++++++++++++++++++---------------- nihil.error/sys_error.ccm | 18 +++ nihil.error/test.cc | 35 ++--- 5 files changed, 267 insertions(+), 313 deletions(-) delete mode 100644 nihil.error/error.cc create mode 100644 nihil.error/sys_error.ccm (limited to 'nihil.error') diff --git a/nihil.error/CMakeLists.txt b/nihil.error/CMakeLists.txt index 37fb3ab..fd5da84 100644 --- a/nihil.error/CMakeLists.txt +++ b/nihil.error/CMakeLists.txt @@ -1,13 +1,11 @@ # This source code is released into the public domain. add_library(nihil.error STATIC) -target_link_libraries(nihil.error PRIVATE nihil.match) +target_link_libraries(nihil.error PRIVATE nihil.std nihil.match) target_sources(nihil.error PUBLIC FILE_SET modules TYPE CXX_MODULES FILES error.ccm - - PRIVATE - error.cc + sys_error.ccm ) if(NIHIL_TESTS) diff --git a/nihil.error/error.cc b/nihil.error/error.cc deleted file mode 100644 index e4023f9..0000000 --- a/nihil.error/error.cc +++ /dev/null @@ -1,160 +0,0 @@ -/* - * This source code is released into the public domain. - */ - -module; - -#include -#include -#include -#include -#include -#include - -module nihil.error; - -import nihil.match; - -namespace nihil { - -auto to_string(error const &self) -> std::string -{ - auto ret = self.str(); - - auto cause = self.cause(); - while (cause) { - ret += ": " + cause->str(); - cause = cause->cause(); - } - - return ret; -} - -error::error() -{ -} - -error::~error() = default; - -error::error(std::string_view what, error cause) - : m_error(std::string(what)) - , m_cause(std::make_shared(std::move(cause))) -{ -} -error::error(std::string_view what) - : m_error(std::string(what)) -{ -} - -error::error(std::error_condition what, error cause) - : m_error(what) - , m_cause(std::make_shared(std::move(cause))) -{ -} - -error::error(std::error_condition what) - : m_error(what) -{ -} - -error::error(std::error_code what, error cause) - : m_error(what) - , m_cause(std::make_shared(std::move(cause))) -{ -} - -error::error(std::error_code what) - : m_error(what) -{ -} - -error::error(error const &) = default; -error::error(error &&) noexcept = default; -auto error::operator=(this error &, error const &) -> error & = default; -auto error::operator=(this error &, error &&) noexcept -> error & = default; - -auto error::cause(this error const &self) -> std::shared_ptr -{ - if (self.m_cause) - return self.m_cause; - return {}; -} - -auto error::root_cause(this error const &self) -> error const & -{ - if (self.m_cause) - return self.m_cause->root_cause(); - - return self; //NOLINT(bugprone-return-const-ref-from-parameter) -} - -auto error::str(this error const &self) -> std::string -{ - return self.m_error | match { - [] (std::monostate) -> std::string { - return "No error"; - }, - [] (std::error_code const &m) { - return m.message(); - }, - [] (std::error_condition const &m) { - return m.message(); - }, - [] (std::string const &m) { - return m; - } - }; -} - -auto error::code(this error const &self) -> std::optional -{ - auto const *code = std::get_if(&self.m_error); - if (code) - return {*code}; - return {}; -} - -auto error::condition(this error const &self) - -> std::optional -{ - auto const *condition = std::get_if(&self.m_error); - if (condition) - return {*condition}; - return {}; -} - -auto error::what() const noexcept -> char const * -{ - if (!m_what) - m_what = to_string(*this); - - return m_what->c_str(); -} - -auto operator==(error const &lhs, error const &rhs) -> bool -{ - return lhs.m_error == rhs.m_error; -} - -auto operator<=>(error const &lhs, error const &rhs) -> std::strong_ordering -{ - return lhs.m_error <=> rhs.m_error; -} - -auto operator==(error const &lhs, std::error_code const &rhs) -> bool -{ - return lhs.code() == rhs; -} - -// Compare an error to an std::error_condition. -auto operator==(error const &lhs, std::error_condition const &rhs) -> bool -{ - return lhs.condition() == rhs; -} - -auto operator<<(std::ostream &strm, error const &e) -> std::ostream & -{ - return strm << to_string(e); -} - -} // namespace nihil diff --git a/nihil.error/error.ccm b/nihil.error/error.ccm index 12d47cc..7ed9d5c 100644 --- a/nihil.error/error.ccm +++ b/nihil.error/error.ccm @@ -1,132 +1,261 @@ -/* - * This source code is released into the public domain. - */ - -module; - -/* - * error: a type representing an error. - * - * An error consists of an immediate cause, which may be a string or - * std:error_code, and an optional proximate cause, which is another error - * object. Any number of error objects may be stacked. - * - * For example, a failure to open a file might be a stack of two errors: - * - * - string, "failed to open /etc/somefile", - * - std::error_code, "No such file or directory". - * - * Calling .str() will format the entire stack starting at that error, - * for example: "failed to open /etc/somefile: No such file or directory". - * - * Errors may be moved and (relatively) cheaply copied, since the cause - * chain is refcounted. - * - * error derives from std::exception, so it may be thrown and caught and - * provides a useful what(). When throwing errors, creating a derived - * error will make it easier to distinguish errors when catching them. - */ - -#include -#include -#include -#include -#include -#include -#include -#include - +// This source code is released into the public domain. export module nihil.error; +// error: a type representing an error. +// +// An error consists of an immediate cause, which may be a string or +// std:error_code, and an optional proximate cause, which is another error +// object. Any number of error objects may be stacked. +// +// For example, a failure to open a file might be a stack of two errors: +// +// - string, "failed to open /etc/somefile", +// - std::error_code, "No such file or directory". +// +// Calling .str() will format the entire stack starting at that error, +// for example: "failed to open /etc/somefile: No such file or directory". +// +// Errors may be moved and (relatively) cheaply copied, since the cause +// chain is refcounted. +// +// error derives from std::exception, so it may be thrown and caught and +// provides a useful what(). When throwing errors, creating a derived +// error will make it easier to distinguish errors when catching them. + +import nihil.std; +import nihil.match; + +export import :sys_error; + namespace nihil { // Things which can be errors. -using error_t = std::variant< - std::monostate, - std::string, - std::error_code, - std::error_condition - >; - -export struct error : std::exception { +using error_t = std::variant; + +export struct error : std::exception +{ // Create an empty error, representing success. - error(); + error() = default; // Destroy an error. - ~error() override; + ~error() override = default; // Create an error from a freeform string. - error(std::string_view what, error cause); - explicit error(std::string_view what); + explicit error(std::string_view what) + : m_error(std::string(what)) + { + } - template + // Create an error from a freeform string and a cause. + error(std::string_view what, error cause) + : m_error(std::string(what)) + , m_cause(std::make_shared(std::move(cause))) + { + } + + // Create an error from a freeform string and an error code enum cause. + template requires(std::is_error_code_enum::value || - std::is_error_condition_enum::value) + std::is_error_condition_enum::value) error(std::string_view what, Cause &&cause) : error(what, error(std::forward(cause))) - {} + { + } + + // Create an error from an std::error_condition. + explicit error(std::error_condition what) + : m_error(what) + { + } + + // Create an error from an std::error_condition and a cause. + error(std::error_condition what, error cause) + : m_error(what) + , m_cause(std::make_shared(std::move(cause))) + { + } // Create an error from an std::error_code. - error(std::error_condition what, error cause); - explicit error(std::error_condition what); + explicit error(std::error_code what) + : m_error(what) + { + } - // Create an error from an std::error_condition. - error(std::error_code what, error cause); - explicit error(std::error_code what); + // Create an error from an std::error_code and a cause. + error(std::error_code what, error cause) + : m_error(what) + , m_cause(std::make_shared(std::move(cause))) + { + } // Create an error from an std::error_code enum. + explicit error(auto errc) + requires(std::is_error_code_enum::value) + : error(make_error_code(errc)) + { + } + + // Create an error from an std::error_code enum and a cause/ error(auto errc, error cause) requires(std::is_error_code_enum::value) : error(make_error_code(errc), std::move(cause)) - {} + { + } + // Create an error from an std::error_condition enum. explicit error(auto errc) - requires(std::is_error_code_enum::value) - : error(make_error_code(errc)) - {} + requires(std::is_error_condition_enum::value) + : error(make_error_condition(errc)) + { + } - // Create an error from an std::error_condition enum. + // Create an error from an std::error_condition enum and a cause. error(auto errc, error cause) requires(std::is_error_condition_enum::value) : error(make_error_condition(errc), std::move(cause)) - {} - - explicit error(auto errc) - requires(std::is_error_condition_enum::value) - : error(make_error_condition(errc)) - {} + { + } - error(error const &); - error(error &&) noexcept; + // Copyable. + error(error const &) = default; + auto operator=(error const &) -> error & = default; - auto operator=(this error &, error const &) -> error &; - auto operator=(this error &, error &&) noexcept -> error &; + // Movable. + error(error &&) noexcept = default; + auto operator=(error &&) noexcept -> error & = default; // Return the cause of this error. - [[nodiscard]] auto cause(this error const &) -> std::shared_ptr; + [[nodiscard]] auto cause(this error const &self) -> std::shared_ptr const & + { + return self.m_cause; + } // Return the root cause of this error, which may be this object. // For errors caused by an OS error, this will typically be the // error_code error. - [[nodiscard]] auto root_cause(this error const &) -> error const &; + [[nodiscard]] auto root_cause(this error const &self) -> error const & + { + auto const *cause = &self; + while (cause->m_cause) + cause = cause->m_cause.get(); + return *cause; + } + + // Format this error without its cause as a string. + [[nodiscard]] auto this_str(this error const &self) -> std::string + { + return self.m_error | match { + [] (std::monostate) -> std::string { + return "No error"; + }, + [] (std::error_code const &m) { + return m.message(); + }, + [] (std::error_condition const &m) { + return m.message(); + }, + [] (std::string const &m) { + return m; + } + }; + } + + // Format this error and its cause as a string. + [[nodiscard]] auto full_str(this error const &self) -> std::string + { + auto str = self.this_str(); + + auto cause = self.cause(); + while (cause) { + str += ": " + cause->this_str(); + cause = cause->cause(); + } - // Format this error as a string. - [[nodiscard]] auto str(this error const &) -> std::string; + return str; + } // Return this error's error_code, if any. - [[nodiscard]] auto code(this error const &) - -> std::optional; + [[nodiscard]] auto code(this error const &self) -> std::optional + { + auto const *code = std::get_if(&self.m_error); + if (code) + return {*code}; + return {}; + } // Return this error's error_condition, if any. - [[nodiscard]] auto condition(this error const &) - -> std::optional; + [[nodiscard]] auto condition(this error const &self) -> std::optional + { + auto const *condition = std::get_if(&self.m_error); + if (condition) + return {*condition}; + return {}; + } + + // Format this error and its cause as a C string and return it. This is for + // compatibility with std::exception. The lifetime of the returned string + // is the same as the error object. + [[nodiscard]] auto what() const noexcept -> char const * final + { + if (!m_what) + m_what = this->full_str(); + return m_what->c_str(); + } + + // Allow error to be implicitly converted to std::expectde and std::unexpected, to make using it + // with std::expected easier. + + template + operator std::expected () && // NOLINT + { + return std::unexpected{std::move(*this)}; + } + + template + operator std::expected () const // NOLINT + { + return std::unexpected{*this}; + } + + operator std::unexpected () && // NOLINT + { + return std::unexpected{std::move(*this)}; + } - [[nodiscard]] auto what() const noexcept -> char const * final; + operator std::unexpected () const // NOLINT + { + return std::unexpected{*this}; + } private: - friend auto operator==(error const &, error const &) -> bool; - friend auto operator<=>(error const &, error const &) - -> std::strong_ordering; + // Equality comparison. + [[nodiscard]] friend auto operator==(error const &lhs, error const &rhs) -> bool + { + return lhs.m_error == rhs.m_error; + } + + [[nodiscard]] friend auto operator<=>(error const &lhs, error const &rhs) -> std::strong_ordering + { + return lhs.m_error <=> rhs.m_error; + } + + // Compare an error with an std::error_code. + [[nodiscard]] friend auto operator==(error const &lhs, std::error_code const &rhs) -> bool + { + return lhs.code() == rhs; + } + + // Compare an error to an std::error_condition. + [[nodiscard]] friend auto operator==(error const &lhs, std::error_condition const &rhs) -> bool + { + return lhs.condition() == rhs; + } + + // Print an error to a stream. + friend auto operator<<(std::ostream &strm, error const &e) -> std::ostream & + { + return strm << e.full_str(); + } // This error. error_t m_error = make_error_code(std::errc()); @@ -137,63 +266,37 @@ private: // For std::exception::what(), we need to keep the string valid // until we're destroyed. mutable std::optional m_what; -}; - -/* - * Format an error and its cause(s) as a string. - */ -export [[nodiscard]] auto to_string(error const &) -> std::string; - -// Compare an error to another error. This only compares the error itself, -// not any nested causes. -export [[nodiscard]] auto operator==(error const &, error const &) - -> bool; -export [[nodiscard]] auto operator<=>(error const &, error const &) - -> std::strong_ordering; - -// Compare an error to an std::error_code. -export [[nodiscard]] auto operator==(error const &, std::error_code const &) - -> bool; - -// Compare an error to an std::error_condition. -export [[nodiscard]] auto operator==(error const &, - std::error_condition const &) - -> bool; - -// Compare an error to an std::error_code enum. -export [[nodiscard]] auto operator==(error const &lhs, auto rhs) -> bool -requires(std::is_error_code_enum::value) -{ - return lhs.code() == rhs; -} -// Compare an error to an std::error_condition enum. -export [[nodiscard]] auto operator==(error const &lhs, auto rhs) -> bool -requires(std::is_error_condition_enum::value) -{ - return lhs.condition() == rhs; -} + // Compare an error to an std::error_code enum. + [[nodiscard]] friend auto operator==(error const &lhs, auto rhs) -> bool + requires(std::is_error_code_enum::value) + { + return lhs.code() == rhs; + } -// Print an error to an ostream. -export [[nodiscard]] auto operator<<(std::ostream &, error const &) - -> std::ostream &; + // Compare an error to an std::error_condition enum. + [[nodiscard]] friend auto operator==(error const &lhs, auto rhs) -> bool + requires(std::is_error_condition_enum::value) + { + return lhs.condition() == rhs; + } +}; } // namespace nihil // Make error formattable. -export template<> +export template <> struct std::formatter { - template + template constexpr auto parse(ParseContext &ctx) -> ParseContext::iterator { return ctx.begin(); } - template - auto format(nihil::error const &e, FormatContext &ctx) const - -> FormatContext::iterator + template + auto format(nihil::error const &e, FormatContext &ctx) const -> FormatContext::iterator { - return std::ranges::copy(to_string(e), ctx.out()).out; + return std::ranges::copy(e.full_str(), ctx.out()).out; } }; diff --git a/nihil.error/sys_error.ccm b/nihil.error/sys_error.ccm new file mode 100644 index 0000000..102f4c5 --- /dev/null +++ b/nihil.error/sys_error.ccm @@ -0,0 +1,18 @@ +// This source code is released into the public domain. +module; + +#include + +export module nihil.error:sys_error; + +import nihil.std; + +namespace nihil { + +// Allow access to errno without having to include . +export [[nodiscard]] auto sys_error() -> std::errc +{ + return static_cast(errno); +} + +} // namespace nihil diff --git a/nihil.error/test.cc b/nihil.error/test.cc index 9b3eef1..0f4f93f 100644 --- a/nihil.error/test.cc +++ b/nihil.error/test.cc @@ -1,13 +1,8 @@ -/* - * This source code is released into the public domain. - */ - -#include -#include -#include +// This source code is released into the public domain. #include +import nihil.std; import nihil.error; TEST_CASE("error: invariants", "[nihil]") @@ -27,9 +22,9 @@ TEST_CASE("error: construct from string", "[nihil]") using namespace nihil; auto e = error("an error"); - REQUIRE(e.str() == to_string(e)); - REQUIRE(to_string(e) == "an error"); - REQUIRE(std::format("{}", e) == to_string(e)); + REQUIRE(e.full_str() == "an error"); + REQUIRE(e.this_str() == e.full_str()); + REQUIRE(std::format("{}", e) == e.full_str()); } TEST_CASE("error: construct from std::error_condition", "[nihil]") @@ -46,9 +41,9 @@ TEST_CASE("error: construct from std::error_condition", "[nihil]") REQUIRE(e == std::errc::invalid_argument); REQUIRE(e != std::errc::no_such_file_or_directory); - REQUIRE(e.str() == to_string(e)); - REQUIRE(to_string(e) == std::strerror(EINVAL)); - REQUIRE(std::format("{}", e) == to_string(e)); + REQUIRE(e.full_str() == std::strerror(EINVAL)); + REQUIRE(e.this_str() == e.full_str()); + REQUIRE(std::format("{}", e) == e.full_str()); } TEST_CASE("error: construct from std::errc", "[nihil]") @@ -64,9 +59,9 @@ TEST_CASE("error: construct from std::errc", "[nihil]") REQUIRE(e == std::errc::invalid_argument); REQUIRE(e != std::errc::no_such_file_or_directory); - REQUIRE(e.str() == to_string(e)); - REQUIRE(to_string(e) == std::strerror(EINVAL)); - REQUIRE(std::format("{}", e) == to_string(e)); + REQUIRE(e.full_str() == std::strerror(EINVAL)); + REQUIRE(e.this_str() == e.full_str()); + REQUIRE(std::format("{}", e) == e.full_str()); } TEST_CASE("error: compound error", "[nihil]") @@ -82,10 +77,10 @@ TEST_CASE("error: compound error", "[nihil]") REQUIRE(e.condition().has_value() == false); REQUIRE(*e.cause() == std::errc::no_such_file_or_directory); - REQUIRE(e.str() == "cannot open file"); - REQUIRE(to_string(e) == ("cannot open file: "s + - std::strerror(ENOENT))); - REQUIRE(std::format("{}", e) == to_string(e)); + + REQUIRE(e.full_str() == ("cannot open file: "s + std::strerror(ENOENT))); + REQUIRE(e.this_str() == "cannot open file"); + REQUIRE(std::format("{}", e) == e.full_str()); } TEST_CASE("error: operator== with strings", "[nihil]") -- cgit v1.2.3