diff options
Diffstat (limited to 'nihil.util')
| -rw-r--r-- | nihil.util/CMakeLists.txt | 6 | ||||
| -rw-r--r-- | nihil.util/error.ccm | 321 | ||||
| -rw-r--r-- | nihil.util/error.test.cc | 273 | ||||
| -rw-r--r-- | nihil.util/match.ccm | 18 | ||||
| -rw-r--r-- | nihil.util/match.test.cc | 32 | ||||
| -rw-r--r-- | nihil.util/monad.test.cc | 1 | ||||
| -rw-r--r-- | nihil.util/nihil.util.ccm | 3 | ||||
| -rw-r--r-- | nihil.util/parse_size.ccm | 2 | ||||
| -rw-r--r-- | nihil.util/parse_size.test.cc | 43 | ||||
| -rw-r--r-- | nihil.util/sys_error.ccm | 18 | ||||
| -rw-r--r-- | nihil.util/tabulate.ccm | 3 |
11 files changed, 695 insertions, 25 deletions
diff --git a/nihil.util/CMakeLists.txt b/nihil.util/CMakeLists.txt index 486a082..30a33b3 100644 --- a/nihil.util/CMakeLists.txt +++ b/nihil.util/CMakeLists.txt @@ -5,7 +5,6 @@ add_library(nihil.util STATIC) target_link_libraries(nihil.util PRIVATE nihil.std nihil.core - nihil.error ) target_sources(nihil.util @@ -15,13 +14,16 @@ target_sources(nihil.util capture_stream.ccm construct.ccm ctype.ccm + error.ccm flagset.ccm guard.ccm + match.ccm monad.ccm parse_size.ccm next_word.ccm save_errno.ccm skipws.ccm + sys_error.ccm tabulate.ccm uuid.ccm ) @@ -32,8 +34,10 @@ if(NIHIL_TESTS) add_executable(nihil.util.test capture_stream.test.cc ctype.test.cc + error.test.cc flagset.test.cc guard.test.cc + match.test.cc monad.test.cc parse_size.test.cc next_word.test.cc diff --git a/nihil.util/error.ccm b/nihil.util/error.ccm new file mode 100644 index 0000000..fd3aac9 --- /dev/null +++ b/nihil.util/error.ccm @@ -0,0 +1,321 @@ +// This source code is released into the public domain. +export module nihil.util: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 :match; + +namespace nihil { + +// Things which can be errors. +using error_t = std::variant<std::monostate, std::string, std::error_code, std::error_condition>; + +// A proxy class used when constructing errors. This has implicit constructors from various types, +// which means we don't have to handle every possible combination of types in error itself. +export struct error_proxy +{ + // Construct from... + + // ... a string_view + error_proxy(std::string_view const what) // NOLINT + : m_error(std::string(what)) + { + } + + // ... an std::string; so we can move the string into place if it's an rvalue. + error_proxy(auto &&what) // NOLINT + requires(std::same_as<std::remove_cvref_t<decltype(what)>, std::string>) + : m_error(std::forward<decltype(what)>(what)) + { + } + + // ... a C string + error_proxy(char const *what) // NOLINT + : m_error(std::string(what)) + { + } + + // ... an std::error_code + error_proxy(std::error_code const what) // NOLINT + : m_error(what) + { + } + + // ... an std::error_condition + error_proxy(std::error_condition const what) // NOLINT + : m_error(what) + { + } + + // ... an error code enum + template <typename T> + requires(std::is_error_code_enum<T>::value) + error_proxy(T what) // NOLINT + : m_error(make_error_code(what)) + { + } + + // ... an error condition enum + template <typename T> + requires(std::is_error_condition_enum<T>::value) + error_proxy(T what) // NOLINT + : m_error(make_error_condition(what)) + { + } + + // Not copyable. + error_proxy(error_proxy const &) = delete; + auto operator=(error_proxy const &) -> error_proxy & = delete; + + // Not movable. + error_proxy(error_proxy &&) = delete; + auto operator=(error_proxy &&) -> error_proxy & = delete; + + ~error_proxy() = default; + + // Let error extract the error_t. + [[nodiscard]] auto error() && -> error_t && + { + return std::move(m_error); + } + +private: + // The error. + error_t m_error; +}; + +// The error class. +export struct error : std::exception +{ + // Create an empty error, representing success. + error() = default; + + // Destroy an error. + ~error() override = default; + + // Create an error from an error proxy. + explicit error(error_proxy &&proxy) + : m_error(std::move(std::move(proxy).error())) + { + } + + // Create an error from an error proxy and an error cause. + error(error_proxy &&proxy, error cause) + : m_error(std::move(std::move(proxy).error())) + , m_cause(std::make_shared<error>(std::move(cause))) + { + } + + // Create an error from an error proxy and an error_proxy cause. + // For example, error("cannot open file", std::errc::permission_denied). + error(error_proxy &&proxy, error_proxy &&cause) + : m_error(std::move(std::move(proxy).error())) + , m_cause(std::make_shared<error>(std::move(cause))) + { + } + + // Copyable. + error(error const &) = default; + auto operator=(error const &) -> error & = default; + + // Movable. + error(error &&) noexcept = default; + auto operator=(error &&) noexcept -> error & = default; + + // Return the cause of this error. + [[nodiscard]] auto cause(this error const &self) -> std::shared_ptr<error> 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 &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(); + } + + return str; + } + + // Return this error's error_code, if any. + [[nodiscard]] auto code(this error const &self) -> std::optional<std::error_code> + { + auto const *code = std::get_if<std::error_code>(&self.m_error); + if (code != nullptr) + return {*code}; + return {}; + } + + // Return this error's error_condition, if any. + [[nodiscard]] auto condition(this error const &self) -> std::optional<std::error_condition> + { + auto const *condition = std::get_if<std::error_condition>(&self.m_error); + if (condition != nullptr) + 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::expected and std::unexpected, to make + // using it with std::expected easier. + + template <typename T> + operator std::expected<T, error> () && // NOLINT + { + return std::unexpected{std::move(*this)}; + } + + template<typename T> + operator std::expected<T, error> () const // NOLINT + { + return std::unexpected{*this}; + } + + operator std::unexpected<error> () && // NOLINT + { + return std::unexpected{std::move(*this)}; + } + + operator std::unexpected<error> () const // NOLINT + { + return std::unexpected{*this}; + } + +private: + // This error. + error_t m_error = make_error_code(std::errc()); + + // The cause of this error, if any. + std::shared_ptr<error> m_cause; + + // For std::exception::what(), we need to keep the string valid + // until we're destroyed. + mutable std::optional<std::string> m_what; + + // 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; + } + + // 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<decltype(rhs)>::value) + { + return lhs.code() == rhs; + } + + // 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<decltype(rhs)>::value) + { + 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(); + } +}; + +} // namespace nihil + +// Make error formattable. +export template <> +struct std::formatter<nihil::error, char> +{ + template <typename ParseContext> + constexpr auto parse(ParseContext &ctx) -> ParseContext::iterator + { + return ctx.begin(); + } + + template <typename FormatContext> + auto format(nihil::error const &e, FormatContext &ctx) const -> FormatContext::iterator + { + return std::ranges::copy(e.full_str(), ctx.out()).out; + } +}; diff --git a/nihil.util/error.test.cc b/nihil.util/error.test.cc new file mode 100644 index 0000000..f4ec1ee --- /dev/null +++ b/nihil.util/error.test.cc @@ -0,0 +1,273 @@ +// This source code is released into the public domain. + +#include <catch2/catch_test_macros.hpp> + +import nihil.std; +import nihil.core; +import nihil.util; + +namespace { +inline constexpr auto *test_tags = "[nihil][nihil.error]"; + +TEST_CASE("error: invariants", test_tags) +{ + static_assert(std::destructible<nihil::error>); + static_assert(std::default_initializable<nihil::error>); + static_assert(std::move_constructible<nihil::error>); + static_assert(std::copy_constructible<nihil::error>); + static_assert(std::equality_comparable<nihil::error>); + static_assert(std::totally_ordered<nihil::error>); + static_assert(std::swappable<nihil::error>); + static_assert(std::regular<nihil::error>); +} + +SCENARIO("A nihil::error can be constructed from a C string", test_tags) +{ + GIVEN ("An error object constructed from a string") { + auto e = nihil::error("an error"); + + THEN ("full_str() should return the string") { + REQUIRE(e.full_str() == "an error"); + } + } +} + +SCENARIO("A nihil::error can be constructed from an std::string lvalue", test_tags) +{ + GIVEN ("An error object constructed from an std::string lvalue") { + auto s = std::string("an error"); + auto e = nihil::error(s); + + THEN ("full_str() should return the string") { + REQUIRE(e.full_str() == "an error"); + } + } +} + +SCENARIO("A nihil::error can be constructed from an std::string rvalue", test_tags) +{ + GIVEN ("An error object constructed from an std::string rvalue") { + auto e = nihil::error(std::string("an error")); + + THEN ("full_str() should return the string") { + REQUIRE(e.full_str() == "an error"); + } + } +} + +SCENARIO("A nihil::error can be constructed from an std::string_view lvalue", test_tags) +{ + GIVEN ("An error object constructed from an std::string_view lvalue") { + auto s = std::string_view("an error"); + auto e = nihil::error(s); + + THEN ("full_str() should return the string") { + REQUIRE(e.full_str() == "an error"); + } + } +} + +SCENARIO("A nihil::error can be constructed from an std::string_view rvalue", test_tags) +{ + GIVEN ("An error object constructed from an std::string_view rvalue") { + auto e = nihil::error(std::string_view("an error")); + + THEN ("full_str() should return the string") { + REQUIRE(e.full_str() == "an error"); + } + } +} + +SCENARIO("A nihil::error can be constructed from an std::error_condition", test_tags) +{ + GIVEN ("An error object constructed from std::errc::invalid_argument") { + auto e = nihil::error(std::error_condition(std::errc::invalid_argument)); + + THEN ("full_str() should return the string") { + REQUIRE(e.full_str() == "Invalid argument"); + } + + AND_THEN ("condition() should return the error code") { + REQUIRE(e.condition().has_value()); + REQUIRE(*e.condition() == std::errc::invalid_argument); + } + + AND_THEN ("The error should be comparable to the error code") { + REQUIRE(e == std::errc::invalid_argument); + } + } +} + +SCENARIO("A nihil::error can be constructed from an std::errc", test_tags) +{ + GIVEN ("An error object constructed from std::errc::invalid_argument") { + auto e = nihil::error(std::errc::invalid_argument); + + THEN ("full_str() should return the string") { + REQUIRE(e.full_str() == "Invalid argument"); + } + + AND_THEN ("condition() should return the error code") { + REQUIRE(e.condition().has_value()); + REQUIRE(*e.condition() == std::errc::invalid_argument); + } + + AND_THEN ("The error should be comparable to the error code") { + REQUIRE(e == std::errc::invalid_argument); + } + } +} + +SCENARIO("A nihil::error can be constructed from a nihil::errc", test_tags) +{ + GIVEN ("An error object constructed from std::errc::invalid_argument") { + auto e = nihil::error(nihil::errc::incomplete_command); + + THEN ("full_str() should return the string") { + REQUIRE(e.full_str() == "Incomplete command"); + } + + AND_THEN ("condition() should return the error code") { + REQUIRE(e.condition().has_value()); + REQUIRE(*e.condition() == nihil::errc::incomplete_command); + } + + AND_THEN ("The error should be comparable to the error code") { + REQUIRE(e == nihil::errc::incomplete_command); + } + } +} + +SCENARIO("A nihil::error can be constructed with a cause", test_tags) +{ + GIVEN ("An error object constructed with a cause") { + auto e = nihil::error("an error", std::errc::invalid_argument); + + THEN ("full_str() should return the string") { + REQUIRE(e.full_str() == "an error: Invalid argument"); + } + + AND_THEN ("cause() should return the cause") { + REQUIRE(e.cause()); + REQUIRE(*e.cause() == std::errc::invalid_argument); + } + } +} + +SCENARIO("std::format with a nihil::error", test_tags) +{ + GIVEN ("A nihil::error with no cause") { + auto e = nihil::error("an error"); + + THEN ("std::format should return the string") { + REQUIRE(std::format("{}", e) == "an error"); + } + + AND_THEN ("std::format should return the same as full_str()") { + REQUIRE(std::format("{}", e) == e.full_str()); + } + } + + GIVEN ("A nihil::error with a cause") { + auto e = nihil::error("an error", std::errc::invalid_argument); + + THEN ("std::format should return the string") { + REQUIRE(std::format("{}", e) == "an error: Invalid argument"); + } + + AND_THEN ("std::format should return the same as full_str()") { + REQUIRE(std::format("{}", e) == e.full_str()); + } + } +} + +SCENARIO("Print a nihil::error to an std::ostream", test_tags) +{ + GIVEN ("A nihil::error with no cause") { + auto e = nihil::error("an error"); + + THEN ("The error should be printed to the stream") { + auto ss = std::stringstream(); + ss << e; + REQUIRE(ss.str() == "an error"); + } + } + + GIVEN ("A nihil::error with a cause") { + auto e = nihil::error("an error", std::errc::invalid_argument); + + THEN ("The error should be printed to the stream") { + auto ss = std::stringstream(); + ss << e; + REQUIRE(ss.str() == "an error: Invalid argument"); + } + } +} + +SCENARIO("Comparison of nihil::error with operator==", test_tags) +{ + GIVEN ("Two nihil::error objects constructed from the same string") { + auto e1 = nihil::error("an error"); + auto e2 = nihil::error("an error"); + + THEN ("The two objects should be equal") { + REQUIRE(e1 == e2); + } + } + + GIVEN ("Two nihil::error objects constructed from different strings") { + auto e1 = nihil::error("an error"); + auto e2 = nihil::error("another error"); + + THEN ("The two objects should not be equal") { + REQUIRE(e1 != e2); + } + } + + GIVEN ("Two nihil::error objects constructed from the same error code") { + auto e1 = nihil::error(std::errc::invalid_argument); + auto e2 = nihil::error(std::errc::invalid_argument); + + THEN ("The two objects should be equal") { + REQUIRE(e1 == e2); + } + } + + GIVEN ("Two nihil::error objects constructed from different error codes") { + auto e1 = nihil::error(std::errc::invalid_argument); + auto e2 = nihil::error(std::errc::permission_denied); + + THEN ("The two objects should not be equal") { + REQUIRE(e1 != e2); + } + } +} + +SCENARIO("Comparison of nihil::error with operator<", test_tags) +{ + GIVEN ("Two nihil::error objects constructed from the same string") { + auto e1 = nihil::error("aaa"); + auto e2 = nihil::error("zzz"); + + THEN ("aaa should be less than zzz") { + REQUIRE(e1 < e2); + } + } +} + +SCENARIO("Throwing and catching a nihil::error object", test_tags) +{ + GIVEN ("A nihil::error object") { + THEN ("We should be able to throw and catch the error") { + REQUIRE_THROWS_AS(throw nihil::error("an error"), nihil::error); + + try { + throw nihil::error("an error"); + } catch (nihil::error const &e) { + REQUIRE(e.full_str() == "an error"); + }; + } + } +} + +} // anonymous namespace diff --git a/nihil.util/match.ccm b/nihil.util/match.ccm new file mode 100644 index 0000000..b72416a --- /dev/null +++ b/nihil.util/match.ccm @@ -0,0 +1,18 @@ +// This source code is released into the public domain. +export module nihil.util:match; + +import nihil.std; + +namespace nihil { + +export template<class... Ts> +struct match : Ts... { using Ts::operator()...; }; + +export template<typename... Ts, typename... Fs> +[[nodiscard]] constexpr decltype(auto) operator| + (std::variant<Ts...> const &v, match<Fs...> const &match) +{ + return std::visit(match, v); +} + +} // namespace nihil diff --git a/nihil.util/match.test.cc b/nihil.util/match.test.cc new file mode 100644 index 0000000..6cc0e27 --- /dev/null +++ b/nihil.util/match.test.cc @@ -0,0 +1,32 @@ +// This source code is released into the public domain. + +#include <catch2/catch_test_macros.hpp> + +import nihil.std; +import nihil.util; + +namespace { +TEST_CASE("match", "[nihil]") +{ + using namespace nihil; + using namespace std::literals; + + auto v = std::variant<int, std::string>(42); + + auto s = v | match { + [](int) { return "int"s; }, + [](std::string const &) { return "string"s; } + }; + + REQUIRE(s == "int"); + + v = "test"s; + + s = v | match { + [](int) { return "int"s; }, + [](std::string const &) { return "string"s; } + }; + + REQUIRE(s == "string"); +} +} // anonymous namespace diff --git a/nihil.util/monad.test.cc b/nihil.util/monad.test.cc index bc9e406..7f39fca 100644 --- a/nihil.util/monad.test.cc +++ b/nihil.util/monad.test.cc @@ -3,7 +3,6 @@ #include <catch2/catch_test_macros.hpp> import nihil.std; -import nihil.error; import nihil.util; namespace { diff --git a/nihil.util/nihil.util.ccm b/nihil.util/nihil.util.ccm index 9e4795b..49e5687 100644 --- a/nihil.util/nihil.util.ccm +++ b/nihil.util/nihil.util.ccm @@ -4,12 +4,15 @@ export module nihil.util; export import :capture_stream; export import :construct; export import :ctype; +export import :error; export import :flagset; export import :guard; +export import :match; export import :monad; export import :parse_size; export import :next_word; export import :save_errno; export import :skipws; +export import :sys_error; export import :tabulate; export import :uuid; diff --git a/nihil.util/parse_size.ccm b/nihil.util/parse_size.ccm index 6219323..51bfb4f 100644 --- a/nihil.util/parse_size.ccm +++ b/nihil.util/parse_size.ccm @@ -3,9 +3,9 @@ export module nihil.util:parse_size; import nihil.std; import nihil.core; -import nihil.error; import :ctype; +import :error; import :monad; namespace nihil { diff --git a/nihil.util/parse_size.test.cc b/nihil.util/parse_size.test.cc index d79912a..ee97996 100644 --- a/nihil.util/parse_size.test.cc +++ b/nihil.util/parse_size.test.cc @@ -4,9 +4,9 @@ import nihil.std; import nihil.core; -import nihil.error; import nihil.util; +namespace { TEST_CASE("parse_size: empty value", "[nihil]") { using namespace nihil; @@ -20,40 +20,40 @@ TEST_CASE("parse_size: basic", "[nihil]") { using namespace nihil; - SECTION("bare number") { + SECTION ("bare number") { auto n = parse_size<std::uint64_t>("1024").value(); REQUIRE(n == 1024); } - SECTION("max value, unsigned") { + SECTION ("max value, unsigned") { auto n = parse_size<std::uint16_t>("65535").value(); REQUIRE(n == 65535); } - SECTION("max value, signed") { + SECTION ("max value, signed") { auto n = parse_size<std::uint16_t>("32767").value(); REQUIRE(n == 32767); } - SECTION("overflow by 1, unsigned") { + SECTION ("overflow by 1, unsigned") { auto n = parse_size<std::uint16_t>("65536"); REQUIRE(!n); REQUIRE(n.error() == std::errc::result_out_of_range); } - SECTION("overflow by 1, signed") { + SECTION ("overflow by 1, signed") { auto n = parse_size<std::int16_t>("32768"); REQUIRE(!n); REQUIRE(n.error() == std::errc::result_out_of_range); } - SECTION("overflow by many, unsigned") { + SECTION ("overflow by many, unsigned") { auto n = parse_size<std::uint16_t>("100000"); REQUIRE(!n); REQUIRE(n.error() == std::errc::result_out_of_range); } - SECTION("overflow by many, signed") { + SECTION ("overflow by many, signed") { auto n = parse_size<std::int16_t>("100000"); REQUIRE(!n); REQUIRE(n.error() == std::errc::result_out_of_range); @@ -79,27 +79,27 @@ TEST_CASE("parse_size: multipliers", "[nihil]") auto sf = static_cast<std::uint64_t>(4); - SECTION("k") { + SECTION ("k") { auto n = parse_size<std::uint64_t>("4k").value(); REQUIRE(n == sf * 1024); } - SECTION("m") { + SECTION ("m") { auto n = parse_size<std::uint64_t>("4m").value(); REQUIRE(n == sf * 1024 * 1024); } - SECTION("g") { + SECTION ("g") { auto n = parse_size<std::uint64_t>("4g").value(); REQUIRE(n == sf * 1024 * 1024 * 1024); } - SECTION("t") { + SECTION ("t") { auto n = parse_size<std::uint64_t>("4t").value(); REQUIRE(n == sf * 1024 * 1024 * 1024 * 1024); } - SECTION("p") { + SECTION ("p") { auto n = parse_size<std::uint64_t>("4p").value(); REQUIRE(n == sf * 1024 * 1024 * 1024 * 1024 * 1024); } @@ -109,13 +109,13 @@ TEST_CASE("parse_size: multiplier overflow", "[nihil]") { using namespace nihil; - SECTION("signed") { + SECTION ("signed") { auto n = parse_size<std::uint16_t>("64k"); REQUIRE(!n); REQUIRE(n.error() == std::errc::result_out_of_range); } - SECTION("unsigned") { + SECTION ("unsigned") { auto n = parse_size<std::int16_t>("32k"); REQUIRE(!n); REQUIRE(n.error() == std::errc::result_out_of_range); @@ -126,7 +126,7 @@ TEST_CASE("parse_size: wide", "[nihil]") { using namespace nihil; - SECTION("bare number") { + SECTION ("bare number") { auto n = parse_size<std::uint64_t>(L"1024").value(); REQUIRE(n == 1024); } @@ -138,28 +138,29 @@ TEST_CASE("parse_size: wide multipliers", "[nihil]") auto sf = static_cast<std::uint64_t>(4); - SECTION("k") { + SECTION ("k") { auto n = parse_size<std::uint64_t>(L"4k").value(); REQUIRE(n == sf * 1024); } - SECTION("m") { + SECTION ("m") { auto n = parse_size<std::uint64_t>(L"4m").value(); REQUIRE(n == sf * 1024 * 1024); } - SECTION("g") { + SECTION ("g") { auto n = parse_size<std::uint64_t>(L"4g").value(); REQUIRE(n == sf * 1024 * 1024 * 1024); } - SECTION("t") { + SECTION ("t") { auto n = parse_size<std::uint64_t>(L"4t").value(); REQUIRE(n == sf * 1024 * 1024 * 1024 * 1024); } - SECTION("p") { + SECTION ("p") { auto n = parse_size<std::uint64_t>(L"4p").value(); REQUIRE(n == sf * 1024 * 1024 * 1024 * 1024 * 1024); } } +} // anonymous namespace diff --git a/nihil.util/sys_error.ccm b/nihil.util/sys_error.ccm new file mode 100644 index 0000000..a39552e --- /dev/null +++ b/nihil.util/sys_error.ccm @@ -0,0 +1,18 @@ +// This source code is released into the public domain. +module; + +#include <cerrno> + +export module nihil.util:sys_error; + +import nihil.std; + +namespace nihil { + +// Allow access to errno without having to include <cerrno>. +export [[nodiscard]] auto sys_error() -> std::errc +{ + return static_cast<std::errc>(errno); // NOLINT +} + +} // namespace nihil diff --git a/nihil.util/tabulate.ccm b/nihil.util/tabulate.ccm index 8f5c22e..762e2a3 100644 --- a/nihil.util/tabulate.ccm +++ b/nihil.util/tabulate.ccm @@ -2,8 +2,9 @@ export module nihil.util:tabulate; import nihil.std; -import nihil.error; + import :ctype; +import :error; namespace nihil { |
