From 5adeb648f74c1771164c0686d6e0fc584cf36d9e Mon Sep 17 00:00:00 2001 From: Lexi Winter Date: Wed, 2 Jul 2025 04:00:06 +0100 Subject: move everything from util to core --- CMakeLists.txt | 1 - nihil.cli/CMakeLists.txt | 6 +- nihil.cli/command_tree.ccm | 3 +- nihil.cli/dispatch_command.test.cc | 2 +- nihil.cli/usage_error.ccm | 2 +- nihil.config/option.cc | 2 +- nihil.config/option.ccm | 2 +- nihil.config/read.ccm | 3 +- nihil.config/store.cc | 2 +- nihil.config/store.ccm | 2 +- nihil.config/string.ccm | 3 +- nihil.config/write.ccm | 3 +- nihil.core/CMakeLists.txt | 16 + nihil.core/capture_stream.ccm | 55 +++ nihil.core/capture_stream.test.cc | 44 ++ nihil.core/construct.ccm | 22 + nihil.core/ctype.ccm | 72 +++ nihil.core/ctype.test.cc | 376 +++++++++++++++ nihil.core/error.ccm | 321 +++++++++++++ nihil.core/error.test.cc | 273 +++++++++++ nihil.core/flagset.ccm | 200 ++++++++ nihil.core/flagset.test.cc | 144 ++++++ nihil.core/guard.ccm | 41 ++ nihil.core/guard.test.cc | 16 + nihil.core/match.ccm | 18 + nihil.core/match.test.cc | 32 ++ nihil.core/monad.ccm | 282 +++++++++++ nihil.core/monad.test.cc | 65 +++ nihil.core/next_word.ccm | 35 ++ nihil.core/next_word.test.cc | 65 +++ nihil.core/nihil.core.ccm | 15 + nihil.core/parse_size.ccm | 90 ++++ nihil.core/parse_size.test.cc | 165 +++++++ nihil.core/save_errno.ccm | 35 ++ nihil.core/skipws.ccm | 26 ++ nihil.core/skipws.test.cc | 48 ++ nihil.core/sys_error.ccm | 18 + nihil.core/tabulate.ccm | 298 ++++++++++++ nihil.core/tabulate.test.cc | 72 +++ nihil.core/uuid.ccm | 768 ++++++++++++++++++++++++++++++ nihil.core/uuid.test.cc | 923 +++++++++++++++++++++++++++++++++++++ nihil.posix/CMakeLists.txt | 3 +- nihil.posix/ensure_dir.ccm | 2 +- nihil.posix/execlp.ccm | 3 +- nihil.posix/execv.ccm | 2 +- nihil.posix/execvp.ccm | 2 +- nihil.posix/fd.ccm | 2 +- nihil.posix/fd.test.cc | 2 +- nihil.posix/find_in_path.ccm | 2 +- nihil.posix/find_in_path.test.cc | 2 +- nihil.posix/getenv.ccm | 2 +- nihil.posix/getenv.test.cc | 2 +- nihil.posix/open.ccm | 2 +- nihil.posix/open.test.cc | 2 +- nihil.posix/open_in_path.ccm | 3 +- nihil.posix/open_in_path.test.cc | 2 +- nihil.posix/process.ccm | 2 +- nihil.posix/read_file.ccm | 3 +- nihil.posix/rename.ccm | 2 +- nihil.posix/spawn.ccm | 3 +- nihil.posix/stat.ccm | 3 +- nihil.posix/stat.test.cc | 2 +- nihil.posix/tempfile.ccm | 3 +- nihil.posix/unistd.ccm | 2 +- nihil.posix/unlink.ccm | 2 +- nihil.posix/write_file.ccm | 3 +- nihil.ucl/CMakeLists.txt | 3 +- nihil.ucl/integer.ccm | 2 +- nihil.ucl/object_cast.ccm | 3 +- nihil.ucl/parser.ccm | 3 +- nihil.ucl/type.ccm | 2 +- nihil.util/CMakeLists.txt | 59 --- nihil.util/capture_stream.ccm | 55 --- nihil.util/capture_stream.test.cc | 44 -- nihil.util/construct.ccm | 22 - nihil.util/ctype.ccm | 72 --- nihil.util/ctype.test.cc | 376 --------------- nihil.util/error.ccm | 321 ------------- nihil.util/error.test.cc | 273 ----------- nihil.util/flagset.ccm | 200 -------- nihil.util/flagset.test.cc | 144 ------ nihil.util/guard.ccm | 41 -- nihil.util/guard.test.cc | 16 - nihil.util/match.ccm | 18 - nihil.util/match.test.cc | 32 -- nihil.util/monad.ccm | 282 ----------- nihil.util/monad.test.cc | 65 --- nihil.util/next_word.ccm | 32 -- nihil.util/next_word.test.cc | 65 --- nihil.util/nihil.util.ccm | 18 - nihil.util/parse_size.ccm | 90 ---- nihil.util/parse_size.test.cc | 166 ------- nihil.util/save_errno.ccm | 35 -- nihil.util/skipws.ccm | 25 - nihil.util/skipws.test.cc | 46 -- nihil.util/sys_error.ccm | 18 - nihil.util/tabulate.ccm | 298 ------------ nihil.util/tabulate.test.cc | 70 --- nihil.util/uuid.ccm | 768 ------------------------------ nihil.util/uuid.test.cc | 923 ------------------------------------- 100 files changed, 4593 insertions(+), 4618 deletions(-) create mode 100644 nihil.core/capture_stream.ccm create mode 100644 nihil.core/capture_stream.test.cc create mode 100644 nihil.core/construct.ccm create mode 100644 nihil.core/ctype.ccm create mode 100644 nihil.core/ctype.test.cc create mode 100644 nihil.core/error.ccm create mode 100644 nihil.core/error.test.cc create mode 100644 nihil.core/flagset.ccm create mode 100644 nihil.core/flagset.test.cc create mode 100644 nihil.core/guard.ccm create mode 100644 nihil.core/guard.test.cc create mode 100644 nihil.core/match.ccm create mode 100644 nihil.core/match.test.cc create mode 100644 nihil.core/monad.ccm create mode 100644 nihil.core/monad.test.cc create mode 100644 nihil.core/next_word.ccm create mode 100644 nihil.core/next_word.test.cc create mode 100644 nihil.core/parse_size.ccm create mode 100644 nihil.core/parse_size.test.cc create mode 100644 nihil.core/save_errno.ccm create mode 100644 nihil.core/skipws.ccm create mode 100644 nihil.core/skipws.test.cc create mode 100644 nihil.core/sys_error.ccm create mode 100644 nihil.core/tabulate.ccm create mode 100644 nihil.core/tabulate.test.cc create mode 100644 nihil.core/uuid.ccm create mode 100644 nihil.core/uuid.test.cc delete mode 100644 nihil.util/CMakeLists.txt delete mode 100644 nihil.util/capture_stream.ccm delete mode 100644 nihil.util/capture_stream.test.cc delete mode 100644 nihil.util/construct.ccm delete mode 100644 nihil.util/ctype.ccm delete mode 100644 nihil.util/ctype.test.cc delete mode 100644 nihil.util/error.ccm delete mode 100644 nihil.util/error.test.cc delete mode 100644 nihil.util/flagset.ccm delete mode 100644 nihil.util/flagset.test.cc delete mode 100644 nihil.util/guard.ccm delete mode 100644 nihil.util/guard.test.cc delete mode 100644 nihil.util/match.ccm delete mode 100644 nihil.util/match.test.cc delete mode 100644 nihil.util/monad.ccm delete mode 100644 nihil.util/monad.test.cc delete mode 100644 nihil.util/next_word.ccm delete mode 100644 nihil.util/next_word.test.cc delete mode 100644 nihil.util/nihil.util.ccm delete mode 100644 nihil.util/parse_size.ccm delete mode 100644 nihil.util/parse_size.test.cc delete mode 100644 nihil.util/save_errno.ccm delete mode 100644 nihil.util/skipws.ccm delete mode 100644 nihil.util/skipws.test.cc delete mode 100644 nihil.util/sys_error.ccm delete mode 100644 nihil.util/tabulate.ccm delete mode 100644 nihil.util/tabulate.test.cc delete mode 100644 nihil.util/uuid.ccm delete mode 100644 nihil.util/uuid.test.cc diff --git a/CMakeLists.txt b/CMakeLists.txt index 84927d8..af2bb8c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -66,7 +66,6 @@ add_subdirectory(nihil.cli) add_subdirectory(nihil.core) add_subdirectory(nihil.generator) add_subdirectory(nihil.posix) -add_subdirectory(nihil.util) if(NIHIL_UCL) add_subdirectory(nihil.ucl) diff --git a/nihil.cli/CMakeLists.txt b/nihil.cli/CMakeLists.txt index 22fc1de..e87d3d4 100644 --- a/nihil.cli/CMakeLists.txt +++ b/nihil.cli/CMakeLists.txt @@ -1,12 +1,14 @@ # This source code is released into the public domain. add_library(nihil.cli STATIC) + target_link_libraries(nihil.cli PRIVATE nihil.std + nihil.core nihil.generator nihil.posix - nihil.util ) + target_sources(nihil.cli PUBLIC FILE_SET modules TYPE CXX_MODULES FILES nihil.cli.ccm @@ -29,8 +31,10 @@ if(NIHIL_TESTS) command_tree.test.cc dispatch_command.test.cc ) + target_link_libraries(nihil.cli.test PRIVATE nihil.std + nihil.core nihil.cli Catch2::Catch2WithMain ) diff --git a/nihil.cli/command_tree.ccm b/nihil.cli/command_tree.ccm index 8ed16c7..844b2cf 100644 --- a/nihil.cli/command_tree.ccm +++ b/nihil.cli/command_tree.ccm @@ -3,7 +3,8 @@ export module nihil.cli:command_tree; import nihil.std; import nihil.generator; -import nihil.util; +import nihil.core; + import :command; import :registry; diff --git a/nihil.cli/dispatch_command.test.cc b/nihil.cli/dispatch_command.test.cc index 8253bdc..a74fa36 100644 --- a/nihil.cli/dispatch_command.test.cc +++ b/nihil.cli/dispatch_command.test.cc @@ -6,7 +6,7 @@ import nihil.std; import nihil.cli; -import nihil.util; +import nihil.core; namespace { diff --git a/nihil.cli/usage_error.ccm b/nihil.cli/usage_error.ccm index 8502755..497af89 100644 --- a/nihil.cli/usage_error.ccm +++ b/nihil.cli/usage_error.ccm @@ -2,7 +2,7 @@ export module nihil.cli:usage_error; import nihil.std; -import nihil.util; +import nihil.core; namespace nihil { diff --git a/nihil.config/option.cc b/nihil.config/option.cc index 5f163ac..5a09ca8 100644 --- a/nihil.config/option.cc +++ b/nihil.config/option.cc @@ -2,8 +2,8 @@ module nihil.config; import nihil.std; +import nihil.core; import nihil.ucl; -import nihil.util; namespace nihil::config { diff --git a/nihil.config/option.ccm b/nihil.config/option.ccm index 8c64653..24a3339 100644 --- a/nihil.config/option.ccm +++ b/nihil.config/option.ccm @@ -2,7 +2,7 @@ export module nihil.config:option; import nihil.std; -import nihil.util; +import nihil.core; import nihil.ucl; namespace nihil::config { diff --git a/nihil.config/read.ccm b/nihil.config/read.ccm index 1c1b897..94e9157 100644 --- a/nihil.config/read.ccm +++ b/nihil.config/read.ccm @@ -2,9 +2,10 @@ export module nihil.config:read; import nihil.std; +import nihil.core; import nihil.posix; import nihil.ucl; -import nihil.util; + import :option; import :store; diff --git a/nihil.config/store.cc b/nihil.config/store.cc index ed8de1d..08850c4 100644 --- a/nihil.config/store.cc +++ b/nihil.config/store.cc @@ -2,8 +2,8 @@ module nihil.config; import nihil.std; +import nihil.core; import nihil.generator; -import nihil.util; namespace nihil::config { diff --git a/nihil.config/store.ccm b/nihil.config/store.ccm index e4229a7..2ceb52b 100644 --- a/nihil.config/store.ccm +++ b/nihil.config/store.ccm @@ -5,7 +5,7 @@ export module nihil.config:store; import nihil.std; import nihil.generator; -import nihil.util; +import nihil.core; namespace nihil::config { diff --git a/nihil.config/string.ccm b/nihil.config/string.ccm index c5e7284..8a114d2 100644 --- a/nihil.config/string.ccm +++ b/nihil.config/string.ccm @@ -2,8 +2,9 @@ export module nihil.config:string; import nihil.std; -import nihil.util; +import nihil.core; import nihil.ucl; + import :option; namespace nihil::config { diff --git a/nihil.config/write.ccm b/nihil.config/write.ccm index be2cfd7..00d7b52 100644 --- a/nihil.config/write.ccm +++ b/nihil.config/write.ccm @@ -2,9 +2,10 @@ export module nihil.config:write; import nihil.std; +import nihil.core; import nihil.posix; import nihil.ucl; -import nihil.util; + import :option; import :store; diff --git a/nihil.core/CMakeLists.txt b/nihil.core/CMakeLists.txt index 637b825..e9998a3 100644 --- a/nihil.core/CMakeLists.txt +++ b/nihil.core/CMakeLists.txt @@ -6,6 +6,22 @@ target_include_directories(nihil.core PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) target_sources(nihil.core PUBLIC FILE_SET modules TYPE CXX_MODULES FILES nihil.core.ccm + + capture_stream.ccm + construct.ccm + ctype.ccm errc.ccm + error.ccm features.ccm + flagset.ccm + guard.ccm + match.ccm + monad.ccm + next_word.ccm + parse_size.ccm + save_errno.ccm + skipws.ccm + sys_error.ccm + tabulate.ccm + uuid.ccm ) diff --git a/nihil.core/capture_stream.ccm b/nihil.core/capture_stream.ccm new file mode 100644 index 0000000..5ab4f2d --- /dev/null +++ b/nihil.core/capture_stream.ccm @@ -0,0 +1,55 @@ +// This source code is released into the public domain. +export module nihil.core:capture_stream; + +import nihil.std; + +namespace nihil { + +// Capture output written to a stream and redirect it to an internal string +// buffer. Call .str() to get the data written. Call .release() to stop +// capturing (or simply delete the capture_stream object). +export template +struct capture_stream { + capture_stream(std::basic_ostream &stream) + : m_stream(&stream) + { + m_old_streambuf = m_stream->rdbuf(); + m_stream->rdbuf(m_buffer.rdbuf()); + } + + ~capture_stream() + { + if (m_old_streambuf == nullptr) + return; + m_stream->rdbuf(m_old_streambuf); + } + + /* + * Release this capture, returning the stream to its previous state. + */ + auto release(this capture_stream &self) -> void + { + if (self.m_old_streambuf == nullptr) + throw std::logic_error( + "release() called on empty capture_stream"); + + self.m_stream->rdbuf(self.m_old_streambuf); + self.m_old_streambuf = nullptr; + } + + /* + * Get the data which has been written to the stream. + */ + [[nodiscard]] auto str(this capture_stream const &self) + -> std::basic_string_view + { + return self.m_buffer.view(); + } + +private: + std::basic_ostringstream m_buffer; + std::basic_ostream *m_stream; + std::streambuf *m_old_streambuf; +}; + +} // namespace nihil diff --git a/nihil.core/capture_stream.test.cc b/nihil.core/capture_stream.test.cc new file mode 100644 index 0000000..4dd7db6 --- /dev/null +++ b/nihil.core/capture_stream.test.cc @@ -0,0 +1,44 @@ +// This source code is released into the public domain. + +#include + +import nihil.std; +import nihil.core; + +namespace { +TEST_CASE("nihil.util: capture", "[nihil][nihil.util]") +{ + SECTION("std::cout with release()") { + auto cap = nihil::capture_stream(std::cout); + + std::cout << 1 << '+' << 1 << '=' << (1 + 1) << '\n'; + REQUIRE(cap.str() == "1+1=2\n"); + + cap.release(); + REQUIRE(cap.str() == "1+1=2\n"); + } + + SECTION("std::cout with dtor") { + auto cap = nihil::capture_stream(std::cout); + std::cout << 1 << '+' << 1 << '=' << (1 + 1) << '\n'; + REQUIRE(cap.str() == "1+1=2\n"); + } + + SECTION("std::cerr with release()") { + auto cap = nihil::capture_stream(std::cerr); + + std::cerr << 1 << '+' << 1 << '=' << (1 + 1) << '\n'; + REQUIRE(cap.str() == "1+1=2\n"); + + cap.release(); + REQUIRE(cap.str() == "1+1=2\n"); + } + + SECTION("std::cerr with dtor") { + auto cap = nihil::capture_stream(std::cerr); + std::cerr << 1 << '+' << 1 << '=' << (1 + 1) << '\n'; + REQUIRE(cap.str() == "1+1=2\n"); + } +} + +} // anonymous namespace diff --git a/nihil.core/construct.ccm b/nihil.core/construct.ccm new file mode 100644 index 0000000..5dd4422 --- /dev/null +++ b/nihil.core/construct.ccm @@ -0,0 +1,22 @@ +// This source code is released into the public domain. +export module nihil.core:construct; + +import nihil.std; + +namespace nihil { + +// A functor that constructs objects of an arbitrary type. +// Useful for std::views::transform. +template +struct construct_fn final +{ + [[nodiscard]] auto operator()(this construct_fn const &, auto &&...args) -> T + { + return T(std::forward(args)...); + } +}; + +export template +inline constexpr auto construct = construct_fn{}; + +} // namespace nihil diff --git a/nihil.core/ctype.ccm b/nihil.core/ctype.ccm new file mode 100644 index 0000000..365edc0 --- /dev/null +++ b/nihil.core/ctype.ccm @@ -0,0 +1,72 @@ +// This source code is released into the public domain. +export module nihil.core:ctype; + +import nihil.std; + +namespace nihil { + +/* + * ctype_is: wrap std::ctype::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 +{ + explicit ctype_is(std::ctype_base::mask mask_, + std::locale const &locale_ = std::locale()) noexcept + : m_mask(mask_) + , m_locale(locale_) + { + } + + [[nodiscard]] auto operator()(this ctype_is const &self, std::integral auto c) + { + using ctype = std::ctype; + auto &facet = std::use_facet(self.m_locale); + return facet.is(self.m_mask, c); + } + +private: + std::ctype_base::mask m_mask; + std::locale m_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. + +//NOLINTBEGIN: Technically, std::locale::classic() can throw. Assume it doesn't. +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()); +//NOLINTEND + +} // namespace nihil diff --git a/nihil.core/ctype.test.cc b/nihil.core/ctype.test.cc new file mode 100644 index 0000000..df73707 --- /dev/null +++ b/nihil.core/ctype.test.cc @@ -0,0 +1,376 @@ +// This source code is released into the public domain. + +#include + +import nihil.std; +import nihil.core; + +namespace { + +TEST_CASE("ctype: space", "[ctype]") { + auto is_utf8_space = + nihil::ctype_is(std::ctype_base::space, + std::locale("C.UTF-8")); + + // '\v' (vertical tab) is a space + REQUIRE(nihil::is_space('\v') == true); + REQUIRE(nihil::is_space(L'\v') == true); + + REQUIRE(nihil::is_c_space('\v') == true); + REQUIRE(nihil::is_c_space(L'\v') == true); + + REQUIRE(is_utf8_space('\v') == true); + REQUIRE(is_utf8_space(L'\v') == true); + + // 'x' is not a space + REQUIRE(nihil::is_space('x') == false); + REQUIRE(nihil::is_space(L'x') == false); + + REQUIRE(nihil::is_c_space('x') == false); + REQUIRE(nihil::is_c_space(L'x') == false); + + REQUIRE(is_utf8_space('x') == false); + REQUIRE(is_utf8_space(L'x') == false); + + // U+2003 EM SPACE is a space + REQUIRE(nihil::is_space(L'\u2003') == false); + REQUIRE(nihil::is_c_space(L'\u2003') == false); + REQUIRE(is_utf8_space(L'\u2003') == true); +} + +TEST_CASE("ctype: print", "[ctype]") { + auto is_utf8_print = + nihil::ctype_is(std::ctype_base::print, + std::locale("C.UTF-8")); + + // 'x' is printable + REQUIRE(nihil::is_print('x') == true); + REQUIRE(nihil::is_print(L'x') == true); + + REQUIRE(nihil::is_c_print('x') == true); + REQUIRE(nihil::is_c_print(L'x') == true); + + REQUIRE(is_utf8_print('x') == true); + REQUIRE(is_utf8_print(L'x') == true); + + // '\003' is not printable + REQUIRE(nihil::is_print('\003') == false); + REQUIRE(nihil::is_print(L'\003') == false); + + REQUIRE(nihil::is_c_print('\003') == false); + REQUIRE(nihil::is_c_print(L'\003') == false); + + REQUIRE(is_utf8_print('\003') == false); + REQUIRE(is_utf8_print(L'\003') == false); + + // U+0410 CYRILLIC CAPITAL LETTER A is printable + REQUIRE(nihil::is_print(L'\u0410') == false); + REQUIRE(nihil::is_c_print(L'\u0410') == false); + REQUIRE(is_utf8_print(L'\u0410') == true); +} + +TEST_CASE("ctype: cntrl", "[ctype]") { + auto is_utf8_cntrl = + nihil::ctype_is(std::ctype_base::cntrl, + std::locale("C.UTF-8")); + + // '\003' is a control character + REQUIRE(nihil::is_cntrl('\003') == true); + REQUIRE(nihil::is_cntrl(L'\003') == true); + + REQUIRE(nihil::is_c_cntrl('\003') == true); + REQUIRE(nihil::is_c_cntrl(L'\003') == true); + + REQUIRE(is_utf8_cntrl('\003') == true); + REQUIRE(is_utf8_cntrl(L'\003') == true); + + + // 'x' is not a control character + REQUIRE(nihil::is_cntrl('x') == false); + REQUIRE(nihil::is_cntrl(L'x') == false); + + REQUIRE(nihil::is_c_cntrl('x') == false); + REQUIRE(nihil::is_c_cntrl(L'x') == false); + + REQUIRE(is_utf8_cntrl('x') == false); + REQUIRE(is_utf8_cntrl(L'x') == false); + + // U+00AD SOFT HYPHEN is a control character. + REQUIRE(nihil::is_cntrl(L'\u00ad') == false); + REQUIRE(nihil::is_c_cntrl(L'\u00ad') == false); + REQUIRE(is_utf8_cntrl(L'\u00ad') == true); +} + +TEST_CASE("ctype: upper", "[ctype]") { + auto is_utf8_upper = + nihil::ctype_is(std::ctype_base::upper, + std::locale("C.UTF-8")); + + // 'A' is upper case + REQUIRE(nihil::is_upper('A') == true); + REQUIRE(nihil::is_upper(L'A') == true); + + REQUIRE(nihil::is_c_upper('A') == true); + REQUIRE(nihil::is_c_upper(L'A') == true); + + REQUIRE(is_utf8_upper('A') == true); + REQUIRE(is_utf8_upper(L'A') == true); + + // 'a' is not upper case + REQUIRE(nihil::is_upper('a') == false); + REQUIRE(nihil::is_upper(L'a') == false); + + REQUIRE(nihil::is_c_upper('a') == false); + REQUIRE(nihil::is_c_upper(L'a') == false); + + REQUIRE(is_utf8_upper('a') == false); + REQUIRE(is_utf8_upper(L'a') == false); + + // U+0410 CYRILLIC CAPITAL LETTER A is upper case + REQUIRE(nihil::is_upper(L'\u0410') == false); + REQUIRE(nihil::is_c_upper(L'\u0410') == false); + REQUIRE(is_utf8_upper(L'\u0410') == true); +} + +TEST_CASE("ctype: lower", "[ctype]") { + auto is_utf8_lower = + nihil::ctype_is(std::ctype_base::lower, + std::locale("C.UTF-8")); + + // 'a' is lower case + REQUIRE(nihil::is_lower('a') == true); + REQUIRE(nihil::is_lower(L'a') == true); + + REQUIRE(nihil::is_c_lower('a') == true); + REQUIRE(nihil::is_c_lower(L'a') == true); + + REQUIRE(is_utf8_lower('a') == true); + REQUIRE(is_utf8_lower(L'a') == true); + + // 'A' is not lower case + REQUIRE(nihil::is_lower('A') == false); + REQUIRE(nihil::is_lower(L'A') == false); + + REQUIRE(nihil::is_c_lower('A') == false); + REQUIRE(nihil::is_c_lower(L'A') == false); + + REQUIRE(is_utf8_lower('A') == false); + REQUIRE(is_utf8_lower(L'A') == false); + + // U+0430 CYRILLIC SMALL LETTER A + REQUIRE(nihil::is_lower(L'\u0430') == false); + REQUIRE(nihil::is_c_lower(L'\u0430') == false); + REQUIRE(is_utf8_lower(L'\u0430') == true); +} + +TEST_CASE("ctype: alpha", "[ctype]") { + auto is_utf8_alpha = + nihil::ctype_is(std::ctype_base::alpha, + std::locale("C.UTF-8")); + + // 'a' is alphabetical + REQUIRE(nihil::is_alpha('a') == true); + REQUIRE(nihil::is_alpha(L'a') == true); + + REQUIRE(nihil::is_c_alpha('a') == true); + REQUIRE(nihil::is_c_alpha(L'a') == true); + + REQUIRE(is_utf8_alpha('a') == true); + REQUIRE(is_utf8_alpha(L'a') == true); + + // '1' is not alphabetical + REQUIRE(nihil::is_alpha('1') == false); + REQUIRE(nihil::is_alpha(L'1') == false); + + REQUIRE(nihil::is_c_alpha('1') == false); + REQUIRE(nihil::is_c_alpha(L'1') == false); + + REQUIRE(is_utf8_alpha('1') == false); + REQUIRE(is_utf8_alpha(L'1') == false); + + // U+0430 CYRILLIC SMALL LETTER A + REQUIRE(nihil::is_alpha(L'\u0430') == false); + REQUIRE(nihil::is_c_alpha(L'\u0430') == false); + REQUIRE(is_utf8_alpha(L'\u0430') == true); +} + +TEST_CASE("ctype: digit", "[ctype]") { + auto is_utf8_digit = + nihil::ctype_is(std::ctype_base::digit, + std::locale("C.UTF-8")); + + // '1' is a digit + REQUIRE(nihil::is_digit('1') == true); + REQUIRE(nihil::is_digit(L'1') == true); + + REQUIRE(nihil::is_c_digit('1') == true); + REQUIRE(nihil::is_c_digit(L'1') == true); + + REQUIRE(is_utf8_digit('1') == true); + REQUIRE(is_utf8_digit(L'1') == true); + + // 'a' is not a digit + REQUIRE(nihil::is_digit('a') == false); + REQUIRE(nihil::is_digit(L'a') == false); + + REQUIRE(nihil::is_c_digit('a') == false); + REQUIRE(nihil::is_c_digit(L'a') == false); + + REQUIRE(is_utf8_digit('a') == false); + REQUIRE(is_utf8_digit(L'a') == false); + + // U+0660 ARABIC-INDIC DIGIT ZERO + REQUIRE(nihil::is_digit(L'\u0660') == false); + REQUIRE(nihil::is_c_digit(L'\u0660') == false); + REQUIRE(is_utf8_digit(L'\u0660') == true); +} + +TEST_CASE("ctype: punct", "[ctype]") { + auto is_utf8_punct = + nihil::ctype_is(std::ctype_base::punct, + std::locale("C.UTF-8")); + + // ';' is punctuation + REQUIRE(nihil::is_punct(';') == true); + REQUIRE(nihil::is_punct(L';') == true); + + REQUIRE(nihil::is_c_punct(';') == true); + REQUIRE(nihil::is_c_punct(L';') == true); + + REQUIRE(is_utf8_punct(';') == true); + REQUIRE(is_utf8_punct(L';') == true); + + // 'a' is not punctuation + REQUIRE(nihil::is_punct('a') == false); + REQUIRE(nihil::is_punct(L'a') == false); + + REQUIRE(nihil::is_c_punct('a') == false); + REQUIRE(nihil::is_c_punct(L'a') == false); + + REQUIRE(is_utf8_punct('a') == false); + REQUIRE(is_utf8_punct(L'a') == false); + + // U+00A1 INVERTED EXCLAMATION MARK + REQUIRE(nihil::is_punct(L'\u00A1') == false); + REQUIRE(nihil::is_c_punct(L'\u00A1') == false); + REQUIRE(is_utf8_punct(L'\u00A1') == true); +} + +TEST_CASE("ctype: xdigit", "[ctype]") { + auto is_utf8_xdigit = + nihil::ctype_is(std::ctype_base::xdigit, + std::locale("C.UTF-8")); + + // 'f' is an xdigit + REQUIRE(nihil::is_xdigit('f') == true); + REQUIRE(nihil::is_xdigit(L'f') == true); + + REQUIRE(nihil::is_c_xdigit('f') == true); + REQUIRE(nihil::is_c_xdigit(L'f') == true); + + REQUIRE(is_utf8_xdigit('f') == true); + REQUIRE(is_utf8_xdigit(L'f') == true); + + // 'g' is not an xdigit + REQUIRE(nihil::is_xdigit('g') == false); + REQUIRE(nihil::is_xdigit(L'g') == false); + + REQUIRE(nihil::is_c_xdigit('g') == false); + REQUIRE(nihil::is_c_xdigit(L'g') == false); + + REQUIRE(is_utf8_xdigit('g') == false); + REQUIRE(is_utf8_xdigit(L'g') == false); +} + +TEST_CASE("ctype: blank", "[ctype]") { + auto is_utf8_blank = + nihil::ctype_is(std::ctype_base::blank, + std::locale("C.UTF-8")); + + // '\t' is a blank + REQUIRE(nihil::is_blank('\t') == true); + REQUIRE(nihil::is_blank(L'\t') == true); + + REQUIRE(nihil::is_c_blank('\t') == true); + REQUIRE(nihil::is_c_blank(L'\t') == true); + + REQUIRE(is_utf8_blank('\t') == true); + REQUIRE(is_utf8_blank(L'\t') == true); + + // '\v' is not a blank + REQUIRE(nihil::is_blank('\v') == false); + REQUIRE(nihil::is_blank(L'\v') == false); + + REQUIRE(nihil::is_c_blank('\v') == false); + REQUIRE(nihil::is_c_blank(L'\v') == false); + + REQUIRE(is_utf8_blank('\v') == false); + REQUIRE(is_utf8_blank(L'\v') == false); + + // There don't seem to be any UTF-8 blank characters, at least + // in FreeBSD libc. +} + +TEST_CASE("ctype: alnum", "[ctype]") { + auto is_utf8_alnum = + nihil::ctype_is(std::ctype_base::alnum, + std::locale("C.UTF-8")); + + // 'a' is alphanumeric + REQUIRE(nihil::is_alnum('a') == true); + REQUIRE(nihil::is_alnum(L'a') == true); + + REQUIRE(nihil::is_c_alnum('a') == true); + REQUIRE(nihil::is_c_alnum(L'a') == true); + + REQUIRE(is_utf8_alnum('a') == true); + REQUIRE(is_utf8_alnum(L'a') == true); + + // '\t' is not a alnum + REQUIRE(nihil::is_alnum('\t') == false); + REQUIRE(nihil::is_alnum(L'\t') == false); + + REQUIRE(nihil::is_c_alnum('\t') == false); + REQUIRE(nihil::is_c_alnum(L'\t') == false); + + REQUIRE(is_utf8_alnum('\t') == false); + REQUIRE(is_utf8_alnum(L'\t') == false); + + // U+0430 CYRILLIC SMALL LETTER A + REQUIRE(nihil::is_alnum(L'\u0430') == false); + REQUIRE(nihil::is_c_alnum(L'\u0430') == false); + REQUIRE(is_utf8_alnum(L'\u0430') == true); +} + +TEST_CASE("ctype: graph", "[ctype]") { + auto is_utf8_graph = + nihil::ctype_is(std::ctype_base::graph, + std::locale("C.UTF-8")); + + // 'a' is graphical + REQUIRE(nihil::is_graph('a') == true); + REQUIRE(nihil::is_graph(L'a') == true); + + REQUIRE(nihil::is_c_graph('a') == true); + REQUIRE(nihil::is_c_graph(L'a') == true); + + REQUIRE(is_utf8_graph('a') == true); + REQUIRE(is_utf8_graph(L'a') == true); + + // '\t' is not graphical + REQUIRE(nihil::is_graph('\t') == false); + REQUIRE(nihil::is_graph(L'\t') == false); + + REQUIRE(nihil::is_c_graph('\t') == false); + REQUIRE(nihil::is_c_graph(L'\t') == false); + + REQUIRE(is_utf8_graph('\t') == false); + REQUIRE(is_utf8_graph(L'\t') == false); + + // U+0430 CYRILLIC SMALL LETTER A + REQUIRE(nihil::is_graph(L'\u0430') == false); + REQUIRE(nihil::is_c_graph(L'\u0430') == false); + REQUIRE(is_utf8_graph(L'\u0430') == true); +} + +} // anonymous namespace diff --git a/nihil.core/error.ccm b/nihil.core/error.ccm new file mode 100644 index 0000000..5aa23f5 --- /dev/null +++ b/nihil.core/error.ccm @@ -0,0 +1,321 @@ +// This source code is released into the public domain. +export module nihil.core: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; + +// 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::string>) + : m_error(std::forward(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 + requires(std::is_error_code_enum::value) + error_proxy(T what) // NOLINT + : m_error(make_error_code(what)) + { + } + + // ... an error condition enum + template + requires(std::is_error_condition_enum::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(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(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 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 + { + auto const *code = std::get_if(&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 + { + auto const *condition = std::get_if(&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 + 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)}; + } + + operator std::unexpected () 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 m_cause; + + // For std::exception::what(), we need to keep the string valid + // until we're destroyed. + mutable std::optional 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::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::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 +{ + template + constexpr auto parse(ParseContext &ctx) -> ParseContext::iterator + { + return ctx.begin(); + } + + template + 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.core/error.test.cc b/nihil.core/error.test.cc new file mode 100644 index 0000000..f4ec1ee --- /dev/null +++ b/nihil.core/error.test.cc @@ -0,0 +1,273 @@ +// This source code is released into the public domain. + +#include + +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); + static_assert(std::default_initializable); + static_assert(std::move_constructible); + static_assert(std::copy_constructible); + static_assert(std::equality_comparable); + static_assert(std::totally_ordered); + static_assert(std::swappable); + static_assert(std::regular); +} + +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.core/flagset.ccm b/nihil.core/flagset.ccm new file mode 100644 index 0000000..cea9889 --- /dev/null +++ b/nihil.core/flagset.ccm @@ -0,0 +1,200 @@ +// This source code is released into the public domain. +export module nihil.core:flagset; + +/* + * flagset: a type-safe flags type. + */ + +import nihil.std; + +namespace nihil { + +export template +struct flagset final +{ + using underlying_type = base_type; + + /* + * Create an empty flags. + */ + flagset() noexcept = default; + + /* + * Copyable. + */ + flagset(flagset const &other) noexcept + : m_value(other.m_value) + { + } + + /* + * Create flags from an integer mask. + */ + template + [[nodiscard]] static constexpr auto mask() noexcept -> flagset + { + return flagset(flag); + } + + /* + * Create flags for a specific bit. + */ + template + [[nodiscard]] static constexpr auto bit() noexcept -> flagset + { + static_assert(bitnr < std::numeric_limits::digits); + return flagset(static_cast(1) << bitnr); + } + + /* + * Create flags from a runtime value. + */ + [[nodiscard]] static auto from_int(base_type value) noexcept -> flagset + { + return flagset(value); + } + + /* + * Assign this flagset. + */ + auto operator=(this flagset &lhs, flagset rhs) noexcept -> flagset & + { + if (&lhs != &rhs) + lhs.m_value = rhs.m_value; + return lhs; + } + + /* + * The integer value of this flagset. + */ + [[nodiscard]] constexpr auto value(this flagset self) noexcept -> base_type + { + return self.m_value; + } + + /* + * True if this flagset has any bits set. + */ + [[nodiscard]] explicit constexpr operator bool(this flagset self) noexcept + { + return self.m_value != 0; + } + + /* + * Set bits. + */ + constexpr auto operator|=(this flagset &lhs, flagset rhs) noexcept -> flagset & + { + lhs.m_value |= rhs.value(); + return lhs; + } + + /* + * Mask bits. + */ + constexpr auto operator&=(this flagset &lhs, flagset rhs) noexcept -> flagset & + { + lhs.m_value &= rhs.value(); + return lhs; + } + + /* + * Invert bits. + */ + [[nodiscard]] constexpr auto operator~(this flagset self) noexcept -> flagset + { + return flagset(~self.m_value); + } + + /* + * xor bits. + */ + constexpr auto operator^=(this flagset &lhs, flagset rhs) noexcept -> flagset + { + lhs.m_value ^= rhs.value(); + return lhs; + } + +private: + base_type m_value = 0; + + explicit constexpr flagset(base_type mask) noexcept + : m_value(mask) + { + } + + [[nodiscard]] friend auto operator|(flagset lhs, flagset rhs) noexcept -> flagset + { + return (lhs |= rhs); + } + + [[nodiscard]] friend auto operator&(flagset lhs, flagset rhs) noexcept -> flagset + { + return (lhs &= rhs); + } + + [[nodiscard]] friend auto operator^(flagset lhs, flagset rhs) noexcept -> flagset + { + return (lhs ^= rhs); + } + + [[nodiscard]] friend auto + operator==(flagset lhs, flagset rhs) noexcept -> bool + { + return lhs.value() == rhs.value(); + } + + friend auto operator<<(std::ostream &strm, flagset flags) -> std::ostream & + { + std::print(strm, "{}", flags); + return strm; + } +}; + +} // namespace nihil + +/* + * Formatting for flagset. + */ +export template +struct std::formatter, Char> +{ + using flags_t = nihil::flagset; + + template + constexpr auto parse(ParseContext &ctx) -> ParseContext::iterator + { + return ctx.begin(); + } + + template + auto format(flags_t flags, FmtContext &ctx) const -> FmtContext::iterator + { + auto constexpr digits = std::numeric_limits::digits; + auto value = flags.value(); + auto it = ctx.out(); + *it++ = Char{'<'}; + + auto printed_any = false; + + for (unsigned i = 0; i < digits; ++i) { + unsigned bit = (digits - 1) - i; + + auto this_bit = static_cast(1) << bit; + if ((value & this_bit) == 0) + continue; + + if (printed_any) + *it++ = Char{','}; + + if (bit > 10) + *it++ = Char{'0'} + (bit / 10); + *it++ = Char{'0'} + (bit % 10); + + printed_any = true; + } + + *it++ = Char{'>'}; + return it; + } +}; diff --git a/nihil.core/flagset.test.cc b/nihil.core/flagset.test.cc new file mode 100644 index 0000000..353b638 --- /dev/null +++ b/nihil.core/flagset.test.cc @@ -0,0 +1,144 @@ +// This source code is released into the public domain. + +#include + +import nihil.std; +import nihil.core; + +namespace { +struct test_tag +{ +}; +using testflags = nihil::flagset; + +constexpr auto zero = testflags::bit<0>(); +constexpr auto one = testflags::bit<1>(); +constexpr auto two = testflags::bit<2>(); +constexpr auto twelve = testflags::bit<12>(); + +TEST_CASE("flagset: invariant", "[nihil]") +{ + static_assert(std::regular); + static_assert(sizeof(testflags) == sizeof(testflags::underlying_type)); +} + +TEST_CASE("flagset: bit<>", "[nihil]") +{ + REQUIRE(zero.value() == 0x1); + REQUIRE(one.value() == 0x2); +} + +TEST_CASE("flagset: mask<>", "[nihil]") +{ + auto zero_ = testflags::mask<0x1>(); + auto one_ = testflags::mask<0x2>(); + + REQUIRE(zero_ == zero); + REQUIRE(one_ == one); +} + +TEST_CASE("flagset: constructor", "[nihil]") +{ + SECTION ("default construct") { + auto flags = testflags(); + REQUIRE(flags.value() == 0); + } + + SECTION ("construct from int") { + auto flags = testflags::from_int(one.value() | zero.value()); + REQUIRE(flags == (one | zero)); + } + + SECTION ("copy construct") { + auto flags = one; + auto flags2(flags); + REQUIRE(flags == flags2); + } +} + +TEST_CASE("flagset: operators", "[nihil]") +{ + SECTION ("operator|") { + REQUIRE((zero | one).value() == 0x3); + } + + SECTION ("operator|=") { + auto flags = zero; + flags |= one; + REQUIRE(flags.value() == 0x3); + } + + SECTION ("operator&") { + auto flags = zero | one; + + REQUIRE((flags & zero) == zero); + } + + SECTION ("operator&=") { + auto flags = zero | one | two; + REQUIRE(flags.value() == 0x7); + flags &= (zero | one); + REQUIRE(flags.value() == 0x3); + } + + SECTION ("operator^") { + auto flags = zero | one; + REQUIRE((flags ^ (one | two)) == (zero | two)); + } + + SECTION ("operator^=") { + auto flags = zero | one; + flags ^= (one | two); + REQUIRE(flags == (zero | two)); + } + + SECTION ("operator~") { + auto flags = ~zero; + REQUIRE(flags.value() == ~static_cast(1)); + } + + SECTION ("operator==") { + auto flags = zero; + REQUIRE(flags == zero); + REQUIRE(flags != one); + } +} + +TEST_CASE("flagset: assignment", "[nihil]") +{ + auto flags = zero; + REQUIRE(flags == zero); + + flags = one; + REQUIRE(flags == one); + REQUIRE(flags != zero); +} + +TEST_CASE("flagset: format", "[nihil]") +{ + REQUIRE(std::format("{}", testflags()) == "<>"); + REQUIRE(std::format("{}", zero) == "<0>"); + REQUIRE(std::format("{}", one) == "<1>"); + REQUIRE(std::format("{}", zero | one) == "<1,0>"); + + REQUIRE(std::format("{}", twelve) == "<12>"); + REQUIRE(std::format("{}", twelve | one) == "<12,1>"); +} + +TEST_CASE("flagset: ostream operator<<", "[nihil]") +{ + auto write = [](testflags flags) -> std::string { + auto strm = std::ostringstream(); + strm << flags; + return strm.str(); + }; + + REQUIRE(write(testflags()) == "<>"); + REQUIRE(write(zero) == "<0>"); + REQUIRE(write(one) == "<1>"); + REQUIRE(write(zero | one) == "<1,0>"); + + REQUIRE(write(twelve) == "<12>"); + REQUIRE(write(twelve | one) == "<12,1>"); +} +} // anonymous namespace diff --git a/nihil.core/guard.ccm b/nihil.core/guard.ccm new file mode 100644 index 0000000..0576042 --- /dev/null +++ b/nihil.core/guard.ccm @@ -0,0 +1,41 @@ +// This source code is released into the public domain. +export module nihil.core:guard; + +import nihil.std; + +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 +struct guard final { + // Initialise the guard with a callable we will invoke later. + explicit guard(F func) : m_func(std::move(func)) {} + + // We are being destroyed, so call the callable. + // If the callable throws, std::terminate() will be called. + ~guard() + { + if (m_func) + std::invoke(*m_func); + } + + // Release the guard. This turns the destructor into a no-op. + auto release(this guard &self) noexcept -> void + { + self.m_func.reset(); + } + + // Not default-constructible, movable or copyable. + guard() = delete; + guard(guard const &) = delete; + guard(guard &&) noexcept = delete; + auto operator=(this guard &, guard const &) -> guard & = delete; + auto operator=(this guard &, guard &&) noexcept -> guard & = delete; + +private: + // The callable to be invoked when we are destroyed. + std::optional m_func; +}; + +} // namespace nihil diff --git a/nihil.core/guard.test.cc b/nihil.core/guard.test.cc new file mode 100644 index 0000000..43076f5 --- /dev/null +++ b/nihil.core/guard.test.cc @@ -0,0 +1,16 @@ +// This source code is released into the public domain. + +#include + +import nihil.core; + +TEST_CASE("guard: basic", "[guard]") { + int n = 0; + + { + auto guard = nihil::guard([&] { n = 1; }); + REQUIRE(n == 0); + } + + REQUIRE(n == 1); +} diff --git a/nihil.core/match.ccm b/nihil.core/match.ccm new file mode 100644 index 0000000..e1c894c --- /dev/null +++ b/nihil.core/match.ccm @@ -0,0 +1,18 @@ +// This source code is released into the public domain. +export module nihil.core:match; + +import nihil.std; + +namespace nihil { + +export template +struct match : Ts... { using Ts::operator()...; }; + +export template +[[nodiscard]] constexpr decltype(auto) operator| + (std::variant const &v, match const &match) +{ + return std::visit(match, v); +} + +} // namespace nihil diff --git a/nihil.core/match.test.cc b/nihil.core/match.test.cc new file mode 100644 index 0000000..2394651 --- /dev/null +++ b/nihil.core/match.test.cc @@ -0,0 +1,32 @@ +// This source code is released into the public domain. + +#include + +import nihil.std; +import nihil.core; + +namespace { +TEST_CASE("match", "[nihil]") +{ + using namespace nihil; + using namespace std::literals; + + auto v = std::variant(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.core/monad.ccm b/nihil.core/monad.ccm new file mode 100644 index 0000000..ae26416 --- /dev/null +++ b/nihil.core/monad.ccm @@ -0,0 +1,282 @@ +/* + * From https://github.com/toby-allsopp/coroutine_monad + * + * Copyright (c) 2017 Toby Allsopp + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + +export module nihil.core:monad; + +import nihil.std; + +namespace nihil { + +/********************************************************************** + * return_object_holder + */ + +// An object that starts out unitialized. Initialized by a call to emplace. +export template +using deferred = std::optional; + +export template +struct return_object_holder { + // The staging object that is returned (by copy/move) to the caller of + // the coroutine. + deferred stage; + return_object_holder*& p; + + // When constructed, we assign a pointer to ourselves to the supplied + // reference to pointer. + return_object_holder(return_object_holder*& p) + : stage{} + , p(p) + { + p = this; + } + + // Copying doesn't make any sense (which copy should the pointer refer + // to?). + return_object_holder(return_object_holder const&) = delete; + + // To move, we just update the pointer to point at the new object. + return_object_holder(return_object_holder&& other) + : stage(std::move(other.stage)) + , p(other.p) + { + p = this; + } + + // Assignment doesn't make sense. + void operator=(return_object_holder const&) = delete; + void operator=(return_object_holder&&) = delete; + + // A non-trivial destructor is required until + // https://bugs.llvm.org//show_bug.cgi?id=28593 is fixed. + ~return_object_holder() {} + + // Construct the staging value; arguments are perfect forwarded to T's + // constructor. + template + void emplace(Args&&... args) + { + stage.emplace(std::forward(args)...); + } + + // We assume that we will be converted only once, so we can move from + // the staging object. We also assume that `emplace` has been called + // at least once. + operator T() + { + return std::move(*stage); + } +}; + +export template +auto make_return_object_holder(return_object_holder*& p) +{ + return return_object_holder{p}; +} + +/********************************************************************** + * std::optional + */ + +template +struct optional_promise { + return_object_holder>* data; + + auto get_return_object() + { + return make_return_object_holder(data); + } + + auto initial_suspend() noexcept -> std::suspend_never + { + return {}; + } + + auto final_suspend() noexcept -> std::suspend_never + { + return {}; + } + + void return_value(T x) + { + data->emplace(std::move(x)); + } + + void unhandled_exception() + { + std::rethrow_exception(std::current_exception()); + } +}; + +} // namespace nihil + +export template +struct std::coroutine_traits, Args...> { + using promise_type = nihil::optional_promise; +}; + +namespace nihil { + +template +struct optional_awaitable { + std::optional o; + + auto await_ready() + { + return o.has_value(); + } + + auto await_resume() + { + return *o; + } + + template + void await_suspend(std::coroutine_handle> h) + { + h.promise().data->emplace(std::nullopt); + h.destroy(); + } +}; + +} // namespace nihil + +namespace std { + +export template +auto operator co_await(std::optional o) { + return nihil::optional_awaitable{std::move(o)}; +} + +} // namespace std + +/********************************************************************** + * std::expected + */ + +namespace nihil { + +export template +struct expected_promise_base { + return_object_holder>* data; + + auto get_return_object() + { + return make_return_object_holder(data); + } + + auto initial_suspend() noexcept -> std::suspend_never + { + return {}; + } + + auto final_suspend() noexcept -> std::suspend_never + { + return {}; + } + + void unhandled_exception() + { + std::rethrow_exception(std::current_exception()); + } +}; + +export template +struct expected_promise : expected_promise_base { + void return_value(this expected_promise &self, std::unexpected err) + { + self.data->emplace(std::move(err)); + } + + void return_value(this expected_promise &self, T o) + { + self.data->emplace(std::move(o)); + } +}; + +export template +struct expected_promise : expected_promise_base { + void return_value(this expected_promise &self, std::unexpected err) + { + self.data->emplace(std::move(err)); + } + + void return_value(this expected_promise &self, std::expected o) + { + self.data->emplace(std::move(o)); + } +}; + +} // namespace nihil + +export template +struct std::coroutine_traits, Args...> { + using promise_type = nihil::expected_promise; +}; + +namespace nihil { + +export template +struct expected_awaitable_base { + std::expected o; + + auto await_ready() + { + return o.has_value(); + } + + template + void await_suspend(std::coroutine_handle

h) + { + h.promise().data->emplace(std::unexpected(o.error())); + h.destroy(); + } +}; + +export template +struct expected_awaitable : expected_awaitable_base { + auto await_resume(this expected_awaitable &self) + { + return std::move(*self.o); + } +}; + +export template +struct expected_awaitable : expected_awaitable_base { + auto await_resume(this expected_awaitable &) + { + return std::expected(); + } +}; + +} // namespace nihil + +namespace std { + +export template +auto operator co_await(std::expected o) { + return nihil::expected_awaitable{std::move(o)}; +} + +} // namespace std diff --git a/nihil.core/monad.test.cc b/nihil.core/monad.test.cc new file mode 100644 index 0000000..e5003ac --- /dev/null +++ b/nihil.core/monad.test.cc @@ -0,0 +1,65 @@ +// This source code is released into the public domain. + +#include + +import nihil.std; +import nihil.core; + +namespace { +TEST_CASE("monad: co_await std::optional<> with value", "[nihil]") +{ + auto get_value = [] -> std::optional { + return 42; + }; + + auto try_get_value = [&get_value] -> std::optional { + co_return co_await get_value(); + }; + + auto o = try_get_value(); + REQUIRE(o == 42); +} + +TEST_CASE("monad: co_await std::optional<> without value", "[nihil]") +{ + auto get_value = [] -> std::optional { + return {}; + }; + + auto try_get_value = [&get_value] -> std::optional { + co_return co_await get_value(); + }; + + auto o = try_get_value(); + REQUIRE(!o.has_value()); +} + +TEST_CASE("monad: co_await std::expected<> with value", "[nihil]") +{ + auto get_value = [] -> std::expected { + return 42; + }; + + auto try_get_value = [&get_value] -> std::expected { + co_return co_await get_value(); + }; + + auto o = try_get_value(); + REQUIRE(o == 42); +} + +TEST_CASE("monad: co_await std::expected<> with error", "[nihil]") +{ + auto get_value = [] -> std::expected { + return std::unexpected("error"); + }; + + auto try_get_value = [&get_value] -> std::expected { + co_return co_await get_value(); + }; + + auto o = try_get_value(); + REQUIRE(!o); + REQUIRE(o.error() == "error"); +} +} // anonymous namespace diff --git a/nihil.core/next_word.ccm b/nihil.core/next_word.ccm new file mode 100644 index 0000000..7fcb2be --- /dev/null +++ b/nihil.core/next_word.ccm @@ -0,0 +1,35 @@ +// This source code is released into the public domain. +export module nihil.core:next_word; + +import nihil.std; + +import :ctype; +import :skipws; + +namespace nihil { + +// Return the next word from a string_view. Skips leading whitespace, so +// calling this repeatedly will return each word from the string. +export template +[[nodiscard]] +auto next_word(std::basic_string_view text, std::locale const &locale = std::locale()) + -> std::pair, std::basic_string_view> +{ + text = skipws(text, locale); + + auto is_space = ctype_is(std::ctype_base::space, locale); + auto split_pos = std::ranges::find_if(text, is_space); + + return {{std::ranges::begin(text), split_pos}, {split_pos, std::ranges::end(text)}}; +} + +export template +auto next_word(std::basic_string_view *text, std::locale const &locale = std::locale()) + -> std::basic_string_view +{ + auto [word, rest] = next_word(*text, locale); + *text = rest; + return word; +} + +} // namespace nihil diff --git a/nihil.core/next_word.test.cc b/nihil.core/next_word.test.cc new file mode 100644 index 0000000..2566ea6 --- /dev/null +++ b/nihil.core/next_word.test.cc @@ -0,0 +1,65 @@ +// This source code is released into the public domain. + +#include + +import nihil.std; +import nihil.core; + +namespace { + +TEST_CASE("next_word: basic", "[next_word]") +{ + using namespace std::literals; + auto s = "foo bar baz"sv; + + auto words = nihil::next_word(s); + REQUIRE(words.first == "foo"); + REQUIRE(words.second == " bar baz"); + + auto word = nihil::next_word(&s); + REQUIRE(word == "foo"); + REQUIRE(s == " bar baz"); +} + +TEST_CASE("next_word: multiple spaces", "[next_word]") +{ + using namespace std::literals; + auto s = "foo bar baz"sv; + + auto words = nihil::next_word(s); + REQUIRE(words.first == "foo"); + REQUIRE(words.second == " bar baz"); + + auto word = nihil::next_word(&s); + REQUIRE(word == "foo"); + REQUIRE(s == " bar baz"); +} + +TEST_CASE("next_word: leading spaces", "[next_word]") +{ + using namespace std::literals; + auto s = " \tfoo bar baz"sv; + + auto words = nihil::next_word(s); + REQUIRE(words.first == "foo"); + REQUIRE(words.second == " bar baz"); + + auto word = nihil::next_word(&s); + REQUIRE(word == "foo"); + REQUIRE(s == " bar baz"); +} + +TEST_CASE("next_word: locale", "[next_word]") +{ + using namespace std::literals; + auto s = L"\u2003foo\u2003bar\u2003baz"sv; + + auto words = nihil::next_word(s); + REQUIRE(words.first == s); + + words = nihil::next_word(s, std::locale("C.UTF-8")); + REQUIRE(words.first == L"foo"); + REQUIRE(words.second == L"\u2003bar\u2003baz"); +} + +} // anonymous namespace diff --git a/nihil.core/nihil.core.ccm b/nihil.core/nihil.core.ccm index 0aa5402..37ad032 100644 --- a/nihil.core/nihil.core.ccm +++ b/nihil.core/nihil.core.ccm @@ -1,5 +1,20 @@ // This source code is released into the public domain. export module nihil.core; +export import :capture_stream; +export import :construct; +export import :ctype; export import :errc; +export import :error; export import :features; +export import :flagset; +export import :guard; +export import :match; +export import :monad; +export import :next_word; +export import :parse_size; +export import :save_errno; +export import :skipws; +export import :sys_error; +export import :tabulate; +export import :uuid; diff --git a/nihil.core/parse_size.ccm b/nihil.core/parse_size.ccm new file mode 100644 index 0000000..5f80755 --- /dev/null +++ b/nihil.core/parse_size.ccm @@ -0,0 +1,90 @@ +// This source code is released into the public domain. +export module nihil.core:parse_size; + +import nihil.std; + +import :ctype; +import :errc; +import :error; +import :monad; + +namespace nihil { + +export template +auto get_multiplier(Char c) -> std::expected +{ + auto ret = std::uint64_t{1}; + + // clang-format off + switch (c) { + case 'p': case 'P': ret *= 1024; // NOLINT + case 't': case 'T': ret *= 1024; // NOLINT + case 'g': case 'G': ret *= 1024; // NOLINT + case 'm': case 'M': ret *= 1024; // NOLINT + case 'k': case 'K': ret *= 1024; // NOLINT + return ret; + + default: + return error(errc::invalid_unit); + } + // clang-format on +} + +// Parse a string containing a human-formatted size, such as "1024" +// or "4g". Parsing is always done in the "C" locale and does not +// recognise thousands separators or negative numbers. +export template +[[nodiscard]] +auto parse_size(std::basic_string_view str) -> std::expected +{ + // Extract the numeric part of the string. + auto it = std::ranges::find_if_not(str, is_c_digit); + auto num_str = std::basic_string_view(std::ranges::begin(str), it); + + if (num_str.empty()) + co_return error(errc::empty_string); + + auto ret = T{0}; + + for (auto c : num_str) { + if (ret > (std::numeric_limits::max() / 10)) + co_return error(std::errc::result_out_of_range); + ret *= 10; + + auto digit = static_cast(c - '0'); + if ((std::numeric_limits::max() - digit) < ret) + co_return error(std::errc::result_out_of_range); + ret += digit; + } + + if (it == str.end()) + // No multiplier. + co_return ret; + + auto mchar = *it++; + + if (it != str.end()) + // Multiplier is more than one character. + co_return error(errc::invalid_unit); + + auto mult = co_await get_multiplier(mchar); + + if (std::cmp_greater(ret, std::numeric_limits::max() / mult)) + co_return error(std::errc::result_out_of_range); + + co_return ret *mult; +} + +export template +[[nodiscard]] auto parse_size(char const *s) +{ + return parse_size(std::string_view(s)); +} + +export template +[[nodiscard]] auto parse_size(wchar_t const *s) +{ + return parse_size(std::wstring_view(s)); +} + +} // namespace nihil diff --git a/nihil.core/parse_size.test.cc b/nihil.core/parse_size.test.cc new file mode 100644 index 0000000..6676543 --- /dev/null +++ b/nihil.core/parse_size.test.cc @@ -0,0 +1,165 @@ +// This source code is released into the public domain. + +#include + +import nihil.std; +import nihil.core; + +namespace { +TEST_CASE("parse_size: empty value", "[nihil]") +{ + using namespace nihil; + + auto n = parse_size(""); + REQUIRE(!n); + REQUIRE(n.error() == nihil::errc::empty_string); +} + +TEST_CASE("parse_size: basic", "[nihil]") +{ + using namespace nihil; + + SECTION ("bare number") { + auto n = parse_size("1024").value(); + REQUIRE(n == 1024); + } + + SECTION ("max value, unsigned") { + auto n = parse_size("65535").value(); + REQUIRE(n == 65535); + } + + SECTION ("max value, signed") { + auto n = parse_size("32767").value(); + REQUIRE(n == 32767); + } + + SECTION ("overflow by 1, unsigned") { + auto n = parse_size("65536"); + REQUIRE(!n); + REQUIRE(n.error() == std::errc::result_out_of_range); + } + + SECTION ("overflow by 1, signed") { + auto n = parse_size("32768"); + REQUIRE(!n); + REQUIRE(n.error() == std::errc::result_out_of_range); + } + + SECTION ("overflow by many, unsigned") { + auto n = parse_size("100000"); + REQUIRE(!n); + REQUIRE(n.error() == std::errc::result_out_of_range); + } + + SECTION ("overflow by many, signed") { + auto n = parse_size("100000"); + REQUIRE(!n); + REQUIRE(n.error() == std::errc::result_out_of_range); + } +} + +TEST_CASE("parse_size: invalid multiplier", "[nihil]") +{ + using namespace nihil; + + auto n = parse_size("4z"); + REQUIRE(!n); + REQUIRE(n.error() == nihil::errc::invalid_unit); + + n = parse_size("4kz"); + REQUIRE(!n); + REQUIRE(n.error() == nihil::errc::invalid_unit); +} + +TEST_CASE("parse_size: multipliers", "[nihil]") +{ + using namespace nihil; + + auto sf = static_cast(4); + + SECTION ("k") { + auto n = parse_size("4k").value(); + REQUIRE(n == sf * 1024); + } + + SECTION ("m") { + auto n = parse_size("4m").value(); + REQUIRE(n == sf * 1024 * 1024); + } + + SECTION ("g") { + auto n = parse_size("4g").value(); + REQUIRE(n == sf * 1024 * 1024 * 1024); + } + + SECTION ("t") { + auto n = parse_size("4t").value(); + REQUIRE(n == sf * 1024 * 1024 * 1024 * 1024); + } + + SECTION ("p") { + auto n = parse_size("4p").value(); + REQUIRE(n == sf * 1024 * 1024 * 1024 * 1024 * 1024); + } +} + +TEST_CASE("parse_size: multiplier overflow", "[nihil]") +{ + using namespace nihil; + + SECTION ("signed") { + auto n = parse_size("64k"); + REQUIRE(!n); + REQUIRE(n.error() == std::errc::result_out_of_range); + } + + SECTION ("unsigned") { + auto n = parse_size("32k"); + REQUIRE(!n); + REQUIRE(n.error() == std::errc::result_out_of_range); + } +} + +TEST_CASE("parse_size: wide", "[nihil]") +{ + using namespace nihil; + + SECTION ("bare number") { + auto n = parse_size(L"1024").value(); + REQUIRE(n == 1024); + } +} + +TEST_CASE("parse_size: wide multipliers", "[nihil]") +{ + using namespace nihil; + + auto sf = static_cast(4); + + SECTION ("k") { + auto n = parse_size(L"4k").value(); + REQUIRE(n == sf * 1024); + } + + SECTION ("m") { + auto n = parse_size(L"4m").value(); + REQUIRE(n == sf * 1024 * 1024); + } + + SECTION ("g") { + auto n = parse_size(L"4g").value(); + REQUIRE(n == sf * 1024 * 1024 * 1024); + } + + SECTION ("t") { + auto n = parse_size(L"4t").value(); + REQUIRE(n == sf * 1024 * 1024 * 1024 * 1024); + } + + SECTION ("p") { + auto n = parse_size(L"4p").value(); + REQUIRE(n == sf * 1024 * 1024 * 1024 * 1024 * 1024); + } +} +} // anonymous namespace diff --git a/nihil.core/save_errno.ccm b/nihil.core/save_errno.ccm new file mode 100644 index 0000000..56eafa8 --- /dev/null +++ b/nihil.core/save_errno.ccm @@ -0,0 +1,35 @@ +// This source code is released into the public domain. +module; + +#include + +export module nihil.core:save_errno; + +// save_errno: save the current value of errno and restore it when we're destroyed. +// this allows wrappers around C functions that use errno to preserve the caller's +// errno value. + +namespace nihil { + +export struct save_errno final +{ + save_errno() : m_errno(errno) {} + + ~save_errno() + { + errno = m_errno; + } + + // Not copyable + save_errno(const save_errno&) = delete; + auto operator=(const save_errno&) -> save_errno & = delete; + + // Not movable + save_errno(save_errno&&) = delete; + auto operator=(save_errno&&) -> save_errno & = delete; + +private: + int m_errno; +}; + +} // namespace nihil diff --git a/nihil.core/skipws.ccm b/nihil.core/skipws.ccm new file mode 100644 index 0000000..3901120 --- /dev/null +++ b/nihil.core/skipws.ccm @@ -0,0 +1,26 @@ +// This source code is released into the public domain. +export module nihil.core:skipws; + +import nihil.std; +import :ctype; + +namespace nihil { + +// Remove leading whitespace from a string. +export template +[[nodiscard]] +auto skipws(std::basic_string_view text, std::locale const &locale = std::locale()) + -> std::basic_string_view +{ + auto is_space = ctype_is(std::ctype_base::space, locale); + auto nonws = std::ranges::find_if_not(text, is_space); + return {nonws, std::ranges::end(text)}; +} + +export template +auto skipws(std::basic_string_view *text, std::locale const &locale = std::locale()) -> void +{ + *text = skipws(*text, locale); +} + +} // namespace nihil diff --git a/nihil.core/skipws.test.cc b/nihil.core/skipws.test.cc new file mode 100644 index 0000000..c8163ae --- /dev/null +++ b/nihil.core/skipws.test.cc @@ -0,0 +1,48 @@ +// This source code is released into the public domain. + +#include + +import nihil.std; +import nihil.core; + +namespace { +TEST_CASE("skipws: basic", "[skipws]") +{ + using namespace std::literals; + + REQUIRE(nihil::skipws("foo"sv) == "foo"); + REQUIRE(nihil::skipws(" foo"sv) == "foo"); + REQUIRE(nihil::skipws("foo "sv) == "foo "); + REQUIRE(nihil::skipws("foo bar"sv) == "foo bar"); +} + +TEST_CASE("skipws: pointer", "[skipws]") +{ + using namespace std::literals; + + auto s = "foo"sv; + nihil::skipws(&s); + REQUIRE(s == "foo"); + + s = " foo"sv; + nihil::skipws(&s); + REQUIRE(s == "foo"); + + s = "foo "sv; + nihil::skipws(&s); + REQUIRE(s == "foo "); + + s = "foo bar"sv; + nihil::skipws(&s); + REQUIRE(s == "foo bar"); +} + +TEST_CASE("skipws: locale", "[skipws]") +{ + using namespace std::literals; + + // Assume the default locale is C. + REQUIRE(nihil::skipws(L"\u2003foo"sv) == L"\u2003foo"); + REQUIRE(nihil::skipws(L"\u2003foo"sv, std::locale("C.UTF-8")) == L"foo"); +} +} // anonymous namespace diff --git a/nihil.core/sys_error.ccm b/nihil.core/sys_error.ccm new file mode 100644 index 0000000..3e5911f --- /dev/null +++ b/nihil.core/sys_error.ccm @@ -0,0 +1,18 @@ +// This source code is released into the public domain. +module; + +#include + +export module nihil.core: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); // NOLINT +} + +} // namespace nihil diff --git a/nihil.core/tabulate.ccm b/nihil.core/tabulate.ccm new file mode 100644 index 0000000..98a957c --- /dev/null +++ b/nihil.core/tabulate.ccm @@ -0,0 +1,298 @@ +// This source code is released into the public domain. +export module nihil.core:tabulate; + +import nihil.std; + +import :ctype; +import :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 : error { + explicit table_spec_error(std::string_view what) + : error(what) + { + } +}; + +// The specification for a single field. +template +struct field_spec { + enum align_t { left, right }; + + // Get the name of this field. + auto name(this field_spec const &self) + -> std::basic_string_view + { + return self.m_name; + } + + // Set the name of this field. + auto name(this field_spec &self, + std::basic_string_view new_name) + -> void + { + self.m_name = new_name; + } + + // Set this field's alignment. + auto align(this field_spec &self, align_t new_align) -> void + { + self.m_align = new_align; + } + + // Ensure the length of this field is at least the given width. + auto ensure_width(this field_spec &self, std::size_t newwidth) + -> void + { + self.m_width = std::max(self.m_width, newwidth); + } + + // Format an object to a string based on our field spec. + [[nodiscard]] auto format(this field_spec const &, auto &&obj) + -> std::basic_string + { + auto format_string = std::basic_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(this field_spec const &self, + std::basic_string_view value, + std::output_iterator auto &out, + bool is_last) + -> void + { + auto padding = self.m_width - value.size(); + + if (self.m_align == right) + for (std::size_t i = 0; i < padding; ++i) + *out++ = ' '; + + std::ranges::copy(value, out); + + if (!is_last && self.m_align == left) + for (std::size_t i = 0; i < padding; ++i) + *out++ = ' '; + } + +private: + std::basic_string_view m_name; + std::size_t m_width = 0; + align_t m_align = left; +}; + +/* + * The specification for an entire table. + */ +template +struct table_spec { + // Add a new field spec to this table. + auto add(this table_spec &self, field_spec field) -> void + { + self.m_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. + [[nodiscard]] auto field(this table_spec &self, std::size_t fieldnr) + -> field_spec & + { + if (fieldnr >= self.m_fields.size()) + self.m_fields.resize(fieldnr + 1); + return self.m_fields.at(fieldnr); + } + + // The number of columns in this table. + [[nodiscard]] auto columns(this table_spec const &self) -> std::size_t + { + return self.m_fields.size(); + } + + // Return all the fields in this table. + [[nodiscard]] auto fields(this table_spec const &self) + -> std::vector> const & + { + return self.m_fields; + } + +private: + std::vector> m_fields; +}; + +// Parse the field flags, e.g. '<'. +template Sentinel> +auto parse_field_flags(field_spec &field, Iterator &pos, Sentinel end) + -> void +{ + while (pos < end) { + switch (*pos) { + case '<': + field.align(field_spec::left); + break; + case '>': + field.align(field_spec::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 Sentinel> +[[nodiscard]] auto parse_field(Iterator &pos, Sentinel end) + -> field_spec +{ + auto field = field_spec{}; + + 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(pos, brace)); + pos = std::next(brace); + + // The field must be at least as wide as its header. + field.ensure_width(field.name().size()); + + return field; +} + +template +[[nodiscard]] auto parse_table_spec(std::basic_string_view spec) + -> table_spec +{ + auto table = table_spec(); + + 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(pos, end)); + } + + return table; +} + +export template Iterator> +auto basic_tabulate(std::basic_string_view 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>>(); + // 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 auto &&out) +{ + return basic_tabulate(table_spec, + std::forward(range), + std::forward(out)); +} + +export auto wtabulate(std::wstring_view table_spec, + std::ranges::range auto &&range, + std::output_iterator auto &&out) +{ + return basic_tabulate(table_spec, + std::forward(range), + std::forward(out)); +} + +} // namespace nihil diff --git a/nihil.core/tabulate.test.cc b/nihil.core/tabulate.test.cc new file mode 100644 index 0000000..e1ea32f --- /dev/null +++ b/nihil.core/tabulate.test.cc @@ -0,0 +1,72 @@ +// This source code is released into the public domain. + +#include + +import nihil.std; +import nihil.core; + +using namespace std::literals; +using namespace nihil; + +namespace { +TEST_CASE("tabulate: basic", "[tabulate]") +{ + auto input = std::vector{ + std::vector{"a", "foo", "b"}, + std::vector{"bar", "c", "baz"}, + }; + + auto result = std::string(); + tabulate("{:1} {:2} {:3}", input, std::back_inserter(result)); + REQUIRE(result == +"1 2 3\n" +"a foo b\n" +"bar c baz\n"); +} + +TEST_CASE("tabulate: basic wide", "[tabulate]") +{ + auto input = std::vector{ + std::vector{L"a", L"foo", L"b"}, + std::vector{L"bar", L"c", L"baz"}, + }; + + auto result = std::wstring(); + wtabulate(L"{:1} {:2} {:3}", input, std::back_inserter(result)); + + REQUIRE(result == +L"1 2 3\n" +"a foo b\n" +"bar c baz\n"); +} + +TEST_CASE("tabulate: jagged", "[tabulate]") +{ + auto input = std::vector{ + std::vector{"a", "foo", "b"}, + std::vector{"bar", "baz"}, + }; + + auto result = std::string(); + tabulate("{:1} {:2} {:3}", input, std::back_inserter(result)); + REQUIRE(result == +"1 2 3\n" +"a foo b\n" +"bar baz\n"); +} + +TEST_CASE("tabulate: align", "[tabulate]") +{ + auto input = std::vector{ + std::vector{"a", "longvalue", "s"}, + std::vector{"a", "s", "longvalue"}, + }; + + auto result = std::string(); + tabulate("{:1} {<:2} {>:3}", input, std::back_inserter(result)); + REQUIRE(result == +"1 2 3\n" +"a longvalue s\n" +"a s longvalue\n"); +} +} // anonymous namespace diff --git a/nihil.core/uuid.ccm b/nihil.core/uuid.ccm new file mode 100644 index 0000000..b1b5d5f --- /dev/null +++ b/nihil.core/uuid.ccm @@ -0,0 +1,768 @@ +// From https://github.com/mariusbancila/stduuid +// +// Copyright (c) 2017 +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. + +export module nihil.core:uuid; + +import nihil.std; + +namespace nihil { + +template +[[nodiscard]] constexpr auto hex2char(TChar const ch) noexcept -> unsigned char +{ + if (ch >= static_cast('0') && ch <= static_cast('9')) + return static_cast(ch - static_cast('0')); + + if (ch >= static_cast('a') && ch <= static_cast('f')) + return static_cast(10 + ch - static_cast('a')); + + if (ch >= static_cast('A') && ch <= static_cast('F')) + return static_cast(10 + ch - static_cast('A')); + + return 0; +} + +template +[[nodiscard]] constexpr auto is_hex(TChar const ch) noexcept -> bool +{ + return (ch >= static_cast('0') && ch <= static_cast('9')) || + (ch >= static_cast('a') && ch <= static_cast('f')) || + (ch >= static_cast('A') && ch <= static_cast('F')); +} + +template +[[nodiscard]] constexpr auto +to_string_view(TChar const *str) noexcept -> std::basic_string_view +{ + if (str) + return str; + return {}; +} + +template +[[nodiscard]] constexpr auto to_string_view(StringType const &str) noexcept + -> std::basic_string_view +{ + return str; +} + +struct sha1 +{ + using digest32_t = std::array; + using digest8_t = std::array; + + static constexpr unsigned int block_bytes = 64; + + sha1() + { + reset(); + } + + [[nodiscard]] static auto + left_rotate(std::uint32_t value, std::size_t const count) noexcept -> std::uint32_t + { + return (value << count) ^ (value >> (32 - count)); + } + + auto reset(this sha1 &self) noexcept -> void + { + self.m_digest[0] = 0x67452301; + self.m_digest[1] = 0xEFCDAB89; + self.m_digest[2] = 0x98BADCFE; + self.m_digest[3] = 0x10325476; + self.m_digest[4] = 0xC3D2E1F0; + self.m_blockByteIndex = 0; + self.m_byteCount = 0; + } + + auto process_byte(this sha1 &self, std::uint8_t octet) -> void + { + self.m_block.at(self.m_blockByteIndex++) = octet; + ++self.m_byteCount; + + if (self.m_blockByteIndex == block_bytes) { + self.m_blockByteIndex = 0; + self.process_block(); + } + } + + auto process_block(this sha1 &self, void const *const start, void const *const end) -> void + { + auto const *first = static_cast(start); + auto const *last = static_cast(end); + + while (first != last) { + self.process_byte(*first); + first++; + } + } + + auto process_bytes(this sha1 &self, void const *const data, std::size_t const len) -> void + { + auto *block = static_cast(data); + self.process_block(block, block + len); + } + + auto get_digest(this sha1 &self) -> digest32_t + { + auto const bit_count = self.m_byteCount * 8; + + self.process_byte(0x80); + if (self.m_blockByteIndex > 56) { + while (self.m_blockByteIndex != 0) + self.process_byte(0); + + while (self.m_blockByteIndex < 56) + self.process_byte(0); + } else { + while (self.m_blockByteIndex < 56) + self.process_byte(0); + } + + self.process_byte(0); + self.process_byte(0); + self.process_byte(0); + self.process_byte(0); + self.process_byte(static_cast((bit_count >> 24U) & 0xFFU)); + self.process_byte(static_cast((bit_count >> 16U) & 0xFFU)); + self.process_byte(static_cast((bit_count >> 8U) & 0xFFU)); + self.process_byte(static_cast((bit_count) & 0xFFU)); + + return self.m_digest; + } + + auto get_digest_bytes(this sha1 &self) -> digest8_t + { + auto d32 = self.get_digest(); + + return { + static_cast(d32[0] >> 24U), + static_cast(d32[0] >> 16U), + static_cast(d32[0] >> 8U), + static_cast(d32[0] >> 0U), + + static_cast(d32[1] >> 24U), + static_cast(d32[1] >> 16U), + static_cast(d32[1] >> 8U), + static_cast(d32[1] >> 0U), + + static_cast(d32[2] >> 24U), + static_cast(d32[2] >> 16U), + static_cast(d32[2] >> 8U), + static_cast(d32[2] >> 0U), + + static_cast(d32[3] >> 24U), + static_cast(d32[3] >> 16U), + static_cast(d32[3] >> 8U), + static_cast(d32[3] >> 0U), + + static_cast(d32[4] >> 24U), + static_cast(d32[4] >> 16U), + static_cast(d32[4] >> 8U), + static_cast(d32[4] >> 0U), + }; + } + +private: + auto process_block(this sha1 &self) -> void + { + auto w = std::array{}; + + for (std::size_t i = 0; i < 16; i++) { + w.at(i) = static_cast(self.m_block.at((i * 4) + 0)) << 24U; + w.at(i) |= static_cast(self.m_block.at((i * 4) + 1)) << 16U; + w.at(i) |= static_cast(self.m_block.at((i * 4) + 2)) << 8U; + w.at(i) |= static_cast(self.m_block.at((i * 4) + 3)); + } + + for (std::size_t i = 16; i < 80; i++) { + w.at(i) = left_rotate( + (w.at(i - 3) ^ w.at(i - 8) ^ w.at(i - 14) ^ w.at(i - 16)), 1); + } + + auto a = self.m_digest[0]; + auto b = self.m_digest[1]; + auto c = self.m_digest[2]; + auto d = self.m_digest[3]; + auto e = self.m_digest[4]; + + for (std::size_t i = 0; i < 80; ++i) { + auto f = std::uint32_t{0}; + auto k = std::uint32_t{0}; + + if (i < 20) { + f = (b & c) | (~b & d); + k = 0x5A827999; + } else if (i < 40) { + f = b ^ c ^ d; + k = 0x6ED9EBA1; + } else if (i < 60) { + f = (b & c) | (b & d) | (c & d); + k = 0x8F1BBCDC; + } else { + f = b ^ c ^ d; + k = 0xCA62C1D6; + } + + auto temp = std::uint32_t{left_rotate(a, 5) + f + e + k + w.at(i)}; + e = d; + d = c; + c = left_rotate(b, 30); + b = a; + a = temp; + } + + self.m_digest[0] += a; + self.m_digest[1] += b; + self.m_digest[2] += c; + self.m_digest[3] += d; + self.m_digest[4] += e; + } + + digest32_t m_digest{}; + std::array m_block{}; + std::size_t m_blockByteIndex{}; + std::size_t m_byteCount{}; +}; + +template +inline constexpr std::string_view empty_guid = "00000000-0000-0000-0000-000000000000"; + +template <> +inline constexpr std::wstring_view empty_guid = L"00000000-0000-0000-0000-000000000000"; + +template +inline constexpr std::string_view guid_encoder = "0123456789abcdef"; + +template <> +inline constexpr std::wstring_view guid_encoder = L"0123456789abcdef"; + +// --------------------------------------------------------------------- +// UUID format https://tools.ietf.org/html/rfc4122 +// --------------------------------------------------------------------- + +// --------------------------------------------------------------------- +// Field NDR Data Type Octet # Note +// Note +// --------------------------------------------------------------------- +// time_low unsigned long 0 - 3 +// The low field of the timestamp. +// time_mid unsigned short 4 - 5 +// The middle field of the timestamp. +// time_hi_and_version unsigned short 6 - 7 +// The high field of the timestamp multiplexed with the version number. +// clock_seq_hi_and_reserved unsigned small 8 +// The high field of the clock sequence multiplexed with the variant. +// clock_seq_low unsigned small 9 +// The low field of the clock sequence. +// node character 10 - 15 +// The spatially unique node identifier. +// --------------------------------------------------------------------- +// 0 1 2 3 +// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | time_low | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | time_mid | time_hi_and_version | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// |clk_seq_hi_res | clk_seq_low | node (0-1) | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | node (2-5) | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + +// --------------------------------------------------------------------- +// enumerations +// --------------------------------------------------------------------- + +// indicated by a bit pattern in octet 8, marked with N in +// xxxxxxxx-xxxx-xxxx-Nxxx-xxxxxxxxxxxx +export enum struct uuid_variant : std::uint8_t { + // NCS backward compatibility (with the obsolete Apollo Network + // Computing System 1.5 UUID format). + // N bit pattern: 0xxx + // > the first 6 octets of the UUID are a 48-bit timestamp (the number + // of 4 microsecond units of time since 1 Jan 1980 UTC); + // > the next 2 octets are reserved; + // > the next octet is the "address family"; + // > the final 7 octets are a 56-bit host ID in the form specified by + // the address family + ncs, + + // RFC 4122/DCE 1.1 + // N bit pattern: 10xx + // > big-endian byte order + rfc, + + // Microsoft Corporation backward compatibility + // N bit pattern: 110x + // > little endian byte order + // > formely used in the Component Object Model (COM) library + microsoft, + + // reserved for possible future definition + // N bit pattern: 111x + reserved +}; + +// indicated by a bit pattern in octet 6, marked with M in +// xxxxxxxx-xxxx-Mxxx-xxxx-xxxxxxxxxxxx +export enum struct uuid_version : std::uint8_t { + // only possible for nil or invalid uuids + none = 0, + // The time-based version specified in RFC 4122 + time_based = 1, + // DCE Security version, with embedded POSIX UIDs. + dce_security = 2, + // The name-based version specified in RFS 4122 with MD5 hashing + name_based_md5 = 3, + // The randomly or pseudo-randomly generated version specified in RFS 4122 + random_number_based = 4, + // The name-based version specified in RFS 4122 with SHA1 hashing + name_based_sha1 = 5 +}; + +// Forward declare uuid and to_string so that we can declare to_string as a +// friend later. +export struct uuid; +export template , + typename Allocator = std::allocator> +auto to_string(uuid const &id) -> std::basic_string; + +// -------------------------------------------------------------------------------------------------------------------------- +// uuid class +// -------------------------------------------------------------------------------------------------------------------------- +export struct uuid +{ + using value_type = std::uint8_t; + + constexpr uuid() noexcept = default; + + uuid(value_type (&arr)[16]) noexcept // NOLINT + { + std::ranges::copy(arr, std::ranges::begin(data)); + } + + explicit constexpr uuid(std::array const &arr) noexcept + : data{arr} + { + } + + explicit uuid(std::span bytes) + { + std::ranges::copy(bytes, std::ranges::begin(data)); + } + + explicit uuid(std::span bytes) + { + if (bytes.size() != 16) + throw std::logic_error("wrong size for uuid"); + std::ranges::copy(bytes, std::ranges::begin(data)); + } + + template + explicit uuid(ForwardIterator first, ForwardIterator last) + { + if (std::distance(first, last) != 16) + throw std::logic_error("wrong size for uuid"); + + std::copy(first, last, std::begin(data)); + } + + [[nodiscard]] constexpr auto variant() const noexcept -> uuid_variant + { + if ((data[8] & 0x80U) == 0x00U) + return uuid_variant::ncs; + else if ((data[8] & 0xC0U) == 0x80U) + return uuid_variant::rfc; + else if ((data[8] & 0xE0U) == 0xC0U) + return uuid_variant::microsoft; + else + return uuid_variant::reserved; + } + + [[nodiscard]] constexpr auto version() const noexcept -> uuid_version + { + if ((data[6] & 0xF0U) == 0x10U) + return uuid_version::time_based; + else if ((data[6] & 0xF0U) == 0x20U) + return uuid_version::dce_security; + else if ((data[6] & 0xF0U) == 0x30U) + return uuid_version::name_based_md5; + else if ((data[6] & 0xF0U) == 0x40U) + return uuid_version::random_number_based; + else if ((data[6] & 0xF0U) == 0x50U) + return uuid_version::name_based_sha1; + else + return uuid_version::none; + } + + [[nodiscard]] constexpr auto is_nil() const noexcept -> bool + { + return std::ranges::all_of(data, [](auto i) { return i == 0; }); + } + + auto swap(uuid &other) noexcept -> void + { + data.swap(other.data); + } + + [[nodiscard]] auto as_bytes() const -> std::span + { + return std::span( + reinterpret_cast(data.data()), 16); + } + + template + [[nodiscard]] constexpr static auto is_valid_uuid(StringType const &in_str) noexcept -> bool + { + auto str = to_string_view(in_str); + auto firstDigit = true; + auto hasBraces = std::size_t{0}; + auto index = std::size_t{0}; + + if (str.empty()) + return false; + + if (str.front() == '{') + hasBraces = 1; + + if (hasBraces && str.back() != '}') + return false; + + for (std::size_t i = hasBraces; i < str.size() - hasBraces; ++i) { + if (str[i] == '-') + continue; + + if (index >= 16 || !is_hex(str[i])) + return false; + + if (firstDigit) { + firstDigit = false; + } else { + index++; + firstDigit = true; + } + } + + if (index < 16) + return false; + + return true; + } + + template + [[nodiscard]] constexpr static auto + from_string(StringType const &in_str) -> std::optional + { + auto str = to_string_view(in_str); + bool firstDigit = true; + auto hasBraces = std::size_t{0}; + auto index = std::size_t{0}; + + auto data = std::array{}; + + if (str.empty()) + return {}; + + if (str.front() == '{') + hasBraces = 1; + if (hasBraces && str.back() != '}') + return {}; + + for (std::size_t i = hasBraces; i < str.size() - hasBraces; ++i) { + if (str[i] == '-') + continue; + + if (index >= 16 || !is_hex(str[i])) { + return {}; + } + + if (firstDigit) { + data.at(index) = static_cast(hex2char(str[i]) << 4); + firstDigit = false; + } else { + data.at(index) = + static_cast(data.at(index) | hex2char(str[i])); + index++; + firstDigit = true; + } + } + + if (index < 16) { + return {}; + } + + return uuid{data}; + } + +private: + std::array data{{0}}; + + friend auto operator==(uuid const &, uuid const &) noexcept -> bool; + friend auto operator<(uuid const &, uuid const &) noexcept -> bool; + + template + friend auto operator<<(std::basic_ostream &s, uuid const &id) + -> std::basic_ostream &; + + template + friend auto to_string(uuid const &id) -> std::basic_string; + + friend std::hash; +}; + +// -------------------------------------------------------------------------------------------------------------------------- +// operators and non-member functions +// -------------------------------------------------------------------------------------------------------------------------- + +export [[nodiscard]] +auto operator==(uuid const &lhs, uuid const &rhs) noexcept -> bool +{ + return lhs.data == rhs.data; +} + +export [[nodiscard]] +auto operator!=(uuid const &lhs, uuid const &rhs) noexcept -> bool +{ + return !(lhs == rhs); +} + +export [[nodiscard]] +auto operator<(uuid const &lhs, uuid const &rhs) noexcept -> bool +{ + return lhs.data < rhs.data; +} + +export template +[[nodiscard]] auto to_string(uuid const &id) -> std::basic_string +{ + auto uustr = + std::basic_string(std::from_range, empty_guid); + + for (std::size_t i = 0, index = 0; i < 36; ++i) { + if (i == 8 || i == 13 || i == 18 || i == 23) + continue; + + uustr[i] = guid_encoder[id.data.at(index) >> 4U & 0x0FU]; + uustr[++i] = guid_encoder[id.data.at(index) & 0x0FU]; + index++; + } + + return uustr; +} + +export template +auto operator<<(std::basic_ostream &s, uuid const &id) + -> std::basic_ostream & +{ + return s << to_string(id); +} + +export auto swap(uuid &lhs, uuid &rhs) noexcept -> void +{ + lhs.swap(rhs); +} + +/*********************************************************************** + * namespace IDs that could be used for generating name-based uuids + */ + +// Name string is a fully-qualified domain name +export uuid uuid_namespace_dns{ + {0x6b, 0xa7, 0xb8, 0x10, 0x9d, 0xad, 0x11, 0xd1, 0x80, 0xb4, 0x00, 0xc0, 0x4f, 0xd4, 0x30, + 0xc8} +}; + +// Name string is a URL +export uuid uuid_namespace_url{ + {0x6b, 0xa7, 0xb8, 0x11, 0x9d, 0xad, 0x11, 0xd1, 0x80, 0xb4, 0x00, 0xc0, 0x4f, 0xd4, 0x30, + 0xc8} +}; + +// Name string is an ISO OID (See https://oidref.com/, +// https://en.wikipedia.org/wiki/Object_identifier) +export uuid uuid_namespace_oid{ + {0x6b, 0xa7, 0xb8, 0x12, 0x9d, 0xad, 0x11, 0xd1, 0x80, 0xb4, 0x00, 0xc0, 0x4f, 0xd4, 0x30, + 0xc8} +}; + +// Name string is an X.500 DN, in DER or a text output format (See +// https://en.wikipedia.org/wiki/X.500, +// https://en.wikipedia.org/wiki/Abstract_Syntax_Notation_One) +export uuid uuid_namespace_x500{ + {0x6b, 0xa7, 0xb8, 0x14, 0x9d, 0xad, 0x11, 0xd1, 0x80, 0xb4, 0x00, 0xc0, 0x4f, 0xd4, 0x30, + 0xc8} +}; + +/*********************************************************************** + * uuid generators + */ + +export template +struct basic_uuid_random_generator +{ + using engine_type = UniformRandomNumberGenerator; + + explicit basic_uuid_random_generator(engine_type &gen) + : generator(&gen, [](auto) {}) + { + } + + explicit basic_uuid_random_generator(engine_type *gen) + : generator(gen, [](auto) {}) + { + } + + [[nodiscard]] auto operator()() -> uuid + { + auto bytes = std::array{}; + std::ranges::generate(bytes, [&] { return distribution(*generator); }); + + // variant must be 10xxxxxx + bytes[8] &= 0xBFU; + bytes[8] |= 0x80U; + + // version must be 0100xxxx + bytes[6] &= 0x4FU; + bytes[6] |= 0x40U; + + return uuid{std::begin(bytes), std::end(bytes)}; + } + +private: + std::uniform_int_distribution distribution; + std::shared_ptr generator; +}; + +export using uuid_random_generator = basic_uuid_random_generator; + +export struct uuid_name_generator +{ + explicit uuid_name_generator(uuid const &namespace_uuid) noexcept + : nsuuid(namespace_uuid) + { + } + + template + [[nodiscard]] auto operator()(StringType const &name) -> uuid + { + reset(); + process_characters(to_string_view(name)); + return make_uuid(); + } + +private: + auto reset() -> void + { + hasher.reset(); + + auto nsbytes = nsuuid.as_bytes(); + + auto bytes = std::array(); + std::ranges::copy(nsbytes, std::ranges::begin(bytes)); + + hasher.process_bytes(bytes.data(), bytes.size()); + } + + template + auto process_characters(std::basic_string_view const str) -> void + { + for (std::uint32_t c : str) { + hasher.process_byte(static_cast(c & 0xFFU)); + if constexpr (!std::is_same_v) { + hasher.process_byte(static_cast((c >> 8U) & 0xFFU)); + hasher.process_byte(static_cast((c >> 16U) & 0xFFU)); + hasher.process_byte(static_cast((c >> 24U) & 0xFFU)); + } + } + } + + [[nodiscard]] auto make_uuid() -> uuid + { + auto digest = hasher.get_digest_bytes(); + + // variant must be 0b10xxxxxx + digest[8] &= 0xBFU; + digest[8] |= 0x80U; + + // version must be 0b0101xxxx + digest[6] &= 0x5FU; + digest[6] |= 0x50U; + + return uuid(std::span(digest).subspan(0, 16)); + } + + uuid nsuuid; + sha1 hasher; +}; + +/* + * Create a random UUID. + */ +export auto random_uuid() -> uuid +{ + auto rd = std::random_device(); + auto seed_data = std::array{}; + std::ranges::generate(seed_data, std::ref(rd)); + + auto seq = std::seed_seq(std::ranges::begin(seed_data), std::ranges::end(seed_data)); + auto generator = std::mt19937(seq); + auto gen = uuid_random_generator{generator}; + + return gen(); +} + +} // namespace nihil + +namespace std { + +export template <> +struct hash +{ + using argument_type = nihil::uuid; + using result_type = std::size_t; + + [[nodiscard]] auto operator()(argument_type const &uuid) const noexcept -> result_type + { + auto const l = static_cast(uuid.data[0]) << 56U | + static_cast(uuid.data[1]) << 48U | + static_cast(uuid.data[2]) << 40U | + static_cast(uuid.data[3]) << 32U | + static_cast(uuid.data[4]) << 24U | + static_cast(uuid.data[5]) << 16U | + static_cast(uuid.data[6]) << 8U | + static_cast(uuid.data[7]); + + auto const h = static_cast(uuid.data[8]) << 56U | + static_cast(uuid.data[9]) << 48U | + static_cast(uuid.data[10]) << 40U | + static_cast(uuid.data[11]) << 32U | + static_cast(uuid.data[12]) << 24U | + static_cast(uuid.data[13]) << 16U | + static_cast(uuid.data[14]) << 8U | + static_cast(uuid.data[15]); + + return std::hash{}(l ^ h); + } +}; + +} // namespace std diff --git a/nihil.core/uuid.test.cc b/nihil.core/uuid.test.cc new file mode 100644 index 0000000..407f4e1 --- /dev/null +++ b/nihil.core/uuid.test.cc @@ -0,0 +1,923 @@ +/* + * From https://github.com/mariusbancila/stduuid + * + * Copyright (c) 2017 + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + +#include + +import nihil.std; +import nihil.core; + +// NOLINTBEGIN(bugprone-unchecked-optional-access) + +namespace { +// http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2016/p0205r0.html +template +void seed_rng(EngineT &engine) +{ + using engine_type = typename EngineT::result_type; + using device_type = std::random_device::result_type; + using seedseq_type = std::seed_seq::result_type; + + constexpr auto bytes_needed = StateSize * sizeof(engine_type); + constexpr auto numbers_needed = (sizeof(device_type) < sizeof(seedseq_type)) + ? (bytes_needed / sizeof(device_type)) + : (bytes_needed / sizeof(seedseq_type)); + + auto numbers = std::array{}; + auto rnddev = std::random_device{}; + std::ranges::generate(numbers, std::ref(rnddev)); + + auto seedseq = std::seed_seq(std::cbegin(numbers), std::cend(numbers)); + engine.seed(seedseq); +} + +using namespace nihil; + +TEST_CASE("uuid: Test multiple default generators", "[uuid]") +{ + uuid id1; + uuid id2; + + { + std::random_device rd; + auto seed_data = std::array{}; + std::ranges::generate(seed_data, std::ref(rd)); + std::seed_seq seq(std::begin(seed_data), std::end(seed_data)); + std::mt19937 generator(seq); + + id1 = uuid_random_generator{generator}(); + REQUIRE(!id1.is_nil()); + REQUIRE(id1.version() == uuid_version::random_number_based); + REQUIRE(id1.variant() == uuid_variant::rfc); + } + + { + std::random_device rd; + auto seed_data = std::array{}; + std::ranges::generate(seed_data, std::ref(rd)); + std::seed_seq seq(std::begin(seed_data), std::end(seed_data)); + std::mt19937 generator(seq); + + id2 = uuid_random_generator{generator}(); + REQUIRE(!id2.is_nil()); + REQUIRE(id2.version() == uuid_version::random_number_based); + REQUIRE(id2.variant() == uuid_variant::rfc); + } + + REQUIRE(id1 != id2); +} + +TEST_CASE("uuid: Test default generator", "[uuid]") +{ + std::random_device rd; + auto seed_data = std::array{}; + std::ranges::generate(seed_data, std::ref(rd)); + std::seed_seq seq(std::begin(seed_data), std::end(seed_data)); + std::mt19937 generator(seq); + + uuid const guid = uuid_random_generator{generator}(); + REQUIRE(!guid.is_nil()); + REQUIRE(guid.version() == uuid_version::random_number_based); + REQUIRE(guid.variant() == uuid_variant::rfc); +} + +TEST_CASE("uuid: Test random generator (conversion ctor w/ smart ptr)", "[uuid]") +{ + std::random_device rd; + auto seed_data = std::array{}; + std::ranges::generate(seed_data, std::ref(rd)); + std::seed_seq seq(std::begin(seed_data), std::end(seed_data)); + std::mt19937 generator(seq); + + uuid_random_generator dgen(&generator); + auto id1 = dgen(); + REQUIRE(!id1.is_nil()); + REQUIRE(id1.version() == uuid_version::random_number_based); + REQUIRE(id1.variant() == uuid_variant::rfc); + + auto id2 = dgen(); + REQUIRE(!id2.is_nil()); + REQUIRE(id2.version() == uuid_version::random_number_based); + REQUIRE(id2.variant() == uuid_variant::rfc); + + REQUIRE(id1 != id2); +} + +TEST_CASE("uuid: Test random generator (conversion ctor w/ ptr)", "[uuid]") +{ + std::random_device rd; + auto seed_data = std::array{}; + std::ranges::generate(seed_data, std::ref(rd)); + std::seed_seq seq(std::begin(seed_data), std::end(seed_data)); + auto generator = std::make_unique(seq); + + uuid_random_generator dgen(generator.get()); + auto id1 = dgen(); + REQUIRE(!id1.is_nil()); + REQUIRE(id1.version() == uuid_version::random_number_based); + REQUIRE(id1.variant() == uuid_variant::rfc); + + auto id2 = dgen(); + REQUIRE(!id1.is_nil()); + REQUIRE(id2.version() == uuid_version::random_number_based); + REQUIRE(id2.variant() == uuid_variant::rfc); + + REQUIRE(id1 != id2); +} + +TEST_CASE("uuid: Test random generator (conversion ctor w/ ref)", "[uuid]") +{ + std::random_device rd; + auto seed_data = std::array{}; + std::ranges::generate(seed_data, std::ref(rd)); + std::seed_seq seq(std::begin(seed_data), std::end(seed_data)); + std::mt19937 generator(seq); + + uuid_random_generator dgen(generator); + auto id1 = dgen(); + REQUIRE(!id1.is_nil()); + REQUIRE(id1.version() == uuid_version::random_number_based); + REQUIRE(id1.variant() == uuid_variant::rfc); + + auto id2 = dgen(); + REQUIRE(!id2.is_nil()); + REQUIRE(id2.version() == uuid_version::random_number_based); + REQUIRE(id2.variant() == uuid_variant::rfc); + + REQUIRE(id1 != id2); +} + +TEST_CASE("uuid: Test basic random generator (conversion ctor w/ ptr) " + "w/ ranlux48_base", + "[uuid]") +{ + std::random_device rd; + auto seed_data = std::array{}; + std::ranges::generate(seed_data, std::ref(rd)); + std::seed_seq seq(std::begin(seed_data), std::end(seed_data)); + std::ranlux48_base generator(seq); + + basic_uuid_random_generator dgen(&generator); + auto id1 = dgen(); + REQUIRE(!id1.is_nil()); + REQUIRE(id1.version() == uuid_version::random_number_based); + REQUIRE(id1.variant() == uuid_variant::rfc); + + auto id2 = dgen(); + REQUIRE(!id2.is_nil()); + REQUIRE(id2.version() == uuid_version::random_number_based); + REQUIRE(id2.variant() == uuid_variant::rfc); + + REQUIRE(id1 != id2); +} + +TEST_CASE("uuid: Test basic random generator (conversion ctor w/ smart ptr) " + "w/ ranlux48_base", + "[uuid]") +{ + std::random_device rd; + auto seed_data = std::array{}; + std::ranges::generate(seed_data, std::ref(rd)); + std::seed_seq seq(std::begin(seed_data), std::end(seed_data)); + auto generator = std::make_unique(seq); + + basic_uuid_random_generator dgen(generator.get()); + auto id1 = dgen(); + REQUIRE(!id1.is_nil()); + REQUIRE(id1.version() == uuid_version::random_number_based); + REQUIRE(id1.variant() == uuid_variant::rfc); + + auto id2 = dgen(); + REQUIRE(!id2.is_nil()); + REQUIRE(id2.version() == uuid_version::random_number_based); + REQUIRE(id2.variant() == uuid_variant::rfc); + + REQUIRE(id1 != id2); +} + +TEST_CASE("uuid: Test basic random generator (conversion ctor w/ ref) " + "w/ ranlux48_base", + "[uuid]") +{ + std::random_device rd; + auto seed_data = std::array{}; + std::ranges::generate(seed_data, std::ref(rd)); + std::seed_seq seq(std::begin(seed_data), std::end(seed_data)); + std::ranlux48_base generator(seq); + + basic_uuid_random_generator dgen(generator); + auto id1 = dgen(); + REQUIRE(!id1.is_nil()); + REQUIRE(id1.version() == uuid_version::random_number_based); + REQUIRE(id1.variant() == uuid_variant::rfc); + + auto id2 = dgen(); + REQUIRE(!id2.is_nil()); + REQUIRE(id2.version() == uuid_version::random_number_based); + REQUIRE(id2.variant() == uuid_variant::rfc); + + REQUIRE(id1 != id2); +} + +TEST_CASE("uuid: Test namespaces", "[uuid]") +{ + REQUIRE(uuid_namespace_dns == uuid::from_string("6ba7b810-9dad-11d1-80b4-00c04fd430c8")); + REQUIRE(uuid_namespace_url == uuid::from_string("6ba7b811-9dad-11d1-80b4-00c04fd430c8")); + REQUIRE(uuid_namespace_oid == uuid::from_string("6ba7b812-9dad-11d1-80b4-00c04fd430c8")); + REQUIRE(uuid_namespace_x500 == uuid::from_string("6ba7b814-9dad-11d1-80b4-00c04fd430c8")); +} + +TEST_CASE("uuid: Test name generator (char*)", "[uuid]") +{ + uuid_name_generator dgen(uuid::from_string("47183823-2574-4bfd-b411-99ed177d3e43").value()); + + auto id1 = dgen("john"); + REQUIRE(!id1.is_nil()); + REQUIRE(id1.version() == uuid_version::name_based_sha1); + REQUIRE(id1.variant() == uuid_variant::rfc); + + auto id2 = dgen("jane"); + REQUIRE(!id2.is_nil()); + REQUIRE(id2.version() == uuid_version::name_based_sha1); + REQUIRE(id2.variant() == uuid_variant::rfc); + + auto id3 = dgen("jane"); + REQUIRE(!id3.is_nil()); + REQUIRE(id3.version() == uuid_version::name_based_sha1); + REQUIRE(id3.variant() == uuid_variant::rfc); + + auto id4 = dgen(L"jane"); + REQUIRE(!id4.is_nil()); + REQUIRE(id4.version() == uuid_version::name_based_sha1); + REQUIRE(id4.variant() == uuid_variant::rfc); + + REQUIRE(id1 != id2); + REQUIRE(id2 == id3); + REQUIRE(id3 != id4); +} + +TEST_CASE("uuid: Test name generator (std::string)", "[uuid]") +{ + using namespace std::string_literals; + + uuid_name_generator dgen(uuid::from_string("47183823-2574-4bfd-b411-99ed177d3e43").value()); + auto id1 = dgen("john"s); + REQUIRE(!id1.is_nil()); + REQUIRE(id1.version() == uuid_version::name_based_sha1); + REQUIRE(id1.variant() == uuid_variant::rfc); + + auto id2 = dgen("jane"s); + REQUIRE(!id2.is_nil()); + REQUIRE(id2.version() == uuid_version::name_based_sha1); + REQUIRE(id2.variant() == uuid_variant::rfc); + + auto id3 = dgen("jane"s); + REQUIRE(!id3.is_nil()); + REQUIRE(id3.version() == uuid_version::name_based_sha1); + REQUIRE(id3.variant() == uuid_variant::rfc); + + auto id4 = dgen(L"jane"s); + REQUIRE(!id4.is_nil()); + REQUIRE(id4.version() == uuid_version::name_based_sha1); + REQUIRE(id4.variant() == uuid_variant::rfc); + + REQUIRE(id1 != id2); + REQUIRE(id2 == id3); + REQUIRE(id3 != id4); +} + +TEST_CASE("uuid: Test name generator (std::string_view)", "[uuid]") +{ + using namespace std::string_view_literals; + + uuid_name_generator dgen(uuid::from_string("47183823-2574-4bfd-b411-99ed177d3e43").value()); + auto id1 = dgen("john"sv); + REQUIRE(!id1.is_nil()); + REQUIRE(id1.version() == uuid_version::name_based_sha1); + REQUIRE(id1.variant() == uuid_variant::rfc); + + auto id2 = dgen("jane"sv); + REQUIRE(!id2.is_nil()); + REQUIRE(id2.version() == uuid_version::name_based_sha1); + REQUIRE(id2.variant() == uuid_variant::rfc); + + auto id3 = dgen("jane"sv); + REQUIRE(!id3.is_nil()); + REQUIRE(id3.version() == uuid_version::name_based_sha1); + REQUIRE(id3.variant() == uuid_variant::rfc); + + auto id4 = dgen(L"jane"sv); + REQUIRE(!id4.is_nil()); + REQUIRE(id4.version() == uuid_version::name_based_sha1); + REQUIRE(id4.variant() == uuid_variant::rfc); + + REQUIRE(id1 != id2); + REQUIRE(id2 == id3); + REQUIRE(id3 != id4); +} + +TEST_CASE("uuid: Test name generator equality (char const*, std::string, " + "std::string_view)", + "[uuid]") +{ + using namespace std::literals; + + auto dgen = uuid_name_generator( + uuid::from_string("47183823-2574-4bfd-b411-99ed177d3e43").value()); + auto id1 = dgen("john"); + auto id2 = dgen("john"s); + auto id3 = dgen("john"sv); + + REQUIRE(id1 == id2); + REQUIRE(id2 == id3); +} + +TEST_CASE("uuid: Test default constructor", "[uuid]") +{ + auto empty = uuid(); + REQUIRE(empty.is_nil()); +} + +TEST_CASE("uuid: Test string conversion", "[uuid]") +{ + auto empty = uuid(); + REQUIRE(to_string(empty) == "00000000-0000-0000-0000-000000000000"); + REQUIRE(to_string(empty) == L"00000000-0000-0000-0000-000000000000"); +} + +TEST_CASE("uuid: Test is_valid_uuid(char*)", "[uuid]") +{ + REQUIRE(uuid::is_valid_uuid("47183823-2574-4bfd-b411-99ed177d3e43")); + REQUIRE(uuid::is_valid_uuid("{47183823-2574-4bfd-b411-99ed177d3e43}")); + REQUIRE(uuid::is_valid_uuid(L"47183823-2574-4bfd-b411-99ed177d3e43")); + REQUIRE(uuid::is_valid_uuid(L"{47183823-2574-4bfd-b411-99ed177d3e43}")); + REQUIRE(uuid::is_valid_uuid("00000000-0000-0000-0000-000000000000")); + REQUIRE(uuid::is_valid_uuid("{00000000-0000-0000-0000-000000000000}")); + REQUIRE(uuid::is_valid_uuid(L"00000000-0000-0000-0000-000000000000")); + REQUIRE(uuid::is_valid_uuid(L"{00000000-0000-0000-0000-000000000000}")); +} + +TEST_CASE("uuid: Test is_valid_uuid(basic_string)", "[uuid]") +{ + using namespace std::string_literals; + + { + auto str = "47183823-2574-4bfd-b411-99ed177d3e43"s; + REQUIRE(uuid::is_valid_uuid(str)); + } + + { + auto str = "{47183823-2574-4bfd-b411-99ed177d3e43}"s; + REQUIRE(uuid::is_valid_uuid(str)); + } + + { + auto str = L"47183823-2574-4bfd-b411-99ed177d3e43"s; + REQUIRE(uuid::is_valid_uuid(str)); + } + + { + auto str = L"{47183823-2574-4bfd-b411-99ed177d3e43}"s; + REQUIRE(uuid::is_valid_uuid(str)); + } + + { + auto str = "00000000-0000-0000-0000-000000000000"s; + REQUIRE(uuid::is_valid_uuid(str)); + } + + { + auto str = "{00000000-0000-0000-0000-000000000000}"s; + REQUIRE(uuid::is_valid_uuid(str)); + } + + { + auto str = L"00000000-0000-0000-0000-000000000000"s; + REQUIRE(uuid::is_valid_uuid(str)); + } + + { + auto str = L"{00000000-0000-0000-0000-000000000000}"s; + REQUIRE(uuid::is_valid_uuid(str)); + } +} + +TEST_CASE("uuid: Test is_valid_uuid(basic_string_view)", "[uuid]") +{ + using namespace std::string_view_literals; + + REQUIRE(uuid::is_valid_uuid("47183823-2574-4bfd-b411-99ed177d3e43"sv)); + REQUIRE(uuid::is_valid_uuid("{47183823-2574-4bfd-b411-99ed177d3e43}"sv)); + REQUIRE(uuid::is_valid_uuid(L"47183823-2574-4bfd-b411-99ed177d3e43"sv)); + REQUIRE(uuid::is_valid_uuid(L"{47183823-2574-4bfd-b411-99ed177d3e43}"sv)); + REQUIRE(uuid::is_valid_uuid("00000000-0000-0000-0000-000000000000"sv)); + REQUIRE(uuid::is_valid_uuid("{00000000-0000-0000-0000-000000000000}"sv)); + REQUIRE(uuid::is_valid_uuid(L"00000000-0000-0000-0000-000000000000"sv)); + REQUIRE(uuid::is_valid_uuid(L"{00000000-0000-0000-0000-000000000000}"sv)); +} + +TEST_CASE("uuid: Test is_valid_uuid(char*) invalid format", "[uuid]") +{ + REQUIRE(!uuid::is_valid_uuid("")); + REQUIRE(!uuid::is_valid_uuid("{}")); + REQUIRE(!uuid::is_valid_uuid("47183823-2574-4bfd-b411-99ed177d3e4")); + REQUIRE(!uuid::is_valid_uuid("47183823-2574-4bfd-b411-99ed177d3e430")); + REQUIRE(!uuid::is_valid_uuid("{47183823-2574-4bfd-b411-99ed177d3e43")); + REQUIRE(!uuid::is_valid_uuid("47183823-2574-4bfd-b411-99ed177d3e43}")); +} + +TEST_CASE("uuid: Test is_valid_uuid(basic_string) invalid format", "[uuid]") +{ + using namespace std::string_literals; + + { + auto str = ""s; + REQUIRE(!uuid::is_valid_uuid(str)); + } + + { + auto str = "{}"s; + REQUIRE(!uuid::is_valid_uuid(str)); + } + + { + auto str = "47183823-2574-4bfd-b411-99ed177d3e4"s; + REQUIRE(!uuid::is_valid_uuid(str)); + } + + { + auto str = "47183823-2574-4bfd-b411-99ed177d3e430"s; + REQUIRE(!uuid::is_valid_uuid(str)); + } + + { + auto str = "{47183823-2574-4bfd-b411-99ed177d3e43"s; + REQUIRE(!uuid::is_valid_uuid(str)); + } + + { + auto str = "47183823-2574-4bfd-b411-99ed177d3e43}"s; + REQUIRE(!uuid::is_valid_uuid(str)); + } +} + +TEST_CASE("uuid: Test is_valid_uuid(basic_string_view) invalid format", "[uuid]") +{ + using namespace std::string_view_literals; + + REQUIRE(!uuid::is_valid_uuid(""sv)); + REQUIRE(!uuid::is_valid_uuid("{}"sv)); + REQUIRE(!uuid::is_valid_uuid("47183823-2574-4bfd-b411-99ed177d3e4"sv)); + REQUIRE(!uuid::is_valid_uuid("47183823-2574-4bfd-b411-99ed177d3e430"sv)); + REQUIRE(!uuid::is_valid_uuid("{47183823-2574-4bfd-b411-99ed177d3e43"sv)); + REQUIRE(!uuid::is_valid_uuid("47183823-2574-4bfd-b411-99ed177d3e43}"sv)); +} + +TEST_CASE("uuid: Test from_string(char*)", "[uuid]") +{ + { + auto str = "47183823-2574-4bfd-b411-99ed177d3e43"; + auto guid = uuid::from_string(str).value(); + REQUIRE(to_string(guid) == str); + } + + { + auto str = "{47183823-2574-4bfd-b411-99ed177d3e43}"; + auto guid = uuid::from_string(str).value(); + REQUIRE(to_string(guid) == "47183823-2574-4bfd-b411-99ed177d3e43"); + } + + { + auto guid = uuid::from_string("47183823-2574-4bfd-b411-99ed177d3e43").value(); + REQUIRE(to_string(guid) == "47183823-2574-4bfd-b411-99ed177d3e43"); + REQUIRE(to_string(guid) == L"47183823-2574-4bfd-b411-99ed177d3e43"); + } + + { + auto str = L"47183823-2574-4bfd-b411-99ed177d3e43"; + auto guid = uuid::from_string(str).value(); + REQUIRE(to_string(guid) == str); + } + + { + auto str = "4718382325744bfdb41199ed177d3e43"; + REQUIRE_NOTHROW(uuid::from_string(str)); + REQUIRE(uuid::from_string(str).has_value()); + } + + { + auto str = "00000000-0000-0000-0000-000000000000"; + auto guid = uuid::from_string(str).value(); + REQUIRE(guid.is_nil()); + } + + { + auto str = "{00000000-0000-0000-0000-000000000000}"; + auto guid = uuid::from_string(str).value(); + REQUIRE(guid.is_nil()); + } + + { + auto str = L"00000000-0000-0000-0000-000000000000"; + auto guid = uuid::from_string(str).value(); + REQUIRE(guid.is_nil()); + } + + { + auto str = L"{00000000-0000-0000-0000-000000000000}"; + auto guid = uuid::from_string(str).value(); + REQUIRE(guid.is_nil()); + } +} + +TEST_CASE("uuid: Test from_string(basic_string)", "[uuid]") +{ + using namespace std::string_literals; + + { + auto str = "47183823-2574-4bfd-b411-99ed177d3e43"s; + auto guid = uuid::from_string(str).value(); + REQUIRE(to_string(guid) == str); + } + + { + auto str = "{47183823-2574-4bfd-b411-99ed177d3e43}"s; + auto guid = uuid::from_string(str).value(); + REQUIRE(to_string(guid) == "47183823-2574-4bfd-b411-99ed177d3e43"); + } + + { + auto guid = uuid::from_string("47183823-2574-4bfd-b411-99ed177d3e43"s).value(); + REQUIRE(to_string(guid) == "47183823-2574-4bfd-b411-99ed177d3e43"); + REQUIRE(to_string(guid) == L"47183823-2574-4bfd-b411-99ed177d3e43"); + } + + { + auto str = L"47183823-2574-4bfd-b411-99ed177d3e43"s; + auto guid = uuid::from_string(str).value(); + REQUIRE(to_string(guid) == str); + } + + { + auto str = "4718382325744bfdb41199ed177d3e43"s; + REQUIRE_NOTHROW(uuid::from_string(str)); + REQUIRE(uuid::from_string(str).has_value()); + } + + { + auto str = "00000000-0000-0000-0000-000000000000"s; + auto guid = uuid::from_string(str).value(); + REQUIRE(guid.is_nil()); + } + + { + auto str = "{00000000-0000-0000-0000-000000000000}"s; + auto guid = uuid::from_string(str).value(); + REQUIRE(guid.is_nil()); + } + + { + auto str = L"00000000-0000-0000-0000-000000000000"s; + auto guid = uuid::from_string(str).value(); + REQUIRE(guid.is_nil()); + } + + { + auto str = L"{00000000-0000-0000-0000-000000000000}"s; + auto guid = uuid::from_string(str).value(); + REQUIRE(guid.is_nil()); + } +} + +TEST_CASE("uuid: Test from_string(basic_string_view)", "[uuid]") +{ + using namespace std::string_view_literals; + + { + auto str = "47183823-2574-4bfd-b411-99ed177d3e43"sv; + auto guid = uuid::from_string(str).value(); + REQUIRE(to_string(guid) == str); + } + + { + auto str = "{47183823-2574-4bfd-b411-99ed177d3e43}"sv; + auto guid = uuid::from_string(str).value(); + REQUIRE(to_string(guid) == "47183823-2574-4bfd-b411-99ed177d3e43"); + } + + { + auto guid = uuid::from_string("47183823-2574-4bfd-b411-99ed177d3e43"sv).value(); + REQUIRE(to_string(guid) == "47183823-2574-4bfd-b411-99ed177d3e43"); + REQUIRE(to_string(guid) == L"47183823-2574-4bfd-b411-99ed177d3e43"); + } + + { + auto str = L"47183823-2574-4bfd-b411-99ed177d3e43"sv; + auto guid = uuid::from_string(str).value(); + REQUIRE(to_string(guid) == str); + } + + { + auto str = "4718382325744bfdb41199ed177d3e43"sv; + REQUIRE_NOTHROW(uuid::from_string(str)); + REQUIRE(uuid::from_string(str).has_value()); + } + + { + auto str = "00000000-0000-0000-0000-000000000000"sv; + auto guid = uuid::from_string(str).value(); + REQUIRE(guid.is_nil()); + } + + { + auto str = "{00000000-0000-0000-0000-000000000000}"sv; + auto guid = uuid::from_string(str).value(); + REQUIRE(guid.is_nil()); + } + + { + auto str = L"00000000-0000-0000-0000-000000000000"sv; + auto guid = uuid::from_string(str).value(); + REQUIRE(guid.is_nil()); + } + + { + auto str = L"{00000000-0000-0000-0000-000000000000}"sv; + auto guid = uuid::from_string(str).value(); + REQUIRE(guid.is_nil()); + } +} + +TEST_CASE("uuid: Test constexpr from_string", "[uuid]") +{ + constexpr uuid value = uuid::from_string("47183823-2574-4bfd-b411-99ed177d3e43").value(); + static_assert(!value.is_nil()); + static_assert(value.variant() == uuid_variant::rfc); + static_assert(value.version() != uuid_version::none); +} + +TEST_CASE("uuid: Test from_string(char*) invalid format", "[uuid]") +{ + REQUIRE(!uuid::from_string("").has_value()); + REQUIRE(!uuid::from_string("{}").has_value()); + REQUIRE(!uuid::from_string("47183823-2574-4bfd-b411-99ed177d3e4").has_value()); + REQUIRE(!uuid::from_string("47183823-2574-4bfd-b411-99ed177d3e430").has_value()); + REQUIRE(!uuid::from_string("{47183823-2574-4bfd-b411-99ed177d3e43").has_value()); + REQUIRE(!uuid::from_string("47183823-2574-4bfd-b411-99ed177d3e43}").has_value()); +} + +TEST_CASE("uuid: Test from_string(basic_string) invalid format", "[uuid]") +{ + using namespace std::string_literals; + + { + auto str = ""s; + REQUIRE(!uuid::from_string(str).has_value()); + } + + { + auto str = "{}"s; + REQUIRE(!uuid::from_string(str).has_value()); + } + + { + auto str = "47183823-2574-4bfd-b411-99ed177d3e4"s; + REQUIRE(!uuid::from_string(str).has_value()); + } + + { + auto str = "47183823-2574-4bfd-b411-99ed177d3e430"s; + REQUIRE(!uuid::from_string(str).has_value()); + } + + { + auto str = "{47183823-2574-4bfd-b411-99ed177d3e43"s; + REQUIRE(!uuid::from_string(str).has_value()); + } + + { + auto str = "47183823-2574-4bfd-b411-99ed177d3e43}"s; + REQUIRE(!uuid::from_string(str).has_value()); + } +} + +TEST_CASE("uuid: Test from_string(basic_string_view) invalid format", "[uuid]") +{ + using namespace std::string_view_literals; + + REQUIRE(!uuid::from_string(""sv).has_value()); + REQUIRE(!uuid::from_string("{}"sv).has_value()); + REQUIRE(!uuid::from_string("47183823-2574-4bfd-b411-99ed177d3e4"sv).has_value()); + REQUIRE(!uuid::from_string("47183823-2574-4bfd-b411-99ed177d3e430"sv).has_value()); + REQUIRE(!uuid::from_string("{47183823-2574-4bfd-b411-99ed177d3e43"sv).has_value()); + REQUIRE(!uuid::from_string("47183823-2574-4bfd-b411-99ed177d3e43}"sv).has_value()); +} + +TEST_CASE("uuid: Test iterators constructor", "[uuid]") +{ + using namespace std::string_literals; + + { + std::array arr{ + {0x47, 0x18, 0x38, 0x23, 0x25, 0x74, 0x4b, 0xfd, 0xb4, 0x11, 0x99, 0xed, + 0x17, 0x7d, 0x3e, 0x43} + }; + + auto const guid = uuid(std::begin(arr), std::end(arr)); + REQUIRE(to_string(guid) == "47183823-2574-4bfd-b411-99ed177d3e43"s); + } + + { + uuid::value_type arr[16] = {// NOLINT + 0x47, 0x18, 0x38, 0x23, 0x25, 0x74, 0x4b, 0xfd, + 0xb4, 0x11, 0x99, 0xed, 0x17, 0x7d, 0x3e, 0x43}; + + auto const guid = uuid(std::begin(arr), std::end(arr)); + REQUIRE(to_string(guid) == "47183823-2574-4bfd-b411-99ed177d3e43"s); + } +} + +TEST_CASE("uuid: Test array constructors", "[uuid]") +{ + using namespace std::string_literals; + + { + auto const guid = uuid { + {0x47, 0x18, 0x38, 0x23, 0x25, 0x74, 0x4b, 0xfd, 0xb4, 0x11, 0x99, 0xed, + 0x17, 0x7d, 0x3e, 0x43} + }; + + REQUIRE(to_string(guid) == "47183823-2574-4bfd-b411-99ed177d3e43"s); + } + + { + auto arr = std::array{ + {0x47, 0x18, 0x38, 0x23, 0x25, 0x74, 0x4b, 0xfd, 0xb4, 0x11, 0x99, 0xed, + 0x17, 0x7d, 0x3e, 0x43} + }; + + auto const guid = uuid(arr); + REQUIRE(to_string(guid) == "47183823-2574-4bfd-b411-99ed177d3e43"s); + } + + { + uuid::value_type arr[16]{// NOLINT + 0x47, 0x18, 0x38, 0x23, 0x25, 0x74, 0x4b, 0xfd, + 0xb4, 0x11, 0x99, 0xed, 0x17, 0x7d, 0x3e, 0x43}; + + auto const guid = uuid(arr); + REQUIRE(to_string(guid) == "47183823-2574-4bfd-b411-99ed177d3e43"s); + } +} + +TEST_CASE("uuid: Test equality", "[uuid]") +{ + uuid empty; + + auto engine = uuid_random_generator::engine_type{}; + seed_rng(engine); + uuid guid = uuid_random_generator{engine}(); + + REQUIRE(empty == empty); + REQUIRE(guid == guid); + REQUIRE(empty != guid); +} + +TEST_CASE("Test comparison", "[uuid]") +{ + auto empty = uuid{}; + + auto engine = uuid_random_generator::engine_type{}; + seed_rng(engine); + + auto gen = uuid_random_generator{engine}; + auto id = gen(); + + REQUIRE(empty < id); + + auto ids = std::set{uuid{}, gen(), gen(), gen(), gen()}; + + REQUIRE(ids.size() == 5); + REQUIRE(ids.contains(uuid{}) == true); +} + +TEST_CASE("uuid: Test hashing", "[uuid]") +{ + using namespace std::string_literals; + + auto str = "47183823-2574-4bfd-b411-99ed177d3e43"s; + auto guid = uuid::from_string(str).value(); + + auto h1 = std::hash{}; + auto h2 = std::hash{}; + REQUIRE(h1(str) != h2(guid)); + + auto engine = uuid_random_generator::engine_type{}; + seed_rng(engine); + uuid_random_generator gen{engine}; + + std::unordered_set ids{uuid{}, gen(), gen(), gen(), gen()}; + + REQUIRE(ids.size() == 5); + REQUIRE(ids.find(uuid{}) != ids.end()); +} + +TEST_CASE("uuid: Test swap", "[uuid]") +{ + uuid empty; + + auto engine = uuid_random_generator::engine_type{}; + seed_rng(engine); + uuid guid = uuid_random_generator{engine}(); + + REQUIRE(empty.is_nil()); + REQUIRE(!guid.is_nil()); + + std::swap(empty, guid); + + REQUIRE(!empty.is_nil()); + REQUIRE(guid.is_nil()); + + empty.swap(guid); + + REQUIRE(empty.is_nil()); + REQUIRE(!guid.is_nil()); +} + +TEST_CASE("uuid: Test constexpr", "[uuid]") +{ + constexpr uuid empty; + static_assert(empty.is_nil()); + static_assert(empty.variant() == uuid_variant::ncs); + static_assert(empty.version() == uuid_version::none); +} + +TEST_CASE("uuid: Test size", "[uuid]") +{ + REQUIRE(sizeof(uuid) == 16); +} + +TEST_CASE("uuid: Test assignment", "[uuid]") +{ + auto id1 = uuid::from_string("47183823-2574-4bfd-b411-99ed177d3e43").value(); + auto id2 = id1; + REQUIRE(id1 == id2); + + id1 = uuid::from_string("{fea43102-064f-4444-adc2-02cec42623f8}").value(); + REQUIRE(id1 != id2); + + auto id3 = std::move(id2); + REQUIRE(to_string(id3) == "47183823-2574-4bfd-b411-99ed177d3e43"); +} + +TEST_CASE("uuid: Test trivial", "[uuid]") +{ + REQUIRE(std::is_trivially_copyable_v); +} + +TEST_CASE("uuid: Test as_bytes", "[uuid]") +{ + std::array arr{ + {0x47, 0x18, 0x38, 0x23, 0x25, 0x74, 0x4b, 0xfd, 0xb4, 0x11, 0x99, 0xed, 0x17, 0x7d, + 0x3e, 0x43} + }; + + { + uuid id{arr}; + REQUIRE(!id.is_nil()); + + auto view = id.as_bytes(); + REQUIRE(memcmp(view.data(), arr.data(), arr.size()) == 0); + } + + { + const uuid id{arr}; + REQUIRE(!id.is_nil()); + + auto view = id.as_bytes(); + REQUIRE(memcmp(view.data(), arr.data(), arr.size()) == 0); + } +} +} // anonymous namespace + +// NOLINTEND(bugprone-unchecked-optional-access) diff --git a/nihil.posix/CMakeLists.txt b/nihil.posix/CMakeLists.txt index 3fcf625..4fc7d0c 100644 --- a/nihil.posix/CMakeLists.txt +++ b/nihil.posix/CMakeLists.txt @@ -4,7 +4,6 @@ add_library(nihil.posix STATIC) target_link_libraries(nihil.posix PRIVATE nihil.std nihil.core - nihil.util ) target_sources(nihil.posix @@ -58,8 +57,8 @@ if(NIHIL_TESTS) target_link_libraries(nihil.posix.test PRIVATE nihil.std + nihil.core nihil.posix - nihil.util Catch2::Catch2WithMain ) diff --git a/nihil.posix/ensure_dir.ccm b/nihil.posix/ensure_dir.ccm index 9ae6d80..6534736 100644 --- a/nihil.posix/ensure_dir.ccm +++ b/nihil.posix/ensure_dir.ccm @@ -2,7 +2,7 @@ export module nihil.posix:ensure_dir; import nihil.std; -import nihil.util; +import nihil.core; namespace nihil { diff --git a/nihil.posix/execlp.ccm b/nihil.posix/execlp.ccm index ba80504..02f9f6f 100644 --- a/nihil.posix/execlp.ccm +++ b/nihil.posix/execlp.ccm @@ -2,7 +2,8 @@ export module nihil.posix:execlp; import nihil.std; -import nihil.util; +import nihil.core; + import :argv; import :execvp; diff --git a/nihil.posix/execv.ccm b/nihil.posix/execv.ccm index 2a77326..5c5a891 100644 --- a/nihil.posix/execv.ccm +++ b/nihil.posix/execv.ccm @@ -10,7 +10,7 @@ extern char **environ; // NOLINT export module nihil.posix:execv; import nihil.std; -import nihil.util; +import nihil.core; import :argv; import :executor; import :fd; diff --git a/nihil.posix/execvp.ccm b/nihil.posix/execvp.ccm index 0a0106d..df28fe8 100644 --- a/nihil.posix/execvp.ccm +++ b/nihil.posix/execvp.ccm @@ -3,7 +3,7 @@ export module nihil.posix:execvp; import nihil.std; import nihil.core; -import nihil.util; + import :argv; import :execv; import :find_in_path; diff --git a/nihil.posix/fd.ccm b/nihil.posix/fd.ccm index 1de343b..54a4502 100644 --- a/nihil.posix/fd.ccm +++ b/nihil.posix/fd.ccm @@ -7,7 +7,7 @@ module; export module nihil.posix:fd; import nihil.std; -import nihil.util; +import nihil.core; namespace nihil { diff --git a/nihil.posix/fd.test.cc b/nihil.posix/fd.test.cc index a0828e5..c164290 100644 --- a/nihil.posix/fd.test.cc +++ b/nihil.posix/fd.test.cc @@ -6,8 +6,8 @@ #include import nihil.std; +import nihil.core; import nihil.posix; -import nihil.util; using namespace std::literals; diff --git a/nihil.posix/find_in_path.ccm b/nihil.posix/find_in_path.ccm index 353750b..f68e61d 100644 --- a/nihil.posix/find_in_path.ccm +++ b/nihil.posix/find_in_path.ccm @@ -6,7 +6,7 @@ module; export module nihil.posix:find_in_path; import nihil.std; -import nihil.util; +import nihil.core; import :fd; import :getenv; import :paths; diff --git a/nihil.posix/find_in_path.test.cc b/nihil.posix/find_in_path.test.cc index 65cbe87..2c07ce2 100644 --- a/nihil.posix/find_in_path.test.cc +++ b/nihil.posix/find_in_path.test.cc @@ -3,8 +3,8 @@ #include import nihil.std; +import nihil.core; import nihil.posix; -import nihil.util; namespace { diff --git a/nihil.posix/getenv.ccm b/nihil.posix/getenv.ccm index f3490a2..bc7d841 100644 --- a/nihil.posix/getenv.ccm +++ b/nihil.posix/getenv.ccm @@ -9,7 +9,7 @@ module; export module nihil.posix:getenv; import nihil.std; -import nihil.util; +import nihil.core; namespace nihil { diff --git a/nihil.posix/getenv.test.cc b/nihil.posix/getenv.test.cc index 83c46dc..2c3d882 100644 --- a/nihil.posix/getenv.test.cc +++ b/nihil.posix/getenv.test.cc @@ -5,8 +5,8 @@ #include import nihil.std; +import nihil.core; import nihil.posix; -import nihil.util; namespace { TEST_CASE("getenv: existing value", "[getenv]") diff --git a/nihil.posix/open.ccm b/nihil.posix/open.ccm index a2fc9f4..c204633 100644 --- a/nihil.posix/open.ccm +++ b/nihil.posix/open.ccm @@ -7,7 +7,7 @@ module; export module nihil.posix:open; import nihil.std; -import nihil.util; +import nihil.core; import :fd; namespace nihil { diff --git a/nihil.posix/open.test.cc b/nihil.posix/open.test.cc index 932c03b..f530e6d 100644 --- a/nihil.posix/open.test.cc +++ b/nihil.posix/open.test.cc @@ -3,8 +3,8 @@ #include import nihil.std; +import nihil.core; import nihil.posix; -import nihil.util; namespace { diff --git a/nihil.posix/open_in_path.ccm b/nihil.posix/open_in_path.ccm index 577ab9d..beacefa 100644 --- a/nihil.posix/open_in_path.ccm +++ b/nihil.posix/open_in_path.ccm @@ -2,7 +2,8 @@ export module nihil.posix:open_in_path; import nihil.std; -import nihil.util; +import nihil.core; + import :fd; import :getenv; import :open; diff --git a/nihil.posix/open_in_path.test.cc b/nihil.posix/open_in_path.test.cc index ace8f1e..4bf0d7c 100644 --- a/nihil.posix/open_in_path.test.cc +++ b/nihil.posix/open_in_path.test.cc @@ -3,8 +3,8 @@ #include import nihil.std; +import nihil.core; import nihil.posix; -import nihil.util; namespace { diff --git a/nihil.posix/process.ccm b/nihil.posix/process.ccm index e990f62..98195b3 100644 --- a/nihil.posix/process.ccm +++ b/nihil.posix/process.ccm @@ -6,7 +6,7 @@ module; export module nihil.posix:process; import nihil.std; -import nihil.util; +import nihil.core; namespace nihil { diff --git a/nihil.posix/read_file.ccm b/nihil.posix/read_file.ccm index f7d06cb..0648b91 100644 --- a/nihil.posix/read_file.ccm +++ b/nihil.posix/read_file.ccm @@ -2,7 +2,8 @@ export module nihil.posix:read_file; import nihil.std; -import nihil.util; +import nihil.core; + import :fd; import :open; diff --git a/nihil.posix/rename.ccm b/nihil.posix/rename.ccm index 6b640b1..145fea3 100644 --- a/nihil.posix/rename.ccm +++ b/nihil.posix/rename.ccm @@ -2,7 +2,7 @@ export module nihil.posix:rename; import nihil.std; -import nihil.util; +import nihil.core; namespace nihil { diff --git a/nihil.posix/spawn.ccm b/nihil.posix/spawn.ccm index ea84f4d..0945466 100644 --- a/nihil.posix/spawn.ccm +++ b/nihil.posix/spawn.ccm @@ -4,7 +4,8 @@ export module nihil.posix:spawn; // spawn(): fork and execute a child process. import nihil.std; -import nihil.util; +import nihil.core; + import :argv; import :executor; import :fd; diff --git a/nihil.posix/stat.ccm b/nihil.posix/stat.ccm index ee8113b..ac19f97 100644 --- a/nihil.posix/stat.ccm +++ b/nihil.posix/stat.ccm @@ -8,7 +8,8 @@ module; export module nihil.posix:stat; import nihil.std; -import nihil.util; +import nihil.core; + import :fd; namespace nihil { diff --git a/nihil.posix/stat.test.cc b/nihil.posix/stat.test.cc index cb199f6..4d6c658 100644 --- a/nihil.posix/stat.test.cc +++ b/nihil.posix/stat.test.cc @@ -1,8 +1,8 @@ // This source code is released into the public domain. import nihil.std; +import nihil.core; import nihil.posix; -import nihil.util; #include diff --git a/nihil.posix/tempfile.ccm b/nihil.posix/tempfile.ccm index 15edccb..892d12b 100644 --- a/nihil.posix/tempfile.ccm +++ b/nihil.posix/tempfile.ccm @@ -2,7 +2,8 @@ export module nihil.posix:tempfile; import nihil.std; -import nihil.util; +import nihil.core; + import :fd; import :getenv; import :open; diff --git a/nihil.posix/unistd.ccm b/nihil.posix/unistd.ccm index 65cd015..6f3b914 100644 --- a/nihil.posix/unistd.ccm +++ b/nihil.posix/unistd.ccm @@ -6,7 +6,7 @@ module; export module nihil.posix:unistd; import nihil.std; -import nihil.util; +import nihil.core; // Symbols from unistd.h that might be useful. diff --git a/nihil.posix/unlink.ccm b/nihil.posix/unlink.ccm index f2f5faa..6496d8f 100644 --- a/nihil.posix/unlink.ccm +++ b/nihil.posix/unlink.ccm @@ -8,7 +8,7 @@ export module nihil.posix:unlink; // unlink: simple wrapper around ::unlink() import nihil.std; -import nihil.util; +import nihil.core; namespace nihil { diff --git a/nihil.posix/write_file.ccm b/nihil.posix/write_file.ccm index 471ef85..9f26bb1 100644 --- a/nihil.posix/write_file.ccm +++ b/nihil.posix/write_file.ccm @@ -2,7 +2,8 @@ export module nihil.posix:write_file; import nihil.std; -import nihil.util; +import nihil.core; + import :fd; import :open; import :rename; diff --git a/nihil.ucl/CMakeLists.txt b/nihil.ucl/CMakeLists.txt index a4d69e6..477dcf7 100644 --- a/nihil.ucl/CMakeLists.txt +++ b/nihil.ucl/CMakeLists.txt @@ -6,7 +6,6 @@ add_library(nihil.ucl STATIC) target_link_libraries(nihil.ucl PRIVATE nihil.std nihil.core - nihil.util ) target_sources(nihil.ucl @@ -46,7 +45,7 @@ if(NIHIL_TESTS) target_link_libraries(nihil.ucl.test PRIVATE nihil.std - nihil.util + nihil.core nihil.ucl Catch2::Catch2WithMain) diff --git a/nihil.ucl/integer.ccm b/nihil.ucl/integer.ccm index e3cf98d..8734c2d 100644 --- a/nihil.ucl/integer.ccm +++ b/nihil.ucl/integer.ccm @@ -7,7 +7,7 @@ export module nihil.ucl:integer; import nihil.std; import nihil.core; -import nihil.util; + import :object; import :type; diff --git a/nihil.ucl/object_cast.ccm b/nihil.ucl/object_cast.ccm index 04d75a9..793bce9 100644 --- a/nihil.ucl/object_cast.ccm +++ b/nihil.ucl/object_cast.ccm @@ -6,7 +6,8 @@ module; export module nihil.ucl:object_cast; import nihil.std; -import nihil.util; +import nihil.core; + import :type; import :object; import :array; diff --git a/nihil.ucl/parser.ccm b/nihil.ucl/parser.ccm index 306f068..bfd4742 100644 --- a/nihil.ucl/parser.ccm +++ b/nihil.ucl/parser.ccm @@ -6,7 +6,8 @@ module; export module nihil.ucl:parser; import nihil.std; -import nihil.util; +import nihil.core; + import :object; import :map; diff --git a/nihil.ucl/type.ccm b/nihil.ucl/type.ccm index e7843d2..b0403c1 100644 --- a/nihil.ucl/type.ccm +++ b/nihil.ucl/type.ccm @@ -6,7 +6,7 @@ module; export module nihil.ucl:type; import nihil.std; -import nihil.util; +import nihil.core; namespace nihil::ucl { diff --git a/nihil.util/CMakeLists.txt b/nihil.util/CMakeLists.txt deleted file mode 100644 index 30a33b3..0000000 --- a/nihil.util/CMakeLists.txt +++ /dev/null @@ -1,59 +0,0 @@ -# This source code is released into the public domain. - -add_library(nihil.util STATIC) - -target_link_libraries(nihil.util PRIVATE - nihil.std - nihil.core -) - -target_sources(nihil.util - PUBLIC FILE_SET modules TYPE CXX_MODULES FILES - nihil.util.ccm - - 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 -) - -if(NIHIL_TESTS) - enable_testing() - - 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 - skipws.test.cc - tabulate.test.cc - uuid.test.cc - ) - - target_link_libraries(nihil.util.test PRIVATE - nihil.std - nihil.core - nihil.util - Catch2::Catch2WithMain - ) - - include(CTest) - include(Catch) - catch_discover_tests(nihil.util.test) -endif() diff --git a/nihil.util/capture_stream.ccm b/nihil.util/capture_stream.ccm deleted file mode 100644 index f061558..0000000 --- a/nihil.util/capture_stream.ccm +++ /dev/null @@ -1,55 +0,0 @@ -// This source code is released into the public domain. -export module nihil.util:capture_stream; - -import nihil.std; - -namespace nihil { - -// Capture output written to a stream and redirect it to an internal string -// buffer. Call .str() to get the data written. Call .release() to stop -// capturing (or simply delete the capture_stream object). -export template -struct capture_stream { - capture_stream(std::basic_ostream &stream) - : m_stream(&stream) - { - m_old_streambuf = m_stream->rdbuf(); - m_stream->rdbuf(m_buffer.rdbuf()); - } - - ~capture_stream() - { - if (m_old_streambuf == nullptr) - return; - m_stream->rdbuf(m_old_streambuf); - } - - /* - * Release this capture, returning the stream to its previous state. - */ - auto release(this capture_stream &self) -> void - { - if (self.m_old_streambuf == nullptr) - throw std::logic_error( - "release() called on empty capture_stream"); - - self.m_stream->rdbuf(self.m_old_streambuf); - self.m_old_streambuf = nullptr; - } - - /* - * Get the data which has been written to the stream. - */ - [[nodiscard]] auto str(this capture_stream const &self) - -> std::basic_string_view - { - return self.m_buffer.view(); - } - -private: - std::basic_ostringstream m_buffer; - std::basic_ostream *m_stream; - std::streambuf *m_old_streambuf; -}; - -} // namespace nihil diff --git a/nihil.util/capture_stream.test.cc b/nihil.util/capture_stream.test.cc deleted file mode 100644 index a4821b7..0000000 --- a/nihil.util/capture_stream.test.cc +++ /dev/null @@ -1,44 +0,0 @@ -// This source code is released into the public domain. - -#include - -import nihil.std; -import nihil.util; - -namespace { -TEST_CASE("nihil.util: capture", "[nihil][nihil.util]") -{ - SECTION("std::cout with release()") { - auto cap = nihil::capture_stream(std::cout); - - std::cout << 1 << '+' << 1 << '=' << (1 + 1) << '\n'; - REQUIRE(cap.str() == "1+1=2\n"); - - cap.release(); - REQUIRE(cap.str() == "1+1=2\n"); - } - - SECTION("std::cout with dtor") { - auto cap = nihil::capture_stream(std::cout); - std::cout << 1 << '+' << 1 << '=' << (1 + 1) << '\n'; - REQUIRE(cap.str() == "1+1=2\n"); - } - - SECTION("std::cerr with release()") { - auto cap = nihil::capture_stream(std::cerr); - - std::cerr << 1 << '+' << 1 << '=' << (1 + 1) << '\n'; - REQUIRE(cap.str() == "1+1=2\n"); - - cap.release(); - REQUIRE(cap.str() == "1+1=2\n"); - } - - SECTION("std::cerr with dtor") { - auto cap = nihil::capture_stream(std::cerr); - std::cerr << 1 << '+' << 1 << '=' << (1 + 1) << '\n'; - REQUIRE(cap.str() == "1+1=2\n"); - } -} - -} // anonymous namespace diff --git a/nihil.util/construct.ccm b/nihil.util/construct.ccm deleted file mode 100644 index 894a446..0000000 --- a/nihil.util/construct.ccm +++ /dev/null @@ -1,22 +0,0 @@ -// This source code is released into the public domain. -export module nihil.util:construct; - -import nihil.std; - -namespace nihil { - -// A functor that constructs objects of an arbitrary type. -// Useful for std::views::transform. -template -struct construct_fn final -{ - [[nodiscard]] auto operator()(this construct_fn const &, auto &&...args) -> T - { - return T(std::forward(args)...); - } -}; - -export template -inline constexpr auto construct = construct_fn{}; - -} // namespace nihil diff --git a/nihil.util/ctype.ccm b/nihil.util/ctype.ccm deleted file mode 100644 index 8f5de27..0000000 --- a/nihil.util/ctype.ccm +++ /dev/null @@ -1,72 +0,0 @@ -// This source code is released into the public domain. -export module nihil.util:ctype; - -import nihil.std; - -namespace nihil { - -/* - * ctype_is: wrap std::ctype::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 -{ - explicit ctype_is(std::ctype_base::mask mask_, - std::locale const &locale_ = std::locale()) noexcept - : m_mask(mask_) - , m_locale(locale_) - { - } - - [[nodiscard]] auto operator()(this ctype_is const &self, std::integral auto c) - { - using ctype = std::ctype; - auto &facet = std::use_facet(self.m_locale); - return facet.is(self.m_mask, c); - } - -private: - std::ctype_base::mask m_mask; - std::locale m_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. - -//NOLINTBEGIN: Technically, std::locale::classic() can throw. Assume it doesn't. -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()); -//NOLINTEND - -} // namespace nihil diff --git a/nihil.util/ctype.test.cc b/nihil.util/ctype.test.cc deleted file mode 100644 index d000b45..0000000 --- a/nihil.util/ctype.test.cc +++ /dev/null @@ -1,376 +0,0 @@ -// This source code is released into the public domain. - -#include - -import nihil.std; -import nihil.util; - -namespace { - -TEST_CASE("ctype: space", "[ctype]") { - auto is_utf8_space = - nihil::ctype_is(std::ctype_base::space, - std::locale("C.UTF-8")); - - // '\v' (vertical tab) is a space - REQUIRE(nihil::is_space('\v') == true); - REQUIRE(nihil::is_space(L'\v') == true); - - REQUIRE(nihil::is_c_space('\v') == true); - REQUIRE(nihil::is_c_space(L'\v') == true); - - REQUIRE(is_utf8_space('\v') == true); - REQUIRE(is_utf8_space(L'\v') == true); - - // 'x' is not a space - REQUIRE(nihil::is_space('x') == false); - REQUIRE(nihil::is_space(L'x') == false); - - REQUIRE(nihil::is_c_space('x') == false); - REQUIRE(nihil::is_c_space(L'x') == false); - - REQUIRE(is_utf8_space('x') == false); - REQUIRE(is_utf8_space(L'x') == false); - - // U+2003 EM SPACE is a space - REQUIRE(nihil::is_space(L'\u2003') == false); - REQUIRE(nihil::is_c_space(L'\u2003') == false); - REQUIRE(is_utf8_space(L'\u2003') == true); -} - -TEST_CASE("ctype: print", "[ctype]") { - auto is_utf8_print = - nihil::ctype_is(std::ctype_base::print, - std::locale("C.UTF-8")); - - // 'x' is printable - REQUIRE(nihil::is_print('x') == true); - REQUIRE(nihil::is_print(L'x') == true); - - REQUIRE(nihil::is_c_print('x') == true); - REQUIRE(nihil::is_c_print(L'x') == true); - - REQUIRE(is_utf8_print('x') == true); - REQUIRE(is_utf8_print(L'x') == true); - - // '\003' is not printable - REQUIRE(nihil::is_print('\003') == false); - REQUIRE(nihil::is_print(L'\003') == false); - - REQUIRE(nihil::is_c_print('\003') == false); - REQUIRE(nihil::is_c_print(L'\003') == false); - - REQUIRE(is_utf8_print('\003') == false); - REQUIRE(is_utf8_print(L'\003') == false); - - // U+0410 CYRILLIC CAPITAL LETTER A is printable - REQUIRE(nihil::is_print(L'\u0410') == false); - REQUIRE(nihil::is_c_print(L'\u0410') == false); - REQUIRE(is_utf8_print(L'\u0410') == true); -} - -TEST_CASE("ctype: cntrl", "[ctype]") { - auto is_utf8_cntrl = - nihil::ctype_is(std::ctype_base::cntrl, - std::locale("C.UTF-8")); - - // '\003' is a control character - REQUIRE(nihil::is_cntrl('\003') == true); - REQUIRE(nihil::is_cntrl(L'\003') == true); - - REQUIRE(nihil::is_c_cntrl('\003') == true); - REQUIRE(nihil::is_c_cntrl(L'\003') == true); - - REQUIRE(is_utf8_cntrl('\003') == true); - REQUIRE(is_utf8_cntrl(L'\003') == true); - - - // 'x' is not a control character - REQUIRE(nihil::is_cntrl('x') == false); - REQUIRE(nihil::is_cntrl(L'x') == false); - - REQUIRE(nihil::is_c_cntrl('x') == false); - REQUIRE(nihil::is_c_cntrl(L'x') == false); - - REQUIRE(is_utf8_cntrl('x') == false); - REQUIRE(is_utf8_cntrl(L'x') == false); - - // U+00AD SOFT HYPHEN is a control character. - REQUIRE(nihil::is_cntrl(L'\u00ad') == false); - REQUIRE(nihil::is_c_cntrl(L'\u00ad') == false); - REQUIRE(is_utf8_cntrl(L'\u00ad') == true); -} - -TEST_CASE("ctype: upper", "[ctype]") { - auto is_utf8_upper = - nihil::ctype_is(std::ctype_base::upper, - std::locale("C.UTF-8")); - - // 'A' is upper case - REQUIRE(nihil::is_upper('A') == true); - REQUIRE(nihil::is_upper(L'A') == true); - - REQUIRE(nihil::is_c_upper('A') == true); - REQUIRE(nihil::is_c_upper(L'A') == true); - - REQUIRE(is_utf8_upper('A') == true); - REQUIRE(is_utf8_upper(L'A') == true); - - // 'a' is not upper case - REQUIRE(nihil::is_upper('a') == false); - REQUIRE(nihil::is_upper(L'a') == false); - - REQUIRE(nihil::is_c_upper('a') == false); - REQUIRE(nihil::is_c_upper(L'a') == false); - - REQUIRE(is_utf8_upper('a') == false); - REQUIRE(is_utf8_upper(L'a') == false); - - // U+0410 CYRILLIC CAPITAL LETTER A is upper case - REQUIRE(nihil::is_upper(L'\u0410') == false); - REQUIRE(nihil::is_c_upper(L'\u0410') == false); - REQUIRE(is_utf8_upper(L'\u0410') == true); -} - -TEST_CASE("ctype: lower", "[ctype]") { - auto is_utf8_lower = - nihil::ctype_is(std::ctype_base::lower, - std::locale("C.UTF-8")); - - // 'a' is lower case - REQUIRE(nihil::is_lower('a') == true); - REQUIRE(nihil::is_lower(L'a') == true); - - REQUIRE(nihil::is_c_lower('a') == true); - REQUIRE(nihil::is_c_lower(L'a') == true); - - REQUIRE(is_utf8_lower('a') == true); - REQUIRE(is_utf8_lower(L'a') == true); - - // 'A' is not lower case - REQUIRE(nihil::is_lower('A') == false); - REQUIRE(nihil::is_lower(L'A') == false); - - REQUIRE(nihil::is_c_lower('A') == false); - REQUIRE(nihil::is_c_lower(L'A') == false); - - REQUIRE(is_utf8_lower('A') == false); - REQUIRE(is_utf8_lower(L'A') == false); - - // U+0430 CYRILLIC SMALL LETTER A - REQUIRE(nihil::is_lower(L'\u0430') == false); - REQUIRE(nihil::is_c_lower(L'\u0430') == false); - REQUIRE(is_utf8_lower(L'\u0430') == true); -} - -TEST_CASE("ctype: alpha", "[ctype]") { - auto is_utf8_alpha = - nihil::ctype_is(std::ctype_base::alpha, - std::locale("C.UTF-8")); - - // 'a' is alphabetical - REQUIRE(nihil::is_alpha('a') == true); - REQUIRE(nihil::is_alpha(L'a') == true); - - REQUIRE(nihil::is_c_alpha('a') == true); - REQUIRE(nihil::is_c_alpha(L'a') == true); - - REQUIRE(is_utf8_alpha('a') == true); - REQUIRE(is_utf8_alpha(L'a') == true); - - // '1' is not alphabetical - REQUIRE(nihil::is_alpha('1') == false); - REQUIRE(nihil::is_alpha(L'1') == false); - - REQUIRE(nihil::is_c_alpha('1') == false); - REQUIRE(nihil::is_c_alpha(L'1') == false); - - REQUIRE(is_utf8_alpha('1') == false); - REQUIRE(is_utf8_alpha(L'1') == false); - - // U+0430 CYRILLIC SMALL LETTER A - REQUIRE(nihil::is_alpha(L'\u0430') == false); - REQUIRE(nihil::is_c_alpha(L'\u0430') == false); - REQUIRE(is_utf8_alpha(L'\u0430') == true); -} - -TEST_CASE("ctype: digit", "[ctype]") { - auto is_utf8_digit = - nihil::ctype_is(std::ctype_base::digit, - std::locale("C.UTF-8")); - - // '1' is a digit - REQUIRE(nihil::is_digit('1') == true); - REQUIRE(nihil::is_digit(L'1') == true); - - REQUIRE(nihil::is_c_digit('1') == true); - REQUIRE(nihil::is_c_digit(L'1') == true); - - REQUIRE(is_utf8_digit('1') == true); - REQUIRE(is_utf8_digit(L'1') == true); - - // 'a' is not a digit - REQUIRE(nihil::is_digit('a') == false); - REQUIRE(nihil::is_digit(L'a') == false); - - REQUIRE(nihil::is_c_digit('a') == false); - REQUIRE(nihil::is_c_digit(L'a') == false); - - REQUIRE(is_utf8_digit('a') == false); - REQUIRE(is_utf8_digit(L'a') == false); - - // U+0660 ARABIC-INDIC DIGIT ZERO - REQUIRE(nihil::is_digit(L'\u0660') == false); - REQUIRE(nihil::is_c_digit(L'\u0660') == false); - REQUIRE(is_utf8_digit(L'\u0660') == true); -} - -TEST_CASE("ctype: punct", "[ctype]") { - auto is_utf8_punct = - nihil::ctype_is(std::ctype_base::punct, - std::locale("C.UTF-8")); - - // ';' is punctuation - REQUIRE(nihil::is_punct(';') == true); - REQUIRE(nihil::is_punct(L';') == true); - - REQUIRE(nihil::is_c_punct(';') == true); - REQUIRE(nihil::is_c_punct(L';') == true); - - REQUIRE(is_utf8_punct(';') == true); - REQUIRE(is_utf8_punct(L';') == true); - - // 'a' is not punctuation - REQUIRE(nihil::is_punct('a') == false); - REQUIRE(nihil::is_punct(L'a') == false); - - REQUIRE(nihil::is_c_punct('a') == false); - REQUIRE(nihil::is_c_punct(L'a') == false); - - REQUIRE(is_utf8_punct('a') == false); - REQUIRE(is_utf8_punct(L'a') == false); - - // U+00A1 INVERTED EXCLAMATION MARK - REQUIRE(nihil::is_punct(L'\u00A1') == false); - REQUIRE(nihil::is_c_punct(L'\u00A1') == false); - REQUIRE(is_utf8_punct(L'\u00A1') == true); -} - -TEST_CASE("ctype: xdigit", "[ctype]") { - auto is_utf8_xdigit = - nihil::ctype_is(std::ctype_base::xdigit, - std::locale("C.UTF-8")); - - // 'f' is an xdigit - REQUIRE(nihil::is_xdigit('f') == true); - REQUIRE(nihil::is_xdigit(L'f') == true); - - REQUIRE(nihil::is_c_xdigit('f') == true); - REQUIRE(nihil::is_c_xdigit(L'f') == true); - - REQUIRE(is_utf8_xdigit('f') == true); - REQUIRE(is_utf8_xdigit(L'f') == true); - - // 'g' is not an xdigit - REQUIRE(nihil::is_xdigit('g') == false); - REQUIRE(nihil::is_xdigit(L'g') == false); - - REQUIRE(nihil::is_c_xdigit('g') == false); - REQUIRE(nihil::is_c_xdigit(L'g') == false); - - REQUIRE(is_utf8_xdigit('g') == false); - REQUIRE(is_utf8_xdigit(L'g') == false); -} - -TEST_CASE("ctype: blank", "[ctype]") { - auto is_utf8_blank = - nihil::ctype_is(std::ctype_base::blank, - std::locale("C.UTF-8")); - - // '\t' is a blank - REQUIRE(nihil::is_blank('\t') == true); - REQUIRE(nihil::is_blank(L'\t') == true); - - REQUIRE(nihil::is_c_blank('\t') == true); - REQUIRE(nihil::is_c_blank(L'\t') == true); - - REQUIRE(is_utf8_blank('\t') == true); - REQUIRE(is_utf8_blank(L'\t') == true); - - // '\v' is not a blank - REQUIRE(nihil::is_blank('\v') == false); - REQUIRE(nihil::is_blank(L'\v') == false); - - REQUIRE(nihil::is_c_blank('\v') == false); - REQUIRE(nihil::is_c_blank(L'\v') == false); - - REQUIRE(is_utf8_blank('\v') == false); - REQUIRE(is_utf8_blank(L'\v') == false); - - // There don't seem to be any UTF-8 blank characters, at least - // in FreeBSD libc. -} - -TEST_CASE("ctype: alnum", "[ctype]") { - auto is_utf8_alnum = - nihil::ctype_is(std::ctype_base::alnum, - std::locale("C.UTF-8")); - - // 'a' is alphanumeric - REQUIRE(nihil::is_alnum('a') == true); - REQUIRE(nihil::is_alnum(L'a') == true); - - REQUIRE(nihil::is_c_alnum('a') == true); - REQUIRE(nihil::is_c_alnum(L'a') == true); - - REQUIRE(is_utf8_alnum('a') == true); - REQUIRE(is_utf8_alnum(L'a') == true); - - // '\t' is not a alnum - REQUIRE(nihil::is_alnum('\t') == false); - REQUIRE(nihil::is_alnum(L'\t') == false); - - REQUIRE(nihil::is_c_alnum('\t') == false); - REQUIRE(nihil::is_c_alnum(L'\t') == false); - - REQUIRE(is_utf8_alnum('\t') == false); - REQUIRE(is_utf8_alnum(L'\t') == false); - - // U+0430 CYRILLIC SMALL LETTER A - REQUIRE(nihil::is_alnum(L'\u0430') == false); - REQUIRE(nihil::is_c_alnum(L'\u0430') == false); - REQUIRE(is_utf8_alnum(L'\u0430') == true); -} - -TEST_CASE("ctype: graph", "[ctype]") { - auto is_utf8_graph = - nihil::ctype_is(std::ctype_base::graph, - std::locale("C.UTF-8")); - - // 'a' is graphical - REQUIRE(nihil::is_graph('a') == true); - REQUIRE(nihil::is_graph(L'a') == true); - - REQUIRE(nihil::is_c_graph('a') == true); - REQUIRE(nihil::is_c_graph(L'a') == true); - - REQUIRE(is_utf8_graph('a') == true); - REQUIRE(is_utf8_graph(L'a') == true); - - // '\t' is not graphical - REQUIRE(nihil::is_graph('\t') == false); - REQUIRE(nihil::is_graph(L'\t') == false); - - REQUIRE(nihil::is_c_graph('\t') == false); - REQUIRE(nihil::is_c_graph(L'\t') == false); - - REQUIRE(is_utf8_graph('\t') == false); - REQUIRE(is_utf8_graph(L'\t') == false); - - // U+0430 CYRILLIC SMALL LETTER A - REQUIRE(nihil::is_graph(L'\u0430') == false); - REQUIRE(nihil::is_c_graph(L'\u0430') == false); - REQUIRE(is_utf8_graph(L'\u0430') == true); -} - -} // anonymous namespace diff --git a/nihil.util/error.ccm b/nihil.util/error.ccm deleted file mode 100644 index fd3aac9..0000000 --- a/nihil.util/error.ccm +++ /dev/null @@ -1,321 +0,0 @@ -// 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; - -// 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::string>) - : m_error(std::forward(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 - requires(std::is_error_code_enum::value) - error_proxy(T what) // NOLINT - : m_error(make_error_code(what)) - { - } - - // ... an error condition enum - template - requires(std::is_error_condition_enum::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(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(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 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 - { - auto const *code = std::get_if(&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 - { - auto const *condition = std::get_if(&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 - 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)}; - } - - operator std::unexpected () 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 m_cause; - - // For std::exception::what(), we need to keep the string valid - // until we're destroyed. - mutable std::optional 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::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::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 -{ - template - constexpr auto parse(ParseContext &ctx) -> ParseContext::iterator - { - return ctx.begin(); - } - - template - 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 deleted file mode 100644 index f4ec1ee..0000000 --- a/nihil.util/error.test.cc +++ /dev/null @@ -1,273 +0,0 @@ -// This source code is released into the public domain. - -#include - -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); - static_assert(std::default_initializable); - static_assert(std::move_constructible); - static_assert(std::copy_constructible); - static_assert(std::equality_comparable); - static_assert(std::totally_ordered); - static_assert(std::swappable); - static_assert(std::regular); -} - -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/flagset.ccm b/nihil.util/flagset.ccm deleted file mode 100644 index 4c42223..0000000 --- a/nihil.util/flagset.ccm +++ /dev/null @@ -1,200 +0,0 @@ -// This source code is released into the public domain. -export module nihil.util:flagset; - -/* - * flagset: a type-safe flags type. - */ - -import nihil.std; - -namespace nihil { - -export template -struct flagset final -{ - using underlying_type = base_type; - - /* - * Create an empty flags. - */ - flagset() noexcept = default; - - /* - * Copyable. - */ - flagset(flagset const &other) noexcept - : m_value(other.m_value) - { - } - - /* - * Create flags from an integer mask. - */ - template - [[nodiscard]] static constexpr auto mask() noexcept -> flagset - { - return flagset(flag); - } - - /* - * Create flags for a specific bit. - */ - template - [[nodiscard]] static constexpr auto bit() noexcept -> flagset - { - static_assert(bitnr < std::numeric_limits::digits); - return flagset(static_cast(1) << bitnr); - } - - /* - * Create flags from a runtime value. - */ - [[nodiscard]] static auto from_int(base_type value) noexcept -> flagset - { - return flagset(value); - } - - /* - * Assign this flagset. - */ - auto operator=(this flagset &lhs, flagset rhs) noexcept -> flagset & - { - if (&lhs != &rhs) - lhs.m_value = rhs.m_value; - return lhs; - } - - /* - * The integer value of this flagset. - */ - [[nodiscard]] constexpr auto value(this flagset self) noexcept -> base_type - { - return self.m_value; - } - - /* - * True if this flagset has any bits set. - */ - [[nodiscard]] explicit constexpr operator bool(this flagset self) noexcept - { - return self.m_value != 0; - } - - /* - * Set bits. - */ - constexpr auto operator|=(this flagset &lhs, flagset rhs) noexcept -> flagset & - { - lhs.m_value |= rhs.value(); - return lhs; - } - - /* - * Mask bits. - */ - constexpr auto operator&=(this flagset &lhs, flagset rhs) noexcept -> flagset & - { - lhs.m_value &= rhs.value(); - return lhs; - } - - /* - * Invert bits. - */ - [[nodiscard]] constexpr auto operator~(this flagset self) noexcept -> flagset - { - return flagset(~self.m_value); - } - - /* - * xor bits. - */ - constexpr auto operator^=(this flagset &lhs, flagset rhs) noexcept -> flagset - { - lhs.m_value ^= rhs.value(); - return lhs; - } - -private: - base_type m_value = 0; - - explicit constexpr flagset(base_type mask) noexcept - : m_value(mask) - { - } - - [[nodiscard]] friend auto operator|(flagset lhs, flagset rhs) noexcept -> flagset - { - return (lhs |= rhs); - } - - [[nodiscard]] friend auto operator&(flagset lhs, flagset rhs) noexcept -> flagset - { - return (lhs &= rhs); - } - - [[nodiscard]] friend auto operator^(flagset lhs, flagset rhs) noexcept -> flagset - { - return (lhs ^= rhs); - } - - [[nodiscard]] friend auto - operator==(flagset lhs, flagset rhs) noexcept -> bool - { - return lhs.value() == rhs.value(); - } - - friend auto operator<<(std::ostream &strm, flagset flags) -> std::ostream & - { - std::print(strm, "{}", flags); - return strm; - } -}; - -} // namespace nihil - -/* - * Formatting for flagset. - */ -export template -struct std::formatter, Char> -{ - using flags_t = nihil::flagset; - - template - constexpr auto parse(ParseContext &ctx) -> ParseContext::iterator - { - return ctx.begin(); - } - - template - auto format(flags_t flags, FmtContext &ctx) const -> FmtContext::iterator - { - auto constexpr digits = std::numeric_limits::digits; - auto value = flags.value(); - auto it = ctx.out(); - *it++ = Char{'<'}; - - auto printed_any = false; - - for (unsigned i = 0; i < digits; ++i) { - unsigned bit = (digits - 1) - i; - - auto this_bit = static_cast(1) << bit; - if ((value & this_bit) == 0) - continue; - - if (printed_any) - *it++ = Char{','}; - - if (bit > 10) - *it++ = Char{'0'} + (bit / 10); - *it++ = Char{'0'} + (bit % 10); - - printed_any = true; - } - - *it++ = Char{'>'}; - return it; - } -}; diff --git a/nihil.util/flagset.test.cc b/nihil.util/flagset.test.cc deleted file mode 100644 index 85cd0d3..0000000 --- a/nihil.util/flagset.test.cc +++ /dev/null @@ -1,144 +0,0 @@ -// This source code is released into the public domain. - -#include - -import nihil.std; -import nihil.util; - -namespace { -struct test_tag -{ -}; -using testflags = nihil::flagset; - -constexpr auto zero = testflags::bit<0>(); -constexpr auto one = testflags::bit<1>(); -constexpr auto two = testflags::bit<2>(); -constexpr auto twelve = testflags::bit<12>(); - -TEST_CASE("flagset: invariant", "[nihil]") -{ - static_assert(std::regular); - static_assert(sizeof(testflags) == sizeof(testflags::underlying_type)); -} - -TEST_CASE("flagset: bit<>", "[nihil]") -{ - REQUIRE(zero.value() == 0x1); - REQUIRE(one.value() == 0x2); -} - -TEST_CASE("flagset: mask<>", "[nihil]") -{ - auto zero_ = testflags::mask<0x1>(); - auto one_ = testflags::mask<0x2>(); - - REQUIRE(zero_ == zero); - REQUIRE(one_ == one); -} - -TEST_CASE("flagset: constructor", "[nihil]") -{ - SECTION ("default construct") { - auto flags = testflags(); - REQUIRE(flags.value() == 0); - } - - SECTION ("construct from int") { - auto flags = testflags::from_int(one.value() | zero.value()); - REQUIRE(flags == (one | zero)); - } - - SECTION ("copy construct") { - auto flags = one; - auto flags2(flags); - REQUIRE(flags == flags2); - } -} - -TEST_CASE("flagset: operators", "[nihil]") -{ - SECTION ("operator|") { - REQUIRE((zero | one).value() == 0x3); - } - - SECTION ("operator|=") { - auto flags = zero; - flags |= one; - REQUIRE(flags.value() == 0x3); - } - - SECTION ("operator&") { - auto flags = zero | one; - - REQUIRE((flags & zero) == zero); - } - - SECTION ("operator&=") { - auto flags = zero | one | two; - REQUIRE(flags.value() == 0x7); - flags &= (zero | one); - REQUIRE(flags.value() == 0x3); - } - - SECTION ("operator^") { - auto flags = zero | one; - REQUIRE((flags ^ (one | two)) == (zero | two)); - } - - SECTION ("operator^=") { - auto flags = zero | one; - flags ^= (one | two); - REQUIRE(flags == (zero | two)); - } - - SECTION ("operator~") { - auto flags = ~zero; - REQUIRE(flags.value() == ~static_cast(1)); - } - - SECTION ("operator==") { - auto flags = zero; - REQUIRE(flags == zero); - REQUIRE(flags != one); - } -} - -TEST_CASE("flagset: assignment", "[nihil]") -{ - auto flags = zero; - REQUIRE(flags == zero); - - flags = one; - REQUIRE(flags == one); - REQUIRE(flags != zero); -} - -TEST_CASE("flagset: format", "[nihil]") -{ - REQUIRE(std::format("{}", testflags()) == "<>"); - REQUIRE(std::format("{}", zero) == "<0>"); - REQUIRE(std::format("{}", one) == "<1>"); - REQUIRE(std::format("{}", zero | one) == "<1,0>"); - - REQUIRE(std::format("{}", twelve) == "<12>"); - REQUIRE(std::format("{}", twelve | one) == "<12,1>"); -} - -TEST_CASE("flagset: ostream operator<<", "[nihil]") -{ - auto write = [](testflags flags) -> std::string { - auto strm = std::ostringstream(); - strm << flags; - return strm.str(); - }; - - REQUIRE(write(testflags()) == "<>"); - REQUIRE(write(zero) == "<0>"); - REQUIRE(write(one) == "<1>"); - REQUIRE(write(zero | one) == "<1,0>"); - - REQUIRE(write(twelve) == "<12>"); - REQUIRE(write(twelve | one) == "<12,1>"); -} -} // anonymous namespace diff --git a/nihil.util/guard.ccm b/nihil.util/guard.ccm deleted file mode 100644 index 77394fc..0000000 --- a/nihil.util/guard.ccm +++ /dev/null @@ -1,41 +0,0 @@ -// This source code is released into the public domain. -export module nihil.util:guard; - -import nihil.std; - -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 -struct guard final { - // Initialise the guard with a callable we will invoke later. - explicit guard(F func) : m_func(std::move(func)) {} - - // We are being destroyed, so call the callable. - // If the callable throws, std::terminate() will be called. - ~guard() - { - if (m_func) - std::invoke(*m_func); - } - - // Release the guard. This turns the destructor into a no-op. - auto release(this guard &self) noexcept -> void - { - self.m_func.reset(); - } - - // Not default-constructible, movable or copyable. - guard() = delete; - guard(guard const &) = delete; - guard(guard &&) noexcept = delete; - auto operator=(this guard &, guard const &) -> guard & = delete; - auto operator=(this guard &, guard &&) noexcept -> guard & = delete; - -private: - // The callable to be invoked when we are destroyed. - std::optional m_func; -}; - -} // namespace nihil diff --git a/nihil.util/guard.test.cc b/nihil.util/guard.test.cc deleted file mode 100644 index 2c3ac2f..0000000 --- a/nihil.util/guard.test.cc +++ /dev/null @@ -1,16 +0,0 @@ -// This source code is released into the public domain. - -#include - -import nihil.util; - -TEST_CASE("guard: basic", "[guard]") { - int n = 0; - - { - auto guard = nihil::guard([&] { n = 1; }); - REQUIRE(n == 0); - } - - REQUIRE(n == 1); -} diff --git a/nihil.util/match.ccm b/nihil.util/match.ccm deleted file mode 100644 index b72416a..0000000 --- a/nihil.util/match.ccm +++ /dev/null @@ -1,18 +0,0 @@ -// This source code is released into the public domain. -export module nihil.util:match; - -import nihil.std; - -namespace nihil { - -export template -struct match : Ts... { using Ts::operator()...; }; - -export template -[[nodiscard]] constexpr decltype(auto) operator| - (std::variant const &v, match const &match) -{ - return std::visit(match, v); -} - -} // namespace nihil diff --git a/nihil.util/match.test.cc b/nihil.util/match.test.cc deleted file mode 100644 index 6cc0e27..0000000 --- a/nihil.util/match.test.cc +++ /dev/null @@ -1,32 +0,0 @@ -// This source code is released into the public domain. - -#include - -import nihil.std; -import nihil.util; - -namespace { -TEST_CASE("match", "[nihil]") -{ - using namespace nihil; - using namespace std::literals; - - auto v = std::variant(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.ccm b/nihil.util/monad.ccm deleted file mode 100644 index eefa1fe..0000000 --- a/nihil.util/monad.ccm +++ /dev/null @@ -1,282 +0,0 @@ -/* - * From https://github.com/toby-allsopp/coroutine_monad - * - * Copyright (c) 2017 Toby Allsopp - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to - * deal in the Software without restriction, including without limitation the - * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or - * sell copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS - * IN THE SOFTWARE. - */ - -export module nihil.util:monad; - -import nihil.std; - -namespace nihil { - -/********************************************************************** - * return_object_holder - */ - -// An object that starts out unitialized. Initialized by a call to emplace. -export template -using deferred = std::optional; - -export template -struct return_object_holder { - // The staging object that is returned (by copy/move) to the caller of - // the coroutine. - deferred stage; - return_object_holder*& p; - - // When constructed, we assign a pointer to ourselves to the supplied - // reference to pointer. - return_object_holder(return_object_holder*& p) - : stage{} - , p(p) - { - p = this; - } - - // Copying doesn't make any sense (which copy should the pointer refer - // to?). - return_object_holder(return_object_holder const&) = delete; - - // To move, we just update the pointer to point at the new object. - return_object_holder(return_object_holder&& other) - : stage(std::move(other.stage)) - , p(other.p) - { - p = this; - } - - // Assignment doesn't make sense. - void operator=(return_object_holder const&) = delete; - void operator=(return_object_holder&&) = delete; - - // A non-trivial destructor is required until - // https://bugs.llvm.org//show_bug.cgi?id=28593 is fixed. - ~return_object_holder() {} - - // Construct the staging value; arguments are perfect forwarded to T's - // constructor. - template - void emplace(Args&&... args) - { - stage.emplace(std::forward(args)...); - } - - // We assume that we will be converted only once, so we can move from - // the staging object. We also assume that `emplace` has been called - // at least once. - operator T() - { - return std::move(*stage); - } -}; - -export template -auto make_return_object_holder(return_object_holder*& p) -{ - return return_object_holder{p}; -} - -/********************************************************************** - * std::optional - */ - -template -struct optional_promise { - return_object_holder>* data; - - auto get_return_object() - { - return make_return_object_holder(data); - } - - auto initial_suspend() noexcept -> std::suspend_never - { - return {}; - } - - auto final_suspend() noexcept -> std::suspend_never - { - return {}; - } - - void return_value(T x) - { - data->emplace(std::move(x)); - } - - void unhandled_exception() - { - std::rethrow_exception(std::current_exception()); - } -}; - -} // namespace nihil - -export template -struct std::coroutine_traits, Args...> { - using promise_type = nihil::optional_promise; -}; - -namespace nihil { - -template -struct optional_awaitable { - std::optional o; - - auto await_ready() - { - return o.has_value(); - } - - auto await_resume() - { - return *o; - } - - template - void await_suspend(std::coroutine_handle> h) - { - h.promise().data->emplace(std::nullopt); - h.destroy(); - } -}; - -} // namespace nihil - -namespace std { - -export template -auto operator co_await(std::optional o) { - return nihil::optional_awaitable{std::move(o)}; -} - -} // namespace std - -/********************************************************************** - * std::expected - */ - -namespace nihil { - -export template -struct expected_promise_base { - return_object_holder>* data; - - auto get_return_object() - { - return make_return_object_holder(data); - } - - auto initial_suspend() noexcept -> std::suspend_never - { - return {}; - } - - auto final_suspend() noexcept -> std::suspend_never - { - return {}; - } - - void unhandled_exception() - { - std::rethrow_exception(std::current_exception()); - } -}; - -export template -struct expected_promise : expected_promise_base { - void return_value(this expected_promise &self, std::unexpected err) - { - self.data->emplace(std::move(err)); - } - - void return_value(this expected_promise &self, T o) - { - self.data->emplace(std::move(o)); - } -}; - -export template -struct expected_promise : expected_promise_base { - void return_value(this expected_promise &self, std::unexpected err) - { - self.data->emplace(std::move(err)); - } - - void return_value(this expected_promise &self, std::expected o) - { - self.data->emplace(std::move(o)); - } -}; - -} // namespace nihil - -export template -struct std::coroutine_traits, Args...> { - using promise_type = nihil::expected_promise; -}; - -namespace nihil { - -export template -struct expected_awaitable_base { - std::expected o; - - auto await_ready() - { - return o.has_value(); - } - - template - void await_suspend(std::coroutine_handle

h) - { - h.promise().data->emplace(std::unexpected(o.error())); - h.destroy(); - } -}; - -export template -struct expected_awaitable : expected_awaitable_base { - auto await_resume(this expected_awaitable &self) - { - return std::move(*self.o); - } -}; - -export template -struct expected_awaitable : expected_awaitable_base { - auto await_resume(this expected_awaitable &) - { - return std::expected(); - } -}; - -} // namespace nihil - -namespace std { - -export template -auto operator co_await(std::expected o) { - return nihil::expected_awaitable{std::move(o)}; -} - -} // namespace std diff --git a/nihil.util/monad.test.cc b/nihil.util/monad.test.cc deleted file mode 100644 index 7f39fca..0000000 --- a/nihil.util/monad.test.cc +++ /dev/null @@ -1,65 +0,0 @@ -// This source code is released into the public domain. - -#include - -import nihil.std; -import nihil.util; - -namespace { -TEST_CASE("monad: co_await std::optional<> with value", "[nihil]") -{ - auto get_value = [] -> std::optional { - return 42; - }; - - auto try_get_value = [&get_value] -> std::optional { - co_return co_await get_value(); - }; - - auto o = try_get_value(); - REQUIRE(o == 42); -} - -TEST_CASE("monad: co_await std::optional<> without value", "[nihil]") -{ - auto get_value = [] -> std::optional { - return {}; - }; - - auto try_get_value = [&get_value] -> std::optional { - co_return co_await get_value(); - }; - - auto o = try_get_value(); - REQUIRE(!o.has_value()); -} - -TEST_CASE("monad: co_await std::expected<> with value", "[nihil]") -{ - auto get_value = [] -> std::expected { - return 42; - }; - - auto try_get_value = [&get_value] -> std::expected { - co_return co_await get_value(); - }; - - auto o = try_get_value(); - REQUIRE(o == 42); -} - -TEST_CASE("monad: co_await std::expected<> with error", "[nihil]") -{ - auto get_value = [] -> std::expected { - return std::unexpected("error"); - }; - - auto try_get_value = [&get_value] -> std::expected { - co_return co_await get_value(); - }; - - auto o = try_get_value(); - REQUIRE(!o); - REQUIRE(o.error() == "error"); -} -} // anonymous namespace diff --git a/nihil.util/next_word.ccm b/nihil.util/next_word.ccm deleted file mode 100644 index 89eeaee..0000000 --- a/nihil.util/next_word.ccm +++ /dev/null @@ -1,32 +0,0 @@ -// This source code is released into the public domain. -export module nihil.util:next_word; - -import :skipws; - -namespace nihil { - -// Return the next word from a string_view. Skips leading whitespace, so -// calling this repeatedly will return each word from the string. -export template -[[nodiscard]] -auto next_word(std::basic_string_view text, std::locale const &locale = std::locale()) - -> std::pair, std::basic_string_view> -{ - text = skipws(text, locale); - - auto is_space = ctype_is(std::ctype_base::space, locale); - auto split_pos = std::ranges::find_if(text, is_space); - - return {{std::ranges::begin(text), split_pos}, {split_pos, std::ranges::end(text)}}; -} - -export template -auto next_word(std::basic_string_view *text, std::locale const &locale = std::locale()) - -> std::basic_string_view -{ - auto [word, rest] = next_word(*text, locale); - *text = rest; - return word; -} - -} // namespace nihil diff --git a/nihil.util/next_word.test.cc b/nihil.util/next_word.test.cc deleted file mode 100644 index 87d491a..0000000 --- a/nihil.util/next_word.test.cc +++ /dev/null @@ -1,65 +0,0 @@ -// This source code is released into the public domain. - -#include - -import nihil.std; -import nihil.util; - -namespace { - -TEST_CASE("next_word: basic", "[next_word]") -{ - using namespace std::literals; - auto s = "foo bar baz"sv; - - auto words = nihil::next_word(s); - REQUIRE(words.first == "foo"); - REQUIRE(words.second == " bar baz"); - - auto word = nihil::next_word(&s); - REQUIRE(word == "foo"); - REQUIRE(s == " bar baz"); -} - -TEST_CASE("next_word: multiple spaces", "[next_word]") -{ - using namespace std::literals; - auto s = "foo bar baz"sv; - - auto words = nihil::next_word(s); - REQUIRE(words.first == "foo"); - REQUIRE(words.second == " bar baz"); - - auto word = nihil::next_word(&s); - REQUIRE(word == "foo"); - REQUIRE(s == " bar baz"); -} - -TEST_CASE("next_word: leading spaces", "[next_word]") -{ - using namespace std::literals; - auto s = " \tfoo bar baz"sv; - - auto words = nihil::next_word(s); - REQUIRE(words.first == "foo"); - REQUIRE(words.second == " bar baz"); - - auto word = nihil::next_word(&s); - REQUIRE(word == "foo"); - REQUIRE(s == " bar baz"); -} - -TEST_CASE("next_word: locale", "[next_word]") -{ - using namespace std::literals; - auto s = L"\u2003foo\u2003bar\u2003baz"sv; - - auto words = nihil::next_word(s); - REQUIRE(words.first == s); - - words = nihil::next_word(s, std::locale("C.UTF-8")); - REQUIRE(words.first == L"foo"); - REQUIRE(words.second == L"\u2003bar\u2003baz"); -} - -} // anonymous namespace diff --git a/nihil.util/nihil.util.ccm b/nihil.util/nihil.util.ccm deleted file mode 100644 index 49e5687..0000000 --- a/nihil.util/nihil.util.ccm +++ /dev/null @@ -1,18 +0,0 @@ -// This source code is released into the public domain. -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 deleted file mode 100644 index 51bfb4f..0000000 --- a/nihil.util/parse_size.ccm +++ /dev/null @@ -1,90 +0,0 @@ -// This source code is released into the public domain. -export module nihil.util:parse_size; - -import nihil.std; -import nihil.core; - -import :ctype; -import :error; -import :monad; - -namespace nihil { - -export template -auto get_multiplier(Char c) -> std::expected -{ - auto ret = std::uint64_t{1}; - - // clang-format off - switch (c) { - case 'p': case 'P': ret *= 1024; // NOLINT - case 't': case 'T': ret *= 1024; // NOLINT - case 'g': case 'G': ret *= 1024; // NOLINT - case 'm': case 'M': ret *= 1024; // NOLINT - case 'k': case 'K': ret *= 1024; // NOLINT - return ret; - - default: - return error(errc::invalid_unit); - } - // clang-format on -} - -// Parse a string containing a human-formatted size, such as "1024" -// or "4g". Parsing is always done in the "C" locale and does not -// recognise thousands separators or negative numbers. -export template -[[nodiscard]] -auto parse_size(std::basic_string_view str) -> std::expected -{ - // Extract the numeric part of the string. - auto it = std::ranges::find_if_not(str, is_c_digit); - auto num_str = std::basic_string_view(std::ranges::begin(str), it); - - if (num_str.empty()) - co_return error(errc::empty_string); - - auto ret = T{0}; - - for (auto c : num_str) { - if (ret > (std::numeric_limits::max() / 10)) - co_return error(std::errc::result_out_of_range); - ret *= 10; - - auto digit = static_cast(c - '0'); - if ((std::numeric_limits::max() - digit) < ret) - co_return error(std::errc::result_out_of_range); - ret += digit; - } - - if (it == str.end()) - // No multiplier. - co_return ret; - - auto mchar = *it++; - - if (it != str.end()) - // Multiplier is more than one character. - co_return error(errc::invalid_unit); - - auto mult = co_await get_multiplier(mchar); - - if (std::cmp_greater(ret, std::numeric_limits::max() / mult)) - co_return error(std::errc::result_out_of_range); - - co_return ret *mult; -} - -export template -[[nodiscard]] auto parse_size(char const *s) -{ - return parse_size(std::string_view(s)); -} - -export template -[[nodiscard]] auto parse_size(wchar_t const *s) -{ - return parse_size(std::wstring_view(s)); -} - -} // namespace nihil diff --git a/nihil.util/parse_size.test.cc b/nihil.util/parse_size.test.cc deleted file mode 100644 index ee97996..0000000 --- a/nihil.util/parse_size.test.cc +++ /dev/null @@ -1,166 +0,0 @@ -// This source code is released into the public domain. - -#include - -import nihil.std; -import nihil.core; -import nihil.util; - -namespace { -TEST_CASE("parse_size: empty value", "[nihil]") -{ - using namespace nihil; - - auto n = parse_size(""); - REQUIRE(!n); - REQUIRE(n.error() == nihil::errc::empty_string); -} - -TEST_CASE("parse_size: basic", "[nihil]") -{ - using namespace nihil; - - SECTION ("bare number") { - auto n = parse_size("1024").value(); - REQUIRE(n == 1024); - } - - SECTION ("max value, unsigned") { - auto n = parse_size("65535").value(); - REQUIRE(n == 65535); - } - - SECTION ("max value, signed") { - auto n = parse_size("32767").value(); - REQUIRE(n == 32767); - } - - SECTION ("overflow by 1, unsigned") { - auto n = parse_size("65536"); - REQUIRE(!n); - REQUIRE(n.error() == std::errc::result_out_of_range); - } - - SECTION ("overflow by 1, signed") { - auto n = parse_size("32768"); - REQUIRE(!n); - REQUIRE(n.error() == std::errc::result_out_of_range); - } - - SECTION ("overflow by many, unsigned") { - auto n = parse_size("100000"); - REQUIRE(!n); - REQUIRE(n.error() == std::errc::result_out_of_range); - } - - SECTION ("overflow by many, signed") { - auto n = parse_size("100000"); - REQUIRE(!n); - REQUIRE(n.error() == std::errc::result_out_of_range); - } -} - -TEST_CASE("parse_size: invalid multiplier", "[nihil]") -{ - using namespace nihil; - - auto n = parse_size("4z"); - REQUIRE(!n); - REQUIRE(n.error() == nihil::errc::invalid_unit); - - n = parse_size("4kz"); - REQUIRE(!n); - REQUIRE(n.error() == nihil::errc::invalid_unit); -} - -TEST_CASE("parse_size: multipliers", "[nihil]") -{ - using namespace nihil; - - auto sf = static_cast(4); - - SECTION ("k") { - auto n = parse_size("4k").value(); - REQUIRE(n == sf * 1024); - } - - SECTION ("m") { - auto n = parse_size("4m").value(); - REQUIRE(n == sf * 1024 * 1024); - } - - SECTION ("g") { - auto n = parse_size("4g").value(); - REQUIRE(n == sf * 1024 * 1024 * 1024); - } - - SECTION ("t") { - auto n = parse_size("4t").value(); - REQUIRE(n == sf * 1024 * 1024 * 1024 * 1024); - } - - SECTION ("p") { - auto n = parse_size("4p").value(); - REQUIRE(n == sf * 1024 * 1024 * 1024 * 1024 * 1024); - } -} - -TEST_CASE("parse_size: multiplier overflow", "[nihil]") -{ - using namespace nihil; - - SECTION ("signed") { - auto n = parse_size("64k"); - REQUIRE(!n); - REQUIRE(n.error() == std::errc::result_out_of_range); - } - - SECTION ("unsigned") { - auto n = parse_size("32k"); - REQUIRE(!n); - REQUIRE(n.error() == std::errc::result_out_of_range); - } -} - -TEST_CASE("parse_size: wide", "[nihil]") -{ - using namespace nihil; - - SECTION ("bare number") { - auto n = parse_size(L"1024").value(); - REQUIRE(n == 1024); - } -} - -TEST_CASE("parse_size: wide multipliers", "[nihil]") -{ - using namespace nihil; - - auto sf = static_cast(4); - - SECTION ("k") { - auto n = parse_size(L"4k").value(); - REQUIRE(n == sf * 1024); - } - - SECTION ("m") { - auto n = parse_size(L"4m").value(); - REQUIRE(n == sf * 1024 * 1024); - } - - SECTION ("g") { - auto n = parse_size(L"4g").value(); - REQUIRE(n == sf * 1024 * 1024 * 1024); - } - - SECTION ("t") { - auto n = parse_size(L"4t").value(); - REQUIRE(n == sf * 1024 * 1024 * 1024 * 1024); - } - - SECTION ("p") { - auto n = parse_size(L"4p").value(); - REQUIRE(n == sf * 1024 * 1024 * 1024 * 1024 * 1024); - } -} -} // anonymous namespace diff --git a/nihil.util/save_errno.ccm b/nihil.util/save_errno.ccm deleted file mode 100644 index 27567f8..0000000 --- a/nihil.util/save_errno.ccm +++ /dev/null @@ -1,35 +0,0 @@ -// This source code is released into the public domain. -module; - -#include - -export module nihil.util:save_errno; - -// save_errno: save the current value of errno and restore it when we're destroyed. -// this allows wrappers around C functions that use errno to preserve the caller's -// errno value. - -namespace nihil { - -export struct save_errno final -{ - save_errno() : m_errno(errno) {} - - ~save_errno() - { - errno = m_errno; - } - - // Not copyable - save_errno(const save_errno&) = delete; - auto operator=(const save_errno&) -> save_errno & = delete; - - // Not movable - save_errno(save_errno&&) = delete; - auto operator=(save_errno&&) -> save_errno & = delete; - -private: - int m_errno; -}; - -} // namespace nihil diff --git a/nihil.util/skipws.ccm b/nihil.util/skipws.ccm deleted file mode 100644 index 0a15775..0000000 --- a/nihil.util/skipws.ccm +++ /dev/null @@ -1,25 +0,0 @@ -// This source code is released into the public domain. -export module nihil.util:skipws; - -import :ctype; - -namespace nihil { - -// Remove leading whitespace from a string. -export template -[[nodiscard]] -auto skipws(std::basic_string_view text, std::locale const &locale = std::locale()) - -> std::basic_string_view -{ - auto is_space = ctype_is(std::ctype_base::space, locale); - auto nonws = std::ranges::find_if_not(text, is_space); - return {nonws, std::ranges::end(text)}; -} - -export template -auto skipws(std::basic_string_view *text, std::locale const &locale = std::locale()) -> void -{ - *text = skipws(*text, locale); -} - -} // namespace nihil diff --git a/nihil.util/skipws.test.cc b/nihil.util/skipws.test.cc deleted file mode 100644 index 0cb741c..0000000 --- a/nihil.util/skipws.test.cc +++ /dev/null @@ -1,46 +0,0 @@ -// This source code is released into the public domain. - -#include - -import nihil.std; -import nihil.util; - -TEST_CASE("skipws: basic", "[skipws]") -{ - using namespace std::literals; - - REQUIRE(nihil::skipws("foo"sv) == "foo"); - REQUIRE(nihil::skipws(" foo"sv) == "foo"); - REQUIRE(nihil::skipws("foo "sv) == "foo "); - REQUIRE(nihil::skipws("foo bar"sv) == "foo bar"); -} - -TEST_CASE("skipws: pointer", "[skipws]") -{ - using namespace std::literals; - - auto s = "foo"sv; - nihil::skipws(&s); - REQUIRE(s == "foo"); - - s = " foo"sv; - nihil::skipws(&s); - REQUIRE(s == "foo"); - - s = "foo "sv; - nihil::skipws(&s); - REQUIRE(s == "foo "); - - s = "foo bar"sv; - nihil::skipws(&s); - REQUIRE(s == "foo bar"); -} - -TEST_CASE("skipws: locale", "[skipws]") -{ - using namespace std::literals; - - // Assume the default locale is C. - REQUIRE(nihil::skipws(L"\u2003foo"sv) == L"\u2003foo"); - REQUIRE(nihil::skipws(L"\u2003foo"sv, std::locale("C.UTF-8")) == L"foo"); -} diff --git a/nihil.util/sys_error.ccm b/nihil.util/sys_error.ccm deleted file mode 100644 index a39552e..0000000 --- a/nihil.util/sys_error.ccm +++ /dev/null @@ -1,18 +0,0 @@ -// This source code is released into the public domain. -module; - -#include - -export module nihil.util: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); // NOLINT -} - -} // namespace nihil diff --git a/nihil.util/tabulate.ccm b/nihil.util/tabulate.ccm deleted file mode 100644 index 762e2a3..0000000 --- a/nihil.util/tabulate.ccm +++ /dev/null @@ -1,298 +0,0 @@ -// This source code is released into the public domain. -export module nihil.util:tabulate; - -import nihil.std; - -import :ctype; -import :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 : error { - explicit table_spec_error(std::string_view what) - : error(what) - { - } -}; - -// The specification for a single field. -template -struct field_spec { - enum align_t { left, right }; - - // Get the name of this field. - auto name(this field_spec const &self) - -> std::basic_string_view - { - return self.m_name; - } - - // Set the name of this field. - auto name(this field_spec &self, - std::basic_string_view new_name) - -> void - { - self.m_name = new_name; - } - - // Set this field's alignment. - auto align(this field_spec &self, align_t new_align) -> void - { - self.m_align = new_align; - } - - // Ensure the length of this field is at least the given width. - auto ensure_width(this field_spec &self, std::size_t newwidth) - -> void - { - self.m_width = std::max(self.m_width, newwidth); - } - - // Format an object to a string based on our field spec. - [[nodiscard]] auto format(this field_spec const &, auto &&obj) - -> std::basic_string - { - auto format_string = std::basic_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(this field_spec const &self, - std::basic_string_view value, - std::output_iterator auto &out, - bool is_last) - -> void - { - auto padding = self.m_width - value.size(); - - if (self.m_align == right) - for (std::size_t i = 0; i < padding; ++i) - *out++ = ' '; - - std::ranges::copy(value, out); - - if (!is_last && self.m_align == left) - for (std::size_t i = 0; i < padding; ++i) - *out++ = ' '; - } - -private: - std::basic_string_view m_name; - std::size_t m_width = 0; - align_t m_align = left; -}; - -/* - * The specification for an entire table. - */ -template -struct table_spec { - // Add a new field spec to this table. - auto add(this table_spec &self, field_spec field) -> void - { - self.m_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. - [[nodiscard]] auto field(this table_spec &self, std::size_t fieldnr) - -> field_spec & - { - if (fieldnr >= self.m_fields.size()) - self.m_fields.resize(fieldnr + 1); - return self.m_fields.at(fieldnr); - } - - // The number of columns in this table. - [[nodiscard]] auto columns(this table_spec const &self) -> std::size_t - { - return self.m_fields.size(); - } - - // Return all the fields in this table. - [[nodiscard]] auto fields(this table_spec const &self) - -> std::vector> const & - { - return self.m_fields; - } - -private: - std::vector> m_fields; -}; - -// Parse the field flags, e.g. '<'. -template Sentinel> -auto parse_field_flags(field_spec &field, Iterator &pos, Sentinel end) - -> void -{ - while (pos < end) { - switch (*pos) { - case '<': - field.align(field_spec::left); - break; - case '>': - field.align(field_spec::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 Sentinel> -[[nodiscard]] auto parse_field(Iterator &pos, Sentinel end) - -> field_spec -{ - auto field = field_spec{}; - - 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(pos, brace)); - pos = std::next(brace); - - // The field must be at least as wide as its header. - field.ensure_width(field.name().size()); - - return field; -} - -template -[[nodiscard]] auto parse_table_spec(std::basic_string_view spec) - -> table_spec -{ - auto table = table_spec(); - - 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(pos, end)); - } - - return table; -} - -export template Iterator> -auto basic_tabulate(std::basic_string_view 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>>(); - // 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 auto &&out) -{ - return basic_tabulate(table_spec, - std::forward(range), - std::forward(out)); -} - -export auto wtabulate(std::wstring_view table_spec, - std::ranges::range auto &&range, - std::output_iterator auto &&out) -{ - return basic_tabulate(table_spec, - std::forward(range), - std::forward(out)); -} - -} // namespace nihil diff --git a/nihil.util/tabulate.test.cc b/nihil.util/tabulate.test.cc deleted file mode 100644 index 408cc18..0000000 --- a/nihil.util/tabulate.test.cc +++ /dev/null @@ -1,70 +0,0 @@ -// This source code is released into the public domain. - -#include - -import nihil.std; -import nihil.util; - -using namespace std::literals; -using namespace nihil; - -TEST_CASE("tabulate: basic", "[tabulate]") -{ - auto input = std::vector{ - std::vector{"a", "foo", "b"}, - std::vector{"bar", "c", "baz"}, - }; - - auto result = std::string(); - tabulate("{:1} {:2} {:3}", input, std::back_inserter(result)); - REQUIRE(result == -"1 2 3\n" -"a foo b\n" -"bar c baz\n"); -} - -TEST_CASE("tabulate: basic wide", "[tabulate]") -{ - auto input = std::vector{ - std::vector{L"a", L"foo", L"b"}, - std::vector{L"bar", L"c", L"baz"}, - }; - - auto result = std::wstring(); - wtabulate(L"{:1} {:2} {:3}", input, std::back_inserter(result)); - - REQUIRE(result == -L"1 2 3\n" -"a foo b\n" -"bar c baz\n"); -} - -TEST_CASE("tabulate: jagged", "[tabulate]") -{ - auto input = std::vector{ - std::vector{"a", "foo", "b"}, - std::vector{"bar", "baz"}, - }; - - auto result = std::string(); - tabulate("{:1} {:2} {:3}", input, std::back_inserter(result)); - REQUIRE(result == -"1 2 3\n" -"a foo b\n" -"bar baz\n"); -} - -TEST_CASE("tabulate: align", "[tabulate]") -{ - auto input = std::vector{ - std::vector{"a", "longvalue", "s"}, - std::vector{"a", "s", "longvalue"}, - }; - - auto result = std::string(); - tabulate("{:1} {<:2} {>:3}", input, std::back_inserter(result)); - REQUIRE(result == -"1 2 3\n" -"a longvalue s\n" -"a s longvalue\n"); -} diff --git a/nihil.util/uuid.ccm b/nihil.util/uuid.ccm deleted file mode 100644 index 7b5727c..0000000 --- a/nihil.util/uuid.ccm +++ /dev/null @@ -1,768 +0,0 @@ -// From https://github.com/mariusbancila/stduuid -// -// Copyright (c) 2017 -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to -// deal in the Software without restriction, including without limitation the -// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or -// sell copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS -// IN THE SOFTWARE. - -export module nihil.util:uuid; - -import nihil.std; - -namespace nihil { - -template -[[nodiscard]] constexpr auto hex2char(TChar const ch) noexcept -> unsigned char -{ - if (ch >= static_cast('0') && ch <= static_cast('9')) - return static_cast(ch - static_cast('0')); - - if (ch >= static_cast('a') && ch <= static_cast('f')) - return static_cast(10 + ch - static_cast('a')); - - if (ch >= static_cast('A') && ch <= static_cast('F')) - return static_cast(10 + ch - static_cast('A')); - - return 0; -} - -template -[[nodiscard]] constexpr auto is_hex(TChar const ch) noexcept -> bool -{ - return (ch >= static_cast('0') && ch <= static_cast('9')) || - (ch >= static_cast('a') && ch <= static_cast('f')) || - (ch >= static_cast('A') && ch <= static_cast('F')); -} - -template -[[nodiscard]] constexpr auto -to_string_view(TChar const *str) noexcept -> std::basic_string_view -{ - if (str) - return str; - return {}; -} - -template -[[nodiscard]] constexpr auto to_string_view(StringType const &str) noexcept - -> std::basic_string_view -{ - return str; -} - -struct sha1 -{ - using digest32_t = std::array; - using digest8_t = std::array; - - static constexpr unsigned int block_bytes = 64; - - sha1() - { - reset(); - } - - [[nodiscard]] static auto - left_rotate(std::uint32_t value, std::size_t const count) noexcept -> std::uint32_t - { - return (value << count) ^ (value >> (32 - count)); - } - - auto reset(this sha1 &self) noexcept -> void - { - self.m_digest[0] = 0x67452301; - self.m_digest[1] = 0xEFCDAB89; - self.m_digest[2] = 0x98BADCFE; - self.m_digest[3] = 0x10325476; - self.m_digest[4] = 0xC3D2E1F0; - self.m_blockByteIndex = 0; - self.m_byteCount = 0; - } - - auto process_byte(this sha1 &self, std::uint8_t octet) -> void - { - self.m_block.at(self.m_blockByteIndex++) = octet; - ++self.m_byteCount; - - if (self.m_blockByteIndex == block_bytes) { - self.m_blockByteIndex = 0; - self.process_block(); - } - } - - auto process_block(this sha1 &self, void const *const start, void const *const end) -> void - { - auto const *first = static_cast(start); - auto const *last = static_cast(end); - - while (first != last) { - self.process_byte(*first); - first++; - } - } - - auto process_bytes(this sha1 &self, void const *const data, std::size_t const len) -> void - { - auto *block = static_cast(data); - self.process_block(block, block + len); - } - - auto get_digest(this sha1 &self) -> digest32_t - { - auto const bit_count = self.m_byteCount * 8; - - self.process_byte(0x80); - if (self.m_blockByteIndex > 56) { - while (self.m_blockByteIndex != 0) - self.process_byte(0); - - while (self.m_blockByteIndex < 56) - self.process_byte(0); - } else { - while (self.m_blockByteIndex < 56) - self.process_byte(0); - } - - self.process_byte(0); - self.process_byte(0); - self.process_byte(0); - self.process_byte(0); - self.process_byte(static_cast((bit_count >> 24U) & 0xFFU)); - self.process_byte(static_cast((bit_count >> 16U) & 0xFFU)); - self.process_byte(static_cast((bit_count >> 8U) & 0xFFU)); - self.process_byte(static_cast((bit_count) & 0xFFU)); - - return self.m_digest; - } - - auto get_digest_bytes(this sha1 &self) -> digest8_t - { - auto d32 = self.get_digest(); - - return { - static_cast(d32[0] >> 24U), - static_cast(d32[0] >> 16U), - static_cast(d32[0] >> 8U), - static_cast(d32[0] >> 0U), - - static_cast(d32[1] >> 24U), - static_cast(d32[1] >> 16U), - static_cast(d32[1] >> 8U), - static_cast(d32[1] >> 0U), - - static_cast(d32[2] >> 24U), - static_cast(d32[2] >> 16U), - static_cast(d32[2] >> 8U), - static_cast(d32[2] >> 0U), - - static_cast(d32[3] >> 24U), - static_cast(d32[3] >> 16U), - static_cast(d32[3] >> 8U), - static_cast(d32[3] >> 0U), - - static_cast(d32[4] >> 24U), - static_cast(d32[4] >> 16U), - static_cast(d32[4] >> 8U), - static_cast(d32[4] >> 0U), - }; - } - -private: - auto process_block(this sha1 &self) -> void - { - auto w = std::array{}; - - for (std::size_t i = 0; i < 16; i++) { - w.at(i) = static_cast(self.m_block.at((i * 4) + 0)) << 24U; - w.at(i) |= static_cast(self.m_block.at((i * 4) + 1)) << 16U; - w.at(i) |= static_cast(self.m_block.at((i * 4) + 2)) << 8U; - w.at(i) |= static_cast(self.m_block.at((i * 4) + 3)); - } - - for (std::size_t i = 16; i < 80; i++) { - w.at(i) = left_rotate( - (w.at(i - 3) ^ w.at(i - 8) ^ w.at(i - 14) ^ w.at(i - 16)), 1); - } - - auto a = self.m_digest[0]; - auto b = self.m_digest[1]; - auto c = self.m_digest[2]; - auto d = self.m_digest[3]; - auto e = self.m_digest[4]; - - for (std::size_t i = 0; i < 80; ++i) { - auto f = std::uint32_t{0}; - auto k = std::uint32_t{0}; - - if (i < 20) { - f = (b & c) | (~b & d); - k = 0x5A827999; - } else if (i < 40) { - f = b ^ c ^ d; - k = 0x6ED9EBA1; - } else if (i < 60) { - f = (b & c) | (b & d) | (c & d); - k = 0x8F1BBCDC; - } else { - f = b ^ c ^ d; - k = 0xCA62C1D6; - } - - auto temp = std::uint32_t{left_rotate(a, 5) + f + e + k + w.at(i)}; - e = d; - d = c; - c = left_rotate(b, 30); - b = a; - a = temp; - } - - self.m_digest[0] += a; - self.m_digest[1] += b; - self.m_digest[2] += c; - self.m_digest[3] += d; - self.m_digest[4] += e; - } - - digest32_t m_digest{}; - std::array m_block{}; - std::size_t m_blockByteIndex{}; - std::size_t m_byteCount{}; -}; - -template -inline constexpr std::string_view empty_guid = "00000000-0000-0000-0000-000000000000"; - -template <> -inline constexpr std::wstring_view empty_guid = L"00000000-0000-0000-0000-000000000000"; - -template -inline constexpr std::string_view guid_encoder = "0123456789abcdef"; - -template <> -inline constexpr std::wstring_view guid_encoder = L"0123456789abcdef"; - -// --------------------------------------------------------------------- -// UUID format https://tools.ietf.org/html/rfc4122 -// --------------------------------------------------------------------- - -// --------------------------------------------------------------------- -// Field NDR Data Type Octet # Note -// Note -// --------------------------------------------------------------------- -// time_low unsigned long 0 - 3 -// The low field of the timestamp. -// time_mid unsigned short 4 - 5 -// The middle field of the timestamp. -// time_hi_and_version unsigned short 6 - 7 -// The high field of the timestamp multiplexed with the version number. -// clock_seq_hi_and_reserved unsigned small 8 -// The high field of the clock sequence multiplexed with the variant. -// clock_seq_low unsigned small 9 -// The low field of the clock sequence. -// node character 10 - 15 -// The spatially unique node identifier. -// --------------------------------------------------------------------- -// 0 1 2 3 -// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 -// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ -// | time_low | -// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ -// | time_mid | time_hi_and_version | -// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ -// |clk_seq_hi_res | clk_seq_low | node (0-1) | -// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ -// | node (2-5) | -// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - -// --------------------------------------------------------------------- -// enumerations -// --------------------------------------------------------------------- - -// indicated by a bit pattern in octet 8, marked with N in -// xxxxxxxx-xxxx-xxxx-Nxxx-xxxxxxxxxxxx -export enum struct uuid_variant : std::uint8_t { - // NCS backward compatibility (with the obsolete Apollo Network - // Computing System 1.5 UUID format). - // N bit pattern: 0xxx - // > the first 6 octets of the UUID are a 48-bit timestamp (the number - // of 4 microsecond units of time since 1 Jan 1980 UTC); - // > the next 2 octets are reserved; - // > the next octet is the "address family"; - // > the final 7 octets are a 56-bit host ID in the form specified by - // the address family - ncs, - - // RFC 4122/DCE 1.1 - // N bit pattern: 10xx - // > big-endian byte order - rfc, - - // Microsoft Corporation backward compatibility - // N bit pattern: 110x - // > little endian byte order - // > formely used in the Component Object Model (COM) library - microsoft, - - // reserved for possible future definition - // N bit pattern: 111x - reserved -}; - -// indicated by a bit pattern in octet 6, marked with M in -// xxxxxxxx-xxxx-Mxxx-xxxx-xxxxxxxxxxxx -export enum struct uuid_version : std::uint8_t { - // only possible for nil or invalid uuids - none = 0, - // The time-based version specified in RFC 4122 - time_based = 1, - // DCE Security version, with embedded POSIX UIDs. - dce_security = 2, - // The name-based version specified in RFS 4122 with MD5 hashing - name_based_md5 = 3, - // The randomly or pseudo-randomly generated version specified in RFS 4122 - random_number_based = 4, - // The name-based version specified in RFS 4122 with SHA1 hashing - name_based_sha1 = 5 -}; - -// Forward declare uuid and to_string so that we can declare to_string as a -// friend later. -export struct uuid; -export template , - typename Allocator = std::allocator> -auto to_string(uuid const &id) -> std::basic_string; - -// -------------------------------------------------------------------------------------------------------------------------- -// uuid class -// -------------------------------------------------------------------------------------------------------------------------- -export struct uuid -{ - using value_type = std::uint8_t; - - constexpr uuid() noexcept = default; - - uuid(value_type (&arr)[16]) noexcept // NOLINT - { - std::ranges::copy(arr, std::ranges::begin(data)); - } - - explicit constexpr uuid(std::array const &arr) noexcept - : data{arr} - { - } - - explicit uuid(std::span bytes) - { - std::ranges::copy(bytes, std::ranges::begin(data)); - } - - explicit uuid(std::span bytes) - { - if (bytes.size() != 16) - throw std::logic_error("wrong size for uuid"); - std::ranges::copy(bytes, std::ranges::begin(data)); - } - - template - explicit uuid(ForwardIterator first, ForwardIterator last) - { - if (std::distance(first, last) != 16) - throw std::logic_error("wrong size for uuid"); - - std::copy(first, last, std::begin(data)); - } - - [[nodiscard]] constexpr auto variant() const noexcept -> uuid_variant - { - if ((data[8] & 0x80U) == 0x00U) - return uuid_variant::ncs; - else if ((data[8] & 0xC0U) == 0x80U) - return uuid_variant::rfc; - else if ((data[8] & 0xE0U) == 0xC0U) - return uuid_variant::microsoft; - else - return uuid_variant::reserved; - } - - [[nodiscard]] constexpr auto version() const noexcept -> uuid_version - { - if ((data[6] & 0xF0U) == 0x10U) - return uuid_version::time_based; - else if ((data[6] & 0xF0U) == 0x20U) - return uuid_version::dce_security; - else if ((data[6] & 0xF0U) == 0x30U) - return uuid_version::name_based_md5; - else if ((data[6] & 0xF0U) == 0x40U) - return uuid_version::random_number_based; - else if ((data[6] & 0xF0U) == 0x50U) - return uuid_version::name_based_sha1; - else - return uuid_version::none; - } - - [[nodiscard]] constexpr auto is_nil() const noexcept -> bool - { - return std::ranges::all_of(data, [](auto i) { return i == 0; }); - } - - auto swap(uuid &other) noexcept -> void - { - data.swap(other.data); - } - - [[nodiscard]] auto as_bytes() const -> std::span - { - return std::span( - reinterpret_cast(data.data()), 16); - } - - template - [[nodiscard]] constexpr static auto is_valid_uuid(StringType const &in_str) noexcept -> bool - { - auto str = to_string_view(in_str); - auto firstDigit = true; - auto hasBraces = std::size_t{0}; - auto index = std::size_t{0}; - - if (str.empty()) - return false; - - if (str.front() == '{') - hasBraces = 1; - - if (hasBraces && str.back() != '}') - return false; - - for (std::size_t i = hasBraces; i < str.size() - hasBraces; ++i) { - if (str[i] == '-') - continue; - - if (index >= 16 || !is_hex(str[i])) - return false; - - if (firstDigit) { - firstDigit = false; - } else { - index++; - firstDigit = true; - } - } - - if (index < 16) - return false; - - return true; - } - - template - [[nodiscard]] constexpr static auto - from_string(StringType const &in_str) -> std::optional - { - auto str = to_string_view(in_str); - bool firstDigit = true; - auto hasBraces = std::size_t{0}; - auto index = std::size_t{0}; - - auto data = std::array{}; - - if (str.empty()) - return {}; - - if (str.front() == '{') - hasBraces = 1; - if (hasBraces && str.back() != '}') - return {}; - - for (std::size_t i = hasBraces; i < str.size() - hasBraces; ++i) { - if (str[i] == '-') - continue; - - if (index >= 16 || !is_hex(str[i])) { - return {}; - } - - if (firstDigit) { - data.at(index) = static_cast(hex2char(str[i]) << 4); - firstDigit = false; - } else { - data.at(index) = - static_cast(data.at(index) | hex2char(str[i])); - index++; - firstDigit = true; - } - } - - if (index < 16) { - return {}; - } - - return uuid{data}; - } - -private: - std::array data{{0}}; - - friend auto operator==(uuid const &, uuid const &) noexcept -> bool; - friend auto operator<(uuid const &, uuid const &) noexcept -> bool; - - template - friend auto operator<<(std::basic_ostream &s, uuid const &id) - -> std::basic_ostream &; - - template - friend auto to_string(uuid const &id) -> std::basic_string; - - friend std::hash; -}; - -// -------------------------------------------------------------------------------------------------------------------------- -// operators and non-member functions -// -------------------------------------------------------------------------------------------------------------------------- - -export [[nodiscard]] -auto operator==(uuid const &lhs, uuid const &rhs) noexcept -> bool -{ - return lhs.data == rhs.data; -} - -export [[nodiscard]] -auto operator!=(uuid const &lhs, uuid const &rhs) noexcept -> bool -{ - return !(lhs == rhs); -} - -export [[nodiscard]] -auto operator<(uuid const &lhs, uuid const &rhs) noexcept -> bool -{ - return lhs.data < rhs.data; -} - -export template -[[nodiscard]] auto to_string(uuid const &id) -> std::basic_string -{ - auto uustr = - std::basic_string(std::from_range, empty_guid); - - for (std::size_t i = 0, index = 0; i < 36; ++i) { - if (i == 8 || i == 13 || i == 18 || i == 23) - continue; - - uustr[i] = guid_encoder[id.data.at(index) >> 4U & 0x0FU]; - uustr[++i] = guid_encoder[id.data.at(index) & 0x0FU]; - index++; - } - - return uustr; -} - -export template -auto operator<<(std::basic_ostream &s, uuid const &id) - -> std::basic_ostream & -{ - return s << to_string(id); -} - -export auto swap(uuid &lhs, uuid &rhs) noexcept -> void -{ - lhs.swap(rhs); -} - -/*********************************************************************** - * namespace IDs that could be used for generating name-based uuids - */ - -// Name string is a fully-qualified domain name -export uuid uuid_namespace_dns{ - {0x6b, 0xa7, 0xb8, 0x10, 0x9d, 0xad, 0x11, 0xd1, 0x80, 0xb4, 0x00, 0xc0, 0x4f, 0xd4, 0x30, - 0xc8} -}; - -// Name string is a URL -export uuid uuid_namespace_url{ - {0x6b, 0xa7, 0xb8, 0x11, 0x9d, 0xad, 0x11, 0xd1, 0x80, 0xb4, 0x00, 0xc0, 0x4f, 0xd4, 0x30, - 0xc8} -}; - -// Name string is an ISO OID (See https://oidref.com/, -// https://en.wikipedia.org/wiki/Object_identifier) -export uuid uuid_namespace_oid{ - {0x6b, 0xa7, 0xb8, 0x12, 0x9d, 0xad, 0x11, 0xd1, 0x80, 0xb4, 0x00, 0xc0, 0x4f, 0xd4, 0x30, - 0xc8} -}; - -// Name string is an X.500 DN, in DER or a text output format (See -// https://en.wikipedia.org/wiki/X.500, -// https://en.wikipedia.org/wiki/Abstract_Syntax_Notation_One) -export uuid uuid_namespace_x500{ - {0x6b, 0xa7, 0xb8, 0x14, 0x9d, 0xad, 0x11, 0xd1, 0x80, 0xb4, 0x00, 0xc0, 0x4f, 0xd4, 0x30, - 0xc8} -}; - -/*********************************************************************** - * uuid generators - */ - -export template -struct basic_uuid_random_generator -{ - using engine_type = UniformRandomNumberGenerator; - - explicit basic_uuid_random_generator(engine_type &gen) - : generator(&gen, [](auto) {}) - { - } - - explicit basic_uuid_random_generator(engine_type *gen) - : generator(gen, [](auto) {}) - { - } - - [[nodiscard]] auto operator()() -> uuid - { - auto bytes = std::array{}; - std::ranges::generate(bytes, [&] { return distribution(*generator); }); - - // variant must be 10xxxxxx - bytes[8] &= 0xBFU; - bytes[8] |= 0x80U; - - // version must be 0100xxxx - bytes[6] &= 0x4FU; - bytes[6] |= 0x40U; - - return uuid{std::begin(bytes), std::end(bytes)}; - } - -private: - std::uniform_int_distribution distribution; - std::shared_ptr generator; -}; - -export using uuid_random_generator = basic_uuid_random_generator; - -export struct uuid_name_generator -{ - explicit uuid_name_generator(uuid const &namespace_uuid) noexcept - : nsuuid(namespace_uuid) - { - } - - template - [[nodiscard]] auto operator()(StringType const &name) -> uuid - { - reset(); - process_characters(to_string_view(name)); - return make_uuid(); - } - -private: - auto reset() -> void - { - hasher.reset(); - - auto nsbytes = nsuuid.as_bytes(); - - auto bytes = std::array(); - std::ranges::copy(nsbytes, std::ranges::begin(bytes)); - - hasher.process_bytes(bytes.data(), bytes.size()); - } - - template - auto process_characters(std::basic_string_view const str) -> void - { - for (std::uint32_t c : str) { - hasher.process_byte(static_cast(c & 0xFFU)); - if constexpr (!std::is_same_v) { - hasher.process_byte(static_cast((c >> 8U) & 0xFFU)); - hasher.process_byte(static_cast((c >> 16U) & 0xFFU)); - hasher.process_byte(static_cast((c >> 24U) & 0xFFU)); - } - } - } - - [[nodiscard]] auto make_uuid() -> uuid - { - auto digest = hasher.get_digest_bytes(); - - // variant must be 0b10xxxxxx - digest[8] &= 0xBFU; - digest[8] |= 0x80U; - - // version must be 0b0101xxxx - digest[6] &= 0x5FU; - digest[6] |= 0x50U; - - return uuid(std::span(digest).subspan(0, 16)); - } - - uuid nsuuid; - sha1 hasher; -}; - -/* - * Create a random UUID. - */ -export auto random_uuid() -> uuid -{ - auto rd = std::random_device(); - auto seed_data = std::array{}; - std::ranges::generate(seed_data, std::ref(rd)); - - auto seq = std::seed_seq(std::ranges::begin(seed_data), std::ranges::end(seed_data)); - auto generator = std::mt19937(seq); - auto gen = uuid_random_generator{generator}; - - return gen(); -} - -} // namespace nihil - -namespace std { - -export template <> -struct hash -{ - using argument_type = nihil::uuid; - using result_type = std::size_t; - - [[nodiscard]] auto operator()(argument_type const &uuid) const noexcept -> result_type - { - auto const l = static_cast(uuid.data[0]) << 56U | - static_cast(uuid.data[1]) << 48U | - static_cast(uuid.data[2]) << 40U | - static_cast(uuid.data[3]) << 32U | - static_cast(uuid.data[4]) << 24U | - static_cast(uuid.data[5]) << 16U | - static_cast(uuid.data[6]) << 8U | - static_cast(uuid.data[7]); - - auto const h = static_cast(uuid.data[8]) << 56U | - static_cast(uuid.data[9]) << 48U | - static_cast(uuid.data[10]) << 40U | - static_cast(uuid.data[11]) << 32U | - static_cast(uuid.data[12]) << 24U | - static_cast(uuid.data[13]) << 16U | - static_cast(uuid.data[14]) << 8U | - static_cast(uuid.data[15]); - - return std::hash{}(l ^ h); - } -}; - -} // namespace std diff --git a/nihil.util/uuid.test.cc b/nihil.util/uuid.test.cc deleted file mode 100644 index eca94d1..0000000 --- a/nihil.util/uuid.test.cc +++ /dev/null @@ -1,923 +0,0 @@ -/* - * From https://github.com/mariusbancila/stduuid - * - * Copyright (c) 2017 - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to - * deal in the Software without restriction, including without limitation the - * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or - * sell copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS - * IN THE SOFTWARE. - */ - -#include - -import nihil.std; -import nihil.util; - -// NOLINTBEGIN(bugprone-unchecked-optional-access) - -namespace { -// http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2016/p0205r0.html -template -void seed_rng(EngineT &engine) -{ - using engine_type = typename EngineT::result_type; - using device_type = std::random_device::result_type; - using seedseq_type = std::seed_seq::result_type; - - constexpr auto bytes_needed = StateSize * sizeof(engine_type); - constexpr auto numbers_needed = (sizeof(device_type) < sizeof(seedseq_type)) - ? (bytes_needed / sizeof(device_type)) - : (bytes_needed / sizeof(seedseq_type)); - - auto numbers = std::array{}; - auto rnddev = std::random_device{}; - std::ranges::generate(numbers, std::ref(rnddev)); - - auto seedseq = std::seed_seq(std::cbegin(numbers), std::cend(numbers)); - engine.seed(seedseq); -} - -using namespace nihil; - -TEST_CASE("uuid: Test multiple default generators", "[uuid]") -{ - uuid id1; - uuid id2; - - { - std::random_device rd; - auto seed_data = std::array{}; - std::ranges::generate(seed_data, std::ref(rd)); - std::seed_seq seq(std::begin(seed_data), std::end(seed_data)); - std::mt19937 generator(seq); - - id1 = uuid_random_generator{generator}(); - REQUIRE(!id1.is_nil()); - REQUIRE(id1.version() == uuid_version::random_number_based); - REQUIRE(id1.variant() == uuid_variant::rfc); - } - - { - std::random_device rd; - auto seed_data = std::array{}; - std::ranges::generate(seed_data, std::ref(rd)); - std::seed_seq seq(std::begin(seed_data), std::end(seed_data)); - std::mt19937 generator(seq); - - id2 = uuid_random_generator{generator}(); - REQUIRE(!id2.is_nil()); - REQUIRE(id2.version() == uuid_version::random_number_based); - REQUIRE(id2.variant() == uuid_variant::rfc); - } - - REQUIRE(id1 != id2); -} - -TEST_CASE("uuid: Test default generator", "[uuid]") -{ - std::random_device rd; - auto seed_data = std::array{}; - std::ranges::generate(seed_data, std::ref(rd)); - std::seed_seq seq(std::begin(seed_data), std::end(seed_data)); - std::mt19937 generator(seq); - - uuid const guid = uuid_random_generator{generator}(); - REQUIRE(!guid.is_nil()); - REQUIRE(guid.version() == uuid_version::random_number_based); - REQUIRE(guid.variant() == uuid_variant::rfc); -} - -TEST_CASE("uuid: Test random generator (conversion ctor w/ smart ptr)", "[uuid]") -{ - std::random_device rd; - auto seed_data = std::array{}; - std::ranges::generate(seed_data, std::ref(rd)); - std::seed_seq seq(std::begin(seed_data), std::end(seed_data)); - std::mt19937 generator(seq); - - uuid_random_generator dgen(&generator); - auto id1 = dgen(); - REQUIRE(!id1.is_nil()); - REQUIRE(id1.version() == uuid_version::random_number_based); - REQUIRE(id1.variant() == uuid_variant::rfc); - - auto id2 = dgen(); - REQUIRE(!id2.is_nil()); - REQUIRE(id2.version() == uuid_version::random_number_based); - REQUIRE(id2.variant() == uuid_variant::rfc); - - REQUIRE(id1 != id2); -} - -TEST_CASE("uuid: Test random generator (conversion ctor w/ ptr)", "[uuid]") -{ - std::random_device rd; - auto seed_data = std::array{}; - std::ranges::generate(seed_data, std::ref(rd)); - std::seed_seq seq(std::begin(seed_data), std::end(seed_data)); - auto generator = std::make_unique(seq); - - uuid_random_generator dgen(generator.get()); - auto id1 = dgen(); - REQUIRE(!id1.is_nil()); - REQUIRE(id1.version() == uuid_version::random_number_based); - REQUIRE(id1.variant() == uuid_variant::rfc); - - auto id2 = dgen(); - REQUIRE(!id1.is_nil()); - REQUIRE(id2.version() == uuid_version::random_number_based); - REQUIRE(id2.variant() == uuid_variant::rfc); - - REQUIRE(id1 != id2); -} - -TEST_CASE("uuid: Test random generator (conversion ctor w/ ref)", "[uuid]") -{ - std::random_device rd; - auto seed_data = std::array{}; - std::ranges::generate(seed_data, std::ref(rd)); - std::seed_seq seq(std::begin(seed_data), std::end(seed_data)); - std::mt19937 generator(seq); - - uuid_random_generator dgen(generator); - auto id1 = dgen(); - REQUIRE(!id1.is_nil()); - REQUIRE(id1.version() == uuid_version::random_number_based); - REQUIRE(id1.variant() == uuid_variant::rfc); - - auto id2 = dgen(); - REQUIRE(!id2.is_nil()); - REQUIRE(id2.version() == uuid_version::random_number_based); - REQUIRE(id2.variant() == uuid_variant::rfc); - - REQUIRE(id1 != id2); -} - -TEST_CASE("uuid: Test basic random generator (conversion ctor w/ ptr) " - "w/ ranlux48_base", - "[uuid]") -{ - std::random_device rd; - auto seed_data = std::array{}; - std::ranges::generate(seed_data, std::ref(rd)); - std::seed_seq seq(std::begin(seed_data), std::end(seed_data)); - std::ranlux48_base generator(seq); - - basic_uuid_random_generator dgen(&generator); - auto id1 = dgen(); - REQUIRE(!id1.is_nil()); - REQUIRE(id1.version() == uuid_version::random_number_based); - REQUIRE(id1.variant() == uuid_variant::rfc); - - auto id2 = dgen(); - REQUIRE(!id2.is_nil()); - REQUIRE(id2.version() == uuid_version::random_number_based); - REQUIRE(id2.variant() == uuid_variant::rfc); - - REQUIRE(id1 != id2); -} - -TEST_CASE("uuid: Test basic random generator (conversion ctor w/ smart ptr) " - "w/ ranlux48_base", - "[uuid]") -{ - std::random_device rd; - auto seed_data = std::array{}; - std::ranges::generate(seed_data, std::ref(rd)); - std::seed_seq seq(std::begin(seed_data), std::end(seed_data)); - auto generator = std::make_unique(seq); - - basic_uuid_random_generator dgen(generator.get()); - auto id1 = dgen(); - REQUIRE(!id1.is_nil()); - REQUIRE(id1.version() == uuid_version::random_number_based); - REQUIRE(id1.variant() == uuid_variant::rfc); - - auto id2 = dgen(); - REQUIRE(!id2.is_nil()); - REQUIRE(id2.version() == uuid_version::random_number_based); - REQUIRE(id2.variant() == uuid_variant::rfc); - - REQUIRE(id1 != id2); -} - -TEST_CASE("uuid: Test basic random generator (conversion ctor w/ ref) " - "w/ ranlux48_base", - "[uuid]") -{ - std::random_device rd; - auto seed_data = std::array{}; - std::ranges::generate(seed_data, std::ref(rd)); - std::seed_seq seq(std::begin(seed_data), std::end(seed_data)); - std::ranlux48_base generator(seq); - - basic_uuid_random_generator dgen(generator); - auto id1 = dgen(); - REQUIRE(!id1.is_nil()); - REQUIRE(id1.version() == uuid_version::random_number_based); - REQUIRE(id1.variant() == uuid_variant::rfc); - - auto id2 = dgen(); - REQUIRE(!id2.is_nil()); - REQUIRE(id2.version() == uuid_version::random_number_based); - REQUIRE(id2.variant() == uuid_variant::rfc); - - REQUIRE(id1 != id2); -} - -TEST_CASE("uuid: Test namespaces", "[uuid]") -{ - REQUIRE(uuid_namespace_dns == uuid::from_string("6ba7b810-9dad-11d1-80b4-00c04fd430c8")); - REQUIRE(uuid_namespace_url == uuid::from_string("6ba7b811-9dad-11d1-80b4-00c04fd430c8")); - REQUIRE(uuid_namespace_oid == uuid::from_string("6ba7b812-9dad-11d1-80b4-00c04fd430c8")); - REQUIRE(uuid_namespace_x500 == uuid::from_string("6ba7b814-9dad-11d1-80b4-00c04fd430c8")); -} - -TEST_CASE("uuid: Test name generator (char*)", "[uuid]") -{ - uuid_name_generator dgen(uuid::from_string("47183823-2574-4bfd-b411-99ed177d3e43").value()); - - auto id1 = dgen("john"); - REQUIRE(!id1.is_nil()); - REQUIRE(id1.version() == uuid_version::name_based_sha1); - REQUIRE(id1.variant() == uuid_variant::rfc); - - auto id2 = dgen("jane"); - REQUIRE(!id2.is_nil()); - REQUIRE(id2.version() == uuid_version::name_based_sha1); - REQUIRE(id2.variant() == uuid_variant::rfc); - - auto id3 = dgen("jane"); - REQUIRE(!id3.is_nil()); - REQUIRE(id3.version() == uuid_version::name_based_sha1); - REQUIRE(id3.variant() == uuid_variant::rfc); - - auto id4 = dgen(L"jane"); - REQUIRE(!id4.is_nil()); - REQUIRE(id4.version() == uuid_version::name_based_sha1); - REQUIRE(id4.variant() == uuid_variant::rfc); - - REQUIRE(id1 != id2); - REQUIRE(id2 == id3); - REQUIRE(id3 != id4); -} - -TEST_CASE("uuid: Test name generator (std::string)", "[uuid]") -{ - using namespace std::string_literals; - - uuid_name_generator dgen(uuid::from_string("47183823-2574-4bfd-b411-99ed177d3e43").value()); - auto id1 = dgen("john"s); - REQUIRE(!id1.is_nil()); - REQUIRE(id1.version() == uuid_version::name_based_sha1); - REQUIRE(id1.variant() == uuid_variant::rfc); - - auto id2 = dgen("jane"s); - REQUIRE(!id2.is_nil()); - REQUIRE(id2.version() == uuid_version::name_based_sha1); - REQUIRE(id2.variant() == uuid_variant::rfc); - - auto id3 = dgen("jane"s); - REQUIRE(!id3.is_nil()); - REQUIRE(id3.version() == uuid_version::name_based_sha1); - REQUIRE(id3.variant() == uuid_variant::rfc); - - auto id4 = dgen(L"jane"s); - REQUIRE(!id4.is_nil()); - REQUIRE(id4.version() == uuid_version::name_based_sha1); - REQUIRE(id4.variant() == uuid_variant::rfc); - - REQUIRE(id1 != id2); - REQUIRE(id2 == id3); - REQUIRE(id3 != id4); -} - -TEST_CASE("uuid: Test name generator (std::string_view)", "[uuid]") -{ - using namespace std::string_view_literals; - - uuid_name_generator dgen(uuid::from_string("47183823-2574-4bfd-b411-99ed177d3e43").value()); - auto id1 = dgen("john"sv); - REQUIRE(!id1.is_nil()); - REQUIRE(id1.version() == uuid_version::name_based_sha1); - REQUIRE(id1.variant() == uuid_variant::rfc); - - auto id2 = dgen("jane"sv); - REQUIRE(!id2.is_nil()); - REQUIRE(id2.version() == uuid_version::name_based_sha1); - REQUIRE(id2.variant() == uuid_variant::rfc); - - auto id3 = dgen("jane"sv); - REQUIRE(!id3.is_nil()); - REQUIRE(id3.version() == uuid_version::name_based_sha1); - REQUIRE(id3.variant() == uuid_variant::rfc); - - auto id4 = dgen(L"jane"sv); - REQUIRE(!id4.is_nil()); - REQUIRE(id4.version() == uuid_version::name_based_sha1); - REQUIRE(id4.variant() == uuid_variant::rfc); - - REQUIRE(id1 != id2); - REQUIRE(id2 == id3); - REQUIRE(id3 != id4); -} - -TEST_CASE("uuid: Test name generator equality (char const*, std::string, " - "std::string_view)", - "[uuid]") -{ - using namespace std::literals; - - auto dgen = uuid_name_generator( - uuid::from_string("47183823-2574-4bfd-b411-99ed177d3e43").value()); - auto id1 = dgen("john"); - auto id2 = dgen("john"s); - auto id3 = dgen("john"sv); - - REQUIRE(id1 == id2); - REQUIRE(id2 == id3); -} - -TEST_CASE("uuid: Test default constructor", "[uuid]") -{ - auto empty = uuid(); - REQUIRE(empty.is_nil()); -} - -TEST_CASE("uuid: Test string conversion", "[uuid]") -{ - auto empty = uuid(); - REQUIRE(to_string(empty) == "00000000-0000-0000-0000-000000000000"); - REQUIRE(to_string(empty) == L"00000000-0000-0000-0000-000000000000"); -} - -TEST_CASE("uuid: Test is_valid_uuid(char*)", "[uuid]") -{ - REQUIRE(uuid::is_valid_uuid("47183823-2574-4bfd-b411-99ed177d3e43")); - REQUIRE(uuid::is_valid_uuid("{47183823-2574-4bfd-b411-99ed177d3e43}")); - REQUIRE(uuid::is_valid_uuid(L"47183823-2574-4bfd-b411-99ed177d3e43")); - REQUIRE(uuid::is_valid_uuid(L"{47183823-2574-4bfd-b411-99ed177d3e43}")); - REQUIRE(uuid::is_valid_uuid("00000000-0000-0000-0000-000000000000")); - REQUIRE(uuid::is_valid_uuid("{00000000-0000-0000-0000-000000000000}")); - REQUIRE(uuid::is_valid_uuid(L"00000000-0000-0000-0000-000000000000")); - REQUIRE(uuid::is_valid_uuid(L"{00000000-0000-0000-0000-000000000000}")); -} - -TEST_CASE("uuid: Test is_valid_uuid(basic_string)", "[uuid]") -{ - using namespace std::string_literals; - - { - auto str = "47183823-2574-4bfd-b411-99ed177d3e43"s; - REQUIRE(uuid::is_valid_uuid(str)); - } - - { - auto str = "{47183823-2574-4bfd-b411-99ed177d3e43}"s; - REQUIRE(uuid::is_valid_uuid(str)); - } - - { - auto str = L"47183823-2574-4bfd-b411-99ed177d3e43"s; - REQUIRE(uuid::is_valid_uuid(str)); - } - - { - auto str = L"{47183823-2574-4bfd-b411-99ed177d3e43}"s; - REQUIRE(uuid::is_valid_uuid(str)); - } - - { - auto str = "00000000-0000-0000-0000-000000000000"s; - REQUIRE(uuid::is_valid_uuid(str)); - } - - { - auto str = "{00000000-0000-0000-0000-000000000000}"s; - REQUIRE(uuid::is_valid_uuid(str)); - } - - { - auto str = L"00000000-0000-0000-0000-000000000000"s; - REQUIRE(uuid::is_valid_uuid(str)); - } - - { - auto str = L"{00000000-0000-0000-0000-000000000000}"s; - REQUIRE(uuid::is_valid_uuid(str)); - } -} - -TEST_CASE("uuid: Test is_valid_uuid(basic_string_view)", "[uuid]") -{ - using namespace std::string_view_literals; - - REQUIRE(uuid::is_valid_uuid("47183823-2574-4bfd-b411-99ed177d3e43"sv)); - REQUIRE(uuid::is_valid_uuid("{47183823-2574-4bfd-b411-99ed177d3e43}"sv)); - REQUIRE(uuid::is_valid_uuid(L"47183823-2574-4bfd-b411-99ed177d3e43"sv)); - REQUIRE(uuid::is_valid_uuid(L"{47183823-2574-4bfd-b411-99ed177d3e43}"sv)); - REQUIRE(uuid::is_valid_uuid("00000000-0000-0000-0000-000000000000"sv)); - REQUIRE(uuid::is_valid_uuid("{00000000-0000-0000-0000-000000000000}"sv)); - REQUIRE(uuid::is_valid_uuid(L"00000000-0000-0000-0000-000000000000"sv)); - REQUIRE(uuid::is_valid_uuid(L"{00000000-0000-0000-0000-000000000000}"sv)); -} - -TEST_CASE("uuid: Test is_valid_uuid(char*) invalid format", "[uuid]") -{ - REQUIRE(!uuid::is_valid_uuid("")); - REQUIRE(!uuid::is_valid_uuid("{}")); - REQUIRE(!uuid::is_valid_uuid("47183823-2574-4bfd-b411-99ed177d3e4")); - REQUIRE(!uuid::is_valid_uuid("47183823-2574-4bfd-b411-99ed177d3e430")); - REQUIRE(!uuid::is_valid_uuid("{47183823-2574-4bfd-b411-99ed177d3e43")); - REQUIRE(!uuid::is_valid_uuid("47183823-2574-4bfd-b411-99ed177d3e43}")); -} - -TEST_CASE("uuid: Test is_valid_uuid(basic_string) invalid format", "[uuid]") -{ - using namespace std::string_literals; - - { - auto str = ""s; - REQUIRE(!uuid::is_valid_uuid(str)); - } - - { - auto str = "{}"s; - REQUIRE(!uuid::is_valid_uuid(str)); - } - - { - auto str = "47183823-2574-4bfd-b411-99ed177d3e4"s; - REQUIRE(!uuid::is_valid_uuid(str)); - } - - { - auto str = "47183823-2574-4bfd-b411-99ed177d3e430"s; - REQUIRE(!uuid::is_valid_uuid(str)); - } - - { - auto str = "{47183823-2574-4bfd-b411-99ed177d3e43"s; - REQUIRE(!uuid::is_valid_uuid(str)); - } - - { - auto str = "47183823-2574-4bfd-b411-99ed177d3e43}"s; - REQUIRE(!uuid::is_valid_uuid(str)); - } -} - -TEST_CASE("uuid: Test is_valid_uuid(basic_string_view) invalid format", "[uuid]") -{ - using namespace std::string_view_literals; - - REQUIRE(!uuid::is_valid_uuid(""sv)); - REQUIRE(!uuid::is_valid_uuid("{}"sv)); - REQUIRE(!uuid::is_valid_uuid("47183823-2574-4bfd-b411-99ed177d3e4"sv)); - REQUIRE(!uuid::is_valid_uuid("47183823-2574-4bfd-b411-99ed177d3e430"sv)); - REQUIRE(!uuid::is_valid_uuid("{47183823-2574-4bfd-b411-99ed177d3e43"sv)); - REQUIRE(!uuid::is_valid_uuid("47183823-2574-4bfd-b411-99ed177d3e43}"sv)); -} - -TEST_CASE("uuid: Test from_string(char*)", "[uuid]") -{ - { - auto str = "47183823-2574-4bfd-b411-99ed177d3e43"; - auto guid = uuid::from_string(str).value(); - REQUIRE(to_string(guid) == str); - } - - { - auto str = "{47183823-2574-4bfd-b411-99ed177d3e43}"; - auto guid = uuid::from_string(str).value(); - REQUIRE(to_string(guid) == "47183823-2574-4bfd-b411-99ed177d3e43"); - } - - { - auto guid = uuid::from_string("47183823-2574-4bfd-b411-99ed177d3e43").value(); - REQUIRE(to_string(guid) == "47183823-2574-4bfd-b411-99ed177d3e43"); - REQUIRE(to_string(guid) == L"47183823-2574-4bfd-b411-99ed177d3e43"); - } - - { - auto str = L"47183823-2574-4bfd-b411-99ed177d3e43"; - auto guid = uuid::from_string(str).value(); - REQUIRE(to_string(guid) == str); - } - - { - auto str = "4718382325744bfdb41199ed177d3e43"; - REQUIRE_NOTHROW(uuid::from_string(str)); - REQUIRE(uuid::from_string(str).has_value()); - } - - { - auto str = "00000000-0000-0000-0000-000000000000"; - auto guid = uuid::from_string(str).value(); - REQUIRE(guid.is_nil()); - } - - { - auto str = "{00000000-0000-0000-0000-000000000000}"; - auto guid = uuid::from_string(str).value(); - REQUIRE(guid.is_nil()); - } - - { - auto str = L"00000000-0000-0000-0000-000000000000"; - auto guid = uuid::from_string(str).value(); - REQUIRE(guid.is_nil()); - } - - { - auto str = L"{00000000-0000-0000-0000-000000000000}"; - auto guid = uuid::from_string(str).value(); - REQUIRE(guid.is_nil()); - } -} - -TEST_CASE("uuid: Test from_string(basic_string)", "[uuid]") -{ - using namespace std::string_literals; - - { - auto str = "47183823-2574-4bfd-b411-99ed177d3e43"s; - auto guid = uuid::from_string(str).value(); - REQUIRE(to_string(guid) == str); - } - - { - auto str = "{47183823-2574-4bfd-b411-99ed177d3e43}"s; - auto guid = uuid::from_string(str).value(); - REQUIRE(to_string(guid) == "47183823-2574-4bfd-b411-99ed177d3e43"); - } - - { - auto guid = uuid::from_string("47183823-2574-4bfd-b411-99ed177d3e43"s).value(); - REQUIRE(to_string(guid) == "47183823-2574-4bfd-b411-99ed177d3e43"); - REQUIRE(to_string(guid) == L"47183823-2574-4bfd-b411-99ed177d3e43"); - } - - { - auto str = L"47183823-2574-4bfd-b411-99ed177d3e43"s; - auto guid = uuid::from_string(str).value(); - REQUIRE(to_string(guid) == str); - } - - { - auto str = "4718382325744bfdb41199ed177d3e43"s; - REQUIRE_NOTHROW(uuid::from_string(str)); - REQUIRE(uuid::from_string(str).has_value()); - } - - { - auto str = "00000000-0000-0000-0000-000000000000"s; - auto guid = uuid::from_string(str).value(); - REQUIRE(guid.is_nil()); - } - - { - auto str = "{00000000-0000-0000-0000-000000000000}"s; - auto guid = uuid::from_string(str).value(); - REQUIRE(guid.is_nil()); - } - - { - auto str = L"00000000-0000-0000-0000-000000000000"s; - auto guid = uuid::from_string(str).value(); - REQUIRE(guid.is_nil()); - } - - { - auto str = L"{00000000-0000-0000-0000-000000000000}"s; - auto guid = uuid::from_string(str).value(); - REQUIRE(guid.is_nil()); - } -} - -TEST_CASE("uuid: Test from_string(basic_string_view)", "[uuid]") -{ - using namespace std::string_view_literals; - - { - auto str = "47183823-2574-4bfd-b411-99ed177d3e43"sv; - auto guid = uuid::from_string(str).value(); - REQUIRE(to_string(guid) == str); - } - - { - auto str = "{47183823-2574-4bfd-b411-99ed177d3e43}"sv; - auto guid = uuid::from_string(str).value(); - REQUIRE(to_string(guid) == "47183823-2574-4bfd-b411-99ed177d3e43"); - } - - { - auto guid = uuid::from_string("47183823-2574-4bfd-b411-99ed177d3e43"sv).value(); - REQUIRE(to_string(guid) == "47183823-2574-4bfd-b411-99ed177d3e43"); - REQUIRE(to_string(guid) == L"47183823-2574-4bfd-b411-99ed177d3e43"); - } - - { - auto str = L"47183823-2574-4bfd-b411-99ed177d3e43"sv; - auto guid = uuid::from_string(str).value(); - REQUIRE(to_string(guid) == str); - } - - { - auto str = "4718382325744bfdb41199ed177d3e43"sv; - REQUIRE_NOTHROW(uuid::from_string(str)); - REQUIRE(uuid::from_string(str).has_value()); - } - - { - auto str = "00000000-0000-0000-0000-000000000000"sv; - auto guid = uuid::from_string(str).value(); - REQUIRE(guid.is_nil()); - } - - { - auto str = "{00000000-0000-0000-0000-000000000000}"sv; - auto guid = uuid::from_string(str).value(); - REQUIRE(guid.is_nil()); - } - - { - auto str = L"00000000-0000-0000-0000-000000000000"sv; - auto guid = uuid::from_string(str).value(); - REQUIRE(guid.is_nil()); - } - - { - auto str = L"{00000000-0000-0000-0000-000000000000}"sv; - auto guid = uuid::from_string(str).value(); - REQUIRE(guid.is_nil()); - } -} - -TEST_CASE("uuid: Test constexpr from_string", "[uuid]") -{ - constexpr uuid value = uuid::from_string("47183823-2574-4bfd-b411-99ed177d3e43").value(); - static_assert(!value.is_nil()); - static_assert(value.variant() == uuid_variant::rfc); - static_assert(value.version() != uuid_version::none); -} - -TEST_CASE("uuid: Test from_string(char*) invalid format", "[uuid]") -{ - REQUIRE(!uuid::from_string("").has_value()); - REQUIRE(!uuid::from_string("{}").has_value()); - REQUIRE(!uuid::from_string("47183823-2574-4bfd-b411-99ed177d3e4").has_value()); - REQUIRE(!uuid::from_string("47183823-2574-4bfd-b411-99ed177d3e430").has_value()); - REQUIRE(!uuid::from_string("{47183823-2574-4bfd-b411-99ed177d3e43").has_value()); - REQUIRE(!uuid::from_string("47183823-2574-4bfd-b411-99ed177d3e43}").has_value()); -} - -TEST_CASE("uuid: Test from_string(basic_string) invalid format", "[uuid]") -{ - using namespace std::string_literals; - - { - auto str = ""s; - REQUIRE(!uuid::from_string(str).has_value()); - } - - { - auto str = "{}"s; - REQUIRE(!uuid::from_string(str).has_value()); - } - - { - auto str = "47183823-2574-4bfd-b411-99ed177d3e4"s; - REQUIRE(!uuid::from_string(str).has_value()); - } - - { - auto str = "47183823-2574-4bfd-b411-99ed177d3e430"s; - REQUIRE(!uuid::from_string(str).has_value()); - } - - { - auto str = "{47183823-2574-4bfd-b411-99ed177d3e43"s; - REQUIRE(!uuid::from_string(str).has_value()); - } - - { - auto str = "47183823-2574-4bfd-b411-99ed177d3e43}"s; - REQUIRE(!uuid::from_string(str).has_value()); - } -} - -TEST_CASE("uuid: Test from_string(basic_string_view) invalid format", "[uuid]") -{ - using namespace std::string_view_literals; - - REQUIRE(!uuid::from_string(""sv).has_value()); - REQUIRE(!uuid::from_string("{}"sv).has_value()); - REQUIRE(!uuid::from_string("47183823-2574-4bfd-b411-99ed177d3e4"sv).has_value()); - REQUIRE(!uuid::from_string("47183823-2574-4bfd-b411-99ed177d3e430"sv).has_value()); - REQUIRE(!uuid::from_string("{47183823-2574-4bfd-b411-99ed177d3e43"sv).has_value()); - REQUIRE(!uuid::from_string("47183823-2574-4bfd-b411-99ed177d3e43}"sv).has_value()); -} - -TEST_CASE("uuid: Test iterators constructor", "[uuid]") -{ - using namespace std::string_literals; - - { - std::array arr{ - {0x47, 0x18, 0x38, 0x23, 0x25, 0x74, 0x4b, 0xfd, 0xb4, 0x11, 0x99, 0xed, - 0x17, 0x7d, 0x3e, 0x43} - }; - - auto const guid = uuid(std::begin(arr), std::end(arr)); - REQUIRE(to_string(guid) == "47183823-2574-4bfd-b411-99ed177d3e43"s); - } - - { - uuid::value_type arr[16] = {// NOLINT - 0x47, 0x18, 0x38, 0x23, 0x25, 0x74, 0x4b, 0xfd, - 0xb4, 0x11, 0x99, 0xed, 0x17, 0x7d, 0x3e, 0x43}; - - auto const guid = uuid(std::begin(arr), std::end(arr)); - REQUIRE(to_string(guid) == "47183823-2574-4bfd-b411-99ed177d3e43"s); - } -} - -TEST_CASE("uuid: Test array constructors", "[uuid]") -{ - using namespace std::string_literals; - - { - auto const guid = uuid { - {0x47, 0x18, 0x38, 0x23, 0x25, 0x74, 0x4b, 0xfd, 0xb4, 0x11, 0x99, 0xed, - 0x17, 0x7d, 0x3e, 0x43} - }; - - REQUIRE(to_string(guid) == "47183823-2574-4bfd-b411-99ed177d3e43"s); - } - - { - auto arr = std::array{ - {0x47, 0x18, 0x38, 0x23, 0x25, 0x74, 0x4b, 0xfd, 0xb4, 0x11, 0x99, 0xed, - 0x17, 0x7d, 0x3e, 0x43} - }; - - auto const guid = uuid(arr); - REQUIRE(to_string(guid) == "47183823-2574-4bfd-b411-99ed177d3e43"s); - } - - { - uuid::value_type arr[16]{// NOLINT - 0x47, 0x18, 0x38, 0x23, 0x25, 0x74, 0x4b, 0xfd, - 0xb4, 0x11, 0x99, 0xed, 0x17, 0x7d, 0x3e, 0x43}; - - auto const guid = uuid(arr); - REQUIRE(to_string(guid) == "47183823-2574-4bfd-b411-99ed177d3e43"s); - } -} - -TEST_CASE("uuid: Test equality", "[uuid]") -{ - uuid empty; - - auto engine = uuid_random_generator::engine_type{}; - seed_rng(engine); - uuid guid = uuid_random_generator{engine}(); - - REQUIRE(empty == empty); - REQUIRE(guid == guid); - REQUIRE(empty != guid); -} - -TEST_CASE("Test comparison", "[uuid]") -{ - auto empty = uuid{}; - - auto engine = uuid_random_generator::engine_type{}; - seed_rng(engine); - - auto gen = uuid_random_generator{engine}; - auto id = gen(); - - REQUIRE(empty < id); - - auto ids = std::set{uuid{}, gen(), gen(), gen(), gen()}; - - REQUIRE(ids.size() == 5); - REQUIRE(ids.contains(uuid{}) == true); -} - -TEST_CASE("uuid: Test hashing", "[uuid]") -{ - using namespace std::string_literals; - - auto str = "47183823-2574-4bfd-b411-99ed177d3e43"s; - auto guid = uuid::from_string(str).value(); - - auto h1 = std::hash{}; - auto h2 = std::hash{}; - REQUIRE(h1(str) != h2(guid)); - - auto engine = uuid_random_generator::engine_type{}; - seed_rng(engine); - uuid_random_generator gen{engine}; - - std::unordered_set ids{uuid{}, gen(), gen(), gen(), gen()}; - - REQUIRE(ids.size() == 5); - REQUIRE(ids.find(uuid{}) != ids.end()); -} - -TEST_CASE("uuid: Test swap", "[uuid]") -{ - uuid empty; - - auto engine = uuid_random_generator::engine_type{}; - seed_rng(engine); - uuid guid = uuid_random_generator{engine}(); - - REQUIRE(empty.is_nil()); - REQUIRE(!guid.is_nil()); - - std::swap(empty, guid); - - REQUIRE(!empty.is_nil()); - REQUIRE(guid.is_nil()); - - empty.swap(guid); - - REQUIRE(empty.is_nil()); - REQUIRE(!guid.is_nil()); -} - -TEST_CASE("uuid: Test constexpr", "[uuid]") -{ - constexpr uuid empty; - static_assert(empty.is_nil()); - static_assert(empty.variant() == uuid_variant::ncs); - static_assert(empty.version() == uuid_version::none); -} - -TEST_CASE("uuid: Test size", "[uuid]") -{ - REQUIRE(sizeof(uuid) == 16); -} - -TEST_CASE("uuid: Test assignment", "[uuid]") -{ - auto id1 = uuid::from_string("47183823-2574-4bfd-b411-99ed177d3e43").value(); - auto id2 = id1; - REQUIRE(id1 == id2); - - id1 = uuid::from_string("{fea43102-064f-4444-adc2-02cec42623f8}").value(); - REQUIRE(id1 != id2); - - auto id3 = std::move(id2); - REQUIRE(to_string(id3) == "47183823-2574-4bfd-b411-99ed177d3e43"); -} - -TEST_CASE("uuid: Test trivial", "[uuid]") -{ - REQUIRE(std::is_trivially_copyable_v); -} - -TEST_CASE("uuid: Test as_bytes", "[uuid]") -{ - std::array arr{ - {0x47, 0x18, 0x38, 0x23, 0x25, 0x74, 0x4b, 0xfd, 0xb4, 0x11, 0x99, 0xed, 0x17, 0x7d, - 0x3e, 0x43} - }; - - { - uuid id{arr}; - REQUIRE(!id.is_nil()); - - auto view = id.as_bytes(); - REQUIRE(memcmp(view.data(), arr.data(), arr.size()) == 0); - } - - { - const uuid id{arr}; - REQUIRE(!id.is_nil()); - - auto view = id.as_bytes(); - REQUIRE(memcmp(view.data(), arr.data(), arr.size()) == 0); - } -} -} // anonymous namespace - -// NOLINTEND(bugprone-unchecked-optional-access) -- cgit v1.2.3