From 2e2d1bd3b6c7776b77c33b94f30ead89367a71e6 Mon Sep 17 00:00:00 2001 From: Lexi Winter Date: Tue, 1 Jul 2025 17:07:04 +0100 Subject: add nihil.std --- nihil.util/CMakeLists.txt | 20 +- nihil.util/capture_stream.ccm | 19 +- nihil.util/capture_stream.test.cc | 44 +++++ nihil.util/ctype.ccm | 73 +++----- nihil.util/ctype.test.cc | 376 ++++++++++++++++++++++++++++++++++++++ nihil.util/next_word.ccm | 37 +--- nihil.util/next_word.test.cc | 65 +++++++ nihil.util/nihil.util.ccm | 8 +- nihil.util/parse_size.ccm | 75 +++----- nihil.util/parse_size.test.cc | 165 +++++++++++++++++ nihil.util/save_errno.ccm | 35 ++++ nihil.util/skipws.ccm | 29 +-- nihil.util/skipws.test.cc | 46 +++++ nihil.util/tabulate.ccm | 55 ++---- nihil.util/tabulate.test.cc | 70 +++++++ nihil.util/test_capture_stream.cc | 44 ----- nihil.util/test_ctype.cc | 373 ------------------------------------- nihil.util/test_next_word.cc | 65 ------- nihil.util/test_parse_size.cc | 169 ----------------- nihil.util/test_skipws.cc | 45 ----- nihil.util/test_tabulate.cc | 75 -------- 21 files changed, 917 insertions(+), 971 deletions(-) create mode 100644 nihil.util/capture_stream.test.cc create mode 100644 nihil.util/ctype.test.cc create mode 100644 nihil.util/next_word.test.cc create mode 100644 nihil.util/parse_size.test.cc create mode 100644 nihil.util/save_errno.ccm create mode 100644 nihil.util/skipws.test.cc create mode 100644 nihil.util/tabulate.test.cc delete mode 100644 nihil.util/test_capture_stream.cc delete mode 100644 nihil.util/test_ctype.cc delete mode 100644 nihil.util/test_next_word.cc delete mode 100644 nihil.util/test_parse_size.cc delete mode 100644 nihil.util/test_skipws.cc delete mode 100644 nihil.util/test_tabulate.cc (limited to 'nihil.util') diff --git a/nihil.util/CMakeLists.txt b/nihil.util/CMakeLists.txt index 2ef916e..109e4d4 100644 --- a/nihil.util/CMakeLists.txt +++ b/nihil.util/CMakeLists.txt @@ -1,7 +1,12 @@ # This source code is released into the public domain. add_library(nihil.util STATIC) -target_link_libraries(nihil.util PRIVATE nihil.core nihil.error nihil.monad) +target_link_libraries(nihil.util PRIVATE + nihil.std + nihil.core + nihil.error + nihil.monad +) target_sources(nihil.util PUBLIC FILE_SET modules TYPE CXX_MODULES FILES nihil.util.ccm @@ -10,6 +15,7 @@ target_sources(nihil.util ctype.ccm parse_size.ccm next_word.ccm + save_errno.ccm skipws.ccm tabulate.ccm ) @@ -18,12 +24,12 @@ if(NIHIL_TESTS) enable_testing() add_executable(nihil.util.test - test_capture_stream.cc - test_ctype.cc - test_parse_size.cc - test_next_word.cc - test_skipws.cc - test_tabulate.cc + capture_stream.test.cc + ctype.test.cc + parse_size.test.cc + next_word.test.cc + skipws.test.cc + tabulate.test.cc ) target_link_libraries(nihil.util.test PRIVATE nihil.util diff --git a/nihil.util/capture_stream.ccm b/nihil.util/capture_stream.ccm index 7ec39a9..f061558 100644 --- a/nihil.util/capture_stream.ccm +++ b/nihil.util/capture_stream.ccm @@ -1,20 +1,13 @@ -/* - * This source code is released into the public domain. - */ - -module; - -#include - +// 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). - */ +// 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) diff --git a/nihil.util/capture_stream.test.cc b/nihil.util/capture_stream.test.cc new file mode 100644 index 0000000..a4821b7 --- /dev/null +++ b/nihil.util/capture_stream.test.cc @@ -0,0 +1,44 @@ +// 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/ctype.ccm b/nihil.util/ctype.ccm index 6d30c4f..8f5de27 100644 --- a/nihil.util/ctype.ccm +++ b/nihil.util/ctype.ccm @@ -1,14 +1,8 @@ -/* - * This source code is released into the public domain. - */ - -module; - -#include -#include - +// This source code is released into the public domain. export module nihil.util:ctype; +import nihil.std; + namespace nihil { /* @@ -21,15 +15,16 @@ namespace nihil { * ctype_is copies the locale, so passing a temporary is fine. */ -export struct ctype_is final { - ctype_is(std::ctype_base::mask mask_, - std::locale const &locale_ = std::locale()) - : m_mask(mask_) - , m_locale(locale_) - {} +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) + [[nodiscard]] auto operator()(this ctype_is const &self, std::integral auto c) { using ctype = std::ctype; auto &facet = std::use_facet(self.m_locale); @@ -37,11 +32,11 @@ export struct ctype_is final { } private: - std::ctype_base::mask m_mask; - std::locale m_locale; + std::ctype_base::mask m_mask; + std::locale m_locale; }; -// Predefined tests for the current global 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); @@ -59,29 +54,19 @@ 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. -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()); +//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 new file mode 100644 index 0000000..d000b45 --- /dev/null +++ b/nihil.util/ctype.test.cc @@ -0,0 +1,376 @@ +// 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/next_word.ccm b/nihil.util/next_word.ccm index c5d3ad7..89eeaee 100644 --- a/nihil.util/next_word.ccm +++ b/nihil.util/next_word.ccm @@ -1,44 +1,27 @@ -/* - * This source code is released into the public domain. - */ - -module; - -#include -#include -#include -#include -#include - +// 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> +// 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)}}; + 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()) +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); diff --git a/nihil.util/next_word.test.cc b/nihil.util/next_word.test.cc new file mode 100644 index 0000000..87d491a --- /dev/null +++ b/nihil.util/next_word.test.cc @@ -0,0 +1,65 @@ +// 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 index 89510c9..d8628a4 100644 --- a/nihil.util/nihil.util.ccm +++ b/nihil.util/nihil.util.ccm @@ -1,14 +1,10 @@ -/* - * This source code is released into the public domain. - */ - -module; - +// This source code is released into the public domain. export module nihil.util; export import :capture_stream; export import :ctype; export import :parse_size; export import :next_word; +export import :save_errno; export import :skipws; export import :tabulate; diff --git a/nihil.util/parse_size.ccm b/nihil.util/parse_size.ccm index c95ac50..7fc3fa4 100644 --- a/nihil.util/parse_size.ccm +++ b/nihil.util/parse_size.ccm @@ -1,20 +1,7 @@ -/* - * This source code is released into the public domain. - */ - -module; - -#include -#include -#include -#include -#include -#include -#include -#include - +// This source code is released into the public domain. export module nihil.util:parse_size; +import nihil.std; import nihil.core; import nihil.error; import nihil.monad; @@ -23,53 +10,50 @@ import :ctype; namespace nihil { -template +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 + 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 std::unexpected(error(errc::invalid_unit)); + 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 +// 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); + auto num_str = std::basic_string_view(std::ranges::begin(str), it); if (num_str.empty()) - co_return std::unexpected(error(errc::empty_string)); + co_return error(errc::empty_string); auto ret = T{0}; for (auto c : num_str) { if (ret > (std::numeric_limits::max() / 10)) - co_return std::unexpected(error( - std::errc::result_out_of_range)); + 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 std::unexpected(error( - std::errc::result_out_of_range)); + co_return error(std::errc::result_out_of_range); ret += digit; } @@ -81,27 +65,26 @@ auto parse_size(std::basic_string_view str) if (it != str.end()) // Multiplier is more than one character. - co_return std::unexpected(error(errc::invalid_unit)); + 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 std::unexpected(error( - std::errc::result_out_of_range)); + co_return error(std::errc::result_out_of_range); - co_return ret * mult; + co_return ret *mult; } -export template -[[nodiscard]] inline auto parse_size(char const *s) +export template +[[nodiscard]] auto parse_size(char const *s) { return parse_size(std::string_view(s)); } -export template -[[nodiscard]] inline auto parse_size(wchar_t const *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 new file mode 100644 index 0000000..d79912a --- /dev/null +++ b/nihil.util/parse_size.test.cc @@ -0,0 +1,165 @@ +// This source code is released into the public domain. + +#include + +import nihil.std; +import nihil.core; +import nihil.error; +import nihil.util; + +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); + } +} diff --git a/nihil.util/save_errno.ccm b/nihil.util/save_errno.ccm new file mode 100644 index 0000000..27567f8 --- /dev/null +++ b/nihil.util/save_errno.ccm @@ -0,0 +1,35 @@ +// 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 index 4813ae8..0a15775 100644 --- a/nihil.util/skipws.ccm +++ b/nihil.util/skipws.ccm @@ -1,27 +1,14 @@ -/* - * This source code is released into the public domain. - */ - -module; - -#include -#include -#include -#include - +// 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()) +// 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); @@ -29,10 +16,8 @@ auto skipws(std::basic_string_view text, return {nonws, std::ranges::end(text)}; } -export template -auto skipws(std::basic_string_view *text, - std::locale const &locale = std::locale()) - -> void +export template +auto skipws(std::basic_string_view *text, std::locale const &locale = std::locale()) -> void { *text = skipws(*text, locale); } diff --git a/nihil.util/skipws.test.cc b/nihil.util/skipws.test.cc new file mode 100644 index 0000000..0cb741c --- /dev/null +++ b/nihil.util/skipws.test.cc @@ -0,0 +1,46 @@ +// 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/tabulate.ccm b/nihil.util/tabulate.ccm index 5998b24..8f5c22e 100644 --- a/nihil.util/tabulate.ccm +++ b/nihil.util/tabulate.ccm @@ -1,53 +1,38 @@ -/* - * This source code is released into the public domain. - */ - -module; - -#include -#include -#include -#include -#include -#include - +// This source code is released into the public domain. export module nihil.util:tabulate; +import nihil.std; import nihil.error; import :ctype; 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 - */ +// 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 { - table_spec_error(std::string_view what) + explicit table_spec_error(std::string_view what) : error(what) { } }; -/* - * The specification for a single field. - */ +// The specification for a single field. template struct field_spec { enum align_t { left, right }; diff --git a/nihil.util/tabulate.test.cc b/nihil.util/tabulate.test.cc new file mode 100644 index 0000000..408cc18 --- /dev/null +++ b/nihil.util/tabulate.test.cc @@ -0,0 +1,70 @@ +// 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/test_capture_stream.cc b/nihil.util/test_capture_stream.cc deleted file mode 100644 index 27c8596..0000000 --- a/nihil.util/test_capture_stream.cc +++ /dev/null @@ -1,44 +0,0 @@ -/* - * This source code is released into the public domain. - */ - -#include - -#include - -import nihil.util; - -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"); - } -} diff --git a/nihil.util/test_ctype.cc b/nihil.util/test_ctype.cc deleted file mode 100644 index 62721d1..0000000 --- a/nihil.util/test_ctype.cc +++ /dev/null @@ -1,373 +0,0 @@ -/* - * This source code is released into the public domain. - */ - -#include - -import nihil.util; - -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); -} diff --git a/nihil.util/test_next_word.cc b/nihil.util/test_next_word.cc deleted file mode 100644 index 7e61237..0000000 --- a/nihil.util/test_next_word.cc +++ /dev/null @@ -1,65 +0,0 @@ -/* - * This source code is released into the public domain. - */ - -#include -#include - -#include - -import nihil.util; - -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"); -} diff --git a/nihil.util/test_parse_size.cc b/nihil.util/test_parse_size.cc deleted file mode 100644 index 692039b..0000000 --- a/nihil.util/test_parse_size.cc +++ /dev/null @@ -1,169 +0,0 @@ -/* - * This source code is released into the public domain. - */ - -#include -#include - -#include - -import nihil.core; -import nihil.error; -import nihil.util; - -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); - } -} diff --git a/nihil.util/test_skipws.cc b/nihil.util/test_skipws.cc deleted file mode 100644 index 837c1f3..0000000 --- a/nihil.util/test_skipws.cc +++ /dev/null @@ -1,45 +0,0 @@ -/* - * This source code is released into the public domain. - */ - -#include -#include -using namespace std::literals; - -#include - -import nihil.util; - -TEST_CASE("skipws: basic", "[skipws]") -{ - 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]") -{ - 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]") -{ - // 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/test_tabulate.cc b/nihil.util/test_tabulate.cc deleted file mode 100644 index 8dee796..0000000 --- a/nihil.util/test_tabulate.cc +++ /dev/null @@ -1,75 +0,0 @@ -/* - * This source code is released into the public domain. - */ - -#include -#include -#include - -#include - -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"); -} -- cgit v1.2.3