aboutsummaryrefslogtreecommitdiffstats
path: root/nihil.util
diff options
context:
space:
mode:
Diffstat (limited to 'nihil.util')
-rw-r--r--nihil.util/CMakeLists.txt6
-rw-r--r--nihil.util/error.ccm321
-rw-r--r--nihil.util/error.test.cc273
-rw-r--r--nihil.util/match.ccm18
-rw-r--r--nihil.util/match.test.cc32
-rw-r--r--nihil.util/monad.test.cc1
-rw-r--r--nihil.util/nihil.util.ccm3
-rw-r--r--nihil.util/parse_size.ccm2
-rw-r--r--nihil.util/parse_size.test.cc43
-rw-r--r--nihil.util/sys_error.ccm18
-rw-r--r--nihil.util/tabulate.ccm3
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 {