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 --- 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 -------------------------------------- 29 files changed, 4574 deletions(-) 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 (limited to 'nihil.util') 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