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 --- .clang-format | 11 + .clang-tidy | 1 + CMakeLists.txt | 4 + nihil.cli/CMakeLists.txt | 2 +- nihil.cli/command.cc | 12 +- nihil.cli/command.ccm | 6 +- nihil.cli/command_node.cc | 19 +- nihil.cli/command_node.ccm | 17 +- nihil.cli/command_tree.cc | 22 +- nihil.cli/command_tree.ccm | 14 +- nihil.cli/dispatch_command.cc | 20 +- nihil.cli/dispatch_command.ccm | 31 +- nihil.cli/registry.cc | 30 +- nihil.cli/registry.ccm | 22 +- nihil.cli/test.cc | 24 +- nihil.cli/usage_error.ccm | 16 +- nihil.config/CMakeLists.txt | 17 +- nihil.config/nihil.config.ccm | 7 +- nihil.config/option.cc | 20 +- nihil.config/option.ccm | 37 +-- nihil.config/read.cc | 50 --- nihil.config/read.ccm | 47 ++- nihil.config/store.cc | 14 +- nihil.config/store.ccm | 27 +- nihil.config/string.cc | 62 ---- nihil.config/string.ccm | 95 +++--- nihil.config/string.test.cc | 32 ++ nihil.config/tests/CMakeLists.txt | 13 - nihil.config/tests/string.cc | 36 --- nihil.config/write.cc | 41 --- nihil.config/write.ccm | 42 ++- nihil.core/CMakeLists.txt | 2 + nihil.core/errc.cc | 16 +- nihil.core/errc.ccm | 41 ++- nihil.core/features.ccm | 13 + nihil.core/nihil.core.ccm | 8 +- nihil.core/nihil.hh | 21 +- nihil.error/CMakeLists.txt | 6 +- nihil.error/error.cc | 160 ---------- nihil.error/error.ccm | 361 ++++++++++++++-------- nihil.error/sys_error.ccm | 18 ++ nihil.error/test.cc | 35 +-- nihil.flagset/CMakeLists.txt | 1 + nihil.flagset/flagset.ccm | 17 +- nihil.flagset/test.cc | 41 ++- nihil.generator/CMakeLists.txt | 1 + nihil.generator/byte_allocator.ccm | 7 +- nihil.generator/coroutine_traits.ccm | 6 +- nihil.generator/elements_of.ccm | 5 +- nihil.generator/forward.ccm | 5 +- nihil.generator/generator.ccm | 7 +- nihil.generator/generator.test.cc | 9 +- nihil.generator/generator_promise.ccm | 6 +- nihil.generator/generator_promise_base.ccm | 7 +- nihil.generator/manual_lifetime.ccm | 7 +- nihil.generator/nihil.generator.ccm | 6 +- nihil.generator/promise_base_alloc.ccm | 6 +- nihil.generator/util.ccm | 7 +- nihil.guard/CMakeLists.txt | 1 + nihil.guard/guard.ccm | 9 +- nihil.guard/test.cc | 6 +- nihil.match/CMakeLists.txt | 1 + nihil.match/match.ccm | 11 +- nihil.match/test.cc | 8 +- nihil.monad/CMakeLists.txt | 2 +- nihil.monad/monad.ccm | 13 +- nihil.monad/test.cc | 9 +- nihil.posix/CMakeLists.txt | 14 +- nihil.posix/argv.ccm | 14 +- nihil.posix/argv.test.cc | 10 +- nihil.posix/ensure_dir.ccm | 12 +- nihil.posix/execl.ccm | 33 +- nihil.posix/execl.test.cc | 5 +- nihil.posix/execlp.ccm | 10 +- nihil.posix/execlp.test.cc | 5 +- nihil.posix/execshell.ccm | 5 +- nihil.posix/execshell.test.cc | 5 +- nihil.posix/executor.ccm | 17 +- nihil.posix/execv.ccm | 43 ++- nihil.posix/execv.test.cc | 5 +- nihil.posix/execvp.ccm | 23 +- nihil.posix/execvp.test.cc | 1 + nihil.posix/fd.ccm | 31 +- nihil.posix/fd.test.cc | 10 +- nihil.posix/fexecv.ccm | 58 ---- nihil.posix/fexecvp.ccm | 37 --- nihil.posix/find_in_path.ccm | 14 +- nihil.posix/getenv.ccm | 22 +- nihil.posix/getenv.test.cc | 12 +- nihil.posix/open.ccm | 10 +- nihil.posix/open.test.cc | 1 + nihil.posix/open_in_path.ccm | 14 +- nihil.posix/open_in_path.test.cc | 1 + nihil.posix/paths.ccm | 27 ++ nihil.posix/posix.ccm | 4 +- nihil.posix/process.ccm | 15 +- nihil.posix/read_file.ccm | 27 +- nihil.posix/rename.ccm | 16 +- nihil.posix/spawn.ccm | 128 +++----- nihil.posix/stat.ccm | 13 +- nihil.posix/stat.test.cc | 1 + nihil.posix/tempfile.ccm | 22 +- nihil.posix/tempfile.test.cc | 7 +- nihil.posix/unistd.ccm | 23 ++ nihil.posix/unlink.ccm | 28 ++ nihil.posix/write_file.ccm | 43 +-- nihil.std/CMakeLists.txt | 7 + nihil.std/nihil.std.ccm | 429 ++++++++++++++++++++++++++ nihil.ucl/CMakeLists.txt | 34 +- nihil.ucl/array.ccm | 424 +++++++++++-------------- nihil.ucl/array.test.cc | 479 +++++++++++++++++++++++++++++ nihil.ucl/boolean.cc | 106 ------- nihil.ucl/boolean.ccm | 138 +++++---- nihil.ucl/boolean.test.cc | 241 +++++++++++++++ nihil.ucl/emit.cc | 21 -- nihil.ucl/emit.ccm | 179 +++++------ nihil.ucl/emit.test.cc | 92 ++++++ nihil.ucl/errc.cc | 49 --- nihil.ucl/errc.ccm | 33 -- nihil.ucl/integer.cc | 102 ------ nihil.ucl/integer.ccm | 165 +++++----- nihil.ucl/integer.test.cc | 244 +++++++++++++++ nihil.ucl/map.ccm | 263 +++++++--------- nihil.ucl/map.test.cc | 192 ++++++++++++ nihil.ucl/nihil.ucl.ccm | 8 +- nihil.ucl/object.cc | 114 ------- nihil.ucl/object.ccm | 182 ++++++++--- nihil.ucl/object.test.cc | 43 +++ nihil.ucl/object_cast.ccm | 19 +- nihil.ucl/parse.test.cc | 55 ++++ nihil.ucl/parser.cc | 102 ------ nihil.ucl/parser.ccm | 158 ++++++---- nihil.ucl/real.cc | 104 ------- nihil.ucl/real.ccm | 151 +++++---- nihil.ucl/real.test.cc | 245 +++++++++++++++ nihil.ucl/string.cc | 187 ----------- nihil.ucl/string.ccm | 357 ++++++++++++--------- nihil.ucl/string.test.cc | 407 ++++++++++++++++++++++++ nihil.ucl/tests/CMakeLists.txt | 20 -- nihil.ucl/tests/array.cc | 478 ---------------------------- nihil.ucl/tests/boolean.cc | 224 -------------- nihil.ucl/tests/emit.cc | 93 ------ nihil.ucl/tests/integer.cc | 247 --------------- nihil.ucl/tests/map.cc | 192 ------------ nihil.ucl/tests/object.cc | 44 --- nihil.ucl/tests/parse.cc | 55 ---- nihil.ucl/tests/real.cc | 248 --------------- nihil.ucl/tests/string.cc | 415 ------------------------- nihil.ucl/type.cc | 62 ---- nihil.ucl/type.ccm | 87 ++++-- 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 ----- nihil.uuid/CMakeLists.txt | 1 + nihil.uuid/test.cc | 334 ++++++++------------ nihil.uuid/uuid.ccm | 446 +++++++++++---------------- 174 files changed, 5797 insertions(+), 6803 deletions(-) delete mode 100644 nihil.config/read.cc delete mode 100644 nihil.config/string.cc create mode 100644 nihil.config/string.test.cc delete mode 100644 nihil.config/tests/CMakeLists.txt delete mode 100644 nihil.config/tests/string.cc delete mode 100644 nihil.config/write.cc create mode 100644 nihil.core/features.ccm delete mode 100644 nihil.error/error.cc create mode 100644 nihil.error/sys_error.ccm delete mode 100644 nihil.posix/fexecv.ccm delete mode 100644 nihil.posix/fexecvp.ccm create mode 100644 nihil.posix/paths.ccm create mode 100644 nihil.posix/unistd.ccm create mode 100644 nihil.posix/unlink.ccm create mode 100644 nihil.std/CMakeLists.txt create mode 100644 nihil.std/nihil.std.ccm create mode 100644 nihil.ucl/array.test.cc delete mode 100644 nihil.ucl/boolean.cc create mode 100644 nihil.ucl/boolean.test.cc delete mode 100644 nihil.ucl/emit.cc create mode 100644 nihil.ucl/emit.test.cc delete mode 100644 nihil.ucl/errc.cc delete mode 100644 nihil.ucl/errc.ccm delete mode 100644 nihil.ucl/integer.cc create mode 100644 nihil.ucl/integer.test.cc create mode 100644 nihil.ucl/map.test.cc delete mode 100644 nihil.ucl/object.cc create mode 100644 nihil.ucl/object.test.cc create mode 100644 nihil.ucl/parse.test.cc delete mode 100644 nihil.ucl/parser.cc delete mode 100644 nihil.ucl/real.cc create mode 100644 nihil.ucl/real.test.cc delete mode 100644 nihil.ucl/string.cc create mode 100644 nihil.ucl/string.test.cc delete mode 100644 nihil.ucl/tests/CMakeLists.txt delete mode 100644 nihil.ucl/tests/array.cc delete mode 100644 nihil.ucl/tests/boolean.cc delete mode 100644 nihil.ucl/tests/emit.cc delete mode 100644 nihil.ucl/tests/integer.cc delete mode 100644 nihil.ucl/tests/map.cc delete mode 100644 nihil.ucl/tests/object.cc delete mode 100644 nihil.ucl/tests/parse.cc delete mode 100644 nihil.ucl/tests/real.cc delete mode 100644 nihil.ucl/tests/string.cc delete mode 100644 nihil.ucl/type.cc 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 diff --git a/.clang-format b/.clang-format index ff932d0..b1f8727 100644 --- a/.clang-format +++ b/.clang-format @@ -23,6 +23,7 @@ PenaltyReturnTypeOnItsOwnLine: 0 BreakTemplateDeclarations: Yes ForEachMacros: + - SECTION - GIVEN - AND_GIVEN - THEN @@ -51,3 +52,13 @@ BraceWrapping: NamespaceIndentation: None FixNamespaceComments: true #WrapNamespaceBodyWithEmptyLines: Always + +AlignArrayOfStructures: Left +AlignConsecutiveBitFields: true +AlignConsecutiveDeclarations: Consecutive +AlignConsecutiveMacros: Consecutive +AlignConsecutiveShortCaseStatements: + Enabled: true +AlignEscapedNewlines: LeftWithLastLine +AlignOperands: AlignAfterOperator +BreakBeforeBinaryOperators: None diff --git a/.clang-tidy b/.clang-tidy index 76636f6..c8d47b0 100644 --- a/.clang-tidy +++ b/.clang-tidy @@ -7,6 +7,7 @@ Checks: > -bugprone-reserved-identifier, -bugprone-easily-swappable-parameters, cert-*, + -cert-dcl58-cpp, concurrency-*, cppcoreguidelines-*, -cppcoreguidelines-avoid-do-while, diff --git a/CMakeLists.txt b/CMakeLists.txt index cf42b4b..32a8f81 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -41,11 +41,15 @@ add_compile_options(-Wextra) add_compile_options(-Werror) add_compile_options(-Wpedantic) +# Enable libc++ hardening +add_compile_definitions(-D_LIBCPP_HARDENING_MODE=_LIBCPP_HARDENING_MODE_EXTENSIVE) + if(NIHIL_TESTS) add_subdirectory(contrib/catch2) enable_testing() endif() +add_subdirectory(nihil.std) add_subdirectory(nihil.cli) add_subdirectory(nihil.core) add_subdirectory(nihil.error) diff --git a/nihil.cli/CMakeLists.txt b/nihil.cli/CMakeLists.txt index 78e9ff1..9f20cf7 100644 --- a/nihil.cli/CMakeLists.txt +++ b/nihil.cli/CMakeLists.txt @@ -1,7 +1,7 @@ # This source code is released into the public domain. add_library(nihil.cli STATIC) -target_link_libraries(nihil.cli PRIVATE nihil.util) +target_link_libraries(nihil.cli PRIVATE nihil.std nihil.util) target_sources(nihil.cli PUBLIC FILE_SET modules TYPE CXX_MODULES FILES nihil.cli.ccm diff --git a/nihil.cli/command.cc b/nihil.cli/command.cc index 725b4eb..6271cc6 100644 --- a/nihil.cli/command.cc +++ b/nihil.cli/command.cc @@ -1,15 +1,6 @@ -/* - * This source code is released into the public domain. - */ - +// This source code is released into the public domain. module; -#include -#include -#include -#include -#include - // For EX_USAGE. While is deprecated, there's no other standard // exit code for 'usage error'; some programs use 2 (common on Linux), but // 2 is also used for many other exit codes. @@ -17,6 +8,7 @@ module; module nihil.cli; +import nihil.std; import nihil.error; import :registry; diff --git a/nihil.cli/command.ccm b/nihil.cli/command.ccm index 74ef030..da3444a 100644 --- a/nihil.cli/command.ccm +++ b/nihil.cli/command.ccm @@ -4,13 +4,10 @@ module; -#include -#include -#include - export module nihil.cli:command; import nihil.error; +import nihil.std; import :command_node; namespace nihil { @@ -40,7 +37,6 @@ export struct command final : command_node { -> std::expected override; private: - std::string_view m_path; std::string_view m_usage; command_function_t m_handler; }; diff --git a/nihil.cli/command_node.cc b/nihil.cli/command_node.cc index dd18716..5936b8c 100644 --- a/nihil.cli/command_node.cc +++ b/nihil.cli/command_node.cc @@ -1,18 +1,11 @@ -/* - * This source code is released into the public domain. - */ - +// This source code is released into the public domain. module; -#include -#include -#include -#include - -#include +#include // _exit module nihil.cli; +import nihil.std; import nihil.core; import nihil.error; @@ -23,14 +16,12 @@ command_node::command_node(std::string_view path) noexcept try : m_path(path) { } catch (std::exception const &exc) { - std::fprintf(stderr, "%s\n", exc.what()); + std::print(std::cerr, "%s\n", exc.what()); _exit(1); /*NOTREACHED*/ } -command_node::~command_node() -{ -} +command_node::~command_node() = default; auto command_node::path(this command_node const &self) noexcept -> std::string_view diff --git a/nihil.cli/command_node.ccm b/nihil.cli/command_node.ccm index 546eb46..25b5006 100644 --- a/nihil.cli/command_node.ccm +++ b/nihil.cli/command_node.ccm @@ -1,18 +1,9 @@ -/* - * This source code is released into the public domain. - */ - -module; - -/* - * command_node represents a possibly-invocable command. - */ - -#include -#include - +// This source code is released into the public domain. export module nihil.cli:command_node; +// command_node represents a possibly-invocable command. + +import nihil.std; import nihil.error; namespace nihil { diff --git a/nihil.cli/command_tree.cc b/nihil.cli/command_tree.cc index 2d14669..a77131a 100644 --- a/nihil.cli/command_tree.cc +++ b/nihil.cli/command_tree.cc @@ -1,18 +1,8 @@ -/* - * 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. module nihil.cli; +import nihil.std; + namespace nihil { command_tree_node::command_tree_node() @@ -64,7 +54,7 @@ auto command_tree_node::get_or_create_child(this command_tree_node &self, -> command_tree_node * { // Return the existing child, if there is one. - if (auto ptr = self.get_child(child); ptr != nullptr) + if (auto *ptr = self.get_child(child); ptr != nullptr) return ptr; // Insert a new child. @@ -135,11 +125,11 @@ auto command_tree::insert(this command_tree &self, auto command_tree::find(this command_tree const &self, int &argc, char **&argv) -> command_tree_node const * { - auto *this_node = &self.m_root_node; + auto const *this_node = &self.m_root_node; // Iterate until we don't find a child command, then return that node. while (argv[0] != nullptr) { - auto *next_node = this_node->get_child(argv[0]); + auto const *next_node = this_node->get_child(argv[0]); if (next_node == nullptr) return this_node; diff --git a/nihil.cli/command_tree.ccm b/nihil.cli/command_tree.ccm index 7297af7..84e4a0d 100644 --- a/nihil.cli/command_tree.ccm +++ b/nihil.cli/command_tree.ccm @@ -1,17 +1,7 @@ -/* - * 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.cli:command_tree; +import nihil.std; import :command; namespace nihil { diff --git a/nihil.cli/dispatch_command.cc b/nihil.cli/dispatch_command.cc index 736e16e..7b4cf39 100644 --- a/nihil.cli/dispatch_command.cc +++ b/nihil.cli/dispatch_command.cc @@ -1,22 +1,12 @@ -/* - * This source code is released into the public domain. - */ - +// This source code is released into the public domain. module; -#include -#include -#include -#include -#include -#include -#include -#include - -#include +#include // getprogname, NOLINT +#include // getopt module nihil.cli; +import nihil.std; import nihil.core; namespace nihil { @@ -45,7 +35,7 @@ auto dispatch_command(int argc, char **argv) -> int * Set the program name to the existing progname plus the full path * to the command being invoked; this makes error messages nicer. */ - auto *old_progname = ::getprogname(); + auto const *old_progname = ::getprogname(); { auto cprogname = std::format("{} {}", ::getprogname(), diff --git a/nihil.cli/dispatch_command.ccm b/nihil.cli/dispatch_command.ccm index 1ba55bb..de94714 100644 --- a/nihil.cli/dispatch_command.ccm +++ b/nihil.cli/dispatch_command.ccm @@ -1,31 +1,14 @@ -/* - * 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.cli:dispatch_command; -import nihil.util; -import :command; -import :usage_error; - namespace nihil { -/* - * Invoke a command (which must have been previously registered) using - * the provided argument vector. - * - * The caller should have already stripped the executable name from argv[0] - * so that the vector starts with the command name. This is implicitly - * done if main() uses getopt(). - */ +// Invoke a command (which must have been previously registered) using +// the provided argument vector. +// +// The caller should have already stripped the executable name from argv[0] +// so that the vector starts with the command name. This is implicitly +// done if main() uses getopt(). export [[nodiscard]] auto dispatch_command(int argc, char **argv) -> int; } // namespace nihil diff --git a/nihil.cli/registry.cc b/nihil.cli/registry.cc index e35078d..0f0041b 100644 --- a/nihil.cli/registry.cc +++ b/nihil.cli/registry.cc @@ -1,16 +1,8 @@ -/* - * This source code is released into the public domain. - */ - -module; - -#include -#include -#include -#include - +// This source code is released into the public domain. module nihil.cli; +import nihil.std; + namespace nihil { /* @@ -22,11 +14,11 @@ try { static auto commands = std::vector>(); return commands; } catch (std::exception const &exc) { - std::printf("%s\n", exc.what()); - std::exit(1); + std::println(std::cerr, "{}", exc.what()); + std::exit(1); // NOLINT } catch (...) { - std::printf("get_registered_commands(): unknown error\n"); - std::exit(1); + std::println(std::cerr, "get_registered_commands(): unknown error\n"); + std::exit(1); // NOLINT } /* @@ -39,11 +31,11 @@ try { auto &commands = get_registry(); commands.emplace_back(cmd, null_deleter); } catch (std::exception const &exc) { - std::printf("%s\n", exc.what()); - std::exit(1); + std::println(std::cerr, "{}", exc.what()); + std::exit(1); // NOLINT } catch (...) { - std::printf("get_registered_commands(): unknown error\n"); - std::exit(1); + std::println(std::cerr, "get_registered_commands(): unknown error\n"); + std::exit(1); // NOLINT } /* diff --git a/nihil.cli/registry.ccm b/nihil.cli/registry.ccm index 0b9754d..673f516 100644 --- a/nihil.cli/registry.ccm +++ b/nihil.cli/registry.ccm @@ -1,28 +1,18 @@ -/* - * This source code is released into the public domain. - */ - -module; - -#include -#include - +// This source code is released into the public domain. export module nihil.cli:registry; +import nihil.std; + namespace nihil { export struct command; export struct command_node; -/* - * Register a command. This is guaranteed not to throw; errors will print - * a diagnostic and exit. - */ +// Register a command. This is guaranteed not to throw; errors will print +// a diagnostic and exit. auto register_command(command *cmd) noexcept -> void; -/* - * Get previously registered commands. - */ +// Get previously registered commands. auto get_registered_commands() -> std::span>; } // namespace nihil diff --git a/nihil.cli/test.cc b/nihil.cli/test.cc index c82281e..61d164d 100644 --- a/nihil.cli/test.cc +++ b/nihil.cli/test.cc @@ -1,12 +1,8 @@ -/* - * This source code is released into the public domain. - */ - -#include -#include +// This source code is released into the public domain. #include +import nihil.std; import nihil.cli; import nihil.util; @@ -34,9 +30,9 @@ TEST_CASE("nihil.cli: dispatch_command: basic", "[nihil.cli]") auto args = std::vector{ "cmd", "sub1", nullptr }; - auto argv = const_cast(args.data()); + auto *argv = const_cast(args.data()); - int ret = nihil::dispatch_command( + auto const ret = nihil::dispatch_command( static_cast(args.size()) - 1, argv); REQUIRE(ret == 0); REQUIRE(cmd_sub1_called == true); @@ -47,9 +43,9 @@ TEST_CASE("nihil.cli: dispatch_command: basic", "[nihil.cli]") auto args = std::vector{ "cmd", "sub2", nullptr }; - auto argv = const_cast(args.data()); + auto *argv = const_cast(args.data()); - int ret = nihil::dispatch_command( + auto const ret = nihil::dispatch_command( static_cast(args.size()) - 1, argv); REQUIRE(ret == 0); REQUIRE(cmd_sub2_called == true); @@ -61,7 +57,7 @@ TEST_CASE("nihil.cli: dispatch_command: unknown command", "[nihil.cli]") auto args = std::vector{ "nocomd", "sub", nullptr }; - auto argv = const_cast(args.data()); + auto *argv = const_cast(args.data()); auto output = std::string(); auto ret = int{}; @@ -75,7 +71,7 @@ TEST_CASE("nihil.cli: dispatch_command: unknown command", "[nihil.cli]") REQUIRE(ret == 1); - auto *progname = ::getprogname(); + auto const *progname = ::getprogname(); REQUIRE(output == std::format("{}: usage:\n cmd\n", progname)); } @@ -84,7 +80,7 @@ TEST_CASE("nihil.cli: dispatch_command: incomplete command", "[nihil.cli]") auto args = std::vector{ "cmd", nullptr }; - auto argv = const_cast(args.data()); + auto *argv = const_cast(args.data()); auto output = std::string(); auto ret = int{}; @@ -98,7 +94,7 @@ TEST_CASE("nihil.cli: dispatch_command: incomplete command", "[nihil.cli]") REQUIRE(ret == 1); - auto *progname = ::getprogname(); + auto const *progname = ::getprogname(); REQUIRE(output == std::format("{}: usage:\n cmd sub1\n cmd sub2\n", progname)); } diff --git a/nihil.cli/usage_error.ccm b/nihil.cli/usage_error.ccm index 61feba7..7de178e 100644 --- a/nihil.cli/usage_error.ccm +++ b/nihil.cli/usage_error.ccm @@ -1,22 +1,14 @@ -/* - * This source code is released into the public domain. - */ - -module; - -#include - +// This source code is released into the public domain. export module nihil.cli:usage_error; +import nihil.std; import nihil.error; namespace nihil { -/* - * Exception thrown to indicate invalid command-line arguments. - */ +// Exception thrown to indicate invalid command-line arguments. export struct usage_error : error { - usage_error(std::string_view what) : error(what) {} + explicit usage_error(std::string_view what) : error(what) {} }; } // namespace nihil diff --git a/nihil.config/CMakeLists.txt b/nihil.config/CMakeLists.txt index 8a52d3c..6ed3651 100644 --- a/nihil.config/CMakeLists.txt +++ b/nihil.config/CMakeLists.txt @@ -2,6 +2,7 @@ add_library(nihil.config STATIC) target_link_libraries(nihil.config PRIVATE + nihil.std nihil.error nihil.generator nihil.posix @@ -19,13 +20,21 @@ target_sources(nihil.config PRIVATE option.cc - read.cc store.cc - string.cc - write.cc ) if(NIHIL_TESTS) - add_subdirectory(tests) + add_executable(nihil.config.test + string.test.cc + ) + + target_link_libraries(nihil.config.test PRIVATE + nihil.config + Catch2::Catch2WithMain) + + include(CTest) + include(Catch) + catch_discover_tests(nihil.config.test) + enable_testing() endif() diff --git a/nihil.config/nihil.config.ccm b/nihil.config/nihil.config.ccm index 8957305..8eedf22 100644 --- a/nihil.config/nihil.config.ccm +++ b/nihil.config/nihil.config.ccm @@ -1,9 +1,4 @@ -/* - * This source code is released into the public domain. - */ - -module; - +// This source code is released into the public domain. export module nihil.config; export import :option; diff --git a/nihil.config/option.cc b/nihil.config/option.cc index 886f4b6..e09842e 100644 --- a/nihil.config/option.cc +++ b/nihil.config/option.cc @@ -1,16 +1,7 @@ -/* - * This source code is released into the public domain. - */ - -module; - -#include -#include -#include -#include - +// This source code is released into the public domain. module nihil.config; +import nihil.std; import nihil.error; import nihil.monad; import nihil.ucl; @@ -18,7 +9,7 @@ import nihil.ucl; namespace nihil::config { //NOLINTNEXTLINE(bugprone-easily-swappable-parameters) -option::option(std::string_view name, std::string_view description) +option::option(std::string_view const name, std::string_view const description) : m_name(name) , m_description(description) { @@ -26,16 +17,15 @@ option::option(std::string_view name, std::string_view description) if (okay) return; - std::print(std::cerr, + std::println(std::cerr, "INTERNAL ERROR: failed to register " "configuration option '{}': {}", m_name, okay.error()); - std::exit(1); + std::exit(1); // NOLINT } option::~option() { - std::ignore = store::get().unregister_option(this); } auto option::name(this option const &self) noexcept diff --git a/nihil.config/option.ccm b/nihil.config/option.ccm index 4b95793..0758c1a 100644 --- a/nihil.config/option.ccm +++ b/nihil.config/option.ccm @@ -1,32 +1,22 @@ -/* - * This source code is released into the public domain. - */ - -module; - -#include -#include -#include - +// This source code is released into the public domain. export module nihil.config:option; +import nihil.std; import nihil.error; import nihil.ucl; namespace nihil::config { -/* - * Base class for options; this is what config_store interacts with. - * - * Base classes should override the four protected functions: - * - * get_string() - * set_string() - * get_ucl() - * set_ucl() - * - * Overriding any other members is not permitted. - */ +// Base class for options; this is what config_store interacts with. +// +// Base classes should override the four protected functions: +// +// get_string() +// set_string() +// get_ucl() +// set_ucl() +// +// Overriding any other members is not permitted. export struct option { @@ -70,6 +60,9 @@ export struct option option(option const &) = delete; auto operator=(option const &) -> option& = delete; + option(option &&) = delete; + auto operator=(option &&) -> option& = delete; + protected: option(std::string_view name, std::string_view description); diff --git a/nihil.config/read.cc b/nihil.config/read.cc deleted file mode 100644 index 48484fb..0000000 --- a/nihil.config/read.cc +++ /dev/null @@ -1,50 +0,0 @@ -/* - * This source code is released into the public domain. - */ - -module; - -#include -#include -#include -#include -#include -#include - -module nihil.config; - -import nihil.error; -import nihil.monad; -import nihil.posix; -import nihil.ucl; - -namespace nihil::config { - -auto read_from(std::filesystem::path const &filename) - -> std::expected -{ - // TODO: nihil.ucl should have a way to load UCL from a filename. - - std::string config_text; - auto err = read_file(filename, std::back_inserter(config_text)); - if (!err) { - // Ignore ENOENT, it simply means we haven't created the - // config file yet, so default values will be used. - if (err.error().root_cause() == std::errc::no_such_file_or_directory) - co_return {}; - auto errstr = std::format("cannot read {}", filename.string()); - co_return std::unexpected(error(errstr, err.error())); - } - - // Parse the UCL. - auto uclconfig = co_await ucl::parse(config_text); - - for (auto &&[key, value] : uclconfig) { - auto opt = co_await store::get().fetch(key); - co_await opt->ucl(value); - } - - co_return {}; -} - -} // namespace nihil::config diff --git a/nihil.config/read.ccm b/nihil.config/read.ccm index 9cf28c9..7065492 100644 --- a/nihil.config/read.ccm +++ b/nihil.config/read.ccm @@ -1,22 +1,41 @@ -/* - * This source code is released into the public domain. - */ - -module; - -#include -#include - +// This source code is released into the public domain. export module nihil.config:read; +import nihil.std; import nihil.error; +import nihil.monad; +import nihil.posix; +import nihil.ucl; +import :option; +import :store; namespace nihil::config { -/* - * Load the configuration from a file. - */ -export [[nodiscard]] auto read_from(std::filesystem::path const &filename) - -> std::expected; +// Load the configuration from a file. +export [[nodiscard]] auto +read_from(std::filesystem::path const &filename) -> std::expected +{ + // TODO: nihil.ucl should have a way to load UCL from a filename. + + auto config_text = std::string(); + auto err = read_file(filename, std::back_inserter(config_text)); + if (!err) { + // Ignore ENOENT, it simply means we haven't created the + // config file yet, so default values will be used. + if (err.error().root_cause() == std::errc::no_such_file_or_directory) + co_return {}; + co_return std::unexpected(error(std::format("cannot read {}", filename.string()), err.error())); + } + + // Parse the UCL. + auto uclconfig = co_await ucl::parse(config_text); + + for (auto &&[key, value] : uclconfig) { + auto *opt = co_await store::get().fetch(key); + co_await opt->ucl(value); + } + + co_return {}; +} } // namespace nihil::config diff --git a/nihil.config/store.cc b/nihil.config/store.cc index 0fb8cc0..e1ca271 100644 --- a/nihil.config/store.cc +++ b/nihil.config/store.cc @@ -1,17 +1,7 @@ -/* - * This source code is released into the public domain. - */ - -module; - -#include -#include -#include -#include -#include - +// This source code is released into the public domain. module nihil.config; +import nihil.std; import nihil.error; import nihil.generator; import nihil.monad; diff --git a/nihil.config/store.ccm b/nihil.config/store.ccm index 4d37ce0..0a92ef0 100644 --- a/nihil.config/store.ccm +++ b/nihil.config/store.ccm @@ -1,25 +1,16 @@ -/* - * This source code is released into the public domain. - */ - -module; - -/* - * The configuration store. There should only be one of these. - */ - -#include -#include -#include -#include - +// This source code is released into the public domain. export module nihil.config:store; +// The configuration store. There should only be one of these. + +import nihil.std; +import nihil.error; import nihil.generator; -import :option; namespace nihil::config { +export struct option; + struct store final { /* * Get the global config store. @@ -57,8 +48,8 @@ struct store final { // Not movable or copyable. store(store const &) = delete; store(store &&) = delete; - store& operator=(store const &) = delete; - store& operator=(store &&) = delete; + auto operator=(store const &) -> store & = delete; + auto operator=(store &&) -> store & = delete; private: store(); diff --git a/nihil.config/string.cc b/nihil.config/string.cc deleted file mode 100644 index 0ca4605..0000000 --- a/nihil.config/string.cc +++ /dev/null @@ -1,62 +0,0 @@ -/* - * This source code is released into the public domain. - */ - -module; - -#include -#include -#include -#include - -module nihil.config; - -import nihil.error; -import nihil.monad; -import nihil.ucl; - -namespace nihil::config { - -string::string( - std::string &storage, - std::string_view name, - std::string_view description) noexcept - : option(name, description) - , m_storage(storage) -{ -} - -string::~string() = default; - -auto string::get_string() const -> std::string -{ - return m_storage; -} - -auto string::set_string(std::string_view new_value) - -> std::expected -{ - m_storage = new_value; - return {}; -} - -auto string::get_ucl() const -> std::expected -{ - return ucl::string(m_storage); -} - -auto string::set_ucl(ucl::object const &uclobj) -> std::expected -{ - auto obj = co_await object_cast(uclobj) - .transform_error([&] (ucl::type_mismatch const &m) { - return error(std::format( - "'{}': expected string, not {}", - name(), str(m.actual_type()))); - }); - - m_storage = obj.value(); - is_default(false); - co_return {}; -} - -} // namespace nihil::config diff --git a/nihil.config/string.ccm b/nihil.config/string.ccm index 668bbc0..12ede7a 100644 --- a/nihil.config/string.ccm +++ b/nihil.config/string.ccm @@ -1,55 +1,68 @@ -/* - * This source code is released into the public domain. - */ - -module; - -#include -#include -#include - +// This source code is released into the public domain. export module nihil.config:string; +import nihil.std; +import nihil.monad; import nihil.ucl; import :option; namespace nihil::config { -/* - * A string option. The backing type is std::string. - */ +// A string option. The backing type is std::string. export struct string final : option { - string(std::string &storage, - std::string_view name, - std::string_view description) noexcept; - - ~string(); - - /* - * Get this option as a string; simply returns the storage. - */ - [[nodiscard]] auto get_string() const -> std::string override; - - /* - * Set this option to a string value; assigns to the storage. - */ - [[nodiscard]] auto set_string(std::string_view new_value) - -> std::expected override; - - /* - * Convert this option to a UCL object. - */ - [[nodiscard]] auto get_ucl() const - -> std::expected override; - - /* - * Set this option from a UCL object. - */ - [[nodiscard]] auto set_ucl(ucl::object const &uclobj) - -> std::expected override; + string(std::string &storage, std::string_view const name, + std::string_view const description) noexcept + : option(name, description) + , m_storage(storage) + { + } + + ~string() override = default; + + // Not copyable. + string(string const &) = delete; + auto operator=(string const &) -> string & = delete; + + // Not movable. + string(string &&) = delete; + auto operator=(string &&) -> string & = delete; private: + // Get this option as a string; simply returns the storage. + [[nodiscard]] auto get_string() const -> std::string override + { + return m_storage; + } + + // Set this option to a string value; assigns to the storage. + [[nodiscard]] auto + set_string(std::string_view const new_value) -> std::expected override + { + m_storage = new_value; + return {}; + } + + // Convert this option to a UCL object. + [[nodiscard]] auto get_ucl() const -> std::expected override + { + return ucl::make_string(m_storage); + } + + // Set this option from a UCL object. + [[nodiscard]] auto set_ucl(ucl::object const &uclobj) -> std::expected override + { + auto obj = co_await object_cast(uclobj).transform_error( + [&](ucl::type_mismatch const &m) { + return error(std::format("'{}': expected string, not {}", name(), + str(m.actual_type()))); + }); + + m_storage = obj.value(); + is_default(false); + co_return {}; + } + std::string &m_storage; }; diff --git a/nihil.config/string.test.cc b/nihil.config/string.test.cc new file mode 100644 index 0000000..322eb79 --- /dev/null +++ b/nihil.config/string.test.cc @@ -0,0 +1,32 @@ +// This source code is released into the public domain. + +#include + +import nihil.std; +import nihil.config; + +namespace { +TEST_CASE("nihil.config: string option", "[nihil][nihil.config]") +{ + auto storage = std::string(); + + REQUIRE(nihil::config::get_option("test_option").has_value() == false); + + { + auto string_option = nihil::config::string( + storage, "test_option", "This is a test option"); + + auto *opt = nihil::config::get_option("test_option").value(); + + REQUIRE(opt->name() == "test_option"); + REQUIRE(opt->description() == "This is a test option"); + REQUIRE(opt->is_default() == true); + REQUIRE(opt->string() == ""); + + REQUIRE(opt->string("testing")); + REQUIRE(storage == "testing"); + } + + REQUIRE(nihil::config::get_option("test_option").has_value() == false); +} +} // anonymous namespace diff --git a/nihil.config/tests/CMakeLists.txt b/nihil.config/tests/CMakeLists.txt deleted file mode 100644 index ffa60c3..0000000 --- a/nihil.config/tests/CMakeLists.txt +++ /dev/null @@ -1,13 +0,0 @@ -# This source code is released into the public domain. - -add_executable(nihil.config.test - string.cc -) - -target_link_libraries(nihil.config.test PRIVATE - nihil.config - Catch2::Catch2WithMain) - -include(CTest) -include(Catch) -catch_discover_tests(nihil.config.test) diff --git a/nihil.config/tests/string.cc b/nihil.config/tests/string.cc deleted file mode 100644 index aeb1ef8..0000000 --- a/nihil.config/tests/string.cc +++ /dev/null @@ -1,36 +0,0 @@ -/* - * This source code is released into the public domain. - */ - -#include - -#include - -import nihil.config; - -TEST_CASE("nihil.config: string option", "[nihil][nihil.config]") -{ - std::string storage; - - auto opt = nihil::config::get_option("test_option"); - REQUIRE(!opt); - - { - auto string_option = nihil::config::string( - storage, "test_option", "This is a test option"); - - auto opt = nihil::config::get_option("test_option"); - REQUIRE(opt); - - REQUIRE((*opt)->name() == "test_option"); - REQUIRE((*opt)->description() == "This is a test option"); - REQUIRE((*opt)->is_default() == true); - REQUIRE((*opt)->string() == ""); - - REQUIRE((*opt)->string("testing")); - REQUIRE(storage == "testing"); - } - - opt = nihil::config::get_option("test_option"); - REQUIRE(!opt); -} diff --git a/nihil.config/write.cc b/nihil.config/write.cc deleted file mode 100644 index 80125a8..0000000 --- a/nihil.config/write.cc +++ /dev/null @@ -1,41 +0,0 @@ -/* - * This source code is released into the public domain. - */ - -module; - -#include -#include -#include -#include -#include - -module nihil.config; - -import nihil.error; -import nihil.monad; -import nihil.posix; -import nihil.ucl; - -namespace nihil::config { - -auto write_to(std::filesystem::path const &filename) - -> std::expected -{ - auto uclconfig = ucl::map(); - - // Add all the options to the UCL object. - for (auto const &option : store::get().all()) { - if (option->is_default()) - continue; - - auto uobj = co_await option->ucl(); - uclconfig.insert({option->name(), uobj}); - } - - auto ucl_text = std::format("{:c}", uclconfig); - co_await safe_write_file(filename, ucl_text); - co_return {}; -} - -}; diff --git a/nihil.config/write.ccm b/nihil.config/write.ccm index 564bb20..a7eddd5 100644 --- a/nihil.config/write.ccm +++ b/nihil.config/write.ccm @@ -1,22 +1,34 @@ -/* - * This source code is released into the public domain. - */ - -module; - -#include -#include - +// This source code is released into the public domain. export module nihil.config:write; +import nihil.std; import nihil.error; +import nihil.monad; +import nihil.posix; +import nihil.ucl; +import :option; +import :store; namespace nihil::config { -/* - * Write all config values (except defaults) to disk. - */ -export [[nodiscard]] auto write_to(std::filesystem::path const &filename) -> - std::expected; +// Write all config values (except defaults) to disk. +export [[nodiscard]] auto +write_to(std::filesystem::path const &filename) -> std::expected +{ + auto uclconfig = ucl::map(); + + // Add all the options to the UCL object. + for (auto const &option : store::get().all()) { + if (option->is_default()) + continue; + + auto uobj = co_await option->ucl(); + uclconfig.insert({option->name(), uobj}); + } + + auto ucl_text = std::format("{:c}", uclconfig); + co_await safe_write_file(filename, ucl_text); + co_return {}; +} -}; +}; // namespace nihil::config diff --git a/nihil.core/CMakeLists.txt b/nihil.core/CMakeLists.txt index 2a7b3e2..cbb1b6b 100644 --- a/nihil.core/CMakeLists.txt +++ b/nihil.core/CMakeLists.txt @@ -1,11 +1,13 @@ # This source code is released into the public domain. add_library(nihil.core STATIC) +target_link_libraries(nihil.core PRIVATE nihil.std) target_include_directories(nihil.core PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) target_sources(nihil.core PUBLIC FILE_SET modules TYPE CXX_MODULES FILES nihil.core.ccm errc.ccm + features.ccm PRIVATE errc.cc diff --git a/nihil.core/errc.cc b/nihil.core/errc.cc index 35c9d8f..411ad66 100644 --- a/nihil.core/errc.cc +++ b/nihil.core/errc.cc @@ -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. module nihil.core; +import nihil.std; + namespace nihil { struct nihil_error_category final : std::error_category { @@ -43,6 +37,10 @@ auto nihil_error_category::message(int err) const -> std::string return "Empty string is not permitted"; case errc::invalid_unit: return "Invalid unit specifier"; + case errc::failed_to_create_object: + return "Failed to create UCL object"; + case errc::type_mismatch: + return "UCL type does not match expected type"; default: return "Undefined error"; } diff --git a/nihil.core/errc.ccm b/nihil.core/errc.ccm index c597faf..f5aac0b 100644 --- a/nihil.core/errc.ccm +++ b/nihil.core/errc.ccm @@ -1,28 +1,29 @@ -/* - * This source code is released into the public domain. - */ - -module; - -#include -#include - +// This source code is released into the public domain. export module nihil.core:errc; +import nihil.std; + namespace nihil { -export enum struct errc { +export enum struct errc : std::uint8_t { no_error = 0, - /* - * nihil.command - */ + // + // nihil.command + // incomplete_command, - /* - * nihil.util - */ + // + // nihil.ucl + // + + failed_to_create_object, + type_mismatch, + + // + // nihil.util + // // Empty string is not allowed. empty_string, @@ -36,9 +37,5 @@ export [[nodiscard]] auto make_error_condition(errc ec) -> std::error_condition; } // namespace nihil -namespace std { - -export template<> -struct is_error_condition_enum : true_type {}; - -} // namespace std +template<> +struct std::is_error_condition_enum : std::true_type {}; diff --git a/nihil.core/features.ccm b/nihil.core/features.ccm new file mode 100644 index 0000000..5c6631b --- /dev/null +++ b/nihil.core/features.ccm @@ -0,0 +1,13 @@ +// This source code is released into the public domain. +module; + +#include "nihil.hh" + +export module nihil.core:features; + +namespace nihil::features { + +export inline constexpr bool fexecve = NIHIL_HAVE_FEXECVE; +export inline constexpr bool getenv_r = NIHIL_HAVE_GETENV_R; + +} // namespace nihil::features diff --git a/nihil.core/nihil.core.ccm b/nihil.core/nihil.core.ccm index a7a4100..0aa5402 100644 --- a/nihil.core/nihil.core.ccm +++ b/nihil.core/nihil.core.ccm @@ -1,9 +1,5 @@ -/* - * This source code is released into the public domain. - */ - -module; - +// This source code is released into the public domain. export module nihil.core; export import :errc; +export import :features; diff --git a/nihil.core/nihil.hh b/nihil.core/nihil.hh index cd7e789..da37895 100644 --- a/nihil.core/nihil.hh +++ b/nihil.core/nihil.hh @@ -1,10 +1,11 @@ -/* -* This source code is released into the public domain. - */ +// This source code is released into the public domain. #ifndef NIHIL_HH_INCLUDED #define NIHIL_HH_INCLUDED +#define NIHIL_HAVE_FEXECVE 0 +#define NIHIL_HAVE_GETENV_R 0 + #if __has_include() # include #endif @@ -12,14 +13,16 @@ #if defined(__FreeBSD_version) /* fexecve() added in FreeBSD 8.0 */ -#if (__FreeBSD_version >= 800000) -# define NIHIL_HAVE_FEXECVE -#endif +# if (__FreeBSD_version >= 800000) +# undef NIHIL_HAVE_FEXECVE +# define NIHIL_HAVE_FEXECVE 1 +# endif /* getenv_r() added in FreeBSD 15.0 */ -#if (__FreeBSD_version >= 1500000) -# define NIHIL_HAVE_GETENV_R -#endif +# if (__FreeBSD_version >= 1500000) +# undef NIHIL_HAVE_GETENV_R +# define NIHIL_HAVE_GETENV_R 1 +# endif #endif // defined(__FreeBSD_version) diff --git a/nihil.error/CMakeLists.txt b/nihil.error/CMakeLists.txt index 37fb3ab..fd5da84 100644 --- a/nihil.error/CMakeLists.txt +++ b/nihil.error/CMakeLists.txt @@ -1,13 +1,11 @@ # This source code is released into the public domain. add_library(nihil.error STATIC) -target_link_libraries(nihil.error PRIVATE nihil.match) +target_link_libraries(nihil.error PRIVATE nihil.std nihil.match) target_sources(nihil.error PUBLIC FILE_SET modules TYPE CXX_MODULES FILES error.ccm - - PRIVATE - error.cc + sys_error.ccm ) if(NIHIL_TESTS) diff --git a/nihil.error/error.cc b/nihil.error/error.cc deleted file mode 100644 index e4023f9..0000000 --- a/nihil.error/error.cc +++ /dev/null @@ -1,160 +0,0 @@ -/* - * This source code is released into the public domain. - */ - -module; - -#include -#include -#include -#include -#include -#include - -module nihil.error; - -import nihil.match; - -namespace nihil { - -auto to_string(error const &self) -> std::string -{ - auto ret = self.str(); - - auto cause = self.cause(); - while (cause) { - ret += ": " + cause->str(); - cause = cause->cause(); - } - - return ret; -} - -error::error() -{ -} - -error::~error() = default; - -error::error(std::string_view what, error cause) - : m_error(std::string(what)) - , m_cause(std::make_shared(std::move(cause))) -{ -} -error::error(std::string_view what) - : m_error(std::string(what)) -{ -} - -error::error(std::error_condition what, error cause) - : m_error(what) - , m_cause(std::make_shared(std::move(cause))) -{ -} - -error::error(std::error_condition what) - : m_error(what) -{ -} - -error::error(std::error_code what, error cause) - : m_error(what) - , m_cause(std::make_shared(std::move(cause))) -{ -} - -error::error(std::error_code what) - : m_error(what) -{ -} - -error::error(error const &) = default; -error::error(error &&) noexcept = default; -auto error::operator=(this error &, error const &) -> error & = default; -auto error::operator=(this error &, error &&) noexcept -> error & = default; - -auto error::cause(this error const &self) -> std::shared_ptr -{ - if (self.m_cause) - return self.m_cause; - return {}; -} - -auto error::root_cause(this error const &self) -> error const & -{ - if (self.m_cause) - return self.m_cause->root_cause(); - - return self; //NOLINT(bugprone-return-const-ref-from-parameter) -} - -auto error::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; - } - }; -} - -auto error::code(this error const &self) -> std::optional -{ - auto const *code = std::get_if(&self.m_error); - if (code) - return {*code}; - return {}; -} - -auto error::condition(this error const &self) - -> std::optional -{ - auto const *condition = std::get_if(&self.m_error); - if (condition) - return {*condition}; - return {}; -} - -auto error::what() const noexcept -> char const * -{ - if (!m_what) - m_what = to_string(*this); - - return m_what->c_str(); -} - -auto operator==(error const &lhs, error const &rhs) -> bool -{ - return lhs.m_error == rhs.m_error; -} - -auto operator<=>(error const &lhs, error const &rhs) -> std::strong_ordering -{ - return lhs.m_error <=> rhs.m_error; -} - -auto operator==(error const &lhs, std::error_code const &rhs) -> bool -{ - return lhs.code() == rhs; -} - -// Compare an error to an std::error_condition. -auto operator==(error const &lhs, std::error_condition const &rhs) -> bool -{ - return lhs.condition() == rhs; -} - -auto operator<<(std::ostream &strm, error const &e) -> std::ostream & -{ - return strm << to_string(e); -} - -} // namespace nihil diff --git a/nihil.error/error.ccm b/nihil.error/error.ccm index 12d47cc..7ed9d5c 100644 --- a/nihil.error/error.ccm +++ b/nihil.error/error.ccm @@ -1,132 +1,261 @@ -/* - * This source code is released into the public domain. - */ - -module; - -/* - * 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. - */ - -#include -#include -#include -#include -#include -#include -#include -#include - +// This source code is released into the public domain. export module nihil.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 nihil.match; + +export import :sys_error; + namespace nihil { // Things which can be errors. -using error_t = std::variant< - std::monostate, - std::string, - std::error_code, - std::error_condition - >; - -export struct error : std::exception { +using error_t = std::variant; + +export struct error : std::exception +{ // Create an empty error, representing success. - error(); + error() = default; // Destroy an error. - ~error() override; + ~error() override = default; // Create an error from a freeform string. - error(std::string_view what, error cause); - explicit error(std::string_view what); + explicit error(std::string_view what) + : m_error(std::string(what)) + { + } - template + // Create an error from a freeform string and a cause. + error(std::string_view what, error cause) + : m_error(std::string(what)) + , m_cause(std::make_shared(std::move(cause))) + { + } + + // Create an error from a freeform string and an error code enum cause. + template requires(std::is_error_code_enum::value || - std::is_error_condition_enum::value) + std::is_error_condition_enum::value) error(std::string_view what, Cause &&cause) : error(what, error(std::forward(cause))) - {} + { + } + + // Create an error from an std::error_condition. + explicit error(std::error_condition what) + : m_error(what) + { + } + + // Create an error from an std::error_condition and a cause. + error(std::error_condition what, error cause) + : m_error(what) + , m_cause(std::make_shared(std::move(cause))) + { + } // Create an error from an std::error_code. - error(std::error_condition what, error cause); - explicit error(std::error_condition what); + explicit error(std::error_code what) + : m_error(what) + { + } - // Create an error from an std::error_condition. - error(std::error_code what, error cause); - explicit error(std::error_code what); + // Create an error from an std::error_code and a cause. + error(std::error_code what, error cause) + : m_error(what) + , m_cause(std::make_shared(std::move(cause))) + { + } // Create an error from an std::error_code enum. + explicit error(auto errc) + requires(std::is_error_code_enum::value) + : error(make_error_code(errc)) + { + } + + // Create an error from an std::error_code enum and a cause/ error(auto errc, error cause) requires(std::is_error_code_enum::value) : error(make_error_code(errc), std::move(cause)) - {} + { + } + // Create an error from an std::error_condition enum. explicit error(auto errc) - requires(std::is_error_code_enum::value) - : error(make_error_code(errc)) - {} + requires(std::is_error_condition_enum::value) + : error(make_error_condition(errc)) + { + } - // Create an error from an std::error_condition enum. + // Create an error from an std::error_condition enum and a cause. error(auto errc, error cause) requires(std::is_error_condition_enum::value) : error(make_error_condition(errc), std::move(cause)) - {} - - explicit error(auto errc) - requires(std::is_error_condition_enum::value) - : error(make_error_condition(errc)) - {} + { + } - error(error const &); - error(error &&) noexcept; + // Copyable. + error(error const &) = default; + auto operator=(error const &) -> error & = default; - auto operator=(this error &, error const &) -> error &; - auto operator=(this error &, error &&) noexcept -> error &; + // Movable. + error(error &&) noexcept = default; + auto operator=(error &&) noexcept -> error & = default; // Return the cause of this error. - [[nodiscard]] auto cause(this error const &) -> std::shared_ptr; + [[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 &) -> error const &; + [[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(); + } - // Format this error as a string. - [[nodiscard]] auto str(this error const &) -> std::string; + return str; + } // Return this error's error_code, if any. - [[nodiscard]] auto code(this error const &) - -> std::optional; + [[nodiscard]] auto code(this error const &self) -> std::optional + { + auto const *code = std::get_if(&self.m_error); + if (code) + return {*code}; + return {}; + } // Return this error's error_condition, if any. - [[nodiscard]] auto condition(this error const &) - -> std::optional; + [[nodiscard]] auto condition(this error const &self) -> std::optional + { + auto const *condition = std::get_if(&self.m_error); + if (condition) + 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::expectde 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)}; + } - [[nodiscard]] auto what() const noexcept -> char const * final; + operator std::unexpected () const // NOLINT + { + return std::unexpected{*this}; + } private: - friend auto operator==(error const &, error const &) -> bool; - friend auto operator<=>(error const &, error const &) - -> std::strong_ordering; + // 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; + } + + // Print an error to a stream. + friend auto operator<<(std::ostream &strm, error const &e) -> std::ostream & + { + return strm << e.full_str(); + } // This error. error_t m_error = make_error_code(std::errc()); @@ -137,63 +266,37 @@ private: // For std::exception::what(), we need to keep the string valid // until we're destroyed. mutable std::optional m_what; -}; - -/* - * Format an error and its cause(s) as a string. - */ -export [[nodiscard]] auto to_string(error const &) -> std::string; - -// Compare an error to another error. This only compares the error itself, -// not any nested causes. -export [[nodiscard]] auto operator==(error const &, error const &) - -> bool; -export [[nodiscard]] auto operator<=>(error const &, error const &) - -> std::strong_ordering; - -// Compare an error to an std::error_code. -export [[nodiscard]] auto operator==(error const &, std::error_code const &) - -> bool; - -// Compare an error to an std::error_condition. -export [[nodiscard]] auto operator==(error const &, - std::error_condition const &) - -> bool; - -// Compare an error to an std::error_code enum. -export [[nodiscard]] 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. -export [[nodiscard]] auto operator==(error const &lhs, auto rhs) -> bool -requires(std::is_error_condition_enum::value) -{ - 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; + } -// Print an error to an ostream. -export [[nodiscard]] auto operator<<(std::ostream &, error const &) - -> std::ostream &; + // 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; + } +}; } // namespace nihil // Make error formattable. -export template<> +export template <> struct std::formatter { - template + template constexpr auto parse(ParseContext &ctx) -> ParseContext::iterator { return ctx.begin(); } - template - auto format(nihil::error const &e, FormatContext &ctx) const - -> FormatContext::iterator + template + auto format(nihil::error const &e, FormatContext &ctx) const -> FormatContext::iterator { - return std::ranges::copy(to_string(e), ctx.out()).out; + return std::ranges::copy(e.full_str(), ctx.out()).out; } }; diff --git a/nihil.error/sys_error.ccm b/nihil.error/sys_error.ccm new file mode 100644 index 0000000..102f4c5 --- /dev/null +++ b/nihil.error/sys_error.ccm @@ -0,0 +1,18 @@ +// This source code is released into the public domain. +module; + +#include + +export module nihil.error: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); +} + +} // namespace nihil diff --git a/nihil.error/test.cc b/nihil.error/test.cc index 9b3eef1..0f4f93f 100644 --- a/nihil.error/test.cc +++ b/nihil.error/test.cc @@ -1,13 +1,8 @@ -/* - * This source code is released into the public domain. - */ - -#include -#include -#include +// This source code is released into the public domain. #include +import nihil.std; import nihil.error; TEST_CASE("error: invariants", "[nihil]") @@ -27,9 +22,9 @@ TEST_CASE("error: construct from string", "[nihil]") using namespace nihil; auto e = error("an error"); - REQUIRE(e.str() == to_string(e)); - REQUIRE(to_string(e) == "an error"); - REQUIRE(std::format("{}", e) == to_string(e)); + REQUIRE(e.full_str() == "an error"); + REQUIRE(e.this_str() == e.full_str()); + REQUIRE(std::format("{}", e) == e.full_str()); } TEST_CASE("error: construct from std::error_condition", "[nihil]") @@ -46,9 +41,9 @@ TEST_CASE("error: construct from std::error_condition", "[nihil]") REQUIRE(e == std::errc::invalid_argument); REQUIRE(e != std::errc::no_such_file_or_directory); - REQUIRE(e.str() == to_string(e)); - REQUIRE(to_string(e) == std::strerror(EINVAL)); - REQUIRE(std::format("{}", e) == to_string(e)); + REQUIRE(e.full_str() == std::strerror(EINVAL)); + REQUIRE(e.this_str() == e.full_str()); + REQUIRE(std::format("{}", e) == e.full_str()); } TEST_CASE("error: construct from std::errc", "[nihil]") @@ -64,9 +59,9 @@ TEST_CASE("error: construct from std::errc", "[nihil]") REQUIRE(e == std::errc::invalid_argument); REQUIRE(e != std::errc::no_such_file_or_directory); - REQUIRE(e.str() == to_string(e)); - REQUIRE(to_string(e) == std::strerror(EINVAL)); - REQUIRE(std::format("{}", e) == to_string(e)); + REQUIRE(e.full_str() == std::strerror(EINVAL)); + REQUIRE(e.this_str() == e.full_str()); + REQUIRE(std::format("{}", e) == e.full_str()); } TEST_CASE("error: compound error", "[nihil]") @@ -82,10 +77,10 @@ TEST_CASE("error: compound error", "[nihil]") REQUIRE(e.condition().has_value() == false); REQUIRE(*e.cause() == std::errc::no_such_file_or_directory); - REQUIRE(e.str() == "cannot open file"); - REQUIRE(to_string(e) == ("cannot open file: "s + - std::strerror(ENOENT))); - REQUIRE(std::format("{}", e) == to_string(e)); + + REQUIRE(e.full_str() == ("cannot open file: "s + std::strerror(ENOENT))); + REQUIRE(e.this_str() == "cannot open file"); + REQUIRE(std::format("{}", e) == e.full_str()); } TEST_CASE("error: operator== with strings", "[nihil]") diff --git a/nihil.flagset/CMakeLists.txt b/nihil.flagset/CMakeLists.txt index bdc31ce..40d106e 100644 --- a/nihil.flagset/CMakeLists.txt +++ b/nihil.flagset/CMakeLists.txt @@ -1,6 +1,7 @@ # This source code is released into the public domain. add_library(nihil.flagset STATIC) +target_link_libraries(nihil.flagset PRIVATE nihil.std) target_sources(nihil.flagset PUBLIC FILE_SET modules TYPE CXX_MODULES FILES flagset.ccm diff --git a/nihil.flagset/flagset.ccm b/nihil.flagset/flagset.ccm index 8369b75..5bd7720 100644 --- a/nihil.flagset/flagset.ccm +++ b/nihil.flagset/flagset.ccm @@ -1,20 +1,11 @@ -/* - * This source code is released into the public domain. - */ - -module; +// This source code is released into the public domain. +export module nihil.flagset; /* - * flagset: a type-type flags type. + * flagset: a type-safe flags type. */ -#include -#include -#include -#include -#include - -export module nihil.flagset; +import nihil.std; namespace nihil { diff --git a/nihil.flagset/test.cc b/nihil.flagset/test.cc index c3ebd35..90eef4a 100644 --- a/nihil.flagset/test.cc +++ b/nihil.flagset/test.cc @@ -1,18 +1,14 @@ -/* - * This source code is released into the public domain. - */ - -#include -#include -#include +// This source code is released into the public domain. #include +import nihil.std; import nihil.flagset; namespace { - -struct test_tag{}; +struct test_tag +{ +}; using testflags = nihil::flagset; constexpr auto zero = testflags::bit<0>(); @@ -20,8 +16,6 @@ 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); @@ -45,17 +39,17 @@ TEST_CASE("flagset: mask<>", "[nihil]") TEST_CASE("flagset: constructor", "[nihil]") { - SECTION("default construct") { + SECTION ("default construct") { auto flags = testflags(); REQUIRE(flags.value() == 0); } - SECTION("construct from int") { + SECTION ("construct from int") { auto flags = testflags::from_int(one.value() | zero.value()); REQUIRE(flags == (one | zero)); } - SECTION("copy construct") { + SECTION ("copy construct") { auto flags = one; auto flags2(flags); REQUIRE(flags == flags2); @@ -64,46 +58,46 @@ TEST_CASE("flagset: constructor", "[nihil]") TEST_CASE("flagset: operators", "[nihil]") { - SECTION("operator|") { + SECTION ("operator|") { REQUIRE((zero | one).value() == 0x3); } - SECTION("operator|=") { + SECTION ("operator|=") { auto flags = zero; flags |= one; REQUIRE(flags.value() == 0x3); } - SECTION("operator&") { + SECTION ("operator&") { auto flags = zero | one; REQUIRE((flags & zero) == zero); } - SECTION("operator&=") { + SECTION ("operator&=") { auto flags = zero | one | two; REQUIRE(flags.value() == 0x7); flags &= (zero | one); REQUIRE(flags.value() == 0x3); } - SECTION("operator^") { + SECTION ("operator^") { auto flags = zero | one; REQUIRE((flags ^ (one | two)) == (zero | two)); } - SECTION("operator^=") { + SECTION ("operator^=") { auto flags = zero | one; flags ^= (one | two); REQUIRE(flags == (zero | two)); } - SECTION("operator~") { + SECTION ("operator~") { auto flags = ~zero; REQUIRE(flags.value() == ~static_cast(1)); } - SECTION("operator==") { + SECTION ("operator==") { auto flags = zero; REQUIRE(flags == zero); REQUIRE(flags != one); @@ -133,7 +127,7 @@ TEST_CASE("flagset: format", "[nihil]") TEST_CASE("flagset: ostream operator<<", "[nihil]") { - auto write = [] (testflags flags) -> std::string { + auto write = [](testflags flags) -> std::string { auto strm = std::ostringstream(); strm << flags; return strm.str(); @@ -147,3 +141,4 @@ TEST_CASE("flagset: ostream operator<<", "[nihil]") REQUIRE(write(twelve) == "<12>"); REQUIRE(write(twelve | one) == "<12,1>"); } +} // anonymous namespace diff --git a/nihil.generator/CMakeLists.txt b/nihil.generator/CMakeLists.txt index e521159..d9eb854 100644 --- a/nihil.generator/CMakeLists.txt +++ b/nihil.generator/CMakeLists.txt @@ -1,6 +1,7 @@ # This source code is released into the public domain. add_library(nihil.generator STATIC) +target_link_libraries(nihil.generator PRIVATE nihil.std) target_sources(nihil.generator PUBLIC FILE_SET modules TYPE CXX_MODULES FILES nihil.generator.ccm diff --git a/nihil.generator/byte_allocator.ccm b/nihil.generator/byte_allocator.ccm index 6d46ec6..86b2edf 100644 --- a/nihil.generator/byte_allocator.ccm +++ b/nihil.generator/byte_allocator.ccm @@ -11,13 +11,10 @@ // (See accompanying file LICENSE or http://www.boost.org/LICENSE_1_0.txt) /////////////////////////////////////////////////////////////////////////////// -module; - -#include -#include - export module nihil.generator:byte_allocator; +import nihil.std; + namespace nihil { template diff --git a/nihil.generator/coroutine_traits.ccm b/nihil.generator/coroutine_traits.ccm index 2a9d51d..fde4393 100644 --- a/nihil.generator/coroutine_traits.ccm +++ b/nihil.generator/coroutine_traits.ccm @@ -11,13 +11,9 @@ // (See accompanying file LICENSE or http://www.boost.org/LICENSE_1_0.txt) /////////////////////////////////////////////////////////////////////////////// -module; - -#include -#include - export module nihil.generator:coroutine_traits; +import nihil.std; import :byte_allocator; import :forward; import :generator_promise; diff --git a/nihil.generator/elements_of.ccm b/nihil.generator/elements_of.ccm index 0e34eb9..74c8b76 100644 --- a/nihil.generator/elements_of.ccm +++ b/nihil.generator/elements_of.ccm @@ -11,12 +11,9 @@ // (See accompanying file LICENSE or http://www.boost.org/LICENSE_1_0.txt) /////////////////////////////////////////////////////////////////////////////// -module; - -#include - export module nihil.generator:elements_of; +import nihil.std; import :util; namespace nihil { diff --git a/nihil.generator/forward.ccm b/nihil.generator/forward.ccm index 8d5ca4d..71e0ddd 100644 --- a/nihil.generator/forward.ccm +++ b/nihil.generator/forward.ccm @@ -11,12 +11,9 @@ // (See accompanying file LICENSE or http://www.boost.org/LICENSE_1_0.txt) /////////////////////////////////////////////////////////////////////////////// -module; - -#include - export module nihil.generator:forward; +import nihil.std; import :util; namespace nihil { diff --git a/nihil.generator/generator.ccm b/nihil.generator/generator.ccm index 96790a8..7627052 100644 --- a/nihil.generator/generator.ccm +++ b/nihil.generator/generator.ccm @@ -14,15 +14,10 @@ module; #include -#include -#include -#include -#include -#include -#include export module nihil.generator:generator; +import nihil.std; import :byte_allocator; import :coroutine_traits; import :elements_of; diff --git a/nihil.generator/generator.test.cc b/nihil.generator/generator.test.cc index 49272b4..59a2c45 100644 --- a/nihil.generator/generator.test.cc +++ b/nihil.generator/generator.test.cc @@ -1,13 +1,8 @@ -/* - * This source code is released into the public domain. - */ - -#include -#include -#include +// This source code is released into the public domain. #include +import nihil.std; import nihil.generator; TEST_CASE("generator: basic", "[generator]") diff --git a/nihil.generator/generator_promise.ccm b/nihil.generator/generator_promise.ccm index b0fd4b1..3e8aa8c 100644 --- a/nihil.generator/generator_promise.ccm +++ b/nihil.generator/generator_promise.ccm @@ -11,13 +11,9 @@ // (See accompanying file LICENSE or http://www.boost.org/LICENSE_1_0.txt) /////////////////////////////////////////////////////////////////////////////// -module; - -#include -#include - export module nihil.generator:generator_promise; +import nihil.std; import :forward; import :generator_promise_base; import :promise_base_alloc; diff --git a/nihil.generator/generator_promise_base.ccm b/nihil.generator/generator_promise_base.ccm index fec9b1b..30c3011 100644 --- a/nihil.generator/generator_promise_base.ccm +++ b/nihil.generator/generator_promise_base.ccm @@ -11,14 +11,9 @@ // (See accompanying file LICENSE or http://www.boost.org/LICENSE_1_0.txt) /////////////////////////////////////////////////////////////////////////////// -module; - -#include -#include -#include - export module nihil.generator:generator_promise_base; +import nihil.std; import :elements_of; import :forward; import :manual_lifetime; diff --git a/nihil.generator/manual_lifetime.ccm b/nihil.generator/manual_lifetime.ccm index 963e6c9..4a383ad 100644 --- a/nihil.generator/manual_lifetime.ccm +++ b/nihil.generator/manual_lifetime.ccm @@ -11,13 +11,10 @@ // (See accompanying file LICENSE or http://www.boost.org/LICENSE_1_0.txt) /////////////////////////////////////////////////////////////////////////////// -module; - -#include -#include - export module nihil.generator:manual_lifetime; +import nihil.std; + namespace nihil { template diff --git a/nihil.generator/nihil.generator.ccm b/nihil.generator/nihil.generator.ccm index 550a4c7..fc6a097 100644 --- a/nihil.generator/nihil.generator.ccm +++ b/nihil.generator/nihil.generator.ccm @@ -11,12 +11,10 @@ // (See accompanying file LICENSE or http://www.boost.org/LICENSE_1_0.txt) /////////////////////////////////////////////////////////////////////////////// -module; - -#include - export module nihil.generator; +import nihil.std; + export import :coroutine_traits; export import :elements_of; export import :generator; diff --git a/nihil.generator/promise_base_alloc.ccm b/nihil.generator/promise_base_alloc.ccm index e59fc57..7fd544b 100644 --- a/nihil.generator/promise_base_alloc.ccm +++ b/nihil.generator/promise_base_alloc.ccm @@ -11,13 +11,9 @@ // (See accompanying file LICENSE or http://www.boost.org/LICENSE_1_0.txt) /////////////////////////////////////////////////////////////////////////////// -module; - -#include -#include - export module nihil.generator:promise_base_alloc; +import nihil.std; import :util; namespace nihil { diff --git a/nihil.generator/util.ccm b/nihil.generator/util.ccm index 4d732b9..259499a 100644 --- a/nihil.generator/util.ccm +++ b/nihil.generator/util.ccm @@ -11,13 +11,10 @@ // (See accompanying file LICENSE or http://www.boost.org/LICENSE_1_0.txt) /////////////////////////////////////////////////////////////////////////////// -module; - -#include -#include - export module nihil.generator:util; +import nihil.std; + namespace nihil { export struct use_allocator_arg {}; diff --git a/nihil.guard/CMakeLists.txt b/nihil.guard/CMakeLists.txt index 436728a..a5e0fc2 100644 --- a/nihil.guard/CMakeLists.txt +++ b/nihil.guard/CMakeLists.txt @@ -1,6 +1,7 @@ # This source code is released into the public domain. add_library(nihil.guard STATIC) +target_link_libraries(nihil.guard PRIVATE nihil.std) target_sources(nihil.guard PUBLIC FILE_SET modules TYPE CXX_MODULES FILES guard.ccm diff --git a/nihil.guard/guard.ccm b/nihil.guard/guard.ccm index 84ff401..fdda45c 100644 --- a/nihil.guard/guard.ccm +++ b/nihil.guard/guard.ccm @@ -1,13 +1,8 @@ // This source code is released into the public domain. -module; - -#include -#include -#include -#include - export module nihil.guard; +import nihil.std; + namespace nihil { // guard: invoke a callable when this object is destroyed; this is similar to diff --git a/nihil.guard/test.cc b/nihil.guard/test.cc index 11f7d37..f1b3f21 100644 --- a/nihil.guard/test.cc +++ b/nihil.guard/test.cc @@ -1,13 +1,9 @@ -/* - * This source code is released into the public domain. - */ +// This source code is released into the public domain. #include import nihil.guard; -using namespace std::literals; - TEST_CASE("guard: basic", "[guard]") { int n = 0; diff --git a/nihil.match/CMakeLists.txt b/nihil.match/CMakeLists.txt index 283c54f..da59663 100644 --- a/nihil.match/CMakeLists.txt +++ b/nihil.match/CMakeLists.txt @@ -1,6 +1,7 @@ # This source code is released into the public domain. add_library(nihil.match STATIC) +target_link_libraries(nihil.match PRIVATE nihil.std) target_sources(nihil.match PUBLIC FILE_SET modules TYPE CXX_MODULES FILES match.ccm diff --git a/nihil.match/match.ccm b/nihil.match/match.ccm index d67bd0b..03730bb 100644 --- a/nihil.match/match.ccm +++ b/nihil.match/match.ccm @@ -1,13 +1,8 @@ -/* - * This source code is released into the public domain. - */ - -module; - -#include - +// This source code is released into the public domain. export module nihil.match; +import nihil.std; + namespace nihil { export template diff --git a/nihil.match/test.cc b/nihil.match/test.cc index 7dd1c34..974a58a 100644 --- a/nihil.match/test.cc +++ b/nihil.match/test.cc @@ -1,12 +1,8 @@ -/* - * This source code is released into the public domain. - */ - -#include -#include +// This source code is released into the public domain. #include +import nihil.std; import nihil.match; TEST_CASE("match", "[nihil]") diff --git a/nihil.monad/CMakeLists.txt b/nihil.monad/CMakeLists.txt index c293d40..f82e464 100644 --- a/nihil.monad/CMakeLists.txt +++ b/nihil.monad/CMakeLists.txt @@ -1,7 +1,7 @@ # This source code is released into the public domain. add_library(nihil.monad STATIC) -target_link_libraries(nihil.monad PRIVATE nihil.error) +target_link_libraries(nihil.monad PRIVATE nihil.std nihil.error) target_sources(nihil.monad PUBLIC FILE_SET modules TYPE CXX_MODULES FILES monad.ccm diff --git a/nihil.monad/monad.ccm b/nihil.monad/monad.ccm index 898f1ee..cd17e0f 100644 --- a/nihil.monad/monad.ccm +++ b/nihil.monad/monad.ccm @@ -22,16 +22,10 @@ * IN THE SOFTWARE. */ -module; - -#include -#include -#include -#include -#include - export module nihil.monad; +import nihil.std; + namespace nihil { /********************************************************************** @@ -228,8 +222,7 @@ struct expected_promise : expected_promise_base { self.data->emplace(std::move(err)); } - void return_value(this expected_promise &self, - std::expected o) + void return_value(this expected_promise &self, std::expected o) { self.data->emplace(std::move(o)); } diff --git a/nihil.monad/test.cc b/nihil.monad/test.cc index 347acdb..2cc743c 100644 --- a/nihil.monad/test.cc +++ b/nihil.monad/test.cc @@ -1,13 +1,8 @@ -/* - * This source code is released into the public domain. - */ - -#include -#include -#include +// This source code is released into the public domain. #include +import nihil.std; import nihil.error; import nihil.monad; diff --git a/nihil.posix/CMakeLists.txt b/nihil.posix/CMakeLists.txt index 1fcc365..d76ae4d 100644 --- a/nihil.posix/CMakeLists.txt +++ b/nihil.posix/CMakeLists.txt @@ -2,7 +2,14 @@ add_library(nihil.posix STATIC) target_link_libraries(nihil.posix PRIVATE - nihil.core nihil.error nihil.flagset nihil.guard nihil.monad) + nihil.std + nihil.core + nihil.error + nihil.flagset + nihil.guard + nihil.monad + nihil.util +) target_sources(nihil.posix PUBLIC FILE_SET modules TYPE CXX_MODULES FILES @@ -17,18 +24,19 @@ target_sources(nihil.posix execvp.ccm executor.ccm fd.ccm - fexecv.ccm - fexecvp.ccm find_in_path.ccm getenv.ccm open.ccm open_in_path.ccm + paths.ccm process.ccm read_file.ccm rename.ccm spawn.ccm stat.ccm tempfile.ccm + unistd.ccm + unlink.ccm write_file.ccm ) diff --git a/nihil.posix/argv.ccm b/nihil.posix/argv.ccm index de75770..230ae9a 100644 --- a/nihil.posix/argv.ccm +++ b/nihil.posix/argv.ccm @@ -1,16 +1,8 @@ -/* - * 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.posix:argv; +import nihil.std; + namespace nihil { /* diff --git a/nihil.posix/argv.test.cc b/nihil.posix/argv.test.cc index 3cc218d..e71c3da 100644 --- a/nihil.posix/argv.test.cc +++ b/nihil.posix/argv.test.cc @@ -1,14 +1,8 @@ -/* - * This source code is released into the public domain - */ - -#include -#include -#include -#include +// This source code is released into the public domain #include +import nihil.std; import nihil.posix; namespace { diff --git a/nihil.posix/ensure_dir.ccm b/nihil.posix/ensure_dir.ccm index 7eecea8..8d3e7a8 100644 --- a/nihil.posix/ensure_dir.ccm +++ b/nihil.posix/ensure_dir.ccm @@ -1,15 +1,7 @@ -/* - * This source code is released into the public domain. - */ - -module; - -#include -#include -#include - +// This source code is released into the public domain. export module nihil.posix:ensure_dir; +import nihil.std; import nihil.error; namespace nihil { diff --git a/nihil.posix/execl.ccm b/nihil.posix/execl.ccm index f3cbf9a..99b9169 100644 --- a/nihil.posix/execl.ccm +++ b/nihil.posix/execl.ccm @@ -1,40 +1,25 @@ -/* - * This source code is released into the public domain. - */ - -module; - -#include -#include - -#include "nihil.hh" - +// This source code is released into the public domain. export module nihil.posix:execl; +import nihil.std; import nihil.error; import :argv; import :execv; -import :fexecv; +import :fd; namespace nihil { -/* - * execl: equivalent to (f)execv, except the arguments are passed as a - * variadic pack of string-like objects. - */ +// execl: equivalent to execv, except the arguments are passed as a +// variadic pack of string-like objects. -export [[nodiscard]] auto execl(std::string_view path, auto &&...args) -> execv +export [[nodiscard]] auto execl(std::filesystem::path path, auto &&...args) -> execv { - return execv(path, argv({std::string_view(args)...})); + return execv(std::move(path), argv({std::string_view(args)...})); } -#ifdef NIHIL_HAVE_FEXECVE - -export [[nodiscard]] auto execl(fd &&executable, auto &&...args) -> fexecv +export [[nodiscard]] auto execl(fd &&executable, auto &&...args) -> execv { - return fexecv(std::move(executable), argv({std::string_view(args)...})); + return execv(std::move(executable), argv({std::string_view(args)...})); } -#endif // NIHIL_HAVE_FEXECVE - } // namespace nihil diff --git a/nihil.posix/execl.test.cc b/nihil.posix/execl.test.cc index 5aaaa25..51c2bb9 100644 --- a/nihil.posix/execl.test.cc +++ b/nihil.posix/execl.test.cc @@ -1,9 +1,8 @@ -/* - * This source code is released into the public domain. - */ +// This source code is released into the public domain. #include +import nihil.std; import nihil.posix; namespace { diff --git a/nihil.posix/execlp.ccm b/nihil.posix/execlp.ccm index ab3737c..12f2c24 100644 --- a/nihil.posix/execlp.ccm +++ b/nihil.posix/execlp.ccm @@ -1,12 +1,7 @@ // This source code is released into the public domain. -module; - -#include -#include -#include - export module nihil.posix:execlp; +import nihil.std; import nihil.error; import :argv; import :execvp; @@ -15,8 +10,9 @@ namespace nihil { // execlp: equivalent to execvp, except the arguments are passed as a // variadic pack of string-like objects. + export [[nodiscard]] auto -execlp(std::string_view file, auto &&...args) -> std::expected +execlp(std::filesystem::path const &file, auto &&...args) -> std::expected { return execvp(file, argv({std::string_view(args)...})); } diff --git a/nihil.posix/execlp.test.cc b/nihil.posix/execlp.test.cc index aa32253..6fdccea 100644 --- a/nihil.posix/execlp.test.cc +++ b/nihil.posix/execlp.test.cc @@ -1,9 +1,8 @@ -/* - * This source code is released into the public domain. - */ +// This source code is released into the public domain. #include +import nihil.std; import nihil.posix; namespace { diff --git a/nihil.posix/execshell.ccm b/nihil.posix/execshell.ccm index 1fbfccf..e0263e5 100644 --- a/nihil.posix/execshell.ccm +++ b/nihil.posix/execshell.ccm @@ -1,10 +1,7 @@ // This source code is released into the public domain. -module; - -#include - export module nihil.posix:execshell; +import nihil.std; import nihil.error; import :execv; import :execl; diff --git a/nihil.posix/execshell.test.cc b/nihil.posix/execshell.test.cc index b64953a..47e3313 100644 --- a/nihil.posix/execshell.test.cc +++ b/nihil.posix/execshell.test.cc @@ -1,9 +1,8 @@ -/* - * This source code is released into the public domain. - */ +// This source code is released into the public domain. #include +import nihil.std; import nihil.posix; namespace { diff --git a/nihil.posix/executor.ccm b/nihil.posix/executor.ccm index f348dc8..d9bde88 100644 --- a/nihil.posix/executor.ccm +++ b/nihil.posix/executor.ccm @@ -1,19 +1,12 @@ -/* - * This source code is released into the public domain. - */ - -module; - -#include -#include - +// This source code is released into the public domain. export module nihil.posix:executor; +import nihil.std; + namespace nihil { -/* - * A concept to mark spawn executors. - */ +// A concept to mark spawn executors, which should contain: +// using tag = exec_tag; export struct exec_tag{}; diff --git a/nihil.posix/execv.ccm b/nihil.posix/execv.ccm index ef9d259..d598d94 100644 --- a/nihil.posix/execv.ccm +++ b/nihil.posix/execv.ccm @@ -1,17 +1,17 @@ // This source code is released into the public domain. module; -#include -#include -#include - -#include +#include // execv() export module nihil.posix:execv; +import nihil.std; import nihil.error; +import nihil.match; +import nihil.util; import :argv; import :executor; +import :fd; namespace nihil { @@ -20,12 +20,19 @@ export struct execv final { using tag = exec_tag; + // Construct an execv from a filename. execv(std::filesystem::path path, argv &&args) noexcept - : m_path(std::move(path)) + : m_executable(std::move(path)) , m_args(std::move(args)) { } + // Construct an execv from a file descriptor + execv(fd &&executable, argv &&argv) noexcept + : m_executable(std::move(executable)) + , m_args(std::move(argv)) + {} + ~execv() = default; // Movable @@ -39,12 +46,30 @@ export struct execv final // Perform the execv(). This only returns on failure. [[nodiscard]] auto exec(this execv &self) -> std::expected { - ::execv(self.m_path.string().c_str(), self.m_args.data()); - return std::unexpected(error("execve failed", error(std::errc(errno)))); + auto guard = save_errno(); + + return self.m_executable | match { + [&] (std::filesystem::path const &path) { + ::execv(path.string().c_str(), self.m_args.data()); + return std::unexpected(error("execve failed", error(sys_error()))); + }, + + [&] (fd const &file) { +#if NIHIL_HAVE_FEXECVE == 1 + ::fexecv(file.get(), self.m_args.data()); + return std::unexpected(error("execve failed", error(sys_error()))); +#else + std::ignore = file; + return std::unexpected(error(std::errc::function_not_supported)); +#endif + } + }; } private: - std::filesystem::path m_path; + // The thing we will execute. + std::variant m_executable; + // Arguments to pass to the thing. argv m_args; }; diff --git a/nihil.posix/execv.test.cc b/nihil.posix/execv.test.cc index aaeead7..8c3ef0c 100644 --- a/nihil.posix/execv.test.cc +++ b/nihil.posix/execv.test.cc @@ -1,9 +1,8 @@ -/* - * This source code is released into the public domain. - */ +// This source code is released into the public domain. #include +import nihil.std; import nihil.posix; namespace { diff --git a/nihil.posix/execvp.ccm b/nihil.posix/execvp.ccm index 270e311..14e548e 100644 --- a/nihil.posix/execvp.ccm +++ b/nihil.posix/execvp.ccm @@ -1,29 +1,26 @@ // This source code is released into the public domain. -module; - -#include -#include -#include -#include - export module nihil.posix:execvp; +import nihil.std; +import nihil.core; import nihil.error; import nihil.monad; import :argv; import :execv; import :find_in_path; +import :open_in_path; namespace nihil { -// execvp: equivalent to execv, except the command is passed as -// a filename instead of a file descriptor. If the filename is not -// an absolute path, it will be searched for in $PATH. +// execvp: equivalent to execv, except the command will be searched for in $PATH. + export [[nodiscard]] auto -execvp(std::string_view file, argv &&argv) -> std::expected +execvp(std::filesystem::path const &file, argv &&args) -> std::expected { - auto filename = co_await find_in_path(file); - co_return execv(std::move(filename), std::move(argv)); + if constexpr (features::fexecve) + co_return execv(co_await open_in_path(file), std::move(args)); + else + co_return execv(co_await find_in_path(file), std::move(args)); } } // namespace nihil diff --git a/nihil.posix/execvp.test.cc b/nihil.posix/execvp.test.cc index e34823d..5f1b979 100644 --- a/nihil.posix/execvp.test.cc +++ b/nihil.posix/execvp.test.cc @@ -2,6 +2,7 @@ #include +import nihil.std; import nihil.posix; namespace { diff --git a/nihil.posix/fd.ccm b/nihil.posix/fd.ccm index 7faf2f1..8210b6d 100644 --- a/nihil.posix/fd.ccm +++ b/nihil.posix/fd.ccm @@ -1,24 +1,23 @@ // This source code is released into the public domain. module; -#include -#include -#include -#include -#include -#include - #include #include export module nihil.posix:fd; +import nihil.std; import nihil.flagset; import nihil.error; import nihil.monad; namespace nihil { +// Useful constants +export inline int constexpr stdin_fileno = STDIN_FILENO; +export inline int constexpr stdout_fileno = STDOUT_FILENO; +export inline int constexpr stderr_fileno = STDERR_FILENO; + // F_{GET,SET}FL flags struct fd_flags_tag { @@ -96,7 +95,7 @@ export struct fd final if (ret == 0) return {}; - return std::unexpected(error(std::errc(errno))); + return std::unexpected(error(sys_error())); } // Return the stored fd. @@ -124,7 +123,7 @@ export struct fd final if (ret >= 0) return ret; - return std::unexpected(error(std::errc(errno))); + return std::unexpected(error(sys_error())); } // Read data from the fd to the provided buffer. Returns a @@ -136,7 +135,7 @@ export struct fd final if (ret >= 0) return buffer.subspan(0, ret); - return std::unexpected(error(std::errc(errno))); + return std::unexpected(error(sys_error())); } private: @@ -152,7 +151,7 @@ export [[nodiscard]] auto dup(fd const &self) -> std::expected if (newfd != -1) return fd(newfd); - return std::unexpected(error(std::errc(errno))); + return std::unexpected(error(sys_error())); } // Create a copy of this fd by calling dup2(). Note that because this results @@ -170,7 +169,7 @@ export [[nodiscard]] auto dup(fd const &self, int newfd) -> std::expected std::expected if (newfd != -1) return newfd; - return std::unexpected(error(std::errc(errno))); + return std::unexpected(error(sys_error())); } // Create a copy of this fd by calling dup2(). @@ -190,7 +189,7 @@ export [[nodiscard]] auto raw_dup(fd const &self, int newfd) -> std::expected std::expected, error> auto fds = std::array{}; if (auto const ret = ::pipe(fds.data()); ret != 0) - return std::unexpected(error(std::errc(errno))); + return std::unexpected(error(sys_error())); return {{fd(fds[0]), fd(fds[1])}}; } diff --git a/nihil.posix/fd.test.cc b/nihil.posix/fd.test.cc index 870ddde..65b2ad3 100644 --- a/nihil.posix/fd.test.cc +++ b/nihil.posix/fd.test.cc @@ -1,15 +1,11 @@ -/* - * This source code is released into the public domain. - */ - -#include -#include -#include +// This source code is released into the public domain. #include +#include #include +import nihil.std; import nihil.error; import nihil.posix; diff --git a/nihil.posix/fexecv.ccm b/nihil.posix/fexecv.ccm deleted file mode 100644 index 4001726..0000000 --- a/nihil.posix/fexecv.ccm +++ /dev/null @@ -1,58 +0,0 @@ -// This source code is released into the public domain. -module; - -#include -#include - -#include "nihil.hh" - -export module nihil.posix:fexecv; - -import nihil.error; -import :argv; -import :executor; -import :fd; - -namespace nihil { - -#ifdef NIHIL_HAVE_FEXECVE - -/* - * fexecv: use a file descriptor and an argument vector to call ::fexecve(). - * This is the lowest-level executor which all others are implemented - * in terms of (if it's available). - * - * TODO: Should have a way to pass the environment (envp). - */ -export struct fexecv final -{ - using tag = exec_tag; - - fexecv(fd &&execfd, argv &&args) noexcept - : m_execfd(std::move(execfd)) - , m_args(std::move(args)) - { - } - - [[nodiscard]] auto exec(this fexecv &self) -> std::expected - { - ::fexecve(self.m_execfd.get(), self.m_args.data(), environ); - return std::unexpected(error("fexecve failed", error(std::errc(errno)))); - } - - // Movable - fexecv(fexecv &&) noexcept = default; - auto operator=(this fexecv &, fexecv &&) noexcept -> fexecv & = default; - - // Not copyable (because we hold the open fd object) - fexecv(fexecv const &) = delete; - auto operator=(this fexecv &, fexecv const &) -> fexecv & = delete; - -private: - fd m_execfd; - argv m_args; -}; - -#endif // NIHIL_HAVE_FEXECVE - -} // namespace nihil diff --git a/nihil.posix/fexecvp.ccm b/nihil.posix/fexecvp.ccm deleted file mode 100644 index d61240c..0000000 --- a/nihil.posix/fexecvp.ccm +++ /dev/null @@ -1,37 +0,0 @@ -// This source code is released into the public domain. -module; - -#include -#include -#include -#include - -#include "nihil.hh" - -export module nihil.posix:fexecvp; - -#ifdef NIHIL_HAVE_FEXECVE - -import nihil.error; -import :argv; -import :execv; -import :open_in_path; - -namespace nihil { - -// execvp: equivalent to execv, except the command is passed as -// a filename instead of a file descriptor. If the filename is not -// an absolute path, it will be searched for in $PATH. -export [[nodiscard]] auto -fexecvp(std::filesystem::path const &file, argv &&argv) -> std::expected -{ - auto execfd = open_in_path(file); - if (!execfd) - return std::unexpected(error( - std::format("executable not found in path: {}", file))); - return fexecv(std::move(*execfd), std::move(argv)); -} - -} // namespace nihil - -#endif // NIHIL_HAVE_FEXECVE diff --git a/nihil.posix/find_in_path.ccm b/nihil.posix/find_in_path.ccm index 61df669..dabe358 100644 --- a/nihil.posix/find_in_path.ccm +++ b/nihil.posix/find_in_path.ccm @@ -1,19 +1,15 @@ // This source code is released into the public domain. module; -#include -#include -#include -#include - -#include -#include +#include // access() export module nihil.posix:find_in_path; +import nihil.std; import nihil.error; import :fd; import :getenv; +import :paths; namespace nihil { @@ -28,7 +24,7 @@ export [[nodiscard]] auto find_in_path(std::filesystem::path const &file, std::s auto ret = ::access(file.string().c_str(), X_OK); if (ret == 0) return {std::move(file)}; - return std::unexpected(error(std::errc(errno))); + return std::unexpected(error(sys_error())); }; // Absolute pathname skips the search. @@ -59,7 +55,7 @@ export [[nodiscard]] auto find_in_path(std::filesystem::path const &file, std::s export [[nodiscard]] auto find_in_path(std::filesystem::path const &file) -> std::expected { - auto const path = getenv("PATH").value_or(_PATH_DEFPATH); // NOLINT + auto const path = getenv("PATH").value_or(std::string(paths::defpath)); // NOLINT return find_in_path(file, path); } diff --git a/nihil.posix/getenv.ccm b/nihil.posix/getenv.ccm index 5967bf7..ddffeb3 100644 --- a/nihil.posix/getenv.ccm +++ b/nihil.posix/getenv.ccm @@ -2,18 +2,15 @@ module; #include -#include -#include -#include -#include - -#include +#include // NOLINT: getenv_r #include "nihil.hh" export module nihil.posix:getenv; +import nihil.std; import nihil.error; +import nihil.util; namespace nihil { @@ -23,9 +20,10 @@ namespace nihil { // future calls to setenv(). export [[nodiscard]] auto getenv(std::string_view varname) -> std::expected { + auto errno_guard = save_errno(); auto cvarname = std::string(varname); -#ifdef NIHIL_HAVE_GETENV_R +#if NIHIL_HAVE_GETENV_R == 1 // Start with a buffer of this size, and double it every iteration. constexpr auto bufinc = std::size_t{1024}; @@ -44,16 +42,14 @@ export [[nodiscard]] auto getenv(std::string_view varname) -> std::expected -#include -#include +// This source code is released into the public domain. #include #include +import nihil.std; import nihil.error; import nihil.posix; +namespace { TEST_CASE("getenv: existing value", "[getenv]") { auto constexpr *name = "NIHIL_TEST_VAR"; @@ -48,3 +44,5 @@ TEST_CASE("getenv: long value") REQUIRE(s); REQUIRE(*s == value); } + +} // anonymous namespace diff --git a/nihil.posix/open.ccm b/nihil.posix/open.ccm index 59f80af..f2f5ecd 100644 --- a/nihil.posix/open.ccm +++ b/nihil.posix/open.ccm @@ -1,20 +1,20 @@ // This source code is released into the public domain. module; -#include -#include - #include #include export module nihil.posix:open; +import nihil.std; import nihil.error; import nihil.flagset; +import nihil.util; import :fd; namespace nihil { +// Flags which can be passed to open(). struct open_flags_tag { }; @@ -70,7 +70,7 @@ export [[nodiscard]] auto open(std::filesystem::path const &filename, open_flags if (fdno != -1) return fd(fdno); - return std::unexpected(error(std::errc(errno))); + return error(sys_error()); } // Like open(), but resolve relative to an open file descriptor, which must refer to a directory. @@ -81,7 +81,7 @@ export [[nodiscard]] auto openat(fd &where, std::filesystem::path const &filenam if (fdno != -1) return fd(fdno); - return std::unexpected(error(std::errc(errno))); + return error(sys_error()); } } // namespace nihil diff --git a/nihil.posix/open.test.cc b/nihil.posix/open.test.cc index bb8bcc9..e49f4c4 100644 --- a/nihil.posix/open.test.cc +++ b/nihil.posix/open.test.cc @@ -2,6 +2,7 @@ #include +import nihil.std; import nihil.error; import nihil.posix; diff --git a/nihil.posix/open_in_path.ccm b/nihil.posix/open_in_path.ccm index e8c1761..0733c8d 100644 --- a/nihil.posix/open_in_path.ccm +++ b/nihil.posix/open_in_path.ccm @@ -1,20 +1,12 @@ // This source code is released into the public domain. -module; - -#include -#include -#include -#include -#include - -#include - export module nihil.posix:open_in_path; +import nihil.std; import nihil.error; import :fd; import :getenv; import :open; +import :paths; namespace nihil { @@ -51,7 +43,7 @@ open_in_path(std::filesystem::path const &file, std::string_view path) -> std::e export [[nodiscard]] auto open_in_path(std::filesystem::path const &file) -> std::expected { - auto const path = getenv("PATH").value_or(_PATH_DEFPATH); // NOLINT + auto const path = getenv("PATH").value_or(std::string(paths::defpath)); // NOLINT return open_in_path(file, path); } diff --git a/nihil.posix/open_in_path.test.cc b/nihil.posix/open_in_path.test.cc index 13d6b49..ebd1405 100644 --- a/nihil.posix/open_in_path.test.cc +++ b/nihil.posix/open_in_path.test.cc @@ -2,6 +2,7 @@ #include +import nihil.std; import nihil.error; import nihil.posix; diff --git a/nihil.posix/paths.ccm b/nihil.posix/paths.ccm new file mode 100644 index 0000000..775e566 --- /dev/null +++ b/nihil.posix/paths.ccm @@ -0,0 +1,27 @@ +// This source code is released into the public domain. +module; + +#include + +export module nihil.posix:paths; + +import nihil.std; + +namespace nihil::paths { + +export inline constexpr auto defpath = std::string_view(_PATH_DEFPATH); +export inline constexpr auto stdpath = std::string_view(_PATH_STDPATH); + +#ifdef _PATH_LOCALBASE +export inline constexpr auto localbase = std::string_view(_PATH_LOCALBASE); +#else +export inline constexpr auto localbase = std::string_view("/usr/local"); +#endif + +#ifdef _PATH_SYSPATH +export inline constexpr auto syspath = std::string_view(_PATH_SYSPATH); +#else +export inline constexpr auto syspath = std::string_view("/sbin:/usr/sbin"); +#endif + +} // namespace nihil::paths diff --git a/nihil.posix/posix.ccm b/nihil.posix/posix.ccm index c49a992..aa21649 100644 --- a/nihil.posix/posix.ccm +++ b/nihil.posix/posix.ccm @@ -13,16 +13,16 @@ export import :execshell; export import :execv; export import :execvp; export import :fd; -export import :fexecv; -export import :fexecvp; export import :find_in_path; export import :getenv; export import :open; export import :open_in_path; +export import :paths; export import :process; export import :read_file; export import :rename; export import :spawn; export import :stat; export import :tempfile; +export import :unlink; export import :write_file; diff --git a/nihil.posix/process.ccm b/nihil.posix/process.ccm index ee7de15..9fbf34c 100644 --- a/nihil.posix/process.ccm +++ b/nihil.posix/process.ccm @@ -1,19 +1,17 @@ // This source code is released into the public domain. module; -#include -#include -#include -#include - #include export module nihil.posix:process; +import nihil.std; import nihil.error; namespace nihil { +export struct process; + // wait_result: the exit status of a process. export struct wait_result final { @@ -58,7 +56,7 @@ private: }; // Represents a process we created, which can be waited for. -export struct process final +struct process final { process() = delete; @@ -83,7 +81,8 @@ export struct process final // Movable. process(process &&other) noexcept : m_pid(std::exchange(other.m_pid, -1)) - {} + { + } auto operator=(this process &self, process &&other) noexcept -> process & { @@ -113,7 +112,7 @@ export struct process final auto status = int{}; auto ret = waitpid(self.m_pid, &status, 0); if (ret == -1) - return std::unexpected(error(std::errc(errno))); + return error(sys_error()); return wait_result(status); } diff --git a/nihil.posix/read_file.ccm b/nihil.posix/read_file.ccm index 3b4fd5b..61c5085 100644 --- a/nihil.posix/read_file.ccm +++ b/nihil.posix/read_file.ccm @@ -1,20 +1,4 @@ -/* - * This source code is released into the public domain. - */ - -module; - -#include -#include -#include -#include -#include -#include -#include - -#include -#include - +// This source code is released into the public domain. export module nihil.posix:read_file; import nihil.error; @@ -24,12 +8,9 @@ import :open; namespace nihil { -/* - * Read the contents of a file into an output iterator. - */ +// Read the contents of a file into an output iterator. export [[nodiscard]] auto -read_file(std::filesystem::path const &filename, - std::output_iterator auto &&iter) +read_file(std::filesystem::path const &filename, std::output_iterator auto &&iter) -> std::expected { auto file = co_await open(filename, open_read); @@ -38,7 +19,7 @@ read_file(std::filesystem::path const &filename, auto buffer = std::array{}; for (;;) { - auto read_buf = co_await(read(file, buffer)); + auto read_buf = co_await (read(file, buffer)); if (read_buf.empty()) co_return {}; diff --git a/nihil.posix/rename.ccm b/nihil.posix/rename.ccm index a1b292e..c46005e 100644 --- a/nihil.posix/rename.ccm +++ b/nihil.posix/rename.ccm @@ -1,12 +1,9 @@ // This source code is released into the public domain. -module; - -#include -#include - export module nihil.posix:rename; +import nihil.std; import nihil.error; +import nihil.util; namespace nihil { @@ -15,12 +12,11 @@ export [[nodiscard]] auto rename(std::filesystem::path const &oldp, std::filesystem::path const &newp) -> std::expected { - auto err = std::error_code(); - - std::filesystem::rename(oldp, newp, err); + auto guard = save_errno(); - if (err) - return std::unexpected(error(err)); + auto const ret = std::rename(oldp.string().c_str(), newp.string().c_str()); + if (ret == -1) + return std::unexpected(error(sys_error())); return {}; } diff --git a/nihil.posix/spawn.ccm b/nihil.posix/spawn.ccm index a185bb3..1e4102a 100644 --- a/nihil.posix/spawn.ccm +++ b/nihil.posix/spawn.ccm @@ -1,52 +1,27 @@ // This source code is released into the public domain. -module; - -/* - * spawn(): fork and execute a child process. - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include - -#include -#include - export module nihil.posix:spawn; +// spawn(): fork and execute a child process. + import nihil.monad; import :argv; import :executor; +import :fd; import :open; import :process; +import :unistd; namespace nihil { -// Useful constants -export inline int constexpr stdin_fileno = STDIN_FILENO; -export inline int constexpr stdout_fileno = STDOUT_FILENO; -export inline int constexpr stderr_fileno = STDERR_FILENO; - -/* - * fd_{read,write}_pipe: create a pipe with one end in the child and the other in the parent. - * The child's side will be dup2()'d to the provided fd number. - * The parent side fd can be retrieved via parent_fd(). - * - * fd_read_pipe() puts the reading side in the child, while fd_write_pipe() puts the writing - * side in the child. - */ +// fd_{read,write}_pipe: create a pipe with one end in the child and the other in the parent. +// The child's side will be dup2()'d to the provided fd number. +// The parent side fd can be retrieved via parent_fd(). +// +// fd_read_pipe() puts the reading side in the child, while fd_write_pipe() puts the writing +// side in the child. -struct fd_pipe_base { +struct fd_pipe_base +{ fd_pipe_base(int fdno, fd &&child_fd, fd &&parent_fd) : m_fdno(fdno) , m_child_fd(std::move(child_fd)) @@ -58,8 +33,8 @@ struct fd_pipe_base { { auto err = raw_dup(self.m_child_fd, self.m_fdno); if (!err) { - std::print("dup: {}\n", err.error()); - _exit(1); + std::println("dup: {}", err.error()); + std::quick_exit(1); } /* @@ -81,35 +56,34 @@ struct fd_pipe_base { } private: - int m_fdno; - fd m_child_fd; - fd m_parent_fd; - + int m_fdno; + fd m_child_fd; + fd m_parent_fd; }; -export struct fd_read_pipe final : fd_pipe_base { +export struct fd_read_pipe final : fd_pipe_base +{ fd_read_pipe(int fdno, fd &&read_fd, fd &&write_fd) : fd_pipe_base(fdno, std::move(read_fd), std::move(write_fd)) { } }; -export struct fd_write_pipe final : fd_pipe_base { +export struct fd_write_pipe final : fd_pipe_base +{ fd_write_pipe(int fdno, fd &&read_fd, fd &&write_fd) : fd_pipe_base(fdno, std::move(write_fd), std::move(read_fd)) { } }; -export [[nodiscard]] auto -make_fd_read_pipe(int fdno) -> std::expected +export [[nodiscard]] auto make_fd_read_pipe(int fdno) -> std::expected { auto fds = co_await pipe(); co_return fd_read_pipe(fdno, std::move(fds.first), std::move(fds.second)); } -export [[nodiscard]] auto -make_fd_write_pipe(int fdno) -> std::expected +export [[nodiscard]] auto make_fd_write_pipe(int fdno) -> std::expected { auto fds = co_await pipe(); co_return fd_write_pipe(fdno, std::move(fds.first), std::move(fds.second)); @@ -119,7 +93,8 @@ make_fd_write_pipe(int fdno) -> std::expected * fd_file: open a file and provide it to the child as a file descriptor. * open_flags and open_mode are as for ::open(). */ -export struct fd_file final { +export struct fd_file final +{ fd_file(int fdno, fd &&file_fd) : m_fdno(fdno) , m_file_fd(std::move(file_fd)) @@ -136,20 +111,19 @@ export struct fd_file final { auto err = raw_dup(self.m_file_fd, self.m_fdno); if (!err) { std::print("dup: {}\n", err.error()); - _exit(1); + std::quick_exit(1); } std::ignore = self.m_file_fd.close(); } private: - int m_fdno; - fd m_file_fd; + int m_fdno; + fd m_file_fd; }; export [[nodiscard]] auto -make_fd_file(int fdno, std::filesystem::path const &file, - open_flags flags, int mode = 0777) +make_fd_file(int fdno, std::filesystem::path const &file, open_flags flags, int mode = 0777) -> std::expected { auto fd = co_await open(file, flags, mode); @@ -160,20 +134,17 @@ make_fd_file(int fdno, std::filesystem::path const &file, * Shorthand for fd_file with /dev/null as the file. */ -export [[nodiscard]] inline auto -stdin_devnull() -> std::expected +export [[nodiscard]] inline auto stdin_devnull() -> std::expected { return make_fd_file(stdin_fileno, "/dev/null", open_read); } -export [[nodiscard]] inline auto -stdout_devnull() -> std::expected +export [[nodiscard]] inline auto stdout_devnull() -> std::expected { return make_fd_file(stdout_fileno, "/dev/null", open_write); } -export [[nodiscard]] inline auto -stderr_devnull() -> std::expected +export [[nodiscard]] inline auto stderr_devnull() -> std::expected { return make_fd_file(stderr_fileno, "/dev/null", open_write); } @@ -182,8 +153,9 @@ stderr_devnull() -> std::expected * Capture the output of a pipe in the parent and read it into an * output iterator. */ -export template Iterator> -struct fd_capture final { +export template Iterator> +struct fd_capture final +{ fd_capture(fd_write_pipe &&pipe, Iterator it) : m_pipe(std::move(pipe)) , m_iterator(std::move(it)) @@ -204,7 +176,7 @@ struct fd_capture final { auto &fd = self.m_pipe.parent_fd(); for (;;) { auto ret = read(fd, buffer); - if (!ret || ret->size() == 0) + if (!ret || ret->empty()) break; std::ranges::copy(*ret, self.m_iterator); @@ -215,21 +187,18 @@ struct fd_capture final { } private: - fd_write_pipe m_pipe; - Iterator m_iterator; + fd_write_pipe m_pipe; + Iterator m_iterator; }; -export [[nodiscard]] auto -make_capture(int fdno, std::output_iterator auto &&it) +export [[nodiscard]] auto make_capture(int fdno, std::output_iterator auto &&it) -> std::expected, error> { auto pipe = co_await make_fd_write_pipe(fdno); - co_return fd_capture(std::move(pipe), - std::forward(it)); + co_return fd_capture(std::move(pipe), std::forward(it)); } -export [[nodiscard]] auto -make_capture(int fdno, std::string &str) +export [[nodiscard]] auto make_capture(int fdno, std::string &str) -> std::expected, error> { auto pipe = co_await make_fd_write_pipe(fdno); @@ -241,14 +210,9 @@ make_capture(int fdno, std::string &str) * Throws exec_error() on failure. */ export [[nodiscard]] auto -spawn(executor auto &&executor, auto &&...actions) - -> std::expected +spawn(executor auto &&executor, auto &&...actions) -> std::expected { - auto const pid = ::fork(); - if (pid == -1) - return std::unexpected(error("fork failed", - error(std::errc(errno)))); - + auto const pid = co_await fork(); auto proc = process(pid); if (pid == 0) { @@ -257,13 +221,13 @@ spawn(executor auto &&executor, auto &&...actions) std::ignore = std::move(proc).release(); auto err = executor.exec(); - std::print("{}\n", err.error()); - _exit(1); + std::println("{}", err.error()); + std::quick_exit(1); } (actions.run_in_parent(proc), ...); - return proc; + co_return proc; } } // namespace nihil diff --git a/nihil.posix/stat.ccm b/nihil.posix/stat.ccm index 6a0cabf..ee8113b 100644 --- a/nihil.posix/stat.ccm +++ b/nihil.posix/stat.ccm @@ -3,13 +3,12 @@ module; // Basic wrappers around stat() and fstat(). -#include -#include - #include export module nihil.posix:stat; +import nihil.std; +import nihil.util; import :fd; namespace nihil { @@ -17,19 +16,23 @@ namespace nihil { export [[nodiscard]] auto stat(std::filesystem::path const &path) -> std::expected { + auto guard = save_errno(); + struct ::stat sb{}; auto ret = ::stat(path.string().c_str(), &sb); if (ret == -1) - return std::unexpected(error(std::errc(errno))); + return std::unexpected(error(sys_error())); return sb; } export [[nodiscard]] auto stat(fd const &fd) -> std::expected { + auto guard = save_errno(); + struct ::stat sb{}; auto ret = ::fstat(fd.get(), &sb); if (ret == -1) - return std::unexpected(error(std::errc(errno))); + return std::unexpected(error(sys_error())); return sb; } diff --git a/nihil.posix/stat.test.cc b/nihil.posix/stat.test.cc index cf1e29c..535273b 100644 --- a/nihil.posix/stat.test.cc +++ b/nihil.posix/stat.test.cc @@ -1,5 +1,6 @@ // This source code is released into the public domain. +import nihil.std; import nihil.error; import nihil.posix; diff --git a/nihil.posix/tempfile.ccm b/nihil.posix/tempfile.ccm index e1510e5..121c636 100644 --- a/nihil.posix/tempfile.ccm +++ b/nihil.posix/tempfile.ccm @@ -1,22 +1,15 @@ // This source code is released into the public domain. -module; - -// tempfile: create a temporary file. - -#include -#include -#include -#include -#include -#include - export module nihil.posix:tempfile; +import nihil.std; import nihil.error; import nihil.flagset; import :fd; import :getenv; import :open; +import :unlink; + +// tempfile: create a temporary file. namespace nihil { @@ -60,9 +53,7 @@ export struct temporary_file final throw std::logic_error("release() called on already-released tempfile"); if (!self.m_path.empty()) { - auto ec = std::error_code(); // ignore errors - remove(self.path(), ec); - + std::ignore = unlink(self.path()); self.m_path.clear(); } @@ -141,8 +132,7 @@ tempfile(tempfile_flags flags = tempfile_none) -> std::expected +// This source code is released into the public domain. #include +import nihil.std; import nihil.posix; TEST_CASE("posix.tempfile: create", "[nihil][nihil.posix]") diff --git a/nihil.posix/unistd.ccm b/nihil.posix/unistd.ccm new file mode 100644 index 0000000..14c19ee --- /dev/null +++ b/nihil.posix/unistd.ccm @@ -0,0 +1,23 @@ +// This source code is released into the public domain. +module; + +#include + +export module nihil.posix:unistd; + +import nihil.std; +import nihil.error; + +// Symbols from unistd.h that might be useful. + +namespace nihil { + +export [[nodiscard]] auto fork() -> std::expected<::pid_t, error> +{ + auto const pid = ::fork(); + if (pid == -1) + return std::unexpected(error(sys_error())); + return pid; +} + +}; diff --git a/nihil.posix/unlink.ccm b/nihil.posix/unlink.ccm new file mode 100644 index 0000000..d6c47cd --- /dev/null +++ b/nihil.posix/unlink.ccm @@ -0,0 +1,28 @@ +// This source code is released into the public domain. +module; + +#include + +export module nihil.posix:unlink; + +// unlink: simple wrapper around ::unlink() + +import nihil.std; +import nihil.error; +import nihil.util; + +namespace nihil { + +export [[nodiscard]] auto unlink(std::filesystem::path const &path) + -> std::expected +{ + auto guard = save_errno(); + + auto const ret = ::unlink(path.string().c_str()); + if (ret == 0) + return {}; + + return std::unexpected(error(sys_error())); +} + +} // namespace nihil diff --git a/nihil.posix/write_file.ccm b/nihil.posix/write_file.ccm index ce21e6b..4bdd6e2 100644 --- a/nihil.posix/write_file.ccm +++ b/nihil.posix/write_file.ccm @@ -1,34 +1,19 @@ -/* - * 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.posix:write_file; +import nihil.std; import nihil.error; import nihil.guard; import nihil.monad; import :fd; import :open; import :rename; +import :unlink; namespace nihil { -/* - * Write the contents of a range to a file. Returns the number of bytes - * written. - */ +// Write the contents of a range to a file. Returns the number of bytes +// written. export [[nodiscard]] auto write_file(std::filesystem::path const &filename, std::ranges::contiguous_range auto &&range, @@ -40,9 +25,7 @@ auto write_file(std::filesystem::path const &filename, co_return nbytes; } -/* - * Utility wrapper for non-contiguous ranges. - */ +// Utility wrapper for non-contiguous ranges. export [[nodiscard]] auto write_file(std::filesystem::path const &filename, std::ranges::range auto &&range) @@ -52,12 +35,10 @@ requires(!std::ranges::contiguous_range) return write_file(filename, std::vector(std::from_range, range)); } -/* - * Write the contents of a range to a file safely. The data will be written - * to ".tmp", and if the write succeeds, the temporary file will be - * renamed to the target filename. If an error occurs, the target file will - * not be modified. - */ +// Write the contents of a range to a file safely. The data will be written +// to ".tmp", and if the write succeeds, the temporary file will be +// renamed to the target filename. If an error occurs, the target file will +// not be modified. export [[nodiscard]] auto safe_write_file(std::filesystem::path const &filename, std::ranges::range auto &&range) @@ -68,11 +49,11 @@ auto safe_write_file(std::filesystem::path const &filename, tmpfile /= (filename.filename().native() + ".tmp"); auto tmpfile_guard = guard([&tmpfile] { - ::unlink(tmpfile.c_str()); + std::ignore = unlink(tmpfile.c_str()); }); co_await write_file(tmpfile, range); - co_await nihil::rename(tmpfile, filename); + co_await rename(tmpfile, filename); tmpfile_guard.release(); co_return {}; diff --git a/nihil.std/CMakeLists.txt b/nihil.std/CMakeLists.txt new file mode 100644 index 0000000..27209a0 --- /dev/null +++ b/nihil.std/CMakeLists.txt @@ -0,0 +1,7 @@ +# This source code is released into the public domain. + +add_library(nihil.std STATIC) +target_sources(nihil.std + PUBLIC FILE_SET modules TYPE CXX_MODULES FILES + nihil.std.ccm +) diff --git a/nihil.std/nihil.std.ccm b/nihil.std/nihil.std.ccm new file mode 100644 index 0000000..2f01c91 --- /dev/null +++ b/nihil.std/nihil.std.ccm @@ -0,0 +1,429 @@ +// This source code is released into the public domain. +module; + +// Export the parts of std that nihil uses. This is technically undefined behaviour since we're +// modifying namespace std, but this is essentially the same as what clang's own std.cppm does. +// This module could be removed if/when we get support for std.cppm in both FreeBSD and CMake. + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +export module nihil.std; + +// NOLINTBEGIN(misc-unused-using-decls,misc-unused-using-namespaces,misc-unused-alias-decls) + +export namespace std { +// Symbols declared in multiple headers +using std::operator+; +using std::operator-; +using std::operator|; +using std::operator|=; +using std::operator&; +using std::operator&=; +using std::operator^; +using std::operator^=; +using std::operator~; +using std::operator<=>; +using std::operator==; +using std::operator!=; +using std::operator<; +using std::operator<=; +using std::operator>; +using std::operator>=; +using std::operator<<; +using std::operator>>; +using std::operator<<=; +using std::operator>>=; +using std::swap; + +// +using std::all_of; +using std::copy; +using std::fill; +using std::fill_n; +using std::find; +using std::find_if; +using std::find_if_not; +using std::ranges::generate; +using std::generate_n; +using std::min; +using std::max; + +namespace ranges { +using std::ranges::all_of; +using std::ranges::copy; +using std::ranges::equal; +using std::ranges::fill; +using std::ranges::fill_n; +using std::ranges::find; +using std::ranges::find_if; +using std::ranges::find_if_not; +using std::ranges::generate; +using std::ranges::generate_n; +} // namespace ranges + +// +using std::array; + +// +using std::from_chars; +using std::to_chars; +using std::to_chars_result; +using std::chars_format; + +// +using std::partial_ordering; +using std::strong_ordering; +using std::cmp_equal; +using std::cmp_greater; +using std::cmp_greater_equal; +using std::cmp_less; +using std::cmp_less_equal; +using std::cmp_not_equal; + +// +using std::convertible_to; +using std::copy_constructible; +using std::copy_constructible; +using std::default_initializable; +using std::derived_from; +using std::destructible; +using std::equality_comparable; +using std::integral; +using std::invocable; +using std::move_constructible; +using std::regular; +using std::same_as; +using std::semiregular; +using std::swappable; +using std::totally_ordered; + +// +using std::coroutine_handle; +using std::coroutine_traits; +using std::noop_coroutine; +using std::suspend_always; +using std::suspend_never; + +// +using std::byte; +using std::ptrdiff_t; +using std::size_t; +using std::to_integer; + +// +using std::int8_t; +using std::int16_t; +using std::int32_t; +using std::int64_t; +using std::uint8_t; +using std::uint16_t; +using std::uint32_t; +using std::uint64_t; + +// +using std::exit; +using std::quick_exit; + +// +using std::FILE; +using std::fprintf; +using std::printf; +using std::rename; + +// +using std::strerror; + +// +using std::current_exception; +using std::exception; +using std::exception_ptr; +using std::rethrow_exception; + +// +using std::unexpected; +using std::bad_expected_access; +using std::unexpect; +using std::unexpect_t; +using std::expected; + +// +namespace filesystem { +using std::filesystem::path; +using std::filesystem::create_directories; +using std::filesystem::exists; +} + +// +using std::format; +using std::formatter; +using std::format_to; +using std::format_to_n; +using std::runtime_format; +using std::format_error; + +// +using std::function; +using std::invoke; +using std::ref; +using std::cref; +using std::reference_wrapper; + +// +using std::cerr; +using std::cin; +using std::clog; +using std::cout; +using std::basic_ostream; +using std::ostream; +using std::istream; +using std::basic_istream; +using std::streambuf; +using std::ostream_iterator; + +// +using std::initializer_list; + +// +using std::back_insert_iterator; +using std::back_inserter; +using std::input_iterator; +using std::input_iterator_tag; +using std::iter_value_t; +using std::output_iterator; +using std::sentinel_for; +using std::next; +using std::prev; +using std::begin; +using std::end; +using std::distance; + +// +using std::numeric_limits; + +// +using std::list; + +// +using std::locale; +using std::ctype; +using std::ctype_base; +using std::ctype_byname; +using std::has_facet; +using std::isalnum; +using std::isalpha; +using std::isblank; +using std::iscntrl; +using std::isdigit; +using std::isgraph; +using std::islower; +using std::isprint; +using std::ispunct; +using std::isspace; +using std::isupper; +using std::isxdigit; +using std::locale; +using std::tolower; +using std::toupper; +using std::use_facet; + +// map +using std::map; + +// +using std::addressof; +using std::allocator; +using std::allocator_arg; +using std::allocator_arg_t; +using std::allocator_traits; +using std::make_shared; +using std::make_unique; +using std::shared_ptr; +using std::unique_ptr; + +// +using std::nullopt; +using std::optional; + +// +using std::print; +using std::println; + +// +using std::default_random_engine; +using std::random_device; +using std::uniform_int_distribution; +using std::mt19937; +using std::ranlux24;; +using std::ranlux24_base; +using std::ranlux48; +using std::ranlux48_base; + +using std::seed_seq; + +// +using std::from_range; +using std::from_range_t; + +namespace ranges { +using std::ranges::range_value_t; + +using std::ranges::contiguous_range; +using std::ranges::enable_view; +using std::ranges::range; +using std::ranges::sized_range; + +using std::ranges::begin; +using std::ranges::empty; +using std::ranges::end; +using std::ranges::data; +using std::ranges::size; +using std::ranges::rbegin; +using std::ranges::rend; +using std::ranges::subrange; + +using std::ranges::split_view; +using std::ranges::transform_view; +using std::ranges::operator|; + +namespace views { +using std::ranges::views::split; +using std::ranges::views::transform; +} // namespace views + +} // namespace ranges + +namespace views = ranges::views; + +// +using std::set; + +// +using std::as_bytes; +using std::as_writable_bytes; +using std::dynamic_extent; +using std::span; + +// +using std::basic_istringstream; +using std::basic_ostringstream; +using std::istringstream; +using std::ostringstream; + +// +using std::logic_error; +using std::runtime_error; +using std::out_of_range; + +// +using std::basic_string; +using std::basic_string_view; +using std::char_traits; +using std::string; +using std::string_view; +using std::wstring; +using std::wstring_view; + +inline namespace literals { +inline namespace string_literals { +using std::literals::string_literals::operator""s; +using std::literals::string_view_literals::operator""sv; +} +} + +// +using std::errc; +using std::error_category; +using std::error_code; +using std::error_condition; +using std::is_error_code_enum; +using std::is_error_condition_enum; +using std::make_error_code; +using std::make_error_condition; +using std::system_error; + +// +using std::add_pointer_t; +using std::false_type; +using std::invoke_result; +using std::is_convertible; +using std::is_convertible_v; +using std::is_default_constructible; +using std::is_default_constructible_v; +using std::is_nothrow_constructible; +using std::is_nothrow_constructible_v; +using std::is_nothrow_destructible; +using std::is_nothrow_destructible_v; +using std::is_nothrow_move_constructible; +using std::is_nothrow_move_constructible_v; +using std::is_reference; +using std::is_reference_v; +using std::is_same; +using std::is_same_v; +using std::remove_const_t; +using std::remove_cv_t; +using std::remove_cvref_t; +using std::true_type; + +// +using std::unordered_set; + +// +using std::exchange; +using std::forward; +using std::hash; +using std::ignore; +using std::make_pair; +using std::move; +using std::pair; + +// +using std::get_if; +using std::monostate; +using std::variant; +using std::visit; + +// +using std::vector; + +} // namespace std + +// NOLINTEND(misc-unused-using-decls,misc-unused-using-namespaces,misc-unused-alias-decls) diff --git a/nihil.ucl/CMakeLists.txt b/nihil.ucl/CMakeLists.txt index 9d8ab3a..5b8ed72 100644 --- a/nihil.ucl/CMakeLists.txt +++ b/nihil.ucl/CMakeLists.txt @@ -3,13 +3,12 @@ pkg_check_modules(LIBUCL REQUIRED libucl) add_library(nihil.ucl STATIC) -target_link_libraries(nihil.ucl PRIVATE nihil.error nihil.monad) +target_link_libraries(nihil.ucl PRIVATE nihil.std nihil.core nihil.error nihil.monad) target_sources(nihil.ucl PUBLIC FILE_SET modules TYPE CXX_MODULES FILES nihil.ucl.ccm emit.ccm - errc.ccm object.ccm object_cast.ccm parser.ccm @@ -21,18 +20,6 @@ target_sources(nihil.ucl map.ccm real.ccm string.ccm - - PRIVATE - emit.cc - errc.cc - parser.cc - type.cc - - object.cc - boolean.cc - integer.cc - real.cc - string.cc ) target_compile_options(nihil.ucl PUBLIC ${LIBUCL_CFLAGS_OTHER}) @@ -41,6 +28,23 @@ target_link_libraries(nihil.ucl PUBLIC ${LIBUCL_LIBRARIES}) target_link_directories(nihil.ucl PUBLIC ${LIBUCL_LIBRARY_DIRS}) if(NIHIL_TESTS) - add_subdirectory(tests) + add_executable(nihil.ucl.test + array.test.cc + boolean.test.cc + emit.test.cc + integer.test.cc + map.test.cc + object.test.cc + parse.test.cc + real.test.cc + string.test.cc + ) + + target_link_libraries(nihil.ucl.test PRIVATE nihil.ucl Catch2::Catch2WithMain) + + include(CTest) + include(Catch) + catch_discover_tests(nihil.ucl.test) + enable_testing() endif() diff --git a/nihil.ucl/array.ccm b/nihil.ucl/array.ccm index e3730ab..3d211d5 100644 --- a/nihil.ucl/array.ccm +++ b/nihil.ucl/array.ccm @@ -1,165 +1,119 @@ -/* - * This source code is released into the public domain. - */ - +// This source code is released into the public domain. module; -#include -#include -#include -#include -#include -#include -#include -#include -#include - #include export module nihil.ucl:array; +import nihil.std; import :object; namespace nihil::ucl { -export template +export template struct array; -export template -struct array_iterator { +// +// The array iterator. This is hardened, i.e. it always checks bounds. +// +export template +struct array_iterator +{ using difference_type = std::ptrdiff_t; using value_type = T; - using reference = T&; - using pointer = T*; + using reference = T &; + using pointer = T *; array_iterator() = default; - [[nodiscard]] auto operator* (this array_iterator const &self) -> T + // Return the value at this position. We don't do type checking here + // since we assume that was done when the array was created or cast. + [[nodiscard]] auto operator*(this array_iterator const &self) -> T { auto arr = self.get_array(); if (self.m_idx >= ::ucl_array_size(arr)) - throw std::logic_error( - "nihil::ucl::array_iterator: " - "access past end of array"); + throw std::logic_error("nihil::ucl::array_iterator: " + "access past end of array"); auto uobj = ::ucl_array_find_index(arr, self.m_idx); if (uobj == nullptr) - throw std::runtime_error( - "nihil::ucl::array_iterator: " - "failed to fetch UCL array index"); + throw std::runtime_error("nihil::ucl::array_iterator: " + "failed to fetch UCL array index"); - return T(nihil::ucl::ref, uobj); + return T(ref, uobj); } - [[nodiscard]] auto operator[] (this array_iterator const &self, - difference_type idx) - -> T + // Return the value at an offset. + [[nodiscard]] auto operator[](this array_iterator const &self, difference_type idx) -> T { return *(self + idx); } - auto operator++ (this array_iterator &self) -> array_iterator & + // Advance this iterator. + auto operator++(this array_iterator &self) -> array_iterator & { auto arr = self.get_array(); + // If we're already at end, don't allow going any further. if (self.m_idx == ::ucl_array_size(arr)) - throw std::logic_error( - "nihil::ucl::array_iterator: " - "iterating past end of array"); + throw std::logic_error("nihil::ucl::array_iterator: " + "iterating past end of array"); ++self.m_idx; return self; } - auto operator++ (this array_iterator &self, int) -> array_iterator + auto operator++(this array_iterator &self, int) -> array_iterator { auto copy = self; ++self; return copy; } - auto operator-- (this array_iterator &self) -> array_iterator& + auto operator--(this array_iterator &self) -> array_iterator & { if (self.m_idx == 0) - throw std::logic_error( - "nihil::ucl::array_iterator: " - "iterating before start of array"); + throw std::logic_error("nihil::ucl::array_iterator: " + "iterating before start of array"); --self.m_idx; return self; } - auto operator-- (this array_iterator &self, int) -> array_iterator + auto operator--(this array_iterator &self, int) -> array_iterator { auto copy = self; --self; return copy; } - [[nodiscard]] auto operator== (this array_iterator const &lhs, - array_iterator const &rhs) - -> bool - { - // Empty iterators should compare equal. - if (lhs.m_array == nullptr && rhs.m_array == nullptr) - return true; - - if (lhs.get_array() != rhs.get_array()) - throw std::logic_error( - "nihil::ucl::array_iterator: " - "comparing iterators of different arrays"); - - return lhs.m_idx == rhs.m_idx; - } - - [[nodiscard]] auto operator<=> (this array_iterator const &lhs, - array_iterator const &rhs) - { - // Empty iterators should compare equal. - if (lhs.m_array == nullptr && rhs.m_array == nullptr) - return std::strong_ordering::equal; - - if (lhs.get_array() != rhs.get_array()) - throw std::logic_error( - "nihil::ucl::array_iterator: " - "comparing iterators of different arrays"); - - return lhs.m_idx <=> rhs.m_idx; - } - - auto operator+= (this array_iterator &lhs, difference_type rhs) - -> array_iterator & + auto operator+=(this array_iterator &lhs, difference_type rhs) -> array_iterator & { auto arr = lhs.get_array(); // m_idx cannot be greater than the array size auto max_inc = ::ucl_array_size(arr) - lhs.m_idx; if (std::cmp_greater(rhs, max_inc)) - throw std::logic_error( - "nihil::ucl::array_iterator: " - "iterating past end of array"); + throw std::logic_error("nihil::ucl::array_iterator: " + "iterating past end of array"); lhs.m_idx += rhs; return lhs; } - auto operator-= (this array_iterator &lhs, difference_type rhs) - -> array_iterator & + auto operator-=(this array_iterator &lhs, difference_type rhs) -> array_iterator & { if (std::cmp_greater(rhs, lhs.m_idx)) - throw std::logic_error( - "nihil::ucl::array_iterator: " - "iterating before start of array"); + throw std::logic_error("nihil::ucl::array_iterator: " + "iterating before start of array"); lhs.m_idx -= rhs; return lhs; } - [[nodiscard]] auto operator- (this array_iterator const &lhs, - array_iterator const &rhs) - -> difference_type + [[nodiscard]] auto + operator-(this array_iterator const &lhs, array_iterator const &rhs) -> difference_type { if (lhs.get_array() != rhs.get_array()) - throw std::logic_error( - "nihil::ucl::array_iterator: " - "comparing iterators of different arrays"); + throw std::logic_error("nihil::ucl::array_iterator: " + "comparing iterators of different arrays"); return lhs.m_idx - rhs.m_idx; } @@ -167,146 +121,148 @@ struct array_iterator { private: friend struct array; - ::ucl_object_t const * m_array{}; - std::size_t m_idx{}; + ::ucl_object_t const *m_array{}; + std::size_t m_idx{}; - [[nodiscard]] auto get_array(this array_iterator const &self) - -> ::ucl_object_t const * + [[nodiscard]] auto get_array(this array_iterator const &self) -> ::ucl_object_t const * { if (self.m_array == nullptr) - throw std::logic_error( - "nihil::ucl::array_iterator: " - "attempt to access an empty iterator"); + throw std::logic_error("nihil::ucl::array_iterator: " + "attempt to access an empty iterator"); return self.m_array; } - - array_iterator(::ucl_object_t const *array, std::size_t idx) + + array_iterator(::ucl_object_t const *array, std::size_t const idx) : m_array(array) , m_idx(idx) - {} -}; + { + } -export template [[nodiscard]] -auto operator+(array_iterator const &lhs, - typename array_iterator::difference_type rhs) --> array_iterator -{ - auto copy = lhs; - copy += rhs; - return copy; -} - -export template [[nodiscard]] -auto operator+(typename array_iterator::difference_type lhs, - array_iterator const &rhs) - -> array_iterator -{ - return rhs - lhs; -} + [[nodiscard]] friend auto + operator==(array_iterator const &lhs, array_iterator const &rhs) -> bool + { + // Empty iterators should compare equal. + if (lhs.m_array == nullptr && rhs.m_array == nullptr) + return true; -export template [[nodiscard]] -auto operator-(array_iterator const &lhs, - typename array_iterator::difference_type rhs) - -> array_iterator -{ - auto copy = lhs; - copy -= rhs; - return copy; -} + if (lhs.get_array() != rhs.get_array()) + throw std::logic_error("nihil::ucl::array_iterator: " + "comparing iterators of different arrays"); -export template -struct array final : object { - inline static constexpr object_type ucl_type = object_type::array; + return lhs.m_idx == rhs.m_idx; + } + + [[nodiscard]] friend auto operator<=>(array_iterator const &lhs, array_iterator const &rhs) + { + // Empty iterators should compare equal. + if (lhs.m_array == nullptr && rhs.m_array == nullptr) + return std::strong_ordering::equal; + + if (lhs.get_array() != rhs.get_array()) + throw std::logic_error("nihil::ucl::array_iterator: " + "comparing iterators of different arrays"); + + return lhs.m_idx <=> rhs.m_idx; + } + + [[nodiscard]] friend auto + operator+(array_iterator const &lhs, difference_type rhs) -> array_iterator + { + auto copy = lhs; + copy += rhs; + return copy; + } + + [[nodiscard]] friend auto + operator+(difference_type lhs, array_iterator const &rhs) -> array_iterator + { + return rhs - lhs; + } + + [[nodiscard]] friend auto + operator-(array_iterator const &lhs, difference_type rhs) -> array_iterator + { + auto copy = lhs; + copy -= rhs; + return copy; + } +}; + +export template +struct array final : object +{ + static constexpr object_type ucl_type = object_type::array; using value_type = T; using size_type = std::size_t; using difference_type = std::ptrdiff_t; using iterator = array_iterator; - /* - * Create an empty array. Throws std::system_error on failure. - */ - array() : object(noref, [] { + // Movable. + array(array &&) noexcept = default; + auto operator=(array &&) noexcept -> array & = default; + + // Copyable. Note that this copies the entire UCL object. + array(array const &) = default; + auto operator=(array const &) -> array & = default; + + ~array() override = default; + + // Create an empty array. Throws std::system_error on failure. + array() + : object(noref, [] { auto *uobj = ::ucl_object_typed_new(UCL_ARRAY); if (uobj == nullptr) - throw std::system_error( - std::make_error_code(std::errc(errno))); + throw std::system_error(std::make_error_code(sys_error())); return uobj; }()) { } - /* - * Create an array from a UCL object. Throws type_mismatch - * on failure. - * - * Unlike object_cast<>, this does not check the type of the contained - * elements, which means object access can throw type_mismatch. - */ + // Create an array from a UCL object. Throws type_mismatch on failure. + // + // Unlike object_cast<>, this does not check the type of the contained + // elements, which means object access can throw type_mismatch. array(ref_t, ::ucl_object_t const *uobj) - : object(nihil::ucl::ref, [&] { - auto actual_type = static_cast( - ::ucl_object_type(uobj)); - if (actual_type != array::ucl_type) - throw type_mismatch(array::ucl_type, - actual_type); - return uobj; - }()) + : object(nihil::ucl::ref, ensure_ucl_type(uobj, array::ucl_type)) { } array(noref_t, ::ucl_object_t *uobj) - : object(nihil::ucl::noref, [&] { - auto actual_type = static_cast( - ::ucl_object_type(uobj)); - if (actual_type != array::ucl_type) - throw type_mismatch(array::ucl_type, - actual_type); - return uobj; - }()) + : object(nihil::ucl::noref, ensure_ucl_type(uobj, array::ucl_type)) { } - /* - * Create an array from an iterator pair. - */ - template - requires(std::convertible_to, T>) - array(Iterator first, Iterator last) - : array() + // Create an array from a range. + template + requires(std::convertible_to, T>) + explicit array(Range const &range) + : array() { // This is exception safe, because if we throw here the // base class destructor will free the array. - while (first != last) { - push_back(*first); - ++first; - } + for (auto &&elm : range) + push_back(elm); } - /* - * Create an array from a range. - */ - template - requires(std::convertible_to, T>) - array(std::from_range_t, Range &&range) - : array(std::ranges::begin(range), - std::ranges::end(range)) + // Create an array from an iterator pair. + template + requires(std::convertible_to, T>) + array(Iterator first, Iterator last) + : array(std::ranges::subrange(first, last)) { } - /* - * Create an array from an initializer_list. - */ + // Create an array from an initializer_list. array(std::initializer_list const &list) - : array(std::ranges::begin(list), - std::ranges::end(list)) + : array(std::ranges::subrange(list)) { } - /* - * Array iterator access. - */ + // + // Array iterator access. + // [[nodiscard]] auto begin(this array const &self) -> iterator { @@ -318,133 +274,111 @@ struct array final : object { return {self.get_ucl_object(), self.size()}; } - /* - * Return the size of this array. - */ + // Return the size of this array. [[nodiscard]] auto size(this array const &self) -> size_type { return ::ucl_array_size(self.get_ucl_object()); } - /* - * Test if this array is empty. - */ + // Test if this array is empty. [[nodiscard]] auto empty(this array const &self) -> bool { return self.size() == 0; } - /* - * Reserve space for future insertions. - */ - auto reserve(this array &self, size_type nelems) -> void + // Reserve space for future insertions. + auto reserve(this array &self, size_type const nelems) -> void { ::ucl_object_reserve(self.get_ucl_object(), nelems); } - /* - * Append an element to the array. - */ + // Append an element to the array. auto push_back(this array &self, value_type const &v) -> void { auto uobj = ::ucl_object_ref(v.get_ucl_object()); ::ucl_array_append(self.get_ucl_object(), uobj); } - /* - * Prepend an element to the array. - */ + // Prepend an element to the array. auto push_front(this array &self, value_type const &v) -> void { auto uobj = ::ucl_object_ref(v.get_ucl_object()); ::ucl_array_prepend(self.get_ucl_object(), uobj); } - /* - * Access an array element by index. - */ - [[nodiscard]] auto at(this array const &self, size_type idx) -> T + // Access an array element by index. + [[nodiscard]] auto at(this array const &self, size_type const idx) -> T { if (idx >= self.size()) throw std::out_of_range("UCL array index out of range"); auto uobj = ::ucl_array_find_index(self.get_ucl_object(), idx); if (uobj == nullptr) - throw std::runtime_error( - "failed to fetch UCL array index"); + throw std::runtime_error("failed to fetch UCL array index"); return T(nihil::ucl::ref, uobj); } - [[nodiscard]] auto operator[] (this array const &self, size_type idx) -> T + [[nodiscard]] auto operator[](this array const &self, size_type const idx) -> T { return self.at(idx); } - /* - * Return the first element. - */ + // Return the first element. [[nodiscard]] auto front(this array const &self) -> T { return self.at(0); } - /* - * Return the last element. - */ + // Return the last element. [[nodiscard]] auto back(this array const &self) -> T { if (self.empty()) throw std::out_of_range("attempt to access back() on " - "empty UCL array"); + "empty UCL array"); return self.at(self.size() - 1); } -}; -/* - * Comparison operators. - */ - -export template [[nodiscard]] -auto operator==(array const &a, array const &b) -> bool -{ - if (a.size() != b.size()) - return false; +private: + // + // Comparison operators. + // - for (typename array::size_type i = 0; i < a.size(); ++i) - if (a.at(i) != b.at(i)) + [[nodiscard]] friend auto operator==(array const &a, array const &b) -> bool + { + if (a.size() != b.size()) return false; - return true; -} + for (size_type i = 0; i < a.size(); ++i) + if (a.at(i) != b.at(i)) + return false; -/* - * Print an array to an ostream; uses the same format as std::format(). - */ -export template -auto operator<<(std::ostream &strm, array const &a) -> std::ostream & -{ - return strm << std::format("{}", a); -} + return true; + } + + // Print an array to an ostream; uses the same format as std::format(). + friend auto operator<<(std::ostream &strm, array const &a) -> std::ostream & + { + return strm << std::format("{}", a); + } +}; } // namespace nihil::ucl -/* - * std::formatter for an array. The output format is a list of values - * on a single line: [1, 2, 3]. - */ -export template +// std::formatter for an array. The output format is a list of values +// on a single line: [1, 2, 3]. +export template struct std::formatter, char> { - template - constexpr ParseContext::iterator parse(ParseContext& ctx) + template + constexpr auto parse(ParseContext &ctx) -> ParseContext::iterator { + (void)this; return ctx.begin(); } - template - FmtContext::iterator format(nihil::ucl::array const &o, - FmtContext& ctx) const + template + auto format(nihil::ucl::array const &o, FmtContext &ctx) const -> FmtContext::iterator { auto it = ctx.out(); bool first = true; diff --git a/nihil.ucl/array.test.cc b/nihil.ucl/array.test.cc new file mode 100644 index 0000000..89394a0 --- /dev/null +++ b/nihil.ucl/array.test.cc @@ -0,0 +1,479 @@ +// This source code is released into the public domain. + +#include +#include + +import nihil.std; +import nihil.ucl; + +namespace { +TEST_CASE("ucl: array: invariants", "[ucl]") +{ + using namespace nihil::ucl; + + REQUIRE(array<>::ucl_type == object_type::array); + REQUIRE(static_cast<::ucl_type>(array<>::ucl_type) == UCL_ARRAY); + + 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::ranges::sized_range>); + static_assert(std::same_as>, integer>); +} + +TEST_CASE("ucl: array: constructor", "[ucl]") +{ + using namespace nihil::ucl; + + SECTION("default") + { + auto arr = array(); + REQUIRE(arr.size() == 0); + REQUIRE(str(arr.type()) == "array"); + } + + SECTION("from range") + { + auto vec = std::vector{integer(1), integer(42)}; + auto arr = array(vec); + + REQUIRE(arr.size() == 2); + REQUIRE(arr[0] == 1); + REQUIRE(arr[1] == 42); + } + + SECTION("from iterator pair") + { + auto vec = std::vector{integer(1), integer(42)}; + auto arr = array(std::ranges::begin(vec), std::ranges::end(vec)); + + REQUIRE(arr.size() == 2); + REQUIRE(arr[0] == 1); + REQUIRE(arr[1] == 42); + } + + SECTION("from initializer_list") + { + auto arr = array{integer(1), integer(42)}; + + REQUIRE(arr.size() == 2); + REQUIRE(arr[0] == 1); + REQUIRE(arr[1] == 42); + } +} + +TEST_CASE("ucl: array: construct from UCL object", "[ucl]") +{ + using namespace nihil::ucl; + + SECTION("ref, correct type") + { + auto *uarr = ::ucl_object_typed_new(UCL_ARRAY); + auto *uint = ::ucl_object_fromint(42); + ::ucl_array_append(uarr, uint); + + auto arr = array(ref, uarr); + REQUIRE(arr[0] == 42); + + ::ucl_object_unref(uarr); + } + + SECTION("noref, correct type") + { + auto *uarr = ::ucl_object_typed_new(UCL_ARRAY); + auto *uint = ::ucl_object_fromint(42); + ::ucl_array_append(uarr, uint); + + auto arr = array(noref, uarr); + REQUIRE(arr[0] == 42); + } + + SECTION("ref, wrong element type") + { + auto *uarr = ::ucl_object_typed_new(UCL_ARRAY); + auto *uint = ::ucl_object_frombool(true); + ::ucl_array_append(uarr, uint); + + auto arr = array(noref, uarr); + REQUIRE_THROWS_AS(arr[0], type_mismatch); + } + + SECTION("ref, wrong type") + { + auto *uobj = ::ucl_object_frombool(true); + + REQUIRE_THROWS_AS(array(ref, uobj), type_mismatch); + + ::ucl_object_unref(uobj); + } + + SECTION("noref, wrong type") + { + auto *uobj = ::ucl_object_frombool(true); + + REQUIRE_THROWS_AS(array(noref, uobj), type_mismatch); + + ::ucl_object_unref(uobj); + } +} + +TEST_CASE("ucl: array: swap", "[ucl]") +{ + // do not add using namespace nihil::ucl + + auto arr1 = nihil::ucl::array{nihil::ucl::integer(1), + nihil::ucl::integer(2)}; + + auto arr2 = nihil::ucl::array{ + nihil::ucl::integer(3), + }; + + swap(arr1, arr2); + + REQUIRE(arr1.size() == 1); + REQUIRE(arr1[0] == 3); + + REQUIRE(arr2.size() == 2); + REQUIRE(arr2[0] == 1); +} + +TEST_CASE("ucl: array: push_back", "[ucl]") +{ + using namespace nihil::ucl; + + auto arr = array(); + REQUIRE(arr.size() == 0); + + arr.push_back(integer(1)); + arr.push_back(integer(42)); + arr.push_back(integer(666)); + + REQUIRE(arr.size() == 3); + REQUIRE(arr[0] == 1); + REQUIRE(arr[1] == 42); + REQUIRE(arr[2] == 666); + + REQUIRE_THROWS_AS(arr[3], std::out_of_range); + + REQUIRE(arr.front() == 1); + REQUIRE(arr.back() == 666); +} + +TEST_CASE("ucl: array: compare", "[ucl]") +{ + using namespace nihil::ucl; + + auto arr = array{integer(1), integer(42), integer(666)}; + + auto arr2 = array(); + REQUIRE(arr != arr2); + + arr2.push_back(integer(1)); + arr2.push_back(integer(42)); + arr2.push_back(integer(666)); + REQUIRE(arr == arr2); + + auto arr3 = array{integer(1), integer(1), integer(1)}; + + REQUIRE(arr != arr3); +} + +TEST_CASE("ucl: array: iterator", "[ucl]") +{ + using namespace nihil::ucl; + + auto arr = array{integer(1), integer(42), integer(666)}; + + auto it = arr.begin(); + REQUIRE(*it == 1); + auto end = arr.end(); + REQUIRE(it != end); + REQUIRE(it < end); + + ++it; + REQUIRE(*it == 42); + + ++it; + REQUIRE(*it == 666); + + --it; + REQUIRE(*it == 42); + + ++it; + REQUIRE(it != end); + ++it; + REQUIRE(it == end); +} + +TEST_CASE("ucl: array: parse", "[ucl]") +{ + using namespace std::literals; + using namespace nihil::ucl; + + auto obj = parse("value = [1, 42, 666]"sv).value(); + + auto arr = object_cast>(obj["value"]).value(); + + REQUIRE(arr.size() == 3); + REQUIRE(arr[0] == 1); + REQUIRE(arr[1] == 42); + REQUIRE(arr[2] == 666); +} + +TEST_CASE("ucl: array: emit", "[ucl]") +{ + using namespace nihil::ucl; + + auto ucl = parse("array = [1, 42, 666];").value(); + + auto output = std::format("{:c}", ucl); + REQUIRE(output == "array [\n" + " 1,\n" + " 42,\n" + " 666,\n" + "]\n"); +} + +TEST_CASE("ucl: array: format", "[ucl]") +{ + using namespace nihil::ucl; + + SECTION("empty array") + { + auto arr = array(); + REQUIRE(std::format("{}", arr) == "[]"); + } + + SECTION("bare array") + { + auto arr = array{integer(1), integer(42), integer(666)}; + + auto output = std::format("{}", arr); + REQUIRE(output == "[1, 42, 666]"); + } + + SECTION("parsed array") + { + auto ucl = parse("array = [1, 42, 666];").value(); + auto arr = object_cast>(ucl["array"]).value(); + + auto output = std::format("{}", arr); + REQUIRE(output == "[1, 42, 666]"); + } +} + +TEST_CASE("ucl: array: print to ostream", "[ucl]") +{ + using namespace nihil::ucl; + + SECTION("empty array") + { + auto arr = array(); + auto strm = std::ostringstream(); + strm << arr; + + REQUIRE(strm.str() == "[]"); + } + + SECTION("bare array") + { + auto arr = array{integer(1), integer(42), integer(666)}; + auto strm = std::ostringstream(); + strm << arr; + + REQUIRE(strm.str() == "[1, 42, 666]"); + } + + SECTION("parsed array") + { + auto ucl = parse("array = [1, 42, 666];").value(); + auto arr = object_cast>(ucl["array"]).value(); + auto strm = std::ostringstream(); + strm << arr; + + REQUIRE(strm.str() == "[1, 42, 666]"); + } +} + +TEST_CASE("ucl: array is a sized_range", "[ucl]") +{ + using namespace nihil::ucl; + + auto arr = array{integer(1), integer(42), integer(666)}; + + auto size = std::ranges::size(arr); + REQUIRE(size == 3); + + auto begin = std::ranges::begin(arr); + static_assert(std::random_access_iterator); + + auto end = std::ranges::end(arr); + static_assert(std::sentinel_for); + + REQUIRE(std::distance(begin, end) == 3); + + auto vec = std::vector(); + std::ranges::copy(arr, std::back_inserter(vec)); + REQUIRE(std::ranges::equal(arr, vec)); + + auto arr_as_ints = arr | std::views::transform(&integer::value); + auto int_vec = std::vector(); + std::ranges::copy(arr_as_ints, std::back_inserter(int_vec)); + REQUIRE(int_vec == std::vector{1, 42, 666}); +} + +TEST_CASE("ucl: array: bad object_cast", "[ucl]") +{ + using namespace nihil::ucl; + + auto arr = array(); + + auto cast_ok = object_cast(arr); + REQUIRE(!cast_ok); +} + +TEST_CASE("ucl: array: heterogeneous elements", "[ucl]") +{ + using namespace std::literals; + using namespace nihil::ucl; + + auto obj_err = parse("array [ 42, true, \"test\" ];"); + REQUIRE(obj_err); + auto obj = *obj_err; + + auto err = object_cast>(obj["array"]); + REQUIRE(err); + + auto arr = *err; + REQUIRE(arr.size() == 3); + + auto int_obj = object_cast(arr[0]); + REQUIRE(int_obj); + REQUIRE(*int_obj == 42); + + auto bool_obj = object_cast(arr[1]); + REQUIRE(bool_obj); + REQUIRE(*bool_obj == true); + + auto string_obj = object_cast(arr[2]); + REQUIRE(string_obj); + REQUIRE(*string_obj == "test"); +} + +TEST_CASE("ucl: array: heterogenous cast", "[ucl]") +{ + using namespace nihil::ucl; + + auto arr = array<>(); + arr.push_back(integer(42)); + arr.push_back(boolean(true)); + + // Converting to an array should fail. + auto cast_ok = object_cast>(arr); + REQUIRE(!cast_ok); + + // Converting to array should succeed. + auto err = object_cast>(arr); + REQUIRE(err); + + auto obj_arr = *err; + REQUIRE(obj_arr[0] == integer(42)); +} + +TEST_CASE("ucl: array: homogeneous cast", "[ucl]") +{ + using namespace nihil::ucl; + + auto arr = array<>(); + arr.push_back(integer(1)); + arr.push_back(integer(42)); + + auto obj = object(ref, arr.get_ucl_object()); + + // Converting to array should fail. + auto cast_ok = object_cast>(obj); + REQUIRE(!cast_ok); + + // Converting to an array should succeed. + auto err = object_cast>(obj); + REQUIRE(err); + + auto obj_arr = *err; + REQUIRE(obj_arr[0] == 1); + REQUIRE(obj_arr[1] == 42); +} + +TEST_CASE("array iterator: empty iterator", "[ucl]") +{ + using namespace nihil::ucl; + + auto it = array_iterator(); + + REQUIRE_THROWS_AS(*it, std::logic_error); + REQUIRE_THROWS_AS(it[0], std::logic_error); + REQUIRE_THROWS_AS(it++, std::logic_error); + REQUIRE_THROWS_AS(++it, std::logic_error); + + auto it2 = array_iterator(); + REQUIRE(it == it2); + REQUIRE((it < it2) == false); + REQUIRE((it > it2) == false); +} + +TEST_CASE("array iterator: invalid operations", "[ucl]") +{ + using namespace nihil::ucl; + + auto arr = array{integer(42)}; + auto it = arr.begin(); + + SECTION("decrement before start") + { + REQUIRE_THROWS_AS(--it, std::logic_error); + REQUIRE_THROWS_AS(it--, std::logic_error); + REQUIRE_THROWS_AS(it - 1, std::logic_error); + } + + SECTION("increment past end") + { + ++it; + REQUIRE(it == arr.end()); + + REQUIRE_THROWS_AS(++it, std::logic_error); + REQUIRE_THROWS_AS(it++, std::logic_error); + REQUIRE_THROWS_AS(it + 1, std::logic_error); + } + + SECTION("dereference iterator at end") + { + REQUIRE_THROWS_AS(it[1], std::logic_error); + + ++it; + REQUIRE(it == arr.end()); + + REQUIRE_THROWS_AS(*it, std::logic_error); + } + + SECTION("compare with different array") + { + auto arr2 = array{integer(42)}; + REQUIRE_THROWS_AS(it == arr2.begin(), std::logic_error); + REQUIRE_THROWS_AS(it > arr2.begin(), std::logic_error); + REQUIRE_THROWS_AS(it - arr2.begin(), std::logic_error); + } + + SECTION("compare with empty iterator") + { + auto it2 = array_iterator(); + REQUIRE_THROWS_AS(it == it2, std::logic_error); + REQUIRE_THROWS_AS(it > it2, std::logic_error); + REQUIRE_THROWS_AS(it - it2, std::logic_error); + } +} + +} // anonymous namespace diff --git a/nihil.ucl/boolean.cc b/nihil.ucl/boolean.cc deleted file mode 100644 index 91f2b17..0000000 --- a/nihil.ucl/boolean.cc +++ /dev/null @@ -1,106 +0,0 @@ -/* - * This source code is released into the public domain. - */ - -module; - -#include -#include -#include -#include - -#include - -module nihil.ucl; - -import nihil.error; - -namespace nihil::ucl { - -auto make_boolean(boolean::contained_type value) - -> std::expected -{ - auto *uobj = ::ucl_object_frombool(value); - if (uobj == nullptr) - return std::unexpected(error( - errc::failed_to_create_object, - error(std::errc(errno)))); - - return boolean(noref, uobj); -} - -boolean::boolean() - : boolean(false) -{ -} - -boolean::boolean(contained_type value) - : object(noref, [&] { - auto *uobj = ::ucl_object_frombool(value); - if (uobj == nullptr) - throw std::system_error( - std::make_error_code(std::errc(errno))); - return uobj; - }()) -{ -} - -boolean::boolean(ref_t, ::ucl_object_t const *uobj) - : object(nihil::ucl::ref, [&] { - auto actual_type = static_cast( - ::ucl_object_type(uobj)); - if (actual_type != boolean::ucl_type) - throw type_mismatch(boolean::ucl_type, actual_type); - return uobj; - }()) -{ -} - -boolean::boolean(noref_t, ::ucl_object_t *uobj) - : object(nihil::ucl::noref, [&] { - auto actual_type = static_cast( - ::ucl_object_type(uobj)); - if (actual_type != boolean::ucl_type) - throw type_mismatch(boolean::ucl_type, actual_type); - return uobj; - }()) -{ -} - -auto boolean::value(this boolean const &self) - -> contained_type -{ - auto v = contained_type{}; - auto const *uobj = self.get_ucl_object(); - - if (::ucl_object_toboolean_safe(uobj, &v)) - return v; - - std::abort(); -} - -auto operator== (boolean const &a, boolean const &b) - -> bool -{ - return a.value() == b.value(); -} - -auto operator<=> (boolean const &a, boolean const &b) - -> std::strong_ordering -{ - return a.value() <=> b.value(); -} - -auto operator== (boolean const &a, boolean::contained_type b) - -> bool -{ - return a.value() == b; -} - -auto operator<=> (boolean const &a, boolean::contained_type b) - -> std::strong_ordering -{ - return a.value() <=> b; -} - -} // namespace nihil::ucl diff --git a/nihil.ucl/boolean.ccm b/nihil.ucl/boolean.ccm index 068dfdd..4cacdc4 100644 --- a/nihil.ucl/boolean.ccm +++ b/nihil.ucl/boolean.ccm @@ -1,90 +1,118 @@ -/* - * This source code is released into the public domain. - */ - +// This source code is released into the public domain. module; -#include -#include -#include -#include -#include -#include - #include export module nihil.ucl:boolean; +import nihil.std; +import nihil.core; import :object; namespace nihil::ucl { -export struct boolean final : object { +export struct boolean final : object +{ using contained_type = bool; - inline static constexpr object_type ucl_type = object_type::boolean; + static constexpr object_type ucl_type = object_type::boolean; + + // Create a boolean holding the value false. Throws std::system_error + // on failure. + boolean() + : boolean(false) + { + } + + // Create a boolean holding a specific value. Throws std::system_error + // on failure. + explicit boolean(bool const value) + : object(noref, [&] { + auto *uobj = ::ucl_object_frombool(value); + if (uobj == nullptr) + throw std::system_error(std::make_error_code(sys_error())); + return uobj; + }()) + { + } - /* - * Create a boolean holding the value false. Throws std::system_error - * on failure. - */ - boolean(); + // Create a new boolean from a UCL object. Throws type_mismatch + // on failure. - /* - * Create a boolean holding a specific value. Throws std::system_error - * on failure. - */ - explicit boolean(bool); + boolean(ref_t, ::ucl_object_t const *uobj) + : object(nihil::ucl::ref, ensure_ucl_type(uobj, boolean::ucl_type)) + { + } - /* - * Create a new boolean from a UCL object. Throws type_mismatch - * on failure. - */ - boolean(ref_t, ::ucl_object_t const *uobj); - boolean(noref_t, ::ucl_object_t *uobj); + boolean(noref_t, ::ucl_object_t *uobj) + : object(nihil::ucl::noref, ensure_ucl_type(uobj, boolean::ucl_type)) + { + } // Return this object's value. - auto value(this boolean const &self) -> contained_type; -}; + auto value(this boolean const &self) -> contained_type + { + auto v = contained_type{}; + auto const *uobj = self.get_ucl_object(); -/* - * Boolean constructors. These return an error instead of throwing. - */ + if (::ucl_object_toboolean_safe(uobj, &v)) + return v; -export [[nodiscard]] auto -make_boolean(boolean::contained_type = false) -> std::expected; + throw std::runtime_error("ucl_object_toboolean_safe failed"); + } + +private: + // Comparison operators. + [[nodiscard]] friend auto operator==(boolean const &a, boolean const &b) -> bool + { + return a.value() == b.value(); + } -/* - * Comparison operators. - */ + [[nodiscard]] friend auto + operator<=>(boolean const &a, boolean const &b) -> std::strong_ordering + { + return static_cast(a.value()) <=> static_cast(b.value()); + } -export auto operator== (boolean const &a, boolean const &b) -> bool; -export auto operator== (boolean const &a, boolean::contained_type b) -> bool; -export auto operator<=> (boolean const &a, boolean const &b) - -> std::strong_ordering; -export auto operator<=> (boolean const &a, boolean::contained_type b) - -> std::strong_ordering; + [[nodiscard]] friend auto operator==(boolean const &a, contained_type const b) -> bool + { + return a.value() == b; + } + + [[nodiscard]] friend auto + operator<=>(boolean const &a, contained_type const b) -> std::strong_ordering + { + return static_cast(a.value()) <=> static_cast(b); + } +}; + +// Boolean constructors. This returns an error instead of throwing. +export [[nodiscard]] auto +make_boolean(boolean::contained_type const value = false) -> std::expected +{ + if (auto *uobj = ::ucl_object_frombool(value); uobj == nullptr) + return error(errc::failed_to_create_object, error(sys_error())); + else + return boolean(noref, uobj); +} } // namespace nihil::ucl -/* - * std::formatter for a boolean. This provides the same format operations - * as std::formatter. - */ -export template<> +// std::formatter for a boolean. This provides the same format operations +// as std::formatter. +export template <> struct std::formatter { std::formatter base_formatter; - template - constexpr ParseContext::iterator parse(ParseContext& ctx) + template + constexpr auto parse(ParseContext &ctx) -> ParseContext::iterator { return base_formatter.parse(ctx); } - template - FmtContext::iterator format(nihil::ucl::boolean const &o, - FmtContext& ctx) const + template + auto format(nihil::ucl::boolean const &o, FmtContext &ctx) const -> FmtContext::iterator { return base_formatter.format(o.value(), ctx); } diff --git a/nihil.ucl/boolean.test.cc b/nihil.ucl/boolean.test.cc new file mode 100644 index 0000000..9fb0148 --- /dev/null +++ b/nihil.ucl/boolean.test.cc @@ -0,0 +1,241 @@ +// This source code is released into the public domain. + +#include +#include + +import nihil.std; +import nihil.ucl; + +namespace { +inline auto constexpr *test_tags = "[nihil][nihil.ucl][nihil.ucl.boolean]"; + +TEST_CASE("ucl: boolean: invariants", test_tags) +{ + using namespace nihil::ucl; + + static_assert(std::same_as); + REQUIRE(boolean::ucl_type == object_type::boolean); + REQUIRE(static_cast<::ucl_type>(boolean::ucl_type) == UCL_BOOLEAN); + + 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); +} + +SCENARIO("Constructing a ucl::boolean", test_tags) +{ + using namespace nihil::ucl; + + GIVEN ("A default-constructed boolean") { + auto b = boolean(); + + THEN ("The value is false") { + REQUIRE(b == false); + } + } + + GIVEN ("A boolean constructed from a true value") { + auto b = boolean(true); + + THEN ("The value is true") { + REQUIRE(b == true); + } + } +} + +TEST_CASE("ucl: boolean: construct from UCL object", "[ucl]") +{ + using namespace nihil::ucl; + + SECTION("ref, correct type") + { + auto *uobj = ::ucl_object_frombool(true); + + auto i = boolean(ref, uobj); + REQUIRE(i == true); + + ::ucl_object_unref(uobj); + } + + SECTION("noref, correct type") + { + auto *uobj = ::ucl_object_frombool(true); + + auto i = boolean(noref, uobj); + REQUIRE(i == true); + } + + SECTION("ref, wrong type") + { + auto *uobj = ::ucl_object_fromint(1); + + REQUIRE_THROWS_AS(boolean(ref, uobj), type_mismatch); + + ::ucl_object_unref(uobj); + } + + SECTION("noref, wrong type") + { + auto *uobj = ::ucl_object_fromint(1); + + REQUIRE_THROWS_AS(boolean(noref, uobj), type_mismatch); + + ::ucl_object_unref(uobj); + } +} + +TEST_CASE("ucl: boolean: make_boolean", "[ucl]") +{ + using namespace nihil::ucl; + + SECTION("default value") + { + auto b = make_boolean().value(); + REQUIRE(b == false); + } + + SECTION("explicit value") + { + auto b = make_boolean(true).value(); + REQUIRE(b == true); + } +} + +TEST_CASE("ucl: boolean: swap", "[ucl]") +{ + // do not add using namespace nihil::ucl + + auto b1 = nihil::ucl::boolean(true); + auto b2 = nihil::ucl::boolean(false); + + swap(b1, b2); + + REQUIRE(b1 == false); + REQUIRE(b2 == true); +} + +TEST_CASE("ucl: boolean: value()", "[ucl]") +{ + auto b = nihil::ucl::boolean(true); + REQUIRE(b.value() == true); +} + +TEST_CASE("ucl: boolean: key()", "[ucl]") +{ + using namespace nihil::ucl; + + auto obj = parse("a_bool = true").value(); + REQUIRE(object_cast(obj["a_bool"]).value().key() == "a_bool"); + + auto b = boolean(true); + REQUIRE(b.key().empty() == true); +} + +TEST_CASE("ucl: boolean: comparison", "[ucl]") +{ + using namespace nihil::ucl; + + auto b = boolean(true); + + SECTION("operator==") + { + REQUIRE(b == true); + REQUIRE(b == boolean(true)); + } + + SECTION("operator!=") + { + REQUIRE(b != false); + REQUIRE(b != boolean(false)); + } + + SECTION("operator<") + { + REQUIRE(b <= true); + REQUIRE(b <= nihil::ucl::boolean(true)); + } + + SECTION("operator>") + { + REQUIRE(b > false); + REQUIRE(b > nihil::ucl::boolean(false)); + } +} + +TEST_CASE("ucl: boolean: parse", "[ucl]") +{ + using namespace nihil::ucl; + + auto obj = parse("value = true").value(); + + auto v = obj["value"]; + REQUIRE(v.key() == "value"); + REQUIRE(object_cast(v).value() == true); +} + +TEST_CASE("ucl: boolean: parse and emit", "[ucl]") +{ + using namespace nihil::ucl; + + auto ucl = parse("bool = true;").value(); + + auto output = std::string(); + emit(ucl, nihil::ucl::emitter::configuration, std::back_inserter(output)); + + REQUIRE(output == "bool = true;\n"); +} + +TEST_CASE("ucl: boolean: format", "[ucl]") +{ + using namespace nihil::ucl; + + SECTION("bare boolean") + { + auto str = std::format("{}", boolean(true)); + REQUIRE(str == "true"); + } + + SECTION("parsed boolean") + { + auto obj = parse("bool = true;").value(); + auto b = object_cast(obj["bool"]).value(); + + auto str = std::format("{}", b); + REQUIRE(str == "true"); + } + + SECTION("with format string") + { + auto str = std::format("{: >5}", boolean(true)); + REQUIRE(str == " true"); + } +} + +TEST_CASE("ucl: boolean: print to ostream", "[ucl]") +{ + using namespace nihil::ucl; + + SECTION("bare boolean") + { + auto strm = std::ostringstream(); + strm << boolean(true); + + REQUIRE(strm.str() == "true"); + } + + SECTION("parsed boolean") + { + auto obj = parse("bool = true;").value(); + auto i = object_cast(obj["bool"]).value(); + + auto strm = std::ostringstream(); + strm << i; + + REQUIRE(strm.str() == "true"); + } +} +} // anonymous namespace diff --git a/nihil.ucl/emit.cc b/nihil.ucl/emit.cc deleted file mode 100644 index 480ddd8..0000000 --- a/nihil.ucl/emit.cc +++ /dev/null @@ -1,21 +0,0 @@ -/* - * This source code is released into the public domain. - */ - -module; - -#include -#include - -module nihil.ucl; - -namespace nihil::ucl { - -auto operator<<(std::ostream &stream, object const &o) --> std::ostream & -{ - emit(o, emitter::json, std::ostream_iterator(stream)); - return stream; -} - -} // namespace nihil::ucl diff --git a/nihil.ucl/emit.ccm b/nihil.ucl/emit.ccm index b88f8e7..64b8f4f 100644 --- a/nihil.ucl/emit.ccm +++ b/nihil.ucl/emit.ccm @@ -1,110 +1,83 @@ -/* - * This source code is released into the public domain. - */ - +// This source code is released into the public domain. module; -#include -#include -#include -#include -#include -#include -#include -#include -#include - #include export module nihil.ucl:emit; +import nihil.std; import :object; namespace nihil::ucl { -export enum struct emitter { +export enum struct emitter : std::uint8_t { configuration = UCL_EMIT_CONFIG, compact_json = UCL_EMIT_JSON_COMPACT, json = UCL_EMIT_JSON, yaml = UCL_EMIT_YAML, }; -/* - * Wrap ucl_emitter_functions for a particular output iterator type. - * - * We can't throw exceptions here since we're called from C code. The emit - * functions return an integer value, but it's not really clear what this is - * for and the C API seems to mostly ignore it. So, we just eat errors and - * keep going. - */ -template Iterator> -struct emit_wrapper { - emit_wrapper(Iterator iterator_) - : iterator(std::move(iterator_)) - {} - - static auto append_character(unsigned char c, std::size_t nchars, - void *ud) - noexcept -> int - try { - auto *self = static_cast(ud); - - while (nchars--) - *self->iterator++ = static_cast(c); +// Wrap ucl_emitter_functions for a particular output iterator type. +// +// We can't throw exceptions here since we're called from C code. The emit +// functions return an integer value, but it's not really clear what this is +// for and the C API seems to mostly ignore it. So, we just eat errors and +// keep going. +template Iterator> +struct emit_wrapper +{ + explicit emit_wrapper(Iterator iterator) + : m_iterator(std::move(iterator)) + { + } + static auto + append_character(unsigned char const c, std::size_t nchars, void *const ud) noexcept -> int + try { + auto &self = check_magic(ud); + self.m_iterator = + std::ranges::fill_n(self.m_iterator, nchars, static_cast(c)); return 0; } catch (...) { return 0; } - static auto append_len(unsigned char const *str, std::size_t len, - void *ud) - noexcept -> int + static auto append_len(unsigned char const *const str, std::size_t const len, + void *const ud) noexcept -> int try { - auto *self = static_cast(ud); - - for (auto c : std::span(str, len)) - *self->iterator++ = static_cast(c); - + auto &self = check_magic(ud); + self.m_iterator = std::ranges::copy(std::span(str, len), self.m_iterator).out; return 0; } catch (...) { return 0; } - static auto append_int(std::int64_t value, void *ud) - noexcept -> int + static auto append_int(std::int64_t const value, void *const ud) noexcept -> int try { - auto constexpr bufsize = - std::numeric_limits::digits10; - auto buf = std::array(); + auto &self = check_magic(ud); - auto *self = static_cast(ud); - auto result = std::to_chars(buf.data(), buf.data() + buf.size(), - value, 10); - - if (result.ec == std::errc()) - for (auto c : std::span(buf.data(), result.ptr)) - *self->iterator++ = c; + auto buf = std::array::digits10>(); + auto result = std::to_chars(begin(buf), end(buf), value, 10); + if (result.ec == std::errc()) { + auto chars = std::span(buf.data(), result.ptr); + self.m_iterator = std::ranges::copy(chars, self.m_iterator).out; + } return 0; } catch (...) { return 0; } - static auto append_double(double value, void *ud) - noexcept -> int + static auto append_double(double const value, void *const ud) noexcept -> int try { - auto constexpr bufsize = - std::numeric_limits::digits10; - auto buf = std::array(); - - auto *self = static_cast(ud); - auto result = std::to_chars(buf.data(), buf.data() + buf.size(), - value); + auto &self = check_magic(ud); - if (result.ec == std::errc()) - for (auto c : std::span(buf.data(), result.ptr)) - *self->iterator++ = c; + auto buf = std::array::digits10>(); + auto result = std::to_chars(begin(buf), end(buf), value); + if (result.ec == std::errc()) { + auto chars = std::span(buf.data(), result.ptr); + self.m_iterator = std::ranges::copy(chars, self.m_iterator).out; + } return 0; } catch (...) { @@ -124,40 +97,61 @@ struct emit_wrapper { return ret; } + [[nodiscard]] auto iterator(this emit_wrapper &self) -> Iterator & + { + return self.m_iterator; + } + + [[nodiscard]] auto iterator(this emit_wrapper const &self) -> Iterator const & + { + return self.m_iterator; + } + private: - Iterator iterator{}; + Iterator m_iterator{}; + std::uint64_t m_magic = wrapper_magic; + + // Harden against memory errors. + static constexpr auto wrapper_magic = std::uint32_t{0x57524150}; + + static auto check_magic(void *p) -> emit_wrapper & + { + auto *ret = static_cast(p); + if (ret->m_magic != wrapper_magic) + throw std::runtime_error("Invalid emit_wrapper pointer"); + return *ret; + } }; -export auto emit(object const &object, emitter format, - std::output_iterator auto &&it) - -> void +export auto +emit(object const &object, emitter const format, std::output_iterator auto &&it) -> void { auto ucl_format = static_cast(format); auto wrapper = emit_wrapper(it); auto functions = wrapper.get_functions(); - ::ucl_object_emit_full(object.get_ucl_object(), ucl_format, - &functions, nullptr); + ::ucl_object_emit_full(object.get_ucl_object(), ucl_format, &functions, nullptr); } -/* - * Basic ostream printer for UCL; default to JSON since it's probably what - * most people expect. - */ -export auto operator<<(std::ostream &, object const &) -> std::ostream &; +// Basic ostream printer for UCL; default to JSON since it's probably what +// most people expect. Note that most derived UCL types override this. +export auto operator<<(std::ostream &stream, object const &o) -> std::ostream & +{ + emit(o, emitter::json, std::ostream_iterator(stream)); + return stream; +} } // namespace nihil::ucl -/* - * Specialisation of std::formatter<> for object. - */ -template T> +// Specialisation of std::formatter<> for object. Note that most derived +// UCL types override this. +template T> struct std::formatter { nihil::ucl::emitter emitter = nihil::ucl::emitter::json; - template - constexpr ParseContext::iterator parse(ParseContext& ctx) + template + constexpr auto parse(ParseContext &ctx) -> ParseContext::iterator { auto it = ctx.begin(); auto end = ctx.end(); @@ -179,8 +173,7 @@ struct std::formatter case '}': return it; default: - throw std::format_error("Invalid format string " - "for UCL object"); + throw std::format_error("Invalid format string for UCL object"); } ++it; @@ -189,9 +182,8 @@ struct std::formatter return it; } - template - FmtContext::iterator format(nihil::ucl::object const &o, - FmtContext& ctx) const + template + auto format(nihil::ucl::object const &o, FmtContext &ctx) const -> FmtContext::iterator { // We can't use emit() here since the context iterator is not // an std::output_iterator. @@ -202,8 +194,7 @@ struct std::formatter auto wrapper = nihil::ucl::emit_wrapper(out); auto functions = wrapper.get_functions(); - ::ucl_object_emit_full(o.get_ucl_object(), ucl_format, - &functions, nullptr); - return out; + ::ucl_object_emit_full(o.get_ucl_object(), ucl_format, &functions, nullptr); + return wrapper.iterator(); } }; diff --git a/nihil.ucl/emit.test.cc b/nihil.ucl/emit.test.cc new file mode 100644 index 0000000..51c4e0e --- /dev/null +++ b/nihil.ucl/emit.test.cc @@ -0,0 +1,92 @@ +// This source code is released into the public domain. + +#include + +import nihil.std; +import nihil.ucl; + +namespace { +TEST_CASE("ucl: emit to std::ostream", "[ucl]") +{ + using namespace std::literals; + + auto obj = nihil::ucl::parse("int = [1, 42, 666]"sv); + REQUIRE(obj); + + auto strm = std::ostringstream(); + strm << *obj; + + // The ostream emitter produces JSON. + REQUIRE(strm.str() == std::format("{:j}", *obj)); +} + +TEST_CASE("ucl: emit JSON with std::format", "[ucl]") +{ + using namespace std::literals; + + auto obj = nihil::ucl::parse("int = [1, 42, 666]"sv); + REQUIRE(obj); + + auto str = std::format("{:j}", *obj); + + REQUIRE(str == +"{\n" +" \"int\": [\n" +" 1,\n" +" 42,\n" +" 666\n" +" ]\n" +"}"); + + // Make sure JSON is the default format. + auto str2 = std::format("{}", *obj); + REQUIRE(str == str2); +} + +TEST_CASE("ucl: emit compact JSON with std::format", "[ucl]") +{ + using namespace std::literals; + + auto obj = nihil::ucl::parse("int = [1, 42, 666]"sv); + REQUIRE(obj); + + auto str = std::format("{:J}", *obj); + + REQUIRE(str == "{\"int\":[1,42,666]}"); +} + +TEST_CASE("ucl: emit configuration with std::format", "[ucl]") +{ + using namespace std::literals; + + auto obj = nihil::ucl::parse("int = [1, 42, 666]"sv); + REQUIRE(obj); + + auto str = std::format("{:c}", *obj); + + REQUIRE(str == +"int [\n" +" 1,\n" +" 42,\n" +" 666,\n" +"]\n"); +} + +TEST_CASE("ucl: emit YAML with std::format", "[ucl]") +{ + using namespace std::literals; + + auto obj = nihil::ucl::parse("int = [1, 42, 666]"sv); + REQUIRE(obj); + + auto str = std::format("{:y}", *obj); + + REQUIRE(str == +"int: [\n" +" 1,\n" +" 42,\n" +" 666\n" +"]"); +} + +} // anonymous namespace diff --git a/nihil.ucl/errc.cc b/nihil.ucl/errc.cc deleted file mode 100644 index 0b65b86..0000000 --- a/nihil.ucl/errc.cc +++ /dev/null @@ -1,49 +0,0 @@ -/* - * This source code is released into the public domain. - */ - -module; - -#include -#include - -module nihil.ucl; - -namespace nihil::ucl { - -struct ucl_error_category final : std::error_category { - auto name() const noexcept -> char const * override; - auto message(int err) const -> std::string override; -}; - -auto ucl_category() noexcept -> std::error_category & -{ - static auto category = ucl_error_category(); - return category; -} - -auto make_error_condition(errc ec) -> std::error_condition -{ - return {static_cast(ec), ucl_category()}; -} - -auto ucl_error_category::name() const noexcept -> char const * -{ - return "nihil.ucl"; -} - -auto ucl_error_category::message(int err) const -> std::string -{ - switch (static_cast(err)) { - case errc::no_error: - return "No error"; - case errc::failed_to_create_object: - return "Failed to create UCL object"; - case errc::type_mismatch: - return "UCL type does not match expected type"; - default: - return "Undefined error"; - } -} - -} // namespace nihil::ucl diff --git a/nihil.ucl/errc.ccm b/nihil.ucl/errc.ccm deleted file mode 100644 index 8f0444d..0000000 --- a/nihil.ucl/errc.ccm +++ /dev/null @@ -1,33 +0,0 @@ -/* - * This source code is released into the public domain. - */ - -module; - -#include -#include - -export module nihil.ucl:errc; - -namespace nihil::ucl { - -export enum struct errc { - no_error = 0, - - // ucl_object_new() or similar failed, e.g. out of memory - failed_to_create_object, - // Trying to create an object from a UCL object of the wrong type - type_mismatch, -}; - -export auto ucl_category() noexcept -> std::error_category &; -export auto make_error_condition(errc ec) -> std::error_condition; - -} // namespace nihil::ucl - -namespace std { - -export template<> -struct is_error_condition_enum : true_type {}; - -} // namespace std diff --git a/nihil.ucl/integer.cc b/nihil.ucl/integer.cc deleted file mode 100644 index 825d8f6..0000000 --- a/nihil.ucl/integer.cc +++ /dev/null @@ -1,102 +0,0 @@ -/* - * This source code is released into the public domain. - */ - -module; - -#include -#include -#include -#include - -#include - -module nihil.ucl; - -import nihil.error; - -namespace nihil::ucl { - -integer::integer() - : integer(0) -{ -} - -integer::integer(contained_type value) - : integer(noref, [&] { - auto *uobj = ::ucl_object_fromint(value); - if (uobj == nullptr) - throw std::system_error( - std::make_error_code(std::errc(errno))); - return uobj; - }()) -{ -} - -integer::integer(ref_t, ::ucl_object_t const *uobj) - : object(nihil::ucl::ref, [&] { - auto actual_type = static_cast( - ::ucl_object_type(uobj)); - if (actual_type != integer::ucl_type) - throw type_mismatch(integer::ucl_type, actual_type); - return uobj; - }()) -{ -} - -integer::integer(noref_t, ::ucl_object_t *uobj) - : object(nihil::ucl::noref, [&] { - auto actual_type = static_cast( - ::ucl_object_type(uobj)); - if (actual_type != integer::ucl_type) - throw type_mismatch(integer::ucl_type, actual_type); - return uobj; - }()) -{ -} - -auto make_integer(integer::contained_type value) - -> std::expected -{ - auto *uobj = ::ucl_object_fromint(value); - if (uobj == nullptr) - return std::unexpected(error( - errc::failed_to_create_object, - error(std::errc(errno)))); - - return integer(noref, uobj); -} - -auto integer::value(this integer const &self) -> contained_type -{ - auto v = contained_type{}; - auto const *uobj = self.get_ucl_object(); - - if (::ucl_object_toint_safe(uobj, &v)) - return v; - - std::abort(); -} - -auto operator== (integer const &a, integer const &b) -> bool -{ - return a.value() == b.value(); -} - -auto operator<=> (integer const &a, integer const &b) -> std::strong_ordering -{ - return a.value() <=> b.value(); -} - -auto operator== (integer const &a, integer::contained_type b) -> bool -{ - return a.value() == b; -} - -auto operator<=> (integer const &a, integer::contained_type b) - -> std::strong_ordering -{ - return a.value() <=> b; -} - -} // namespace nihil::ucl diff --git a/nihil.ucl/integer.ccm b/nihil.ucl/integer.ccm index e35a471..eb7fa6b 100644 --- a/nihil.ucl/integer.ccm +++ b/nihil.ucl/integer.ccm @@ -1,114 +1,139 @@ -/* - * This source code is released into the public domain. - */ - +// This source code is released into the public domain. module; -#include -#include -#include -#include -#include -#include - #include export module nihil.ucl:integer; +import nihil.std; +import nihil.core; +import nihil.error; import :object; import :type; namespace nihil::ucl { -export struct integer final : object { +export struct integer final : object +{ using contained_type = std::int64_t; - inline static constexpr object_type ucl_type = object_type::integer; - - /* - * Create an integer holding the value 0. Throws std::system_error - * on failure. - */ - integer(); - - /* - * Create an integer holding a specific value. Throws std::system_error - * on failure. - */ - explicit integer(contained_type value); - - /* - * Create a new integer from a UCL object. Throws type_mismatch - * on failure. - */ - integer(ref_t, ::ucl_object_t const *uobj); - integer(noref_t, ::ucl_object_t *uobj); + static constexpr object_type ucl_type = object_type::integer; + + // Create an integer holding the value 0. Throws std::system_error + // on failure. + integer() + : integer(0) + { + } + + // Create an integer holding a specific value. Throws std::system_error + // on failure. + explicit integer(contained_type value) + : integer(noref, [&] { + auto *uobj = ::ucl_object_fromint(value); + if (uobj == nullptr) + throw std::system_error(std::make_error_code(sys_error())); + return uobj; + }()) + { + } + + // Create a new integer from a UCL object. Throws type_mismatch + // on failure. + integer(ref_t, ::ucl_object_t const *uobj) + : object(nihil::ucl::ref, ensure_ucl_type(uobj, integer::ucl_type)) + { + } + + integer(noref_t, ::ucl_object_t *uobj) + : object(nihil::ucl::noref, ensure_ucl_type(uobj, integer::ucl_type)) + { + } // Return the value of this object. - [[nodiscard]] auto value(this integer const &self) -> contained_type; -}; + [[nodiscard]] auto value(this integer const &self) -> contained_type + { + auto v = contained_type{}; + auto const *uobj = self.get_ucl_object(); -/* - * Integer constructors. These return an error instead of throwing. - */ + if (::ucl_object_toint_safe(uobj, &v)) + return v; -export [[nodiscard]] auto -make_integer(integer::contained_type = 0) -> std::expected; + throw std::runtime_error("ucl_object_toint_safe failed"); + } -/* - * Comparison operators. - */ +private: + // + // Comparison operators. + // -export [[nodiscard]] auto operator== (integer const &a, - integer const &b) -> bool; + [[nodiscard]] friend auto operator==(integer const &a, integer const &b) -> bool + { + return a.value() == b.value(); + } -export [[nodiscard]] auto operator== (integer const &a, - integer::contained_type b) -> bool; + [[nodiscard]] friend auto operator==(integer const &a, integer::contained_type b) -> bool + { + return a.value() == b; + } -export [[nodiscard]] auto operator<=> (integer const &a, - integer const &b) - -> std::strong_ordering; + [[nodiscard]] friend auto + operator<=>(integer const &a, integer const &b) -> std::strong_ordering + { + return a.value() <=> b.value(); + } -export [[nodiscard]] auto operator<=> (integer const &a, - integer::contained_type b) - -> std::strong_ordering; + [[nodiscard]] friend auto + operator<=>(integer const &a, integer::contained_type b) -> std::strong_ordering + { + return a.value() <=> b; + } +}; + +// Integer constructors. This returns an error instead of throwing. +export [[nodiscard]] auto +make_integer(integer::contained_type value = 0) -> std::expected +{ + auto *uobj = ::ucl_object_fromint(value); + if (uobj == nullptr) + return error(errc::failed_to_create_object, error(sys_error())); + + return integer(noref, uobj); +} -/* - * Literal operator. - */ +// Literal operator for integers. inline namespace literals { -export constexpr auto operator""_ucl (unsigned long long i) -> integer +export constexpr auto operator""_ucl(unsigned long long i) -> integer { if (std::cmp_greater(i, std::numeric_limits::max())) throw std::out_of_range("literal out of range"); return integer(static_cast(i)); } -} // namespace nihil::ucl::literals +} // namespace literals } // namespace nihil::ucl -namespace nihil { inline namespace literals { - export using namespace ::nihil::ucl::literals; -}} // namespace nihil::literals +namespace nihil { +inline namespace literals { +export using namespace ::nihil::ucl::literals; +} // namespace literals +} // namespace nihil -/* - * std::formatter for an integer. This provides the same format operations - * as std::formatter. - */ -export template<> +// std::formatter for an integer. This provides the same format operations +// as std::formatter. +export template <> struct std::formatter { std::formatter base_formatter; - template - constexpr ParseContext::iterator parse(ParseContext& ctx) + template + constexpr auto parse(ParseContext &ctx) -> ParseContext::iterator { return base_formatter.parse(ctx); } - template - FmtContext::iterator format(nihil::ucl::integer const &o, - FmtContext& ctx) const + template + auto format(nihil::ucl::integer const &o, FmtContext &ctx) const -> FmtContext::iterator { return base_formatter.format(o.value(), ctx); } diff --git a/nihil.ucl/integer.test.cc b/nihil.ucl/integer.test.cc new file mode 100644 index 0000000..68567e9 --- /dev/null +++ b/nihil.ucl/integer.test.cc @@ -0,0 +1,244 @@ +// This source code is released into the public domain. + +#include +#include + +import nihil.std; +import nihil.ucl; + +namespace { +TEST_CASE("ucl: integer: invariants", "[ucl]") +{ + using namespace nihil::ucl; + + static_assert(std::same_as); + REQUIRE(integer::ucl_type == object_type::integer); + REQUIRE(static_cast<::ucl_type>(integer::ucl_type) == UCL_INT); + + 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); +} + +TEST_CASE("ucl: integer: constructor", "[ucl]") +{ + using namespace nihil::ucl; + + SECTION("default") { + auto i = integer(); + REQUIRE(i == 0); + } + + SECTION("with value") { + auto i = integer(42); + REQUIRE(i == 42); + } +} + +TEST_CASE("ucl: integer: literal", "[ucl]") +{ + SECTION("with namespace nihil::ucl::literals") { + using namespace nihil::ucl::literals; + + auto i = 42_ucl; + REQUIRE(i.type() == nihil::ucl::object_type::integer); + REQUIRE(i == 42); + } + + SECTION("with namespace nihil::literals") { + using namespace nihil::literals; + + auto i = 42_ucl; + REQUIRE(i.type() == nihil::ucl::object_type::integer); + REQUIRE(i == 42); + } +} + +TEST_CASE("ucl: integer: construct from UCL object", "[ucl]") +{ + using namespace nihil::ucl; + + SECTION("ref, correct type") { + auto *uobj = ::ucl_object_fromint(42); + + auto i = integer(ref, uobj); + REQUIRE(i == 42); + + ::ucl_object_unref(uobj); + } + + SECTION("noref, correct type") { + auto *uobj = ::ucl_object_fromint(42); + + auto i = integer(noref, uobj); + REQUIRE(i == 42); + } + + SECTION("ref, wrong type") { + auto *uobj = ::ucl_object_frombool(true); + + REQUIRE_THROWS_AS(integer(ref, uobj), type_mismatch); + + ::ucl_object_unref(uobj); + } + + SECTION("noref, wrong type") { + auto *uobj = ::ucl_object_frombool(true); + + REQUIRE_THROWS_AS(integer(noref, uobj), type_mismatch); + + ::ucl_object_unref(uobj); + } +} + +TEST_CASE("ucl: integer: make_integer", "[ucl]") +{ + using namespace nihil::ucl; + + SECTION("default value") { + auto i = make_integer().value(); + REQUIRE(i == 0); + } + + SECTION("explicit value") { + auto i = make_integer(42).value(); + REQUIRE(i == 42); + } +} + +TEST_CASE("ucl: integer: swap", "[ucl]") +{ + // do not add using namespace nihil::ucl + + auto i1 = nihil::ucl::integer(1); + auto i2 = nihil::ucl::integer(2); + + swap(i1, i2); + + REQUIRE(i1 == 2); + REQUIRE(i2 == 1); +} + +TEST_CASE("ucl: integer: value()", "[ucl]") +{ + using namespace nihil::ucl; + + auto i = 42_ucl; + REQUIRE(i.value() == 42); +} + +TEST_CASE("ucl: integer: key()", "[ucl]") +{ + using namespace nihil::ucl; + + SECTION("parsed with key") { + auto obj = parse("an_int = 42").value(); + auto i = object_cast(obj["an_int"]).value(); + REQUIRE(i.key() == "an_int"); + } + + SECTION("bare integer, no key") { + auto i = 42_ucl; + REQUIRE(i.key().empty() == true); + } +} + +TEST_CASE("ucl: integer: comparison", "[ucl]") +{ + using namespace nihil::ucl; + + auto i = 42_ucl; + + SECTION("operator==") { + REQUIRE(i == 42); + REQUIRE(i == 42_ucl); + } + + SECTION("operator!=") { + REQUIRE(i != 1); + REQUIRE(i != 1_ucl); + } + + SECTION("operator<") { + REQUIRE(i < 43); + REQUIRE(i < 43_ucl); + } + + SECTION("operator>") { + REQUIRE(i > 1); + REQUIRE(i > 1_ucl); + } +} + +TEST_CASE("ucl: integer: parse", "[ucl]") +{ + using namespace nihil::ucl; + + auto obj = parse("value = 42").value(); + + auto v = obj["value"]; + REQUIRE(v.key() == "value"); + REQUIRE(object_cast(v) == 42); +} + +TEST_CASE("ucl: integer: parse and emit", "[ucl]") +{ + using namespace nihil::ucl; + + auto ucl = parse("int = 42;").value(); + + auto output = std::string(); + emit(ucl, emitter::configuration, std::back_inserter(output)); + + REQUIRE(output == "int = 42;\n"); +} + +TEST_CASE("ucl: integer: format", "[ucl]") +{ + using namespace nihil::ucl; + + SECTION("bare integer") { + auto str = std::format("{}", 42_ucl); + REQUIRE(str == "42"); + } + + SECTION("parsed integer") { + auto obj = parse("int = 42;").value(); + auto i = object_cast(obj["int"]).value(); + + auto str = std::format("{}", i); + REQUIRE(str == "42"); + } + + SECTION("with format string") { + auto str = std::format("{:-05}", 42_ucl); + REQUIRE(str == "00042"); + } +} + +TEST_CASE("ucl: integer: print to ostream", "[ucl]") +{ + using namespace nihil::ucl; + + SECTION("bare integer") { + auto strm = std::ostringstream(); + strm << 42_ucl; + + REQUIRE(strm.str() == "42"); + } + + SECTION("parsed integer") { + auto obj = parse("int = 42;").value(); + auto i = object_cast(obj["int"]).value(); + + auto strm = std::ostringstream(); + strm << i; + + REQUIRE(strm.str() == "42"); + } +} +} // anonymous namespace diff --git a/nihil.ucl/map.ccm b/nihil.ucl/map.ccm index fa77601..73ef583 100644 --- a/nihil.ucl/map.ccm +++ b/nihil.ucl/map.ccm @@ -1,47 +1,41 @@ -/* - * This source code is released into the public domain. - */ - +// This source code is released into the public domain. module; -#include -#include -#include -#include -#include -#include -#include -#include - #include export module nihil.ucl:map; +import nihil.std; import :object; namespace nihil::ucl { // Exception thrown when map::operator[] does not find the key. -export struct key_not_found : error { - key_not_found(std::string_view key) +export struct key_not_found : error +{ + explicit key_not_found(std::string_view key) : error(std::format("key '{}' not found in map", key)) , m_key(key) - {} + { + } auto key(this key_not_found const &self) -> std::string_view { return self.m_key; } - + private: std::string m_key; }; -export template +export template struct map; -template -struct map_iterator { +// The map iterator. UCL doesn't provide a way to copy an iterator, so this is an +// input iterator: it can only go forwards. +template +struct map_iterator +{ using difference_type = std::ptrdiff_t; using value_type = std::pair; using reference = value_type &; @@ -49,242 +43,219 @@ struct map_iterator { using pointer = value_type *; using const_pointer = value_type const *; - struct sentinel{}; + struct sentinel + { + }; - [[nodiscard]] auto operator==(this map_iterator const &self, sentinel) - -> bool + [[nodiscard]] auto operator==(this map_iterator const &self, sentinel) -> bool { - return (self.m_state->cur == nullptr); + return self.m_state->current_object() == nullptr; } auto operator++(this map_iterator &self) -> map_iterator & { - self.m_state->next(); + self.m_state->advance(); return self; } auto operator++(this map_iterator &self, int) -> map_iterator & { - self.m_state->next(); + self.m_state->advance(); return self; } - [[nodiscard]] auto operator*(this map_iterator const &self) - -> value_type + [[nodiscard]] auto operator*(this map_iterator const &self) -> value_type { - auto obj = T(ref, self.m_state->cur); + auto *cur = self.m_state->current_object(); + if (cur == nullptr) + throw std::logic_error("map_iterator::operator* called on end()"); + + auto obj = T(ref, cur); return {obj.key(), std::move(obj)}; } private: friend struct map; - map_iterator(::ucl_object_t const *obj) + explicit map_iterator(::ucl_object_t const *obj) : m_state(std::make_shared(obj)) { ++(*this); } - struct state { - state(::ucl_object_t const *obj) + struct state + { + explicit state(::ucl_object_t const *obj) + : m_ucl_iterator([obj] { + if (auto *iter = ::ucl_object_iterate_new(obj); iter != nullptr) + return iter; + throw std::system_error(make_error_code(sys_error())); + }()) { - iter = ::ucl_object_iterate_new(obj); - if (iter == nullptr) - throw std::system_error(make_error_code( - std::errc(errno))); } state(state const &) = delete; - auto operator=(this state &, state const &) -> state& = delete; + auto operator=(state const &) -> state & = delete; + + state(state &&) = delete; + auto operator=(state &&) -> state & = delete; ~state() { - if (iter != nullptr) - ::ucl_object_iterate_free(iter); + if (m_ucl_iterator != nullptr) + ::ucl_object_iterate_free(m_ucl_iterator); + } + + auto advance(this state &self) -> void + { + self.m_current_object = ::ucl_object_iterate_safe(self.m_ucl_iterator, true); } - auto next() -> void + auto current_object(this state const &self) -> ::ucl_object_t const * { - cur = ::ucl_object_iterate_safe(iter, true); + return self.m_current_object; } - ucl_object_iter_t iter = nullptr; - ucl_object_t const *cur = nullptr; + private: + ucl_object_iter_t m_ucl_iterator = nullptr; + ucl_object_t const *m_current_object = nullptr; }; std::shared_ptr m_state; }; -export template -struct map final : object { - inline static constexpr object_type ucl_type = object_type::object; +export template +struct map final : object +{ + static constexpr auto ucl_type = object_type::object; using value_type = std::pair; using size_type = std::size_t; using difference_type = std::ptrdiff_t; using iterator = map_iterator; + using sentinel = typename iterator::sentinel; - /* - * Create an empty map. Throws std::system_error on failure. - */ - map() : object(noref, [] { + ~map() override = default; + + // Create an empty map. Throws std::system_error on failure. + map() + : object(noref, [] { auto *uobj = ::ucl_object_typed_new(UCL_OBJECT); if (uobj == nullptr) - throw std::system_error( - std::make_error_code(std::errc(errno))); + throw std::system_error(std::make_error_code(sys_error())); return uobj; }()) { } - /* - * Create a map from a UCL object. Throws type_mismatch on failure. - * - * Unlike object_cast<>, this does not check the type of the contained - * elements, which means object access can throw type_mismatch. - */ - map(ref_t, ::ucl_object_t const *uobj) - : object(nihil::ucl::ref, [&] { - auto actual_type = static_cast( - ::ucl_object_type(uobj)); - if (actual_type != map::ucl_type) - throw type_mismatch(map::ucl_type, - actual_type); - return uobj; - }()) + // Create a map from a UCL object. Throws type_mismatch on failure. + // + // Unlike object_cast<>, this does not check the type of the contained + // elements, which means object access can throw type_mismatch. + map(ref_t, ::ucl_object_t const * const uobj) + : object(nihil::ucl::ref, ensure_ucl_type(uobj, map::ucl_type)) { - if (type() != ucl_type) - throw type_mismatch(ucl_type, type()); } - map(noref_t, ::ucl_object_t *uobj) - : object(nihil::ucl::noref, [&] { - auto actual_type = static_cast( - ::ucl_object_type(uobj)); - if (actual_type != map::ucl_type) - throw type_mismatch(map::ucl_type, - actual_type); - return uobj; - }()) + map(noref_t, ::ucl_object_t * const uobj) + : object(nihil::ucl::noref, ensure_ucl_type(uobj, map::ucl_type)) { } - /* - * Create a map from an iterator pair. - */ - template - requires(std::convertible_to, value_type>) - map(Iterator first, Iterator last) - : map() + // Create a map from a range of value types. + template + requires(std::convertible_to, value_type>) + explicit map(Range const &range) + : map() { // This is exception safe, because if we throw here the // base class destructor will free the map. - while (first != last) { - insert(*first); - ++first; - } + for (auto &&v: range) + insert(v); } - /* - * Create a map from a range. - */ - template - requires(std::convertible_to, - value_type>) - map(std::from_range_t, Range &&range) - : map(std::ranges::begin(range), - std::ranges::end(range)) + // Create a map from an iterator pair. + template + requires(std::convertible_to, value_type>) + map(Iterator first, Iterator last) + : map(std::ranges::subrange(first, last)) { } - /* - * Create a map from an initializer_list. - */ + // Create a map from an initializer_list. map(std::initializer_list const &list) - : map(std::ranges::begin(list), std::ranges::end(list)) + : map(std::ranges::subrange(std::ranges::begin(list), std::ranges::end(list))) { } - /* - * Map iterator access. - */ + // Copyable. Note that this copies the entire UCL object. + map(map const &) = default; + auto operator=(map const &) -> map & = default; + + // Movable. + map(map &&) = default; + auto operator=(map &&other) -> map & = default; + + // + // Map iterator access. + // [[nodiscard]] auto begin(this map const &self) -> iterator { - return {self.get_ucl_object()}; + return iterator(self.get_ucl_object()); } - [[nodiscard]] auto end(this map const &) -> iterator::sentinel + [[nodiscard]] auto end(this map const &) -> sentinel { return {}; } - /* - * Reserve space for future insertions. - */ - auto reserve(this map &self, size_type nelems) -> void + // Reserve space for future insertions. + auto reserve(this map &self, size_type const nelems) -> void { ::ucl_object_reserve(self.get_ucl_object(), nelems); } - /* - * Add an element to the map. - */ + // Add an element to the map. auto insert(this map &self, value_type const &v) -> void { auto uobj = ::ucl_object_ref(v.second.get_ucl_object()); - ::ucl_object_insert_key(self.get_ucl_object(), uobj, - v.first.data(), v.first.size(), true); + ::ucl_object_insert_key(self.get_ucl_object(), uobj, v.first.data(), v.first.size(), + true); } - /* - * Access a map element by key. - */ - [[nodiscard]] auto find(this map const &self, std::string_view key) - -> std::optional + // Access a map element by key. + [[nodiscard]] auto find(this map const &self, std::string_view const key) -> std::optional { - auto const *obj = ::ucl_object_lookup_len( - self.get_ucl_object(), - key.data(), key.size()); + auto const *obj = + ::ucl_object_lookup_len(self.get_ucl_object(), key.data(), key.size()); if (obj == nullptr) return {}; return {T(nihil::ucl::ref, obj)}; } - /* - * Remove an object from the map. - */ - auto remove(this map &self, std::string_view key) -> bool + // Remove an object from the map. + auto remove(this map &self, std::string_view const key) -> bool { - return ::ucl_object_delete_keyl(self.get_ucl_object(), - key.data(), key.size()); + return ::ucl_object_delete_keyl(self.get_ucl_object(), key.data(), key.size()); } - /* - * Remove an object from the map and return it. - */ - auto pop(this map &self, std::string_view key) - -> std::optional + // Remove an object from the map and return it. If the map is empty, returns nullopt. + auto pop(this map &self, std::string_view const key) -> std::optional { - auto *uobj = ::ucl_object_pop_keyl(self.get_ucl_object(), - key.data(), key.size()); + auto *uobj = ::ucl_object_pop_keyl(self.get_ucl_object(), key.data(), key.size()); if (uobj) return T(noref, uobj); return {}; } - /* - * Equivalent to find(), except it throws key_not_found if the key - * doesn't exist in the map. - */ - [[nodiscard]] auto operator[] (this map const &self, - std::string_view key) - -> T + // Equivalent to find(), except it throws key_not_found if the key + // doesn't exist in the map. + [[nodiscard]] auto operator[](this map const &self, std::string_view const key) -> T { - auto obj = self.find(key); - if (obj) + if (auto obj = self.find(key); obj ) return *obj; throw key_not_found(key); } diff --git a/nihil.ucl/map.test.cc b/nihil.ucl/map.test.cc new file mode 100644 index 0000000..6d31af2 --- /dev/null +++ b/nihil.ucl/map.test.cc @@ -0,0 +1,192 @@ +// This source code is released into the public domain. + +#include +#include + +import nihil.std; +import nihil.ucl; + +//NOLINTBEGIN(bugprone-unchecked-optional-access) + +namespace { +TEST_CASE("ucl: map: invariants", "[ucl]") +{ + using namespace nihil::ucl; + + REQUIRE(map<>::ucl_type == object_type::object); + REQUIRE(static_cast<::ucl_type>(map<>::ucl_type) == UCL_OBJECT); + + 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::ranges::range>); + static_assert(std::same_as, + std::ranges::range_value_t>>); +} + +TEST_CASE("ucl: map: default construct", "[ucl]") +{ + auto map = nihil::ucl::map<>(); + REQUIRE(str(map.type()) == "object"); +} + +TEST_CASE("ucl: map: construct from initializer_list", "[ucl]") +{ + using namespace nihil::ucl; + using namespace std::literals; + + auto map = nihil::ucl::map{ + {"1"sv, integer(1)}, + {"42"sv, integer(42)}, + }; + + REQUIRE(str(map.type()) == "object"); + REQUIRE(map["1"] == 1); + REQUIRE(map["42"] == 42); +} + +TEST_CASE("ucl: map: construct from range", "[ucl]") +{ + using namespace nihil::ucl; + using namespace std::literals; + + auto vec = std::vector>{ + {"1"sv, integer(1)}, + {"42"sv, integer(42)}, + }; + + auto map = nihil::ucl::map(vec); + + REQUIRE(str(map.type()) == "object"); + REQUIRE(map["1"] == 1); + REQUIRE(map["42"] == 42); +} + +TEST_CASE("ucl: map: construct from iterator pair", "[ucl]") +{ + using namespace nihil::ucl; + using namespace std::literals; + + auto vec = std::vector>{ + {"1"sv, integer(1)}, + {"42"sv, integer(42)}, + }; + + auto map = nihil::ucl::map(std::ranges::begin(vec), + std::ranges::end(vec)); + + REQUIRE(str(map.type()) == "object"); + REQUIRE(map["1"] == 1); + REQUIRE(map["42"] == 42); +} + +TEST_CASE("ucl: map: insert", "[ucl]") +{ + using namespace nihil::ucl; + using namespace std::literals; + + auto m = map(); + + m.insert({"test1"sv, integer(42)}); + m.insert({"test2"sv, integer(666)}); + + REQUIRE(m["test1"] == 42); + REQUIRE(m["test2"] == 666); +} + +TEST_CASE("ucl: map: find", "[ucl]") +{ + using namespace nihil::ucl; + using namespace std::literals; + + auto map = nihil::ucl::map{ + {"1"sv, integer(1)}, + {"42"sv, integer(42)}, + }; + + auto obj = map.find("42"); + REQUIRE(obj.value() == 42); + + obj = map.find("43"); + REQUIRE(!obj.has_value()); +} + +TEST_CASE("ucl: map: iterate", "[ucl]") +{ + using namespace nihil::ucl; + using namespace std::literals; + + auto map = nihil::ucl::map{ + {"1"sv, integer(1)}, + {"42"sv, integer(42)}, + }; + + auto i = 0U; + + for (auto [key, value] : map) { + if (key == "1") + REQUIRE(value == 1); + else if (key == "42") + REQUIRE(value == 42); + else + REQUIRE(false); + ++i; + } + + REQUIRE(i == 2); +} + +TEST_CASE("ucl: map: operator[] throws key_not_found", "[ucl]") +{ + auto map = nihil::ucl::map(); + REQUIRE_THROWS_AS(map["nonesuch"], nihil::ucl::key_not_found); +} + +TEST_CASE("ucl: map: remove", "[uc]") +{ + using namespace std::literals; + using namespace nihil::ucl; + + auto map = nihil::ucl::map{ + {"1"sv, integer(1)}, + {"42"sv, integer(42)}, + }; + + REQUIRE(map.find("42") != std::nullopt); + REQUIRE(map.remove("42") == true); + REQUIRE(map.find("42") == std::nullopt); + REQUIRE(map["1"] == 1); + + REQUIRE(map.remove("42") == false); +} + +TEST_CASE("ucl: map: pop", "[uc]") +{ + using namespace std::literals; + using namespace nihil::ucl; + + auto map = nihil::ucl::map{ + {"1"sv, integer(1)}, + {"42"sv, integer(42)}, + }; + + REQUIRE(map.find("42") != std::nullopt); + + auto obj = map.pop("42"); + REQUIRE(obj.value() == 42); + + REQUIRE(!map.find("42")); + REQUIRE(map["1"] == 1); + + obj = map.pop("42"); + REQUIRE(!obj); +} + +} // anonymous namespace + +//NOLINTEND(bugprone-unchecked-optional-access) diff --git a/nihil.ucl/nihil.ucl.ccm b/nihil.ucl/nihil.ucl.ccm index b16eb3d..daa751b 100644 --- a/nihil.ucl/nihil.ucl.ccm +++ b/nihil.ucl/nihil.ucl.ccm @@ -1,13 +1,7 @@ -/* - * This source code is released into the public domain. - */ - -module; - +// This source code is released into the public domain. export module nihil.ucl; export import :emit; -export import :errc; export import :object; export import :object_cast; export import :parser; diff --git a/nihil.ucl/object.cc b/nihil.ucl/object.cc deleted file mode 100644 index 53fc4c7..0000000 --- a/nihil.ucl/object.cc +++ /dev/null @@ -1,114 +0,0 @@ -/* - * This source code is released into the public domain. - */ - -module; - -#include -#include -#include - -#include - -module nihil.ucl; - -namespace nihil::ucl { - -object::object(ref_t, ::ucl_object_t const *object) - : m_object(::ucl_object_ref(object)) -{ -} - -object::object(noref_t, ::ucl_object_t *object) - : m_object(object) -{ -} - -object::~object() { - if (m_object != nullptr) - ::ucl_object_unref(m_object); -} - -object::object(object &&other) noexcept - : m_object(std::exchange(other.m_object, nullptr)) -{} - -object::object(object const &other) - : m_object(nullptr) -{ - m_object = ::ucl_object_copy(other.get_ucl_object()); - if (m_object == nullptr) - throw std::runtime_error("failed to copy UCL object"); -} - -auto object::operator=(this object &self, object &&other) noexcept - -> object & -{ - if (&self != &other) - self.m_object = std::exchange(other.m_object, nullptr); - return self; -} - -auto object::operator=(this object &self, object const &other) -> object & -{ - return self = object(other); -} - -auto object::ref(this object const &self) -> object -{ - return object(nihil::ucl::ref, self.get_ucl_object()); -} - -auto object::type(this object const &self) -> object_type -{ - auto utype = ::ucl_object_type(self.get_ucl_object()); - return static_cast(utype); -} - -auto object::get_ucl_object(this object &self) -> ::ucl_object_t * -{ - if (self.m_object == nullptr) - throw std::logic_error("attempt to access empty UCL object"); - return self.m_object; -} - -auto object::get_ucl_object(this object const &self) -> ::ucl_object_t const * -{ - if (self.m_object == nullptr) - throw std::logic_error("attempt to access empty UCL object"); - return self.m_object; -} - -// Return the key of this object. -auto object::key(this object const &self) -> std::string_view -{ - auto dlen = std::size_t{}; - auto const *dptr = ::ucl_object_keyl(self.get_ucl_object(), - &dlen); - return {dptr, dlen}; -} - -auto swap(object &a, object &b) -> void -{ - std::swap(a.m_object, b.m_object); -} - -auto operator<=>(object const &lhs, object const &rhs) -> std::strong_ordering -{ - auto cmp = ::ucl_object_compare(lhs.get_ucl_object(), - rhs.get_ucl_object()); - - if (cmp < 0) - return std::strong_ordering::less; - else if (cmp > 0) - return std::strong_ordering::greater; - else - return std::strong_ordering::equal; -} - -auto operator==(object const &lhs, object const &rhs) -> bool -{ - return (lhs <=> rhs) == std::strong_ordering::equal; -} - -} // namespace nihil::ucl diff --git a/nihil.ucl/object.ccm b/nihil.ucl/object.ccm index 9a7eaf7..93dc4db 100644 --- a/nihil.ucl/object.ccm +++ b/nihil.ucl/object.ccm @@ -1,88 +1,172 @@ -/* - * This source code is released into the public domain. - */ - +// This source code is released into the public domain. module; -/* - * A UCL object. The object is immutable and internally refcounted, so it - * may be copied as needed. - * - */ - -#include -#include -#include +// A UCL object. The object is immutable and internally refcounted, so it +// may be copied as needed. #include export module nihil.ucl:object; +import nihil.std; import :type; namespace nihil::ucl { -/*********************************************************************** - * The basic object type. - */ +//*********************************************************************** +// The basic object type. // Ref the UCL object when creating an object. -export inline constexpr struct ref_t {} ref; +export inline constexpr struct ref_t +{ +} ref; + // Don't ref the UCL object. -export inline constexpr struct noref_t {} noref; +export inline constexpr struct noref_t +{ +} noref; -export struct object { - inline static constexpr object_type ucl_type = object_type::object; +export struct object +{ + static constexpr object_type ucl_type = object_type::object; // Create an object from an existing ucl_object_t. The first argument // determines whether we ref the object or not. - object(ref_t, ::ucl_object_t const *object); - object(noref_t, ::ucl_object_t *object); + object(ref_t, ::ucl_object_t const *object) + : m_object(::ucl_object_ref(object)) + { + } + + object(noref_t, ::ucl_object_t *object) + : m_object(object) + { + } // Free our object on destruction. - virtual ~object(); + virtual ~object() + { + if (m_object != nullptr) + ::ucl_object_unref(m_object); + } // Movable. - object(object &&other) noexcept; - auto operator=(this object &self, object &&other) noexcept -> object&; - - // Copyable. - // Note that this copies the entire UCL object. - object(object const &other); - auto operator=(this object &self, object const &other) -> object &; + object(object &&other) noexcept + : m_object(std::exchange(other.m_object, nullptr)) + { + } + + auto operator=(this object &self, object &&other) noexcept -> object & + { + if (&self != &other) + self.m_object = std::exchange(other.m_object, nullptr); + return self; // NOLINT + } + + // Copyable. Note that this copies the entire UCL object. + + object(object const &other) + : m_object([&] { + auto *uobj = ::ucl_object_copy(other.get_ucl_object()); + if (uobj == nullptr) + throw std::runtime_error("failed to copy UCL object"); + return uobj; + }()) + { + } + + auto operator=(this object &self, object const &other) -> object & + { + if (&self != &other) + self = object(other); + return self; // NOLINT + } // Increase the refcount of this object. - [[nodiscard]] auto ref(this object const &self) -> object; + [[nodiscard]] auto ref(this object const &self) -> object + { + return {nihil::ucl::ref, self.get_ucl_object()}; + } // Return the type of this object. - [[nodiscard]] auto type(this object const &self) -> object_type; + [[nodiscard]] auto type(this object const &self) -> object_type + { + auto utype = ::ucl_object_type(self.get_ucl_object()); + return static_cast(utype); + } // Return the underlying object. - [[nodiscard]] auto get_ucl_object(this object &self) - -> ::ucl_object_t *; - - [[nodiscard]] auto get_ucl_object(this object const &self) - -> ::ucl_object_t const *; + [[nodiscard]] auto get_ucl_object(this object &self) -> ::ucl_object_t * + { + if (self.m_object == nullptr) + throw std::logic_error("attempt to access empty UCL object"); + return self.m_object; + } + + [[nodiscard]] auto get_ucl_object(this object const &self) -> ::ucl_object_t const * + { + if (self.m_object == nullptr) + throw std::logic_error("attempt to access empty UCL object"); + return self.m_object; + } // Return the key of this object. - [[nodiscard]] auto key(this object const &self) -> std::string_view; + [[nodiscard]] auto key(this object const &self) -> std::string_view + { + auto dlen = std::size_t{}; + auto const *dptr = ::ucl_object_keyl(self.get_ucl_object(), &dlen); + return {dptr, dlen}; + } protected: + friend auto swap(object &a, object &b) noexcept -> void + { + std::swap(a.m_object, b.m_object); + } + + // Helper to validate the type of a UCL object. Throws type_mismatch if the + // type doesn't match, or else returns the pointer. + [[nodiscard]] static auto ensure_ucl_type(::ucl_object_t const *uobj, object_type type) + -> ::ucl_object_t const * + { + if (static_cast(::ucl_object_type(uobj)) != type) + throw type_mismatch(type, static_cast(::ucl_object_type(uobj))); + return uobj; + } + + [[nodiscard]] static auto ensure_ucl_type(::ucl_object_t *uobj, object_type type) + -> ::ucl_object_t * + { + if (static_cast(::ucl_object_type(uobj)) != type) + throw type_mismatch(type, static_cast(::ucl_object_type(uobj))); + return uobj; + } + +private: // The object we're wrapping. ::ucl_object_t *m_object = nullptr; - friend auto swap(object &a, object &b) -> void; + // + // Object comparison. + // + + [[nodiscard]] friend auto + operator<=>(object const &lhs, object const &rhs) -> std::strong_ordering + { + auto cmp = ::ucl_object_compare(lhs.get_ucl_object(), rhs.get_ucl_object()); + + if (cmp < 0) + return std::strong_ordering::less; + else if (cmp > 0) + return std::strong_ordering::greater; + else + return std::strong_ordering::equal; + } + + [[nodiscard]] friend auto operator==(object const &lhs, object const &rhs) -> bool + { + return (lhs <=> rhs) == std::strong_ordering::equal; + } }; -/*********************************************************************** - * Object comparison. - */ - -export [[nodiscard]] auto operator==(object const &lhs, object const &rhs) - -> bool; - -export [[nodiscard]] auto operator<=>(object const &lhs, object const &rhs) - -> std::strong_ordering; - } // namespace nihil::ucl diff --git a/nihil.ucl/object.test.cc b/nihil.ucl/object.test.cc new file mode 100644 index 0000000..557653c --- /dev/null +++ b/nihil.ucl/object.test.cc @@ -0,0 +1,43 @@ +// This source code is released into the public domain. + +#include + +#include + +import nihil.std; +import nihil.ucl; + +TEST_CASE("ucl object: get_ucl_object", "[ucl]") +{ + auto obj = nihil::ucl::integer(42); + + REQUIRE(obj.get_ucl_object() != nullptr); + static_assert(std::same_as<::ucl_object_t *, + decltype(obj.get_ucl_object())>); + + auto const cobj = obj; + static_assert(std::same_as<::ucl_object_t const *, + decltype(cobj.get_ucl_object())>); +} + +TEST_CASE("ucl object: compare", "[ucl]") +{ + using namespace std::literals; + + auto obj_41 = nihil::ucl::parse("int = 41;"sv); + REQUIRE(obj_41); + + auto obj_42 = nihil::ucl::parse("int = 42;"sv); + REQUIRE(obj_42); + + auto obj_42_2 = nihil::ucl::parse("int = 42;"sv); + REQUIRE(obj_42_2); + + auto obj_43 = nihil::ucl::parse("int = 43;"sv); + REQUIRE(obj_43); + + REQUIRE(*obj_42 == *obj_42_2); + REQUIRE(*obj_42 != *obj_43); + REQUIRE(*obj_42 < *obj_43); + REQUIRE(*obj_42 > *obj_41); +} diff --git a/nihil.ucl/object_cast.ccm b/nihil.ucl/object_cast.ccm index 3fa9eba..5a09085 100644 --- a/nihil.ucl/object_cast.ccm +++ b/nihil.ucl/object_cast.ccm @@ -1,17 +1,11 @@ -/* - * This source code is released into the public domain. - */ - +// This source code is released into the public domain. module; -#include -#include -#include - #include export module nihil.ucl:object_cast; +import nihil.std; import nihil.monad; import :type; import :object; @@ -19,10 +13,9 @@ import :array; namespace nihil::ucl { -/* - * Ensure a UCL object is convertible to another type. Throws type_mismatch - * if not. - */ +// +// Ensure a UCL object is convertible to another type. +// // Implementation for basic types. template @@ -80,7 +73,7 @@ struct convert_check> export template auto object_cast(object const &from) -> std::expected { - auto uobj = from.get_ucl_object(); + auto const *uobj = from.get_ucl_object(); co_await convert_check{}.check(uobj); co_return To(nihil::ucl::ref, uobj); diff --git a/nihil.ucl/parse.test.cc b/nihil.ucl/parse.test.cc new file mode 100644 index 0000000..79a722d --- /dev/null +++ b/nihil.ucl/parse.test.cc @@ -0,0 +1,55 @@ +// This source code is released into the public domain. + +#include +#include + +import nihil.std; +import nihil.ucl; + +namespace { +TEST_CASE("ucl parse: iterate array", "[ucl]") +{ + using namespace std::literals; + using namespace nihil::ucl; + + auto err = parse("value = [1, 42, 666];"sv); + REQUIRE(err); + + auto obj = *err; + + auto arr = obj["value"]; + REQUIRE(arr.key() == "value"); + + auto ints = object_cast>(arr); + REQUIRE(ints); + + auto vec = std::vector(std::from_range, *ints); + + REQUIRE(vec.size() == 3); + REQUIRE(vec[0] == 1); + REQUIRE(vec[1] == 42); + REQUIRE(vec[2] == 666); +} + +TEST_CASE("ucl parse: iterate hash", "[ucl]") +{ + using namespace std::literals; + using namespace nihil::ucl; + + auto input = "int = 42; bool = true; str = \"test\";"sv; + auto obj = parse(input); + REQUIRE(obj); + + for (auto &&[key, value] : *obj) { + REQUIRE(key == value.key()); + + if (key == "int") + REQUIRE(object_cast(value) == 42); + else if (key == "bool") + REQUIRE(object_cast(value) == true); + else if (key == "str") + REQUIRE(object_cast(value) == "test"); + } +} + +} // anonymous namespace diff --git a/nihil.ucl/parser.cc b/nihil.ucl/parser.cc deleted file mode 100644 index 0a08670..0000000 --- a/nihil.ucl/parser.cc +++ /dev/null @@ -1,102 +0,0 @@ -/* - * This source code is released into the public domain. - */ - -module; - -#include -#include -#include - -#include - -module nihil.ucl; - -import nihil.error; - -namespace nihil::ucl { - -auto make_parser(int flags) -> std::expected -{ - auto *p = ::ucl_parser_new(flags); - if (p != nullptr) - return p; - - // TODO: Is there a way to get the actual error here? - return std::unexpected(error("failed to create parser")); -} - -auto macro_handler::handle(unsigned char const *data, - std::size_t len, void *ud) - -> bool -{ - auto handler = static_cast(ud); - auto string = std::string_view( - reinterpret_cast(data), - len); - return handler->callback(string); -} - -parser::parser(::ucl_parser *uclp) - : m_parser(uclp) -{ -} - -parser::~parser() -{ - if (m_parser) - ::ucl_parser_free(m_parser); -} - -parser::parser(parser &&other) noexcept - : m_parser(std::exchange(other.m_parser, nullptr)) - , m_macros(std::move(other.m_macros)) -{ -} - -auto parser::operator=(this parser &self, parser &&other) noexcept - -> parser & -{ - if (&self != &other) { - if (self.m_parser) - ::ucl_parser_free(self.m_parser); - - self.m_parser = std::exchange(other.m_parser, nullptr); - self.m_macros = std::move(other.m_macros); - } - - return self; -} - -auto parser::register_value( - this parser &self, - std::string_view variable, - std::string_view value) - -> void -{ - ::ucl_parser_register_variable( - self.get_parser(), - std::string(variable).c_str(), - std::string(value).c_str()); -} - -auto parser::top(this parser &self) -> map -{ - auto obj = ::ucl_parser_get_object(self.get_parser()); - if (obj != nullptr) - // ucl_parser_get_object() refs the object for us. - return {noref, obj}; - - throw std::logic_error( - "attempt to call top() on an invalid ucl::parser"); -} - -auto parser::get_parser(this parser &self) -> ::ucl_parser * -{ - if (self.m_parser == nullptr) - throw std::logic_error("attempt to fetch a null ucl::parser"); - - return self.m_parser; -} - -} // namespace nihil::ucl diff --git a/nihil.ucl/parser.ccm b/nihil.ucl/parser.ccm index 5fa3495..0100fda 100644 --- a/nihil.ucl/parser.ccm +++ b/nihil.ucl/parser.ccm @@ -1,21 +1,11 @@ -/* - * This source code is released into the public domain. - */ - +// This source code is released into the public domain. module; -#include -#include -#include -#include -#include -#include -#include - #include export module nihil.ucl:parser; +import nihil.std; import nihil.monad; import :object; import :map; @@ -28,79 +18,98 @@ export inline constexpr int parser_zerocopy = UCL_PARSER_ZEROCOPY; export inline constexpr int parser_no_time = UCL_PARSER_NO_TIME; // A macro handler. This proxies the C API callback to the C++ API. -using macro_callback_t = bool (std::string_view); +using macro_callback_t = bool(std::string_view); -struct macro_handler { +struct macro_handler +{ std::function callback; // Handle a callback from the C API. - static auto handle( - unsigned char const *data, - std::size_t len, void - *ud) - -> bool; + static auto handle(unsigned char const *data, std::size_t len, void *ud) -> bool + { + auto handler = static_cast(ud); + auto string = std::string_view(reinterpret_cast(data), len); + return handler->callback(string); + } }; -/* - * A UCL parser. This wraps the C ucl_parser API. - * - * parser itself is not exported; use make_parser() to create one. - */ -struct parser { +// A UCL parser. This wraps the C ucl_parser API. +// +// parser itself is not exported; use make_parser() to create one. +struct parser +{ // Create a parser from a UCL parser. - parser(::ucl_parser *); + explicit parser(::ucl_parser *uclp) + : m_parser(uclp) + { + } // Destroy our parser when we're destroyed. - ~parser(); + ~parser() + { + if (m_parser != nullptr) + ::ucl_parser_free(m_parser); + } // Not copyable. parser(parser const &) = delete; auto operator=(this parser &, parser const &) -> parser & = delete; // Movable. - parser(parser &&) noexcept; - auto operator=(this parser &, parser &&) noexcept -> parser &; + parser(parser &&other) noexcept + : m_parser(std::exchange(other.m_parser, nullptr)) + , m_macros(std::move(other.m_macros)) + { + } + + auto operator=(this parser &self, parser &&other) noexcept -> parser & + { + if (&self != &other) { + if (self.m_parser != nullptr) + ::ucl_parser_free(self.m_parser); + + self.m_parser = std::exchange(other.m_parser, nullptr); + self.m_macros = std::move(other.m_macros); + } + + return self; // NOLINT + } // Add a parser macro. Unlike ucl_parser_register_macro, this doesn't // take a userdata parameter; it's assumed the user will use lambda // capture or similar if needed. - template F> - auto register_macro(this parser &self, - std::string_view name, - F &&func) - -> void - requires (std::same_as>) + + template F> + auto register_macro(this parser &self, std::string_view name, F &&func) -> void + requires(std::same_as>) { - auto handler = std::make_unique( - std::forward(func)); + auto handler = std::make_unique(std::forward(func)); auto cname = std::string(name); - ::ucl_parser_register_macro( - self.get_parser(), cname.c_str(), - ¯o_handler::handle, handler.get()); + ::ucl_parser_register_macro(self.get_parser(), cname.c_str(), + ¯o_handler::handle, handler.get()); self.m_macros.emplace_back(std::move(handler)); } // Add a parser variable. - auto register_value(this parser &self, - std::string_view variable, - std::string_view value) - -> void; + auto + register_value(this parser &self, std::string_view variable, std::string_view value) -> void + { + ::ucl_parser_register_variable(self.get_parser(), std::string(variable).c_str(), + std::string(value).c_str()); + } // Add data to the parser. - [[nodiscard]] auto add(this parser &self, - std::ranges::contiguous_range auto &&data) + [[nodiscard]] auto add(this parser &self, std::ranges::contiguous_range auto &&data) -> std::expected - // Only bytes (chars) are permitted. + // Only bytes (chars) are permitted. requires(sizeof(std::ranges::range_value_t) == 1) { auto *p = self.get_parser(); - auto dptr = reinterpret_cast( - std::ranges::data(data)); + auto dptr = reinterpret_cast(std::ranges::data(data)); - auto ret = ::ucl_parser_add_chunk( - p, dptr, std::ranges::size(data)); + auto ret = ::ucl_parser_add_chunk(p, dptr, std::ranges::size(data)); if (ret == true) return {}; @@ -108,23 +117,34 @@ struct parser { return std::unexpected(error(::ucl_parser_get_error(p))); } - [[nodiscard]] auto add(this parser &self, - std::ranges::range auto &&data) - -> std::expected - requires (!std::ranges::contiguous_range) + [[nodiscard]] auto + add(this parser &self, std::ranges::range auto &&data) -> std::expected + requires(!std::ranges::contiguous_range) { - auto cdata = std::vector( - std::from_range, - std::forward(data)); + auto cdata = std::vector(std::from_range, std::forward(data)); co_await self.add(std::move(cdata)); co_return {}; } // Return the top object of this parser. - [[nodiscard]] auto top(this parser &self) -> map; + [[nodiscard]] auto top(this parser &self) -> map + { + auto *obj = ::ucl_parser_get_object(self.get_parser()); + if (obj != nullptr) + // ucl_parser_get_object() refs the object for us. + return {noref, obj}; + + throw std::logic_error("attempt to call top() on an invalid ucl::parser"); + } // Return the stored parser object. - [[nodiscard]] auto get_parser(this parser &self) -> ::ucl_parser *; + [[nodiscard]] auto get_parser(this parser &self) -> ::ucl_parser * + { + if (self.m_parser == nullptr) + throw std::logic_error("attempt to fetch a null ucl::parser"); + + return self.m_parser; + } private: // The parser object. Should never be null, unless we've been @@ -137,22 +157,26 @@ private: }; // Create a parser with the given flags. -export [[nodiscard]] auto -make_parser(int flags = 0) -> std::expected; +export [[nodiscard]] auto make_parser(int flags = 0) -> std::expected +{ + auto *p = ::ucl_parser_new(flags); + if (p != nullptr) + return {parser(p)}; + + // TODO: Is there a way to get the actual error here? + return std::unexpected(error("failed to create parser")); +} // Utility function to parse something and return the top-level object. export [[nodiscard]] auto -parse(int flags, std::ranges::range auto &&data) - -> std::expected, error> +parse(int flags, std::ranges::range auto &&data) -> std::expected, error> { auto p = co_await make_parser(flags); co_await p.add(std::forward(data)); co_return p.top(); } -export [[nodiscard]] auto -parse(std::ranges::range auto &&data) - -> std::expected, error> +export [[nodiscard]] auto parse(std::ranges::range auto &&data) -> std::expected, error> { co_return co_await parse(0, std::forward(data)); } diff --git a/nihil.ucl/real.cc b/nihil.ucl/real.cc deleted file mode 100644 index 6d9e082..0000000 --- a/nihil.ucl/real.cc +++ /dev/null @@ -1,104 +0,0 @@ -/* - * This source code is released into the public domain. - */ - -module; - -#include -#include -#include -#include -#include -#include - -#include - -module nihil.ucl; - -import nihil.error; - -namespace nihil::ucl { - -auto make_real(real::contained_type value) - -> std::expected -{ - auto *uobj = ::ucl_object_fromdouble(value); - if (uobj == nullptr) - return std::unexpected(error( - errc::failed_to_create_object, - error(std::errc(errno)))); - - return real(noref, uobj); -} - -real::real() - : real(0) -{ -} - -real::real(contained_type value) - : real(noref, [&] { - auto *uobj = ::ucl_object_fromdouble(value); - if (uobj == nullptr) - throw std::system_error( - std::make_error_code(std::errc(errno))); - return uobj; - }()) -{ -} - -real::real(ref_t, ::ucl_object_t const *uobj) - : object(nihil::ucl::ref, [&] { - auto actual_type = static_cast( - ::ucl_object_type(uobj)); - if (actual_type != real::ucl_type) - throw type_mismatch(real::ucl_type, actual_type); - return uobj; - }()) -{ -} - -real::real(noref_t, ::ucl_object_t *uobj) - : object(nihil::ucl::noref, [&] { - auto actual_type = static_cast( - ::ucl_object_type(uobj)); - if (actual_type != real::ucl_type) - throw type_mismatch(real::ucl_type, actual_type); - return uobj; - }()) -{ -} - -auto real::value(this real const &self) -> contained_type -{ - auto v = contained_type{}; - auto const *uobj = self.get_ucl_object(); - - if (::ucl_object_todouble_safe(uobj, &v)) - return v; - - std::abort(); -} - -auto operator== (real const &a, real const &b) -> bool -{ - return a.value() == b.value(); -} - -auto operator<=> (real const &a, real const &b) -> std::partial_ordering -{ - return a.value() <=> b.value(); -} - -auto operator== (real const &a, real::contained_type b) -> bool -{ - return a.value() == b; -} - -auto operator<=> (real const &a, real::contained_type b) - -> std::partial_ordering -{ - return a.value() <=> b; -} - -} // namespace nihil::ucl diff --git a/nihil.ucl/real.ccm b/nihil.ucl/real.ccm index f425a9a..b432617 100644 --- a/nihil.ucl/real.ccm +++ b/nihil.ucl/real.ccm @@ -1,78 +1,106 @@ -/* - * This source code is released into the public domain. - */ - +// This source code is released into the public domain. module; -#include -#include -#include -#include - #include export module nihil.ucl:real; +import nihil.std; +import nihil.core; import :object; import :type; namespace nihil::ucl { -export struct real final : object { +export struct real final : object +{ using contained_type = double; - inline static constexpr object_type ucl_type = object_type::real; + static constexpr object_type ucl_type = object_type::real; - /* - * Create a real holding the value 0. Throws std::system_error - * on failure. - */ - real(); + // Create a real holding the value 0. Throws std::system_error + // on failure. + real() + : real(0) + { + } - /* - * Create a real holding a specific value. Throws std::system_error - * on failure. - */ - explicit real(contained_type value); + // Create a real holding a specific value. Throws std::system_error + // on failure. + explicit real(contained_type value) + : real(noref, [&] { + auto *uobj = ::ucl_object_fromdouble(value); + if (uobj == nullptr) + throw std::system_error(std::make_error_code(sys_error())); + return uobj; + }()) + { + } - /* - * Create a new real from a UCL object. Throws type_mismatch - * on failure. - */ - real(ref_t, ::ucl_object_t const *uobj); - real(noref_t, ::ucl_object_t *uobj); + // Create a new real from a UCL object. Throws type_mismatch + // on failure. + real(ref_t, ::ucl_object_t const *uobj) + : object(nihil::ucl::ref, ensure_ucl_type(uobj, real::ucl_type)) + { + } + + real(noref_t, ::ucl_object_t *uobj) + : object(nihil::ucl::noref, ensure_ucl_type(uobj, real::ucl_type)) + { + } // Return the value of this real. - [[nodiscard]] auto value(this real const &self) -> contained_type; -}; + [[nodiscard]] auto value(this real const &self) -> contained_type + { + auto v = contained_type{}; + auto const *uobj = self.get_ucl_object(); + + if (::ucl_object_todouble_safe(uobj, &v)) + return v; -/* - * Real constructors. These return an error instead of throwing. - */ + throw std::runtime_error("ucl_object_todouble_safe failed"); + } -export [[nodiscard]] auto -make_real(real::contained_type = 0) -> std::expected; +private: + // + // Comparison operators. + // -/* - * Comparison operators. - */ + [[nodiscard]] friend auto operator==(real const &a, real const &b) -> bool + { + return a.value() == b.value(); + } -export [[nodiscard]] auto operator== (real const &a, real const &b) -> bool; + [[nodiscard]] friend auto operator<=>(real const &a, real const &b) -> std::partial_ordering + { + return a.value() <=> b.value(); + } -export [[nodiscard]] auto operator== (real const &a, - real::contained_type b) -> bool; + [[nodiscard]] friend auto operator==(real const &a, real::contained_type b) -> bool + { + return a.value() == b; + } -export [[nodiscard]] auto operator<=> (real const &a, real const &b) - -> std::partial_ordering; + [[nodiscard]] friend auto + operator<=>(real const &a, real::contained_type b) -> std::partial_ordering + { + return a.value() <=> b; + } +}; -export [[nodiscard]] auto operator<=> (real const &a, real::contained_type b) - -> std::partial_ordering; +// Real constructor. This returns an error instead of throwing. +export [[nodiscard]] auto make_real(real::contained_type value = 0) -> std::expected +{ + auto *uobj = ::ucl_object_fromdouble(value); + if (uobj == nullptr) + return std::unexpected(error(errc::failed_to_create_object, error(sys_error()))); -/* - * Literal operator. - */ + return real(noref, uobj); +} + +// Literal operator. inline namespace literals { -export constexpr auto operator""_ucl (long double d) -> real +export constexpr auto operator""_ucl(long double d) -> real { if (d > static_cast(std::numeric_limits::max()) || d < static_cast(std::numeric_limits::min())) @@ -80,32 +108,31 @@ export constexpr auto operator""_ucl (long double d) -> real return real(static_cast(d)); } -} // namespace nihil::ucl::literals +} // namespace literals } // namespace nihil::ucl -namespace nihil { inline namespace literals { - export using namespace ::nihil::ucl::literals; -}} // namespace nihil::literals +namespace nihil { +inline namespace literals { +export using namespace ::nihil::ucl::literals; +} +} // namespace nihil -/* - * std::formatter for a real. This provides the same format operations - * as std::formatter; - */ -export template<> +// std::formatter for a real. This provides the same format operations +// as std::formatter; +export template <> struct std::formatter { std::formatter base_formatter; - template - constexpr ParseContext::iterator parse(ParseContext& ctx) + template + constexpr auto parse(ParseContext &ctx) -> ParseContext::iterator { return base_formatter.parse(ctx); } - template - FmtContext::iterator format(nihil::ucl::real const &o, - FmtContext& ctx) const + template + auto format(nihil::ucl::real const &o, FmtContext &ctx) const -> FmtContext::iterator { return base_formatter.format(o.value(), ctx); } diff --git a/nihil.ucl/real.test.cc b/nihil.ucl/real.test.cc new file mode 100644 index 0000000..e880d9a --- /dev/null +++ b/nihil.ucl/real.test.cc @@ -0,0 +1,245 @@ +// This source code is released into the public domain. + +#include +#include +#include + +import nihil.std; +import nihil.ucl; + +namespace { +TEST_CASE("ucl: real: invariants", "[ucl]") +{ + using namespace nihil::ucl; + + static_assert(std::same_as); + REQUIRE(real::ucl_type == object_type::real); + REQUIRE(static_cast<::ucl_type>(real::ucl_type) == UCL_FLOAT); + + 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); +} + +TEST_CASE("ucl: real: constructor", "[ucl]") +{ + using namespace nihil::ucl; + + SECTION ("default") { + auto r = real(); + REQUIRE(r == 0); + } + + SECTION ("with value") { + auto r = real(42.1); + REQUIRE_THAT(r.value(), Catch::Matchers::WithinRel(42.1)); + } +} + +TEST_CASE("ucl: real: literal", "[ucl]") +{ + SECTION ("with namespace nihil::ucl::literals") { + using namespace nihil::ucl::literals; + + auto r = 42.5_ucl; + REQUIRE(r.type() == nihil::ucl::object_type::real); + REQUIRE_THAT(r.value(), Catch::Matchers::WithinRel(42.5)); + } + + SECTION ("with namespace nihil::literals") { + using namespace nihil::literals; + + auto r = 42.5_ucl; + REQUIRE(r.type() == nihil::ucl::object_type::real); + REQUIRE_THAT(r.value(), Catch::Matchers::WithinRel(42.5)); + } +} + +TEST_CASE("ucl: real: construct from UCL object", "[ucl]") +{ + using namespace nihil::ucl; + + SECTION ("ref, correct type") { + auto *uobj = ::ucl_object_fromdouble(42); + + auto r = real(ref, uobj); + REQUIRE(r == 42); + + ::ucl_object_unref(uobj); + } + + SECTION ("noref, correct type") { + auto *uobj = ::ucl_object_fromdouble(42); + + auto r = real(noref, uobj); + REQUIRE(r == 42); + } + + SECTION ("ref, wrong type") { + auto *uobj = ::ucl_object_fromint(42); + + REQUIRE_THROWS_AS(real(ref, uobj), type_mismatch); + + ::ucl_object_unref(uobj); + } + + SECTION ("noref, wrong type") { + auto *uobj = ::ucl_object_fromint(42); + + REQUIRE_THROWS_AS(real(noref, uobj), type_mismatch); + + ::ucl_object_unref(uobj); + } +} + +TEST_CASE("ucl: real: make_real", "[ucl]") +{ + using namespace nihil::ucl; + + SECTION ("default value") { + auto i = make_real().value(); + REQUIRE(i == 0); + } + + SECTION ("explicit value") { + auto i = make_real(42).value(); + REQUIRE(i == 42); + } +} + +TEST_CASE("ucl: real: swap", "[ucl]") +{ + // do not add using namespace nihil::ucl + + auto r1 = nihil::ucl::real(1); + auto r2 = nihil::ucl::real(2); + + swap(r1, r2); + + REQUIRE(r1 == 2.); + REQUIRE(r2 == 1.); +} + +TEST_CASE("ucl: real: value()", "[ucl]") +{ + using namespace nihil::ucl; + + auto r = 42.5_ucl; + REQUIRE_THAT(r.value(), Catch::Matchers::WithinRel(42.5)); +} + +TEST_CASE("ucl: real: key()", "[ucl]") +{ + using namespace nihil::ucl; + + SECTION ("parsed with key") { + auto obj = parse("a_real = 42.5").value(); + auto r = object_cast(obj["a_real"]).value(); + REQUIRE(r.key() == "a_real"); + } + + SECTION ("bare real, no key") { + auto i = 42.5_ucl; + REQUIRE(i.key().empty() == true); + } +} + +TEST_CASE("ucl: real: comparison", "[ucl]") +{ + using namespace nihil::ucl; + + auto i = nihil::ucl::real(42.5); + + SECTION ("operator==") { + REQUIRE(i == 42.5); + REQUIRE(i == 42.5_ucl); + } + + SECTION ("operator!=") { + REQUIRE(i != 1); + REQUIRE(i != 1._ucl); + } + + SECTION ("operator<") { + REQUIRE(i < 43); + REQUIRE(i < 43._ucl); + } + + SECTION ("operator>") { + REQUIRE(i > 1); + REQUIRE(i > 1._ucl); + } +} + +TEST_CASE("ucl: real: parse", "[ucl]") +{ + using namespace nihil::ucl; + + auto obj = parse("value = 42.1").value(); + + auto v = obj["value"]; + REQUIRE(v.key() == "value"); + REQUIRE_THAT(object_cast(v).value().value(), Catch::Matchers::WithinRel(42.1)); +} + +TEST_CASE("ucl: real: parse and emit", "[ucl]") +{ + using namespace nihil::ucl; + + auto ucl = parse("real = 42.2").value(); + + auto output = std::string(); + emit(ucl, emitter::configuration, std::back_inserter(output)); + + REQUIRE(output == "real = 42.2;\n"); +} + +TEST_CASE("ucl: real: format", "[ucl]") +{ + using namespace nihil::ucl; + + SECTION ("bare real") { + auto str = std::format("{}", 42.5_ucl); + REQUIRE(str == "42.5"); + } + + SECTION ("parsed real") { + auto obj = parse("real = 42.5;").value(); + auto r = object_cast(obj["real"]).value(); + + auto str = std::format("{}", r); + REQUIRE(str == "42.5"); + } + + SECTION ("with format string") { + auto str = std::format("{:10.5f}", 42.5_ucl); + REQUIRE(str == " 42.50000"); + } +} + +TEST_CASE("ucl: real: print to ostream", "[ucl]") +{ + using namespace nihil::ucl; + + SECTION ("bare real") { + auto strm = std::ostringstream(); + strm << 42.5_ucl; + + REQUIRE(strm.str() == "42.5"); + } + + SECTION ("parsed real") { + auto obj = parse("real = 42.5;").value(); + auto i = object_cast(obj["real"]).value(); + + auto strm = std::ostringstream(); + strm << i; + + REQUIRE(strm.str() == "42.5"); + } +} +} // anonymous namespace diff --git a/nihil.ucl/string.cc b/nihil.ucl/string.cc deleted file mode 100644 index 67e97f4..0000000 --- a/nihil.ucl/string.cc +++ /dev/null @@ -1,187 +0,0 @@ -/* - * This source code is released into the public domain. - */ - -module; - -#include -#include -#include -#include -#include - -#include - -module nihil.ucl; - -import nihil.error; - -namespace nihil::ucl { - -auto make_string() -> std::expected -{ - return make_string(std::string_view("")); -} - -auto make_string(char const *s) -> std::expected -{ - return make_string(std::string_view(s)); -} - -auto make_string(std::string_view s) -> std::expected -{ - auto *uobj = ::ucl_object_fromstring_common( - s.data(), s.size(), UCL_STRING_RAW); - - if (uobj == nullptr) - return std::unexpected(error( - errc::failed_to_create_object, - error(std::errc(errno)))); - - return string(noref, uobj); -} - -string::string(ref_t, ::ucl_object_t const *uobj) - : object(nihil::ucl::ref, [&] { - auto actual_type = static_cast( - ::ucl_object_type(uobj)); - if (actual_type != string::ucl_type) - throw type_mismatch(string::ucl_type, actual_type); - return uobj; - }()) -{ -} - -string::string(noref_t, ::ucl_object_t *uobj) - : object(nihil::ucl::noref, [&] { - auto actual_type = static_cast( - ::ucl_object_type(uobj)); - if (actual_type != string::ucl_type) - throw type_mismatch(string::ucl_type, actual_type); - return uobj; - }()) -{ -} - -string::string() - : string(std::string_view("")) -{} - -string::string(std::string_view value) - : string(noref, [&] { - auto *uobj = ::ucl_object_fromstring_common( - value.data(), value.size(), UCL_STRING_RAW); - if (uobj == nullptr) - throw std::system_error( - std::make_error_code(std::errc(errno))); - return uobj; - }()) -{ -} - -string::string(char const *value) - : string(std::string_view(value)) -{ -} - -auto string::value(this string const &self) -> contained_type -{ - char const *dptr{}; - std::size_t dlen; - - auto const *uobj = self.get_ucl_object(); - if (::ucl_object_tolstring_safe(uobj, &dptr, &dlen)) - return {dptr, dlen}; - - // This should never fail. - std::abort(); -} - -auto string::size(this string const &self) -> size_type -{ - return self.value().size(); -} - -auto string::empty(this string const &self) -> bool -{ - return self.size() == 0; -} - -auto string::data(this string const &self) -> pointer -{ - char const *dptr{}; - - auto const *uobj = self.get_ucl_object(); - if (::ucl_object_tostring_safe(uobj, &dptr)) - return dptr; - - // This should never fail. - std::abort(); -} - -auto string::begin(this string const &self) -> iterator -{ - return self.data(); -} - -auto string::end(this string const &self) -> iterator -{ - return self.data() + self.size(); -} - -auto operator== (string const &a, string const &b) - -> bool -{ - return a.value() == b.value(); -} - -auto operator<=> (string const &a, string const &b) - -> std::strong_ordering -{ - return a.value() <=> b.value(); -} - -/* - * For convenience, allow comparison with C++ strings without having to - * construct a temporary UCL object. - */ - -auto operator==(string const &lhs, std::string_view rhs) -> bool -{ - return lhs.value() == rhs; -} - -auto operator<=>(string const &lhs, std::string_view rhs) - -> std::strong_ordering -{ - return lhs.value() <=> rhs; -} - -auto operator==(string const &lhs, std::string const &rhs) -> bool -{ - return lhs == std::string_view(rhs); -} - -auto operator<=>(string const &lhs, std::string const &rhs) - -> std::strong_ordering -{ - return lhs <=> std::string_view(rhs); -} - -auto operator==(string const &lhs, char const *rhs) -> bool -{ - return lhs == std::string_view(rhs); -} - -auto operator<=>(string const &lhs, char const *rhs) - -> std::strong_ordering -{ - return lhs <=> std::string_view(rhs); -} - -auto operator<<(std::ostream &strm, string const &s) -> std::ostream & -{ - return strm << s.value(); -} - -} // namespace nihil::ucl diff --git a/nihil.ucl/string.ccm b/nihil.ucl/string.ccm index c757bf1..4b46e39 100644 --- a/nihil.ucl/string.ccm +++ b/nihil.ucl/string.ccm @@ -1,27 +1,21 @@ -/* - * This source code is released into the public domain. - */ - +// This source code is released into the public domain. module; -#include -#include -#include -#include -#include - #include export module nihil.ucl:string; +import nihil.std; +import nihil.core; import :object; import :type; namespace nihil::ucl { -export struct string final : object { +export struct string final : object +{ using contained_type = std::string_view; - inline static constexpr object_type ucl_type = object_type::string; + static constexpr object_type ucl_type = object_type::string; // string is a container of char using value_type = char const; @@ -31,158 +25,235 @@ export struct string final : object { using pointer = value_type *; using iterator = pointer; - /* - * Create a new empty string. Throws std::system_error on failure. - */ - string(); - - /* - * Create a string from a value. Throws std::system_error on failure. - */ - explicit string(std::string_view); - - /* - * Create a string from a C literal. Throws std::system_error - * on failure. - */ - explicit string(char const *); - - /* - * Create a string from a contiguous range. The range's value type - * must be char. Throws std::system_error on failure. - */ - template - requires (!std::same_as && - std::same_as>) - explicit string(Range &&range) - : string(std::string_view(std::ranges::begin(range), - std::ranges::end(range))) - {} - - /* - * Create a string from a non-contiguous range. This requires a - * temporary value due to limitations of the UCL C API. - */ - template - requires (!std::ranges::contiguous_range && - std::same_as>) - explicit string(Range &&range) - : string(std::string(std::from_range, range)) - {} - - /* - * Create a string from an iterator pair. The iterator's value type - * must be char. If the iterator pair is not contiguous, the value - * will be copied to a temporary first. - * - * Throws std::system_error on failure. - */ - template - requires (std::same_as>) + // Create a new empty string. Throws std::system_error on failure. + string() + : string(std::string_view("")) + { + } + + // Create a string from a value. Throws std::system_error on failure. + explicit string(std::string_view value) + : string(noref, [&] { + auto *uobj = ::ucl_object_fromstring_common(value.data(), value.size(), + UCL_STRING_RAW); + if (uobj == nullptr) + throw std::system_error(std::make_error_code(sys_error())); + return uobj; + }()) + { + } + + // Create a string from a C literal. Throws std::system_error + // on failure. + explicit string(char const *value) + : string(std::string_view(value)) + { + } + + // Create a string from a contiguous range. The range's value type + // must be char. Throws std::system_error on failure. + template + requires(!std::same_as && + std::same_as>) + explicit string(Range const &range) + : string(std::string_view(std::ranges::begin(range), std::ranges::end(range))) + { + } + + // Create a string from a non-contiguous range. This requires a + // temporary value due to limitations of the UCL C API. + template + requires(!std::ranges::contiguous_range && + std::same_as>) + explicit string(Range range) + : string(std::string(std::from_range, std::forward(range))) + { + } + + // Create a string from an iterator pair. The iterator's value type + // must be char. If the iterator pair is not contiguous, the value + // will be copied to a temporary first. + // + // Throws std::system_error on failure. + template + requires(std::same_as>) string(Iterator first, Iterator last) : string(std::ranges::subrange(first, last)) - {} + { + } + + // Create a new string from a UCL object. Throws type_mismatch + // on failure. + string(ref_t, ::ucl_object_t const *uobj) + : object(nihil::ucl::ref, ensure_ucl_type(uobj, string::ucl_type)) + { + } - /* - * Create a new string from a UCL object. Throws type_mismatch - * on failure. - */ - string(ref_t, ::ucl_object_t const *uobj); - string(noref_t, ::ucl_object_t *uobj); + string(noref_t, ::ucl_object_t *uobj) + : object(nihil::ucl::noref, ensure_ucl_type(uobj, string::ucl_type)) + { + } // Return the value of this string. - [[nodiscard]] auto value(this string const &self) -> contained_type; + [[nodiscard]] auto value(this string const &self) -> contained_type + { + char const *dptr{}; + std::size_t dlen{}; + + auto const *uobj = self.get_ucl_object(); + if (::ucl_object_tolstring_safe(uobj, &dptr, &dlen)) + return {dptr, dlen}; + + throw std::runtime_error("ucl_object_tolstring_safe() failed"); + } // Return the size of this string. - [[nodiscard]] auto size(this string const &self) -> size_type; + [[nodiscard]] auto size(this string const &self) -> size_type + { + return self.value().size(); + } // Test if this string is empty. - [[nodiscard]] auto empty(this string const &self) -> bool; + [[nodiscard]] auto empty(this string const &self) -> bool + { + return self.size() == 0; + } // Access this string's data - [[nodiscard]] auto data(this string const &self) -> pointer; + [[nodiscard]] auto data(this string const &self) -> pointer + { + char const *dptr{}; + + auto const *uobj = self.get_ucl_object(); + if (::ucl_object_tostring_safe(uobj, &dptr)) + return dptr; + + throw std::runtime_error("ucl_object_tostring_safe() failed"); + } // Iterator access - [[nodiscard]] auto begin(this string const &self) -> iterator; - [[nodiscard]] auto end(this string const &self) -> iterator; -}; + [[nodiscard]] auto begin(this string const &self) -> iterator + { + return self.data(); + } -/* - * String constructors. These return an error instead of throwing. - */ + [[nodiscard]] auto end(this string const &self) -> iterator + { + return self.data() + self.size(); + } -// Empty string -export [[nodiscard]] auto -make_string() -> std::expected; +private: + // + // Comparison operators. + // + + [[nodiscard]] friend auto operator==(string const &a, string const &b) -> bool + { + return a.value() == b.value(); + } + + [[nodiscard]] friend auto + operator<=>(string const &a, string const &b) -> std::strong_ordering + { + return a.value() <=> b.value(); + } + + // For convenience, allow comparison with C++ strings without having to + // construct a temporary UCL object. + + [[nodiscard]] friend auto operator==(string const &lhs, std::string_view rhs) -> bool + { + return lhs.value() == rhs; + } + + [[nodiscard]] friend auto + operator<=>(string const &lhs, std::string_view rhs) -> std::strong_ordering + { + return lhs.value() <=> rhs; + } + + [[nodiscard]] friend auto operator==(string const &lhs, std::string const &rhs) -> bool + { + return lhs == std::string_view(rhs); + } + + [[nodiscard]] friend auto + operator<=>(string const &lhs, std::string const &rhs) -> std::strong_ordering + { + return lhs <=> std::string_view(rhs); + } + + [[nodiscard]] friend auto operator==(string const &lhs, char const *rhs) -> bool + { + return lhs == std::string_view(rhs); + } + + [[nodiscard]] friend auto + operator<=>(string const &lhs, char const *rhs) -> std::strong_ordering + { + return lhs <=> std::string_view(rhs); + } + + // Stream output. + friend auto operator<<(std::ostream &strm, string const &s) -> std::ostream & + { + return strm << s.value(); + } +}; + +// +// String constructors. These return an error instead of throwing. +// // From string_view -export [[nodiscard]] auto -make_string(std::string_view) -> std::expected; +export [[nodiscard]] auto make_string(std::string_view s) -> std::expected +{ + auto *uobj = ::ucl_object_fromstring_common(s.data(), s.size(), UCL_STRING_RAW); + + if (uobj == nullptr) + return std::unexpected(error(errc::failed_to_create_object, error(sys_error()))); + + return string(noref, uobj); +} + +// Empty string +export [[nodiscard]] auto make_string() -> std::expected +{ + return make_string(std::string_view("")); +} // From C literal -export [[nodiscard]] auto -make_string(char const *) -> std::expected; +export [[nodiscard]] auto make_string(char const *s) -> std::expected +{ + return make_string(std::string_view(s)); +} // From contiguous range -export template -requires (!std::same_as && - std::same_as>) +export template +requires(!std::same_as && + std::same_as>) [[nodiscard]] auto make_string(Range &&range) { - return make_string(std::string_view(range)); + return make_string(std::string_view(std::forward(range))); } // From non-contiguous range -export template -requires (!std::ranges::contiguous_range && - std::same_as>) +export template +requires(!std::ranges::contiguous_range && + std::same_as>) [[nodiscard]] auto make_string(Range &&range) { - return make_string(std::string(std::from_range, range)); + return make_string(std::string(std::from_range, std::forward(range))); } // From iterator pair -export template -requires (std::same_as>) +export template +requires(std::same_as>) [[nodiscard]] auto make_string(Iterator first, Iterator last) { return make_string(std::ranges::subrange(first, last)); } -/* - * Comparison operators. - */ - -export [[nodiscard]] auto operator== (string const &a, string const &b) -> bool; -export [[nodiscard]] auto operator<=> (string const &a, string const &b) - -> std::strong_ordering; - -/* - * For convenience, allow comparison with C++ strings without having to - * construct a temporary UCL object. - */ - -export [[nodiscard]] auto operator==(string const &lhs, - std::string_view rhs) -> bool; - -export [[nodiscard]] auto operator==(string const &lhs, - std::string const &rhs) -> bool; - -export [[nodiscard]] auto operator==(string const &lhs, - char const *rhs) -> bool; - -export [[nodiscard]] auto operator<=>(string const &lhs, - std::string_view rhs) - -> std::strong_ordering; - -export [[nodiscard]] auto operator<=>(string const &lhs, - std::string const &rhs) - -> std::strong_ordering; - -export [[nodiscard]] auto operator<=>(string const &lhs, - char const *rhs) - -> std::strong_ordering; - /* * Print a string to a stream. */ @@ -192,37 +263,37 @@ export auto operator<<(std::ostream &, string const &) -> std::ostream &; * Literal operator. */ inline namespace literals { - export constexpr auto operator""_ucl (char const *s, std::size_t n) - -> string - { - return string(std::string_view(s, n)); - } -} // namespace nihil::ucl::literals +export constexpr auto operator""_ucl(char const *s, std::size_t n) -> string +{ + return string(std::string_view(s, n)); +} +} // namespace literals } // namespace nihil::ucl -namespace nihil { inline namespace literals { - export using namespace ::nihil::ucl::literals; -}} // namespace nihil::literals +namespace nihil { +inline namespace literals { +export using namespace ::nihil::ucl::literals; +} +} // namespace nihil /* * std::formatter for a string. This provides the same format operations * as std::formatter. */ -export template<> +export template <> struct std::formatter { std::formatter base_formatter; - template - constexpr ParseContext::iterator parse(ParseContext& ctx) + template + constexpr auto parse(ParseContext &ctx) -> ParseContext::iterator { return base_formatter.parse(ctx); } - template - FmtContext::iterator format(nihil::ucl::string const &o, - FmtContext& ctx) const + template + auto format(nihil::ucl::string const &o, FmtContext &ctx) const -> FmtContext::iterator { return base_formatter.format(o.value(), ctx); } diff --git a/nihil.ucl/string.test.cc b/nihil.ucl/string.test.cc new file mode 100644 index 0000000..68c57e8 --- /dev/null +++ b/nihil.ucl/string.test.cc @@ -0,0 +1,407 @@ +// This source code is released into the public domain. + +#include +#include + +import nihil.std; +import nihil.ucl; + +namespace { +TEST_CASE("ucl: string: invariants", "[ucl]") +{ + using namespace nihil::ucl; + + static_assert(std::same_as); + REQUIRE(string::ucl_type == object_type::string); + REQUIRE(static_cast<::ucl_type>(string::ucl_type) == UCL_STRING); + + 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::ranges::contiguous_range); + static_assert(std::same_as>); +} + +TEST_CASE("ucl: string: literal", "[ucl]") +{ + SECTION("with namespace nihil::ucl::literals") { + using namespace nihil::ucl::literals; + + auto s = "testing"_ucl; + REQUIRE(s.type() == nihil::ucl::object_type::string); + REQUIRE(s == "testing"); + } + + SECTION("with namespace nihil::literals") { + using namespace nihil::literals; + + auto s = "testing"_ucl; + REQUIRE(s.type() == nihil::ucl::object_type::string); + REQUIRE(s == "testing"); + } +} + +TEST_CASE("ucl: string: construct", "[ucl]") +{ + using namespace nihil::ucl; + using namespace std::literals; + + SECTION("empty string") { + auto str = string(); + REQUIRE(str.type() == object_type::string); + REQUIRE(str == ""); + } + + SECTION("with integer-like value") { + auto str = "42"_ucl; + REQUIRE(str.type() == object_type::string); + REQUIRE(str == "42"); + } + + SECTION("with boolean-like value") { + auto str = "true"_ucl; + REQUIRE(str.type() == object_type::string); + REQUIRE(str == "true"); + } + + SECTION("from string literal") { + auto str = string("testing"); + REQUIRE(str.type() == object_type::string); + REQUIRE(str == "testing"); + } + + SECTION("from std::string") { + auto str = string("testing"s); + REQUIRE(str.type() == object_type::string); + REQUIRE(str == "testing"); + } + + SECTION("from std::string_view") { + auto str = string("testing"sv); + REQUIRE(str.type() == object_type::string); + REQUIRE(str == "testing"); + } + + SECTION("from contiguous range") { + auto s = std::vector{'t', 'e', 's', 't', 'i', 'n', 'g'}; + auto str = string(s); + REQUIRE(str.type() == object_type::string); + REQUIRE(str == "testing"); + } + + SECTION("from non-contiguous range") { + auto s = std::list{'t', 'e', 's', 't', 'i', 'n', 'g'}; + auto str = string(s); + REQUIRE(str.type() == object_type::string); + REQUIRE(str == "testing"); + } + + SECTION("from contiguous iterator pair") { + auto s = std::vector{'t', 'e', 's', 't', 'i', 'n', 'g'}; + auto str = string(s.begin(), s.end()); + REQUIRE(str.type() == object_type::string); + REQUIRE(str == "testing"); + } + + SECTION("from non-contiguous iterator pair") { + auto s = std::list{'t', 'e', 's', 't', 'i', 'n', 'g'}; + auto str = string(s.begin(), s.end()); + REQUIRE(str.type() == object_type::string); + REQUIRE(str == "testing"); + } +} + +TEST_CASE("ucl: string: construct from UCL object", "[ucl]") +{ + using namespace nihil::ucl; + + SECTION("ref, correct type") { + auto *uobj = ::ucl_object_fromstring("testing"); + + auto const s = string(ref, uobj); + REQUIRE(s == "testing"); + + ::ucl_object_unref(uobj); + } + + SECTION("noref, correct type") { + auto *uobj = ::ucl_object_fromstring("testing"); + + auto const s = string(noref, uobj); + REQUIRE(s == "testing"); + } + + SECTION("ref, wrong type") { + auto *uobj = ::ucl_object_frombool(true); + + REQUIRE_THROWS_AS(string(ref, uobj), type_mismatch); + + ::ucl_object_unref(uobj); + } + + SECTION("noref, wrong type") { + auto *uobj = ::ucl_object_frombool(true); + + REQUIRE_THROWS_AS(string(noref, uobj), type_mismatch); + + ::ucl_object_unref(uobj); + } +} + +TEST_CASE("ucl: string: make_string", "[ucl]") +{ + using namespace nihil::ucl; + using namespace std::literals; + + SECTION("empty string") { + auto const str = make_string().value(); + REQUIRE(str.type() == object_type::string); + REQUIRE(str == ""); + } + + SECTION("from string literal") { + auto const str = make_string("testing").value(); + REQUIRE(str.type() == object_type::string); + REQUIRE(str == "testing"); + } + + SECTION("from std::string") { + auto const str = make_string("testing"s).value(); + REQUIRE(str.type() == object_type::string); + REQUIRE(str == "testing"); + } + + SECTION("from std::string_view") { + auto const str = make_string("testing"sv).value(); + REQUIRE(str.type() == object_type::string); + REQUIRE(str == "testing"); + } + + SECTION("from contiguous range") { + auto const s = std::vector{'t', 'e', 's', 't', 'i', 'n', 'g'}; + auto const str = make_string(s).value(); + REQUIRE(str.type() == object_type::string); + REQUIRE(str == "testing"); + } + + SECTION("from non-contiguous range") { + auto const s = std::list{'t', 'e', 's', 't', 'i', 'n', 'g'}; + auto const str = make_string(s).value(); + REQUIRE(str.type() == object_type::string); + REQUIRE(str == "testing"); + } + + SECTION("from contiguous iterator pair") { + auto const s = std::vector{'t', 'e', 's', 't', 'i', 'n', 'g'}; + auto const str = make_string(s.begin(), s.end()).value(); + REQUIRE(str.type() == object_type::string); + REQUIRE(str == "testing"); + } + + SECTION("from non-contiguous iterator pair") { + auto const s = std::list{'t', 'e', 's', 't', 'i', 'n', 'g'}; + auto const str = make_string(s.begin(), s.end()).value(); + REQUIRE(str.type() == object_type::string); + REQUIRE(str == "testing"); + } +} + +TEST_CASE("ucl: string: swap", "[ucl]") +{ + // do not add using namespace nihil::ucl + + auto s1 = nihil::ucl::string("one"); + auto s2 = nihil::ucl::string("two"); + + swap(s1, s2); + + REQUIRE(s1 == "two"); + REQUIRE(s2 == "one"); +} + +TEST_CASE("ucl: string: value()", "[ucl]") +{ + using namespace nihil::ucl; + + auto const s = string(R"(te"st)"); + REQUIRE(s.value() == R"(te"st)"); +} + +TEST_CASE("ucl: string: key()", "[ucl]") +{ + using namespace nihil::ucl; + + auto obj = parse(R"(a_string = "test")").value(); + REQUIRE(object_cast(obj["a_string"]).value().key() == "a_string"); + + auto const s = string("test"); + REQUIRE(s.key().empty() == true); +} + +TEST_CASE("ucl: string: size", "[ucl]") +{ + using namespace nihil::ucl; + + REQUIRE(string().size() == 0); + REQUIRE(string("test").size() == 4); +} + +TEST_CASE("ucl: string: empty", "[ucl]") +{ + using namespace nihil::ucl; + + REQUIRE(string().empty() == true); + REQUIRE(string("test").empty() == false); +} + +TEST_CASE("ucl: string: iterate", "[ucl]") +{ + using namespace nihil::ucl; + + auto str = "test"_ucl; + + SECTION("as iterator pair") { + auto begin = str.begin(); + static_assert(std::contiguous_iterator); + + auto end = str.end(); + static_assert(std::sentinel_for); + + REQUIRE(*begin == 't'); + ++begin; + REQUIRE(*begin == 'e'); + ++begin; + REQUIRE(*begin == 's'); + ++begin; + REQUIRE(*begin == 't'); + ++begin; + + REQUIRE(begin == end); + } + + SECTION("as range") { + auto s = std::string(std::from_range, str); + REQUIRE(s == "test"); + } +} + +TEST_CASE("ucl: string: comparison", "[ucl]") +{ + using namespace nihil::ucl; + + auto str = "testing"_ucl; + + SECTION("operator==") { + REQUIRE(str == "testing"_ucl); + REQUIRE(str == std::string_view("testing")); + REQUIRE(str == std::string("testing")); + REQUIRE(str == "testing"); + } + + SECTION("operator!=") { + REQUIRE(str != "test"_ucl); + REQUIRE(str != std::string_view("test")); + REQUIRE(str != std::string("test")); + REQUIRE(str != "test"); + } + + SECTION("operator<") { + REQUIRE(str < "zzz"_ucl); + REQUIRE(str < std::string_view("zzz")); + REQUIRE(str < std::string("zzz")); + REQUIRE(str < "zzz"); + } + + SECTION("operator>") { + REQUIRE(str > "aaa"_ucl); + REQUIRE(str > std::string_view("aaa")); + REQUIRE(str > std::string("aaa")); + REQUIRE(str > "aaa"); + } +} + +TEST_CASE("ucl: string: parse", "[ucl]") +{ + using namespace nihil::ucl; + + auto obj = parse(R"(value = "te\"st")").value(); + + auto v = obj["value"]; + REQUIRE(v.key() == "value"); + REQUIRE(object_cast(v).value() == "te\"st"); +} + +TEST_CASE("ucl: string: emit", "[ucl]") +{ + using namespace nihil::ucl; + + auto ucl = parse(R"(str = "te\"st";)").value(); + + auto output = std::string(); + emit(ucl, emitter::configuration, std::back_inserter(output)); + + REQUIRE(output == "str = \"te\\\"st\";\n"); +} + +TEST_CASE("ucl: string: format", "[ucl]") +{ + using namespace nihil::ucl; + using namespace std::literals; + + auto constexpr test_string = "te\"st"sv; + + SECTION("bare string") { + auto str = std::format("{}", string(test_string)); + REQUIRE(str == test_string); + } + + SECTION("parsed string") { + auto obj = parse(R"(string = "te\"st";)").value(); + auto s = object_cast(obj["string"]).value(); + + auto str = std::format("{}", s); + REQUIRE(str == test_string); + } + + SECTION("with format string") { + auto str = std::format("{: >10}", string(test_string)); + REQUIRE(str == " te\"st"); + } +} + +TEST_CASE("ucl: string: print to ostream", "[ucl]") +{ + using namespace nihil::ucl; + using namespace std::literals; + + auto constexpr test_string = "te\"st"sv; + + SECTION("bare string") { + auto strm = std::ostringstream(); + strm << string(test_string); + + REQUIRE(strm.str() == test_string); + } + + SECTION("parsed string") { + auto obj = parse(R"(string = "te\"st";)").value(); + auto s = object_cast(obj["string"]).value(); + + auto strm = std::ostringstream(); + strm << s; + + REQUIRE(strm.str() == test_string); + } + + SECTION("with format string") { + auto str = std::format("{: >10}", string(test_string)); + REQUIRE(str == " te\"st"); + } +} +} // anonymous namespace diff --git a/nihil.ucl/tests/CMakeLists.txt b/nihil.ucl/tests/CMakeLists.txt deleted file mode 100644 index 13f30fa..0000000 --- a/nihil.ucl/tests/CMakeLists.txt +++ /dev/null @@ -1,20 +0,0 @@ -# This source code is released into the public domain. - -add_executable(nihil.ucl.test - emit.cc - parse.cc - - object.cc - array.cc - boolean.cc - integer.cc - map.cc - real.cc - string.cc -) - -target_link_libraries(nihil.ucl.test PRIVATE nihil.ucl Catch2::Catch2WithMain) - -include(CTest) -include(Catch) -catch_discover_tests(nihil.ucl.test) diff --git a/nihil.ucl/tests/array.cc b/nihil.ucl/tests/array.cc deleted file mode 100644 index 866fa45..0000000 --- a/nihil.ucl/tests/array.cc +++ /dev/null @@ -1,478 +0,0 @@ -/* - * This source code is released into the public domain. - */ - -#include -#include -#include -#include -#include - -#include -#include - -import nihil.ucl; - -TEST_CASE("ucl: array: invariants", "[ucl]") -{ - using namespace nihil::ucl; - - REQUIRE(array<>::ucl_type == object_type::array); - REQUIRE(static_cast<::ucl_type>(array<>::ucl_type) == UCL_ARRAY); - - 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::ranges::sized_range>); - static_assert(std::same_as>, - integer>); -} - -TEST_CASE("ucl: array: constructor", "[ucl]") -{ - using namespace nihil::ucl; - - SECTION("default") { - auto arr = array(); - REQUIRE(arr.size() == 0); - REQUIRE(str(arr.type()) == "array"); - } - - SECTION("from range") { - auto vec = std::vector{integer(1), integer(42)}; - auto arr = array(std::from_range, vec); - - REQUIRE(arr.size() == 2); - REQUIRE(arr[0] == 1); - REQUIRE(arr[1] == 42); - } - - SECTION("from iterator pair") { - auto vec = std::vector{integer(1), integer(42)}; - auto arr = array(std::ranges::begin(vec), - std::ranges::end(vec)); - - REQUIRE(arr.size() == 2); - REQUIRE(arr[0] == 1); - REQUIRE(arr[1] == 42); - } - - SECTION("from initializer_list") { - auto arr = array{integer(1), integer(42)}; - - REQUIRE(arr.size() == 2); - REQUIRE(arr[0] == 1); - REQUIRE(arr[1] == 42); - } -} - -TEST_CASE("ucl: array: construct from UCL object", "[ucl]") -{ - using namespace nihil::ucl; - - SECTION("ref, correct type") { - auto uarr = ::ucl_object_typed_new(UCL_ARRAY); - auto uint = ::ucl_object_fromint(42); - ::ucl_array_append(uarr, uint); - - auto arr = array(ref, uarr); - REQUIRE(arr[0] == 42); - - ::ucl_object_unref(uarr); - } - - SECTION("noref, correct type") { - auto uarr = ::ucl_object_typed_new(UCL_ARRAY); - auto uint = ::ucl_object_fromint(42); - ::ucl_array_append(uarr, uint); - - auto arr = array(noref, uarr); - REQUIRE(arr[0] == 42); - } - - SECTION("ref, wrong element type") { - auto uarr = ::ucl_object_typed_new(UCL_ARRAY); - auto uint = ::ucl_object_frombool(true); - ::ucl_array_append(uarr, uint); - - auto arr = array(noref, uarr); - REQUIRE_THROWS_AS(arr[0], type_mismatch); - } - - SECTION("ref, wrong type") { - auto uobj = ::ucl_object_frombool(true); - - REQUIRE_THROWS_AS(array(ref, uobj), type_mismatch); - - ::ucl_object_unref(uobj); - } - - SECTION("noref, wrong type") { - auto uobj = ::ucl_object_frombool(true); - - REQUIRE_THROWS_AS(array(noref, uobj), type_mismatch); - - ::ucl_object_unref(uobj); - } -} - -TEST_CASE("ucl: array: swap", "[ucl]") -{ - // do not add using namespace nihil::ucl - - auto arr1 = nihil::ucl::array{ - nihil::ucl::integer(1), - nihil::ucl::integer(2) - }; - - auto arr2 = nihil::ucl::array{ - nihil::ucl::integer(3), - }; - - swap(arr1, arr2); - - REQUIRE(arr1.size() == 1); - REQUIRE(arr1[0] == 3); - - REQUIRE(arr2.size() == 2); - REQUIRE(arr2[0] == 1); -} - -TEST_CASE("ucl: array: push_back", "[ucl]") -{ - using namespace nihil::ucl; - - auto arr = array(); - REQUIRE(arr.size() == 0); - - arr.push_back(integer(1)); - arr.push_back(integer(42)); - arr.push_back(integer(666)); - - REQUIRE(arr.size() == 3); - REQUIRE(arr[0] == 1); - REQUIRE(arr[1] == 42); - REQUIRE(arr[2] == 666); - - REQUIRE_THROWS_AS(arr[3], std::out_of_range); - - REQUIRE(arr.front() == 1); - REQUIRE(arr.back() == 666); -} - -TEST_CASE("ucl: array: compare", "[ucl]") -{ - using namespace nihil::ucl; - - auto arr = array{ - integer(1), integer(42), integer(666) - }; - - auto arr2 = array(); - REQUIRE(arr != arr2); - - arr2.push_back(integer(1)); - arr2.push_back(integer(42)); - arr2.push_back(integer(666)); - REQUIRE(arr == arr2); - - auto arr3 = array{ - integer(1), integer(1), integer(1) - }; - - REQUIRE(arr != arr3); -} - -TEST_CASE("ucl: array: iterator", "[ucl]") -{ - using namespace nihil::ucl; - - auto arr = array{integer(1), integer(42), integer(666)}; - - auto it = arr.begin(); - REQUIRE(*it == 1); - auto end = arr.end(); - REQUIRE(it != end); - REQUIRE(it < end); - - ++it; - REQUIRE(*it == 42); - - ++it; - REQUIRE(*it == 666); - - --it; - REQUIRE(*it == 42); - - ++it; - REQUIRE(it != end); - ++it; - REQUIRE(it == end); -} - -TEST_CASE("ucl: array: parse", "[ucl]") -{ - using namespace std::literals; - using namespace nihil::ucl; - - auto obj = parse("value = [1, 42, 666]"sv).value(); - - auto arr = object_cast>(obj["value"]).value(); - - REQUIRE(arr.size() == 3); - REQUIRE(arr[0] == 1); - REQUIRE(arr[1] == 42); - REQUIRE(arr[2] == 666); -} - -TEST_CASE("ucl: array: emit", "[ucl]") -{ - using namespace nihil::ucl; - - auto ucl = parse("array = [1, 42, 666];").value(); - - auto output = std::format("{:c}", ucl); - REQUIRE(output == -"array [\n" -" 1,\n" -" 42,\n" -" 666,\n" -"]\n"); -} - -TEST_CASE("ucl: array: format", "[ucl]") -{ - using namespace nihil::ucl; - - SECTION("empty array") { - auto arr = array(); - REQUIRE(std::format("{}", arr) == "[]"); - } - - SECTION("bare array") { - auto arr = array{ - integer(1), integer(42), integer(666) - }; - - auto output = std::format("{}", arr); - REQUIRE(output == "[1, 42, 666]"); - } - - SECTION("parsed array") { - auto ucl = parse("array = [1, 42, 666];").value(); - auto arr = object_cast>(ucl["array"]).value(); - - auto output = std::format("{}", arr); - REQUIRE(output == "[1, 42, 666]"); - } -} - -TEST_CASE("ucl: array: print to ostream", "[ucl]") -{ - using namespace nihil::ucl; - - SECTION("empty array") { - auto arr = array(); - auto strm = std::ostringstream(); - strm << arr; - - REQUIRE(strm.str() == "[]"); - } - - SECTION("bare array") { - auto arr = array{ - integer(1), integer(42), integer(666) - }; - auto strm = std::ostringstream(); - strm << arr; - - REQUIRE(strm.str() == "[1, 42, 666]"); - } - - SECTION("parsed array") { - auto ucl = parse("array = [1, 42, 666];").value(); - auto arr = object_cast>(ucl["array"]).value(); - auto strm = std::ostringstream(); - strm << arr; - - REQUIRE(strm.str() == "[1, 42, 666]"); - } -} - -TEST_CASE("ucl: array is a sized_range", "[ucl]") -{ - using namespace nihil::ucl; - - auto arr = array{integer(1), integer(42), integer(666)}; - - auto size = std::ranges::size(arr); - REQUIRE(size == 3); - - auto begin = std::ranges::begin(arr); - static_assert(std::random_access_iterator); - - auto end = std::ranges::end(arr); - static_assert(std::sentinel_for); - - REQUIRE(std::distance(begin, end) == 3); - - auto vec = std::vector(); - std::ranges::copy(arr, std::back_inserter(vec)); - REQUIRE(std::ranges::equal(arr, vec)); - - auto arr_as_ints = - arr | std::views::transform(&integer::value); - auto int_vec = std::vector(); - std::ranges::copy(arr_as_ints, std::back_inserter(int_vec)); - REQUIRE(int_vec == std::vector{1, 42, 666}); - -} - -TEST_CASE("ucl: array: bad object_cast", "[ucl]") -{ - using namespace nihil::ucl; - - auto arr = array(); - - auto cast_ok = object_cast(arr); - REQUIRE(!cast_ok); -} - -TEST_CASE("ucl: array: heterogeneous elements", "[ucl]") -{ - using namespace std::literals; - using namespace nihil::ucl; - - auto obj_err = parse("array [ 42, true, \"test\" ];"); - REQUIRE(obj_err); - auto obj = *obj_err; - - auto err = object_cast>(obj["array"]); - REQUIRE(err); - - auto arr = *err; - REQUIRE(arr.size() == 3); - - auto int_obj = object_cast(arr[0]); - REQUIRE(int_obj); - REQUIRE(*int_obj == 42); - - auto bool_obj = object_cast(arr[1]); - REQUIRE(bool_obj); - REQUIRE(*bool_obj == true); - - auto string_obj = object_cast(arr[2]); - REQUIRE(string_obj); - REQUIRE(*string_obj == "test"); -} - -TEST_CASE("ucl: array: heterogenous cast", "[ucl]") -{ - using namespace nihil::ucl; - - auto arr = array<>(); - arr.push_back(integer(42)); - arr.push_back(boolean(true)); - - // Converting to an array should fail. - auto cast_ok = object_cast>(arr); - REQUIRE(!cast_ok); - - // Converting to array should succeed. - auto err = object_cast>(arr); - REQUIRE(err); - - auto obj_arr = *err; - REQUIRE(obj_arr[0] == integer(42)); -} - -TEST_CASE("ucl: array: homogeneous cast", "[ucl]") -{ - using namespace nihil::ucl; - - auto arr = array<>(); - arr.push_back(integer(1)); - arr.push_back(integer(42)); - - auto obj = object(ref, arr.get_ucl_object()); - - // Converting to array should fail. - auto cast_ok = object_cast>(obj); - REQUIRE(!cast_ok); - - // Converting to an array should succeed. - auto err = object_cast>(obj); - REQUIRE(err); - - auto obj_arr = *err; - REQUIRE(obj_arr[0] == 1); - REQUIRE(obj_arr[1] == 42); -} - -TEST_CASE("array iterator: empty iterator", "[ucl]") -{ - using namespace nihil::ucl; - - auto it = array_iterator(); - - REQUIRE_THROWS_AS(*it, std::logic_error); - REQUIRE_THROWS_AS(it[0], std::logic_error); - REQUIRE_THROWS_AS(it++, std::logic_error); - REQUIRE_THROWS_AS(++it, std::logic_error); - - auto it2 = array_iterator(); - REQUIRE(it == it2); - REQUIRE((it < it2) == false); - REQUIRE((it > it2) == false); -} - -TEST_CASE("array iterator: invalid operations", "[ucl]") -{ - using namespace nihil::ucl; - - auto arr = array{ integer(42) }; - auto it = arr.begin(); - - SECTION("decrement before start") { - REQUIRE_THROWS_AS(--it, std::logic_error); - REQUIRE_THROWS_AS(it--, std::logic_error); - REQUIRE_THROWS_AS(it - 1, std::logic_error); - } - - SECTION("increment past end") { - ++it; - REQUIRE(it == arr.end()); - - REQUIRE_THROWS_AS(++it, std::logic_error); - REQUIRE_THROWS_AS(it++, std::logic_error); - REQUIRE_THROWS_AS(it + 1, std::logic_error); - } - - SECTION("dereference iterator at end") { - REQUIRE_THROWS_AS(it[1], std::logic_error); - - ++it; - REQUIRE(it == arr.end()); - - REQUIRE_THROWS_AS(*it, std::logic_error); - } - - SECTION("compare with different array") { - auto arr2 = array{ integer(42) }; - REQUIRE_THROWS_AS(it == arr2.begin(), std::logic_error); - REQUIRE_THROWS_AS(it > arr2.begin(), std::logic_error); - REQUIRE_THROWS_AS(it - arr2.begin(), std::logic_error); - } - - SECTION("compare with empty iterator") { - auto it2 = array_iterator(); - REQUIRE_THROWS_AS(it == it2, std::logic_error); - REQUIRE_THROWS_AS(it > it2, std::logic_error); - REQUIRE_THROWS_AS(it - it2, std::logic_error); - } -} diff --git a/nihil.ucl/tests/boolean.cc b/nihil.ucl/tests/boolean.cc deleted file mode 100644 index f7ef95e..0000000 --- a/nihil.ucl/tests/boolean.cc +++ /dev/null @@ -1,224 +0,0 @@ -/* - * This source code is released into the public domain. - */ - -#include -#include - -#include -#include - -import nihil.ucl; - -TEST_CASE("ucl: boolean: invariants", "[ucl]") -{ - using namespace nihil::ucl; - - static_assert(std::same_as); - REQUIRE(boolean::ucl_type == object_type::boolean); - REQUIRE(static_cast<::ucl_type>(boolean::ucl_type) == UCL_BOOLEAN); - - 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); -} - -TEST_CASE("ucl: boolean: constructor", "[ucl]") -{ - using namespace nihil::ucl; - - SECTION("default") { - auto b = boolean(); - REQUIRE(b == false); - } - - SECTION("with value") { - auto b = boolean(true); - REQUIRE(b == true); - } -} - -TEST_CASE("ucl: boolean: construct from UCL object", "[ucl]") -{ - using namespace nihil::ucl; - - SECTION("ref, correct type") { - auto uobj = ::ucl_object_frombool(true); - - auto i = boolean(ref, uobj); - REQUIRE(i == true); - - ::ucl_object_unref(uobj); - } - - SECTION("noref, correct type") { - auto uobj = ::ucl_object_frombool(true); - - auto i = boolean(noref, uobj); - REQUIRE(i == true); - } - - SECTION("ref, wrong type") { - auto uobj = ::ucl_object_fromint(1); - - REQUIRE_THROWS_AS(boolean(ref, uobj), type_mismatch); - - ::ucl_object_unref(uobj); - } - - SECTION("noref, wrong type") { - auto uobj = ::ucl_object_fromint(1); - - REQUIRE_THROWS_AS(boolean(noref, uobj), type_mismatch); - - ::ucl_object_unref(uobj); - } -} - -TEST_CASE("ucl: boolean: make_boolean", "[ucl]") -{ - using namespace nihil::ucl; - - SECTION("default value") { - auto b = make_boolean().value(); - REQUIRE(b == false); - } - - SECTION("explicit value") { - auto b = make_boolean(true).value(); - REQUIRE(b == true); - } -} - -TEST_CASE("ucl: boolean: swap", "[ucl]") -{ - // do not add using namespace nihil::ucl - - auto b1 = nihil::ucl::boolean(true); - auto b2 = nihil::ucl::boolean(false); - - swap(b1, b2); - - REQUIRE(b1 == false); - REQUIRE(b2 == true); -} - -TEST_CASE("ucl: boolean: value()", "[ucl]") -{ - auto b = nihil::ucl::boolean(true); - REQUIRE(b.value() == true); -} - -TEST_CASE("ucl: boolean: key()", "[ucl]") -{ - using namespace nihil::ucl; - - auto err = parse("a_bool = true"); - REQUIRE(err); - - auto obj = *err; - REQUIRE(object_cast(obj["a_bool"])->key() == "a_bool"); - - auto b = nihil::ucl::boolean(true); - REQUIRE(b.key() == ""); -} - -TEST_CASE("ucl: boolean: comparison", "[ucl]") -{ - using namespace nihil::ucl; - - auto b = boolean(true); - - SECTION("operator==") { - REQUIRE(b == true); - REQUIRE(b == boolean(true)); - } - - SECTION("operator!=") { - REQUIRE(b != false); - REQUIRE(b != boolean(false)); - } - - SECTION("operator<") { - REQUIRE(b <= true); - REQUIRE(b <= nihil::ucl::boolean(true)); - } - - SECTION("operator>") { - REQUIRE(b > false); - REQUIRE(b > nihil::ucl::boolean(false)); - } -} - -TEST_CASE("ucl: boolean: parse", "[ucl]") -{ - using namespace nihil::ucl; - - auto obj = parse("value = true").value(); - - auto v = obj["value"]; - REQUIRE(v.key() == "value"); - REQUIRE(object_cast(v).value() == true); -} - -TEST_CASE("ucl: boolean: parse and emit", "[ucl]") -{ - using namespace nihil::ucl; - - auto ucl = parse("bool = true;").value(); - - auto output = std::string(); - emit(ucl, nihil::ucl::emitter::configuration, - std::back_inserter(output)); - - REQUIRE(output == "bool = true;\n"); -} - -TEST_CASE("ucl: boolean: format", "[ucl]") -{ - using namespace nihil::ucl; - - SECTION("bare boolean") { - auto str = std::format("{}", boolean(true)); - REQUIRE(str == "true"); - } - - SECTION("parsed boolean") { - auto obj = parse("bool = true;").value(); - auto b = object_cast(obj["bool"]).value(); - - auto str = std::format("{}", b); - REQUIRE(str == "true"); - } - - SECTION("with format string") { - auto str = std::format("{: >5}", boolean(true)); - REQUIRE(str == " true"); - } -} - -TEST_CASE("ucl: boolean: print to ostream", "[ucl]") -{ - using namespace nihil::ucl; - - SECTION("bare boolean") { - auto strm = std::ostringstream(); - strm << boolean(true); - - REQUIRE(strm.str() == "true"); - } - - SECTION("parsed boolean") { - auto obj = parse("bool = true;").value(); - auto i = object_cast(obj["bool"]).value(); - - auto strm = std::ostringstream(); - strm << i; - - REQUIRE(strm.str() == "true"); - } -} diff --git a/nihil.ucl/tests/emit.cc b/nihil.ucl/tests/emit.cc deleted file mode 100644 index a7dcd71..0000000 --- a/nihil.ucl/tests/emit.cc +++ /dev/null @@ -1,93 +0,0 @@ -/* - * This source code is released into the public domain. - */ - -#include -#include - -#include - -import nihil.ucl; - -TEST_CASE("ucl: emit to std::ostream", "[ucl]") -{ - using namespace std::literals; - - auto obj = nihil::ucl::parse("int = [1, 42, 666]"sv); - REQUIRE(obj); - - auto strm = std::ostringstream(); - strm << *obj; - - // The ostream emitter produces JSON. - REQUIRE(strm.str() == std::format("{:j}", *obj)); -} - -TEST_CASE("ucl: emit JSON with std::format", "[ucl]") -{ - using namespace std::literals; - - auto obj = nihil::ucl::parse("int = [1, 42, 666]"sv); - REQUIRE(obj); - - auto str = std::format("{:j}", *obj); - - REQUIRE(str == -"{\n" -" \"int\": [\n" -" 1,\n" -" 42,\n" -" 666\n" -" ]\n" -"}"); - - // Make sure JSON is the default format. - auto str2 = std::format("{}", *obj); - REQUIRE(str == str2); -} - -TEST_CASE("ucl: emit compact JSON with std::format", "[ucl]") -{ - using namespace std::literals; - - auto obj = nihil::ucl::parse("int = [1, 42, 666]"sv); - REQUIRE(obj); - - auto str = std::format("{:J}", *obj); - - REQUIRE(str == "{\"int\":[1,42,666]}"); -} - -TEST_CASE("ucl: emit configuration with std::format", "[ucl]") -{ - using namespace std::literals; - - auto obj = nihil::ucl::parse("int = [1, 42, 666]"sv); - REQUIRE(obj); - - auto str = std::format("{:c}", *obj); - - REQUIRE(str == -"int [\n" -" 1,\n" -" 42,\n" -" 666,\n" -"]\n"); -} - -TEST_CASE("ucl: emit YAML with std::format", "[ucl]") -{ - using namespace std::literals; - - auto obj = nihil::ucl::parse("int = [1, 42, 666]"sv); - REQUIRE(obj); - - auto str = std::format("{:y}", *obj); - - REQUIRE(str == -"int: [\n" -" 1,\n" -" 42,\n" -" 666\n" -"]"); -} diff --git a/nihil.ucl/tests/integer.cc b/nihil.ucl/tests/integer.cc deleted file mode 100644 index 6584764..0000000 --- a/nihil.ucl/tests/integer.cc +++ /dev/null @@ -1,247 +0,0 @@ -/* - * This source code is released into the public domain. - */ - -#include -#include -#include - -#include -#include - -import nihil.ucl; - -TEST_CASE("ucl: integer: invariants", "[ucl]") -{ - using namespace nihil::ucl; - - static_assert(std::same_as); - REQUIRE(integer::ucl_type == object_type::integer); - REQUIRE(static_cast<::ucl_type>(integer::ucl_type) == UCL_INT); - - 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); -} - -TEST_CASE("ucl: integer: constructor", "[ucl]") -{ - using namespace nihil::ucl; - - SECTION("default") { - auto i = integer(); - REQUIRE(i == 0); - } - - SECTION("with value") { - auto i = integer(42); - REQUIRE(i == 42); - } -} - -TEST_CASE("ucl: integer: literal", "[ucl]") -{ - SECTION("with namespace nihil::ucl::literals") { - using namespace nihil::ucl::literals; - - auto i = 42_ucl; - REQUIRE(i.type() == nihil::ucl::object_type::integer); - REQUIRE(i == 42); - } - - SECTION("with namespace nihil::literals") { - using namespace nihil::literals; - - auto i = 42_ucl; - REQUIRE(i.type() == nihil::ucl::object_type::integer); - REQUIRE(i == 42); - } -} - -TEST_CASE("ucl: integer: construct from UCL object", "[ucl]") -{ - using namespace nihil::ucl; - - SECTION("ref, correct type") { - auto uobj = ::ucl_object_fromint(42); - - auto i = integer(ref, uobj); - REQUIRE(i == 42); - - ::ucl_object_unref(uobj); - } - - SECTION("noref, correct type") { - auto uobj = ::ucl_object_fromint(42); - - auto i = integer(noref, uobj); - REQUIRE(i == 42); - } - - SECTION("ref, wrong type") { - auto uobj = ::ucl_object_frombool(true); - - REQUIRE_THROWS_AS(integer(ref, uobj), type_mismatch); - - ::ucl_object_unref(uobj); - } - - SECTION("noref, wrong type") { - auto uobj = ::ucl_object_frombool(true); - - REQUIRE_THROWS_AS(integer(noref, uobj), type_mismatch); - - ::ucl_object_unref(uobj); - } -} - -TEST_CASE("ucl: integer: make_integer", "[ucl]") -{ - using namespace nihil::ucl; - - SECTION("default value") { - auto i = make_integer().value(); - REQUIRE(i == 0); - } - - SECTION("explicit value") { - auto i = make_integer(42).value(); - REQUIRE(i == 42); - } -} - -TEST_CASE("ucl: integer: swap", "[ucl]") -{ - // do not add using namespace nihil::ucl - - auto i1 = nihil::ucl::integer(1); - auto i2 = nihil::ucl::integer(2); - - swap(i1, i2); - - REQUIRE(i1 == 2); - REQUIRE(i2 == 1); -} - -TEST_CASE("ucl: integer: value()", "[ucl]") -{ - using namespace nihil::ucl; - - auto i = 42_ucl; - REQUIRE(i.value() == 42); -} - -TEST_CASE("ucl: integer: key()", "[ucl]") -{ - using namespace nihil::ucl; - - SECTION("parsed with key") { - auto obj = parse("an_int = 42").value(); - auto i = object_cast(obj["an_int"]).value(); - REQUIRE(i.key() == "an_int"); - } - - SECTION("bare integer, no key") { - auto i = 42_ucl; - REQUIRE(i.key() == ""); - } -} - -TEST_CASE("ucl: integer: comparison", "[ucl]") -{ - using namespace nihil::ucl; - - auto i = 42_ucl; - - SECTION("operator==") { - REQUIRE(i == 42); - REQUIRE(i == 42_ucl); - } - - SECTION("operator!=") { - REQUIRE(i != 1); - REQUIRE(i != 1_ucl); - } - - SECTION("operator<") { - REQUIRE(i < 43); - REQUIRE(i < 43_ucl); - } - - SECTION("operator>") { - REQUIRE(i > 1); - REQUIRE(i > 1_ucl); - } -} - -TEST_CASE("ucl: integer: parse", "[ucl]") -{ - using namespace nihil::ucl; - - auto obj = parse("value = 42").value(); - - auto v = obj["value"]; - REQUIRE(v.key() == "value"); - REQUIRE(object_cast(v) == 42); -} - -TEST_CASE("ucl: integer: parse and emit", "[ucl]") -{ - using namespace nihil::ucl; - - auto ucl = parse("int = 42;").value(); - - auto output = std::string(); - emit(ucl, emitter::configuration, std::back_inserter(output)); - - REQUIRE(output == "int = 42;\n"); -} - -TEST_CASE("ucl: integer: format", "[ucl]") -{ - using namespace nihil::ucl; - - SECTION("bare integer") { - auto str = std::format("{}", 42_ucl); - REQUIRE(str == "42"); - } - - SECTION("parsed integer") { - auto obj = parse("int = 42;").value(); - auto i = object_cast(obj["int"]).value(); - - auto str = std::format("{}", i); - REQUIRE(str == "42"); - } - - SECTION("with format string") { - auto str = std::format("{:-05}", 42_ucl); - REQUIRE(str == "00042"); - } -} - -TEST_CASE("ucl: integer: print to ostream", "[ucl]") -{ - using namespace nihil::ucl; - - SECTION("bare integer") { - auto strm = std::ostringstream(); - strm << 42_ucl; - - REQUIRE(strm.str() == "42"); - } - - SECTION("parsed integer") { - auto obj = parse("int = 42;").value(); - auto i = object_cast(obj["int"]).value(); - - auto strm = std::ostringstream(); - strm << i; - - REQUIRE(strm.str() == "42"); - } -} diff --git a/nihil.ucl/tests/map.cc b/nihil.ucl/tests/map.cc deleted file mode 100644 index 7240cb3..0000000 --- a/nihil.ucl/tests/map.cc +++ /dev/null @@ -1,192 +0,0 @@ -/* - * This source code is released into the public domain. - */ - -#include - -#include -#include - -import nihil.ucl; - -//NOLINTBEGIN(bugprone-unchecked-optional-access) - -TEST_CASE("ucl: map: invariants", "[ucl]") -{ - using namespace nihil::ucl; - - REQUIRE(map<>::ucl_type == object_type::object); - REQUIRE(static_cast<::ucl_type>(map<>::ucl_type) == UCL_OBJECT); - - 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::ranges::range>); - static_assert(std::same_as, - std::ranges::range_value_t>>); -} - -TEST_CASE("ucl: map: default construct", "[ucl]") -{ - auto map = nihil::ucl::map<>(); - REQUIRE(str(map.type()) == "object"); -} - -TEST_CASE("ucl: map: construct from initializer_list", "[ucl]") -{ - using namespace nihil::ucl; - using namespace std::literals; - - auto map = nihil::ucl::map{ - {"1"sv, integer(1)}, - {"42"sv, integer(42)}, - }; - - REQUIRE(str(map.type()) == "object"); - REQUIRE(map["1"] == 1); - REQUIRE(map["42"] == 42); -} - -TEST_CASE("ucl: map: construct from range", "[ucl]") -{ - using namespace nihil::ucl; - using namespace std::literals; - - auto vec = std::vector>{ - {"1"sv, integer(1)}, - {"42"sv, integer(42)}, - }; - - auto map = nihil::ucl::map(std::from_range, vec); - - REQUIRE(str(map.type()) == "object"); - REQUIRE(map["1"] == 1); - REQUIRE(map["42"] == 42); -} - -TEST_CASE("ucl: map: construct from iterator pair", "[ucl]") -{ - using namespace nihil::ucl; - using namespace std::literals; - - auto vec = std::vector>{ - {"1"sv, integer(1)}, - {"42"sv, integer(42)}, - }; - - auto map = nihil::ucl::map(std::ranges::begin(vec), - std::ranges::end(vec)); - - REQUIRE(str(map.type()) == "object"); - REQUIRE(map["1"] == 1); - REQUIRE(map["42"] == 42); -} - -TEST_CASE("ucl: map: insert", "[ucl]") -{ - using namespace nihil::ucl; - using namespace std::literals; - - auto m = map(); - - m.insert({"test1"sv, integer(42)}); - m.insert({"test2"sv, integer(666)}); - - REQUIRE(m["test1"] == 42); - REQUIRE(m["test2"] == 666); -} - -TEST_CASE("ucl: map: find", "[ucl]") -{ - using namespace nihil::ucl; - using namespace std::literals; - - auto map = nihil::ucl::map{ - {"1"sv, integer(1)}, - {"42"sv, integer(42)}, - }; - - auto obj = map.find("42"); - REQUIRE(obj.value() == 42); - - obj = map.find("43"); - REQUIRE(!obj.has_value()); -} - -TEST_CASE("ucl: map: iterate", "[ucl]") -{ - using namespace nihil::ucl; - using namespace std::literals; - - auto map = nihil::ucl::map{ - {"1"sv, integer(1)}, - {"42"sv, integer(42)}, - }; - - auto i = 0u; - - for (auto [key, value] : map) { - if (key == "1") - REQUIRE(value == 1); - else if (key == "42") - REQUIRE(value == 42); - else - REQUIRE(false); - ++i; - } - - REQUIRE(i == 2); -} - -TEST_CASE("ucl: map: operator[] throws key_not_found", "[ucl]") -{ - auto map = nihil::ucl::map(); - REQUIRE_THROWS_AS(map["nonesuch"], nihil::ucl::key_not_found); -} - -TEST_CASE("ucl: map: remove", "[uc]") -{ - using namespace std::literals; - using namespace nihil::ucl; - - auto map = nihil::ucl::map{ - {"1"sv, integer(1)}, - {"42"sv, integer(42)}, - }; - - REQUIRE(map.find("42") != std::nullopt); - REQUIRE(map.remove("42") == true); - REQUIRE(map.find("42") == std::nullopt); - REQUIRE(map["1"] == 1); - - REQUIRE(map.remove("42") == false); -} - -TEST_CASE("ucl: map: pop", "[uc]") -{ - using namespace std::literals; - using namespace nihil::ucl; - - auto map = nihil::ucl::map{ - {"1"sv, integer(1)}, - {"42"sv, integer(42)}, - }; - - REQUIRE(map.find("42") != std::nullopt); - - auto obj = map.pop("42"); - REQUIRE(obj.value() == 42); - - REQUIRE(!map.find("42")); - REQUIRE(map["1"] == 1); - - obj = map.pop("42"); - REQUIRE(!obj); -} - -//NOLINTEND(bugprone-unchecked-optional-access) diff --git a/nihil.ucl/tests/object.cc b/nihil.ucl/tests/object.cc deleted file mode 100644 index 3ad180e..0000000 --- a/nihil.ucl/tests/object.cc +++ /dev/null @@ -1,44 +0,0 @@ -/* - * This source code is released into the public domain. - */ - -#include - -#include - -import nihil.ucl; - -TEST_CASE("ucl object: get_ucl_object", "[ucl]") -{ - auto obj = nihil::ucl::integer(42); - - REQUIRE(obj.get_ucl_object() != nullptr); - static_assert(std::same_as<::ucl_object_t *, - decltype(obj.get_ucl_object())>); - - auto const cobj = obj; - static_assert(std::same_as<::ucl_object_t const *, - decltype(cobj.get_ucl_object())>); -} - -TEST_CASE("ucl object: compare", "[ucl]") -{ - using namespace std::literals; - - auto obj_41 = nihil::ucl::parse("int = 41;"sv); - REQUIRE(obj_41); - - auto obj_42 = nihil::ucl::parse("int = 42;"sv); - REQUIRE(obj_42); - - auto obj_42_2 = nihil::ucl::parse("int = 42;"sv); - REQUIRE(obj_42_2); - - auto obj_43 = nihil::ucl::parse("int = 43;"sv); - REQUIRE(obj_43); - - REQUIRE(*obj_42 == *obj_42_2); - REQUIRE(*obj_42 != *obj_43); - REQUIRE(*obj_42 < *obj_43); - REQUIRE(*obj_42 > *obj_41); -} diff --git a/nihil.ucl/tests/parse.cc b/nihil.ucl/tests/parse.cc deleted file mode 100644 index 43ce219..0000000 --- a/nihil.ucl/tests/parse.cc +++ /dev/null @@ -1,55 +0,0 @@ -/* - * This source code is released into the public domain. - */ - -#include - -#include -#include - -import nihil.ucl; - -TEST_CASE("ucl parse: iterate array", "[ucl]") -{ - using namespace std::literals; - using namespace nihil::ucl; - - auto err = parse("value = [1, 42, 666];"sv); - REQUIRE(err); - - auto obj = *err; - - auto arr = obj["value"]; - REQUIRE(arr.key() == "value"); - - auto ints = object_cast>(arr); - REQUIRE(ints); - - auto vec = std::vector(std::from_range, *ints); - - REQUIRE(vec.size() == 3); - REQUIRE(vec[0] == 1); - REQUIRE(vec[1] == 42); - REQUIRE(vec[2] == 666); -} - -TEST_CASE("ucl parse: iterate hash", "[ucl]") -{ - using namespace std::literals; - using namespace nihil::ucl; - - auto input = "int = 42; bool = true; str = \"test\";"sv; - auto obj = parse(input); - REQUIRE(obj); - - for (auto &&[key, value] : *obj) { - REQUIRE(key == value.key()); - - if (key == "int") - REQUIRE(object_cast(value) == 42); - else if (key == "bool") - REQUIRE(object_cast(value) == true); - else if (key == "str") - REQUIRE(object_cast(value) == "test"); - } -} diff --git a/nihil.ucl/tests/real.cc b/nihil.ucl/tests/real.cc deleted file mode 100644 index 421917e..0000000 --- a/nihil.ucl/tests/real.cc +++ /dev/null @@ -1,248 +0,0 @@ -/* - * This source code is released into the public domain. - */ - -#include -#include - -#include -#include -#include - -import nihil.ucl; - -TEST_CASE("ucl: real: invariants", "[ucl]") -{ - using namespace nihil::ucl; - - static_assert(std::same_as); - REQUIRE(real::ucl_type == object_type::real); - REQUIRE(static_cast<::ucl_type>(real::ucl_type) == UCL_FLOAT); - - 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); -} - -TEST_CASE("ucl: real: constructor", "[ucl]") -{ - using namespace nihil::ucl; - - SECTION("default") { - auto r = real(); - REQUIRE(r == 0); - } - - SECTION("with value") { - auto r = real(42.1); - REQUIRE_THAT(r.value(), Catch::Matchers::WithinRel(42.1)); - } -} - -TEST_CASE("ucl: real: literal", "[ucl]") -{ - SECTION("with namespace nihil::ucl::literals") { - using namespace nihil::ucl::literals; - - auto r = 42.5_ucl; - REQUIRE(r.type() == nihil::ucl::object_type::real); - REQUIRE_THAT(r.value(), Catch::Matchers::WithinRel(42.5)); - } - - SECTION("with namespace nihil::literals") { - using namespace nihil::literals; - - auto r = 42.5_ucl; - REQUIRE(r.type() == nihil::ucl::object_type::real); - REQUIRE_THAT(r.value(), Catch::Matchers::WithinRel(42.5)); - } -} - -TEST_CASE("ucl: real: construct from UCL object", "[ucl]") -{ - using namespace nihil::ucl; - - SECTION("ref, correct type") { - auto uobj = ::ucl_object_fromdouble(42); - - auto r = real(ref, uobj); - REQUIRE(r == 42); - - ::ucl_object_unref(uobj); - } - - SECTION("noref, correct type") { - auto uobj = ::ucl_object_fromdouble(42); - - auto r = real(noref, uobj); - REQUIRE(r == 42); - } - - SECTION("ref, wrong type") { - auto uobj = ::ucl_object_fromint(42); - - REQUIRE_THROWS_AS(real(ref, uobj), type_mismatch); - - ::ucl_object_unref(uobj); - } - - SECTION("noref, wrong type") { - auto uobj = ::ucl_object_fromint(42); - - REQUIRE_THROWS_AS(real(noref, uobj), type_mismatch); - - ::ucl_object_unref(uobj); - } -} - -TEST_CASE("ucl: real: make_real", "[ucl]") -{ - using namespace nihil::ucl; - - SECTION("default value") { - auto i = make_real().value(); - REQUIRE(i == 0); - } - - SECTION("explicit value") { - auto i = make_real(42).value(); - REQUIRE(i == 42); - } -} - -TEST_CASE("ucl: real: swap", "[ucl]") -{ - // do not add using namespace nihil::ucl - - auto r1 = nihil::ucl::real(1); - auto r2 = nihil::ucl::real(2); - - swap(r1, r2); - - REQUIRE(r1 == 2.); - REQUIRE(r2 == 1.); -} - -TEST_CASE("ucl: real: value()", "[ucl]") -{ - using namespace nihil::ucl; - - auto r = 42.5_ucl; - REQUIRE_THAT(r.value(), Catch::Matchers::WithinRel(42.5)); -} - -TEST_CASE("ucl: real: key()", "[ucl]") -{ - using namespace nihil::ucl; - - SECTION("parsed with key") { - auto obj = parse("a_real = 42.5").value(); - auto r = object_cast(obj["a_real"]).value(); - REQUIRE(r.key() == "a_real"); - } - - SECTION("bare real, no key") { - auto i = 42.5_ucl; - REQUIRE(i.key() == ""); - } -} - -TEST_CASE("ucl: real: comparison", "[ucl]") -{ - using namespace nihil::ucl; - - auto i = nihil::ucl::real(42.5); - - SECTION("operator==") { - REQUIRE(i == 42.5); - REQUIRE(i == 42.5_ucl); - } - - SECTION("operator!=") { - REQUIRE(i != 1); - REQUIRE(i != 1._ucl); - } - - SECTION("operator<") { - REQUIRE(i < 43); - REQUIRE(i < 43._ucl); - } - - SECTION("operator>") { - REQUIRE(i > 1); - REQUIRE(i > 1._ucl); - } -} - -TEST_CASE("ucl: real: parse", "[ucl]") -{ - using namespace nihil::ucl; - - auto obj = parse("value = 42.1").value(); - - auto v = obj["value"]; - REQUIRE(v.key() == "value"); - REQUIRE_THAT(object_cast(v).value().value(), - Catch::Matchers::WithinRel(42.1)); -} - -TEST_CASE("ucl: real: parse and emit", "[ucl]") -{ - using namespace nihil::ucl; - - auto ucl = parse("real = 42.2").value(); - - auto output = std::string(); - emit(ucl, emitter::configuration, std::back_inserter(output)); - - REQUIRE(output == "real = 42.2;\n"); -} - -TEST_CASE("ucl: real: format", "[ucl]") -{ - using namespace nihil::ucl; - - SECTION("bare real") { - auto str = std::format("{}", 42.5_ucl); - REQUIRE(str == "42.5"); - } - - SECTION("parsed real") { - auto obj = parse("real = 42.5;").value(); - auto r = object_cast(obj["real"]).value(); - - auto str = std::format("{}", r); - REQUIRE(str == "42.5"); - } - - SECTION("with format string") { - auto str = std::format("{:10.5f}", 42.5_ucl); - REQUIRE(str == " 42.50000"); - } -} - -TEST_CASE("ucl: real: print to ostream", "[ucl]") -{ - using namespace nihil::ucl; - - SECTION("bare real") { - auto strm = std::ostringstream(); - strm << 42.5_ucl; - - REQUIRE(strm.str() == "42.5"); - } - - SECTION("parsed real") { - auto obj = parse("real = 42.5;").value(); - auto i = object_cast(obj["real"]).value(); - - auto strm = std::ostringstream(); - strm << i; - - REQUIRE(strm.str() == "42.5"); - } -} diff --git a/nihil.ucl/tests/string.cc b/nihil.ucl/tests/string.cc deleted file mode 100644 index 6409b8d..0000000 --- a/nihil.ucl/tests/string.cc +++ /dev/null @@ -1,415 +0,0 @@ -/* - * This source code is released into the public domain. - */ - -#include -#include -#include -#include -#include - -#include -#include - -import nihil.ucl; - -TEST_CASE("ucl: string: invariants", "[ucl]") -{ - using namespace nihil::ucl; - - static_assert(std::same_as); - REQUIRE(string::ucl_type == object_type::string); - REQUIRE(static_cast<::ucl_type>(string::ucl_type) == UCL_STRING); - - 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::ranges::contiguous_range); - static_assert(std::same_as>); -} - -TEST_CASE("ucl: string: literal", "[ucl]") -{ - SECTION("with namespace nihil::ucl::literals") { - using namespace nihil::ucl::literals; - - auto s = "testing"_ucl; - REQUIRE(s.type() == nihil::ucl::object_type::string); - REQUIRE(s == "testing"); - } - - SECTION("with namespace nihil::literals") { - using namespace nihil::literals; - - auto s = "testing"_ucl; - REQUIRE(s.type() == nihil::ucl::object_type::string); - REQUIRE(s == "testing"); - } -} - -TEST_CASE("ucl: string: construct", "[ucl]") -{ - using namespace nihil::ucl; - using namespace std::literals; - - SECTION("empty string") { - auto str = string(); - REQUIRE(str.type() == object_type::string); - REQUIRE(str == ""); - } - - SECTION("with integer-like value") { - auto str = "42"_ucl; - REQUIRE(str.type() == object_type::string); - REQUIRE(str == "42"); - } - - SECTION("with boolean-like value") { - auto str = "true"_ucl; - REQUIRE(str.type() == object_type::string); - REQUIRE(str == "true"); - } - - SECTION("from string literal") { - auto str = string("testing"); - REQUIRE(str.type() == object_type::string); - REQUIRE(str == "testing"); - } - - SECTION("from std::string") { - auto str = string("testing"s); - REQUIRE(str.type() == object_type::string); - REQUIRE(str == "testing"); - } - - SECTION("from std::string_view") { - auto str = string("testing"sv); - REQUIRE(str.type() == object_type::string); - REQUIRE(str == "testing"); - } - - SECTION("from contiguous range") { - auto s = std::vector{'t', 'e', 's', 't', 'i', 'n', 'g'}; - auto str = string(s); - REQUIRE(str.type() == object_type::string); - REQUIRE(str == "testing"); - } - - SECTION("from non-contiguous range") { - auto s = std::list{'t', 'e', 's', 't', 'i', 'n', 'g'}; - auto str = string(s); - REQUIRE(str.type() == object_type::string); - REQUIRE(str == "testing"); - } - - SECTION("from contiguous iterator pair") { - auto s = std::vector{'t', 'e', 's', 't', 'i', 'n', 'g'}; - auto str = string(s.begin(), s.end()); - REQUIRE(str.type() == object_type::string); - REQUIRE(str == "testing"); - } - - SECTION("from non-contiguous iterator pair") { - auto s = std::list{'t', 'e', 's', 't', 'i', 'n', 'g'}; - auto str = string(s.begin(), s.end()); - REQUIRE(str.type() == object_type::string); - REQUIRE(str == "testing"); - } -} - -TEST_CASE("ucl: string: construct from UCL object", "[ucl]") -{ - using namespace nihil::ucl; - - SECTION("ref, correct type") { - auto uobj = ::ucl_object_fromstring("testing"); - - auto s = string(ref, uobj); - REQUIRE(s == "testing"); - - ::ucl_object_unref(uobj); - } - - SECTION("noref, correct type") { - auto uobj = ::ucl_object_fromstring("testing"); - - auto s = string(noref, uobj); - REQUIRE(s == "testing"); - } - - SECTION("ref, wrong type") { - auto uobj = ::ucl_object_frombool(true); - - REQUIRE_THROWS_AS(string(ref, uobj), type_mismatch); - - ::ucl_object_unref(uobj); - } - - SECTION("noref, wrong type") { - auto uobj = ::ucl_object_frombool(true); - - REQUIRE_THROWS_AS(string(noref, uobj), type_mismatch); - - ::ucl_object_unref(uobj); - } -} - -TEST_CASE("ucl: string: make_string", "[ucl]") -{ - using namespace nihil::ucl; - using namespace std::literals; - - SECTION("empty string") { - auto str = make_string().value(); - REQUIRE(str.type() == object_type::string); - REQUIRE(str == ""); - } - - SECTION("from string literal") { - auto str = make_string("testing").value(); - REQUIRE(str.type() == object_type::string); - REQUIRE(str == "testing"); - } - - SECTION("from std::string") { - auto str = make_string("testing"s).value(); - REQUIRE(str.type() == object_type::string); - REQUIRE(str == "testing"); - } - - SECTION("from std::string_view") { - auto str = make_string("testing"sv).value(); - REQUIRE(str.type() == object_type::string); - REQUIRE(str == "testing"); - } - - SECTION("from contiguous range") { - auto s = std::vector{'t', 'e', 's', 't', 'i', 'n', 'g'}; - auto str = make_string(s).value(); - REQUIRE(str.type() == object_type::string); - REQUIRE(str == "testing"); - } - - SECTION("from non-contiguous range") { - auto s = std::list{'t', 'e', 's', 't', 'i', 'n', 'g'}; - auto str = make_string(s).value(); - REQUIRE(str.type() == object_type::string); - REQUIRE(str == "testing"); - } - - SECTION("from contiguous iterator pair") { - auto s = std::vector{'t', 'e', 's', 't', 'i', 'n', 'g'}; - auto str = make_string(s.begin(), s.end()).value(); - REQUIRE(str.type() == object_type::string); - REQUIRE(str == "testing"); - } - - SECTION("from non-contiguous iterator pair") { - auto s = std::list{'t', 'e', 's', 't', 'i', 'n', 'g'}; - auto str = make_string(s.begin(), s.end()).value(); - REQUIRE(str.type() == object_type::string); - REQUIRE(str == "testing"); - } -} - -TEST_CASE("ucl: string: swap", "[ucl]") -{ - // do not add using namespace nihil::ucl - - auto s1 = nihil::ucl::string("one"); - auto s2 = nihil::ucl::string("two"); - - swap(s1, s2); - - REQUIRE(s1 == "two"); - REQUIRE(s2 == "one"); -} - -TEST_CASE("ucl: string: value()", "[ucl]") -{ - using namespace nihil::ucl; - - auto s = string("te\"st"); - REQUIRE(s.value() == "te\"st"); -} - -TEST_CASE("ucl: string: key()", "[ucl]") -{ - using namespace nihil::ucl; - - auto err = parse("a_string = \"test\""); - REQUIRE(err); - - auto obj = *err; - REQUIRE(object_cast(obj["a_string"])->key() == "a_string"); - - auto s = string("test"); - REQUIRE(s.key() == ""); -} - -TEST_CASE("ucl: string: size", "[ucl]") -{ - using namespace nihil::ucl; - - REQUIRE(string().size() == 0); - REQUIRE(string("test").size() == 4); -} - -TEST_CASE("ucl: string: empty", "[ucl]") -{ - using namespace nihil::ucl; - - REQUIRE(string().empty() == true); - REQUIRE(string("test").empty() == false); -} - -TEST_CASE("ucl: string: iterate", "[ucl]") -{ - using namespace nihil::ucl; - - auto str = "test"_ucl; - - SECTION("as iterator pair") { - auto begin = str.begin(); - static_assert(std::contiguous_iterator); - - auto end = str.end(); - static_assert(std::sentinel_for); - - REQUIRE(*begin == 't'); - ++begin; - REQUIRE(*begin == 'e'); - ++begin; - REQUIRE(*begin == 's'); - ++begin; - REQUIRE(*begin == 't'); - ++begin; - - REQUIRE(begin == end); - } - - SECTION("as range") { - auto s = std::string(std::from_range, str); - REQUIRE(s == "test"); - } -} - -TEST_CASE("ucl: string: comparison", "[ucl]") -{ - using namespace nihil::ucl; - - auto str = "testing"_ucl; - - SECTION("operator==") { - REQUIRE(str == "testing"_ucl); - REQUIRE(str == std::string_view("testing")); - REQUIRE(str == std::string("testing")); - REQUIRE(str == "testing"); - } - - SECTION("operator!=") { - REQUIRE(str != "test"_ucl); - REQUIRE(str != std::string_view("test")); - REQUIRE(str != std::string("test")); - REQUIRE(str != "test"); - } - - SECTION("operator<") { - REQUIRE(str < "zzz"_ucl); - REQUIRE(str < std::string_view("zzz")); - REQUIRE(str < std::string("zzz")); - REQUIRE(str < "zzz"); - } - - SECTION("operator>") { - REQUIRE(str > "aaa"_ucl); - REQUIRE(str > std::string_view("aaa")); - REQUIRE(str > std::string("aaa")); - REQUIRE(str > "aaa"); - } -} - -TEST_CASE("ucl: string: parse", "[ucl]") -{ - using namespace nihil::ucl; - - auto obj = parse("value = \"te\\\"st\"").value(); - - auto v = obj["value"]; - REQUIRE(v.key() == "value"); - REQUIRE(object_cast(v).value() == "te\"st"); -} - -TEST_CASE("ucl: string: emit", "[ucl]") -{ - using namespace nihil::ucl; - - auto ucl = parse("str = \"te\\\"st\";").value(); - - auto output = std::string(); - emit(ucl, emitter::configuration, std::back_inserter(output)); - - REQUIRE(output == "str = \"te\\\"st\";\n"); -} - -TEST_CASE("ucl: string: format", "[ucl]") -{ - using namespace nihil::ucl; - using namespace std::literals; - - auto constexpr test_string = "te\"st"sv; - - SECTION("bare string") { - auto str = std::format("{}", string(test_string)); - REQUIRE(str == test_string); - } - - SECTION("parsed string") { - auto obj = parse("string = \"te\\\"st\";").value(); - auto s = object_cast(obj["string"]).value(); - - auto str = std::format("{}", s); - REQUIRE(str == test_string); - } - - SECTION("with format string") { - auto str = std::format("{: >10}", string(test_string)); - REQUIRE(str == " te\"st"); - } -} - -TEST_CASE("ucl: string: print to ostream", "[ucl]") -{ - using namespace nihil::ucl; - using namespace std::literals; - - auto constexpr test_string = "te\"st"sv; - - SECTION("bare string") { - auto strm = std::ostringstream(); - strm << string(test_string); - - REQUIRE(strm.str() == test_string); - } - - SECTION("parsed string") { - auto obj = parse("string = \"te\\\"st\";").value(); - auto s = object_cast(obj["string"]).value(); - - auto strm = std::ostringstream(); - strm << s; - - REQUIRE(strm.str() == test_string); - } - - SECTION("with format string") { - auto str = std::format("{: >10}", string(test_string)); - REQUIRE(str == " te\"st"); - } -} diff --git a/nihil.ucl/type.cc b/nihil.ucl/type.cc deleted file mode 100644 index 7d9cad7..0000000 --- a/nihil.ucl/type.cc +++ /dev/null @@ -1,62 +0,0 @@ -/* - * This source code is released into the public domain. - */ - -module; - -#include - -module nihil.ucl; - -namespace nihil::ucl { - -auto str(object_type type) -> std::string_view { - using namespace std::literals; - - switch (type) { - case object_type::object: - return "object"sv; - case object_type::array: - return "array"sv; - case object_type::integer: - return "integer"sv; - case object_type::real: - return "real"sv; - case object_type::string: - return "string"sv; - case object_type::boolean: - return "boolean"sv; - case object_type::time: - return "time"sv; - case object_type::userdata: - return "userdata"sv; - case object_type::null: - return "null"sv; - default: - // Don't fail here, since UCL might add more types that we - // don't know about. - return "unknown"sv; - } -} - -type_mismatch::type_mismatch(object_type expected_type, - object_type actual_type) - : error(std::format( - "expected type '{}' != actual type '{}'", - ucl::str(expected_type), ucl::str(actual_type))) - , m_expected_type(expected_type) - , m_actual_type(actual_type) -{ -} - -auto type_mismatch::expected_type(this type_mismatch const &self) -> object_type -{ - return self.m_expected_type; -} - -auto type_mismatch::actual_type(this type_mismatch const &self) -> object_type -{ - return self.m_actual_type; -} - -} // namespace nihil::ucl diff --git a/nihil.ucl/type.ccm b/nihil.ucl/type.ccm index f3b3aef..476546a 100644 --- a/nihil.ucl/type.ccm +++ b/nihil.ucl/type.ccm @@ -1,40 +1,61 @@ -/* - * This source code is released into the public domain. - */ - +// This source code is released into the public domain. module; -#include -#include -#include -#include - #include export module nihil.ucl:type; +import nihil.std; import nihil.error; namespace nihil::ucl { // Our strongly-typed version of ::ucl_type. -export enum struct object_type { - object = UCL_OBJECT, - array = UCL_ARRAY, - integer = UCL_INT, - real = UCL_FLOAT, - string = UCL_STRING, - boolean = UCL_BOOLEAN, - time = UCL_TIME, - userdata = UCL_USERDATA, - null = UCL_NULL, +export enum struct object_type : std::uint8_t { + object = UCL_OBJECT, + array = UCL_ARRAY, + integer = UCL_INT, + real = UCL_FLOAT, + string = UCL_STRING, + boolean = UCL_BOOLEAN, + time = UCL_TIME, + userdata = UCL_USERDATA, + null = UCL_NULL, }; // Get the name of a type. -export auto str(object_type type) -> std::string_view; +export auto str(object_type type) -> std::string_view +{ + using namespace std::literals; + + switch (type) { + case object_type::object: + return "object"sv; + case object_type::array: + return "array"sv; + case object_type::integer: + return "integer"sv; + case object_type::real: + return "real"sv; + case object_type::string: + return "string"sv; + case object_type::boolean: + return "boolean"sv; + case object_type::time: + return "time"sv; + case object_type::userdata: + return "userdata"sv; + case object_type::null: + return "null"sv; + default: + // Don't fail here, since UCL might add more types that we + // don't know about. + return "unknown"sv; + } +} // Concept of a UCL data type. -export template +export template concept datatype = requires(T o) { { o.get_ucl_object() } -> std::convertible_to<::ucl_object_t const *>; { o.type() } -> std::same_as; @@ -42,14 +63,28 @@ concept datatype = requires(T o) { }; // Exception thrown when a type assertion fails. -export struct type_mismatch : error { - type_mismatch(object_type expected_type, object_type actual_type); +export struct type_mismatch : error +{ + type_mismatch(object_type expected_type, object_type actual_type) + : error(std::format("expected type '{}' != actual type '{}'", + ucl::str(expected_type), ucl::str(actual_type))) + , m_expected_type(expected_type) + , m_actual_type(actual_type) + { + } // The type we expected. - auto expected_type(this type_mismatch const &self) -> object_type; + auto expected_type(this type_mismatch const &self) -> object_type + { + return self.m_expected_type; + } + // The type we got. - auto actual_type(this type_mismatch const &self) -> object_type; - + auto actual_type(this type_mismatch const &self) -> object_type + { + return self.m_actual_type; + } + private: object_type m_expected_type; object_type m_actual_type; 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"); -} diff --git a/nihil.uuid/CMakeLists.txt b/nihil.uuid/CMakeLists.txt index f82d308..a210322 100644 --- a/nihil.uuid/CMakeLists.txt +++ b/nihil.uuid/CMakeLists.txt @@ -1,6 +1,7 @@ # This source code is released into the public domain. add_library(nihil.uuid STATIC) +target_link_libraries(nihil.uuid PRIVATE nihil.std) target_sources(nihil.uuid PUBLIC FILE_SET modules TYPE CXX_MODULES FILES uuid.ccm diff --git a/nihil.uuid/test.cc b/nihil.uuid/test.cc index 0f21298..551c491 100644 --- a/nihil.uuid/test.cc +++ b/nihil.uuid/test.cc @@ -2,17 +2,17 @@ * 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 @@ -22,45 +22,35 @@ * IN THE SOFTWARE. */ -#include -#include -#include -#include - #include -//NOLINTBEGIN(bugprone-unchecked-optional-access) +import nihil.std; +import nihil.uuid; -namespace -{ +// 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) +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)); + 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)); + auto seedseq = std::seed_seq(std::cbegin(numbers), std::cend(numbers)); engine.seed(seedseq); } -} // anonymous namespace - -import nihil.uuid; - using namespace nihil; TEST_CASE("uuid: Test multiple default generators", "[uuid]") @@ -70,12 +60,12 @@ TEST_CASE("uuid: Test multiple default generators", "[uuid]") { std::random_device rd; - auto seed_data = std::array {}; + 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); + std::mt19937 generator(seq); - id1 = uuid_random_generator{ generator }(); + id1 = uuid_random_generator{generator}(); REQUIRE(!id1.is_nil()); REQUIRE(id1.version() == uuid_version::random_number_based); REQUIRE(id1.variant() == uuid_variant::rfc); @@ -83,12 +73,12 @@ TEST_CASE("uuid: Test multiple default generators", "[uuid]") { std::random_device rd; - auto seed_data = std::array {}; + 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); + std::mt19937 generator(seq); - id2 = uuid_random_generator{ generator }(); + id2 = uuid_random_generator{generator}(); REQUIRE(!id2.is_nil()); REQUIRE(id2.version() == uuid_version::random_number_based); REQUIRE(id2.variant() == uuid_variant::rfc); @@ -100,10 +90,10 @@ TEST_CASE("uuid: Test multiple default generators", "[uuid]") TEST_CASE("uuid: Test default generator", "[uuid]") { std::random_device rd; - auto seed_data = std::array {}; + 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); + std::mt19937 generator(seq); uuid const guid = uuid_random_generator{generator}(); REQUIRE(!guid.is_nil()); @@ -111,17 +101,16 @@ TEST_CASE("uuid: Test default generator", "[uuid]") REQUIRE(guid.variant() == uuid_variant::rfc); } -TEST_CASE("uuid: Test random generator (conversion ctor w/ smart ptr)", - "[uuid]") +TEST_CASE("uuid: Test random generator (conversion ctor w/ smart ptr)", "[uuid]") { std::random_device rd; - auto seed_data = std::array {}; + 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); + std::mt19937 generator(seq); uuid_random_generator dgen(&generator); - auto id1 = dgen(); + auto id1 = dgen(); REQUIRE(!id1.is_nil()); REQUIRE(id1.version() == uuid_version::random_number_based); REQUIRE(id1.variant() == uuid_variant::rfc); @@ -137,13 +126,13 @@ TEST_CASE("uuid: Test random generator (conversion ctor w/ smart ptr)", TEST_CASE("uuid: Test random generator (conversion ctor w/ ptr)", "[uuid]") { std::random_device rd; - auto seed_data = std::array {}; + 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); + auto generator = std::make_unique(seq); uuid_random_generator dgen(generator.get()); - auto id1 = dgen(); + auto id1 = dgen(); REQUIRE(!id1.is_nil()); REQUIRE(id1.version() == uuid_version::random_number_based); REQUIRE(id1.variant() == uuid_variant::rfc); @@ -159,13 +148,13 @@ TEST_CASE("uuid: Test random generator (conversion ctor w/ ptr)", "[uuid]") TEST_CASE("uuid: Test random generator (conversion ctor w/ ref)", "[uuid]") { std::random_device rd; - auto seed_data = std::array {}; + 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); + std::mt19937 generator(seq); uuid_random_generator dgen(generator); - auto id1 = dgen(); + auto id1 = dgen(); REQUIRE(!id1.is_nil()); REQUIRE(id1.version() == uuid_version::random_number_based); REQUIRE(id1.variant() == uuid_variant::rfc); @@ -179,16 +168,17 @@ TEST_CASE("uuid: Test random generator (conversion ctor w/ ref)", "[uuid]") } TEST_CASE("uuid: Test basic random generator (conversion ctor w/ ptr) " - "w/ ranlux48_base", "[uuid]") + "w/ ranlux48_base", + "[uuid]") { std::random_device rd; - auto seed_data = std::array {}; + 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::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(); + auto id1 = dgen(); REQUIRE(!id1.is_nil()); REQUIRE(id1.version() == uuid_version::random_number_based); REQUIRE(id1.variant() == uuid_variant::rfc); @@ -202,16 +192,17 @@ TEST_CASE("uuid: Test basic random generator (conversion ctor w/ ptr) " } TEST_CASE("uuid: Test basic random generator (conversion ctor w/ smart ptr) " - "w/ ranlux48_base", "[uuid]") + "w/ ranlux48_base", + "[uuid]") { std::random_device rd; - auto seed_data = std::array {}; + 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); + auto generator = std::make_unique(seq); basic_uuid_random_generator dgen(generator.get()); - auto id1 = dgen(); + auto id1 = dgen(); REQUIRE(!id1.is_nil()); REQUIRE(id1.version() == uuid_version::random_number_based); REQUIRE(id1.variant() == uuid_variant::rfc); @@ -225,16 +216,17 @@ TEST_CASE("uuid: Test basic random generator (conversion ctor w/ smart ptr) " } TEST_CASE("uuid: Test basic random generator (conversion ctor w/ ref) " - "w/ ranlux48_base", "[uuid]") + "w/ ranlux48_base", + "[uuid]") { std::random_device rd; - auto seed_data = std::array {}; + 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::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(); + auto id1 = dgen(); REQUIRE(!id1.is_nil()); REQUIRE(id1.version() == uuid_version::random_number_based); REQUIRE(id1.variant() == uuid_variant::rfc); @@ -289,7 +281,7 @@ 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); + auto id1 = dgen("john"s); REQUIRE(!id1.is_nil()); REQUIRE(id1.version() == uuid_version::name_based_sha1); REQUIRE(id1.variant() == uuid_variant::rfc); @@ -319,7 +311,7 @@ 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); + auto id1 = dgen("john"sv); REQUIRE(!id1.is_nil()); REQUIRE(id1.version() == uuid_version::name_based_sha1); REQUIRE(id1.variant() == uuid_variant::rfc); @@ -345,12 +337,13 @@ TEST_CASE("uuid: Test name generator (std::string_view)", "[uuid]") } TEST_CASE("uuid: Test name generator equality (char const*, std::string, " - "std::string_view)", "[uuid]") + "std::string_view)", + "[uuid]") { using namespace std::literals; - auto dgen = uuid_name_generator(uuid::from_string( - "47183823-2574-4bfd-b411-99ed177d3e43").value()); + 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); @@ -368,30 +361,20 @@ TEST_CASE("uuid: Test default constructor", "[uuid]") 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"); + 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}")); + 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]") @@ -443,36 +426,24 @@ 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)); + 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}")); + 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]") @@ -510,21 +481,16 @@ TEST_CASE("uuid: Test is_valid_uuid(basic_string) invalid format", "[uuid]") } } -TEST_CASE("uuid: Test is_valid_uuid(basic_string_view) invalid format", - "[uuid]") +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)); + 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]") @@ -714,14 +680,10 @@ 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()); + 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]") @@ -765,14 +727,10 @@ TEST_CASE("uuid: Test from_string(basic_string_view) invalid format", "[uuid]") 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()); + 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]") @@ -780,31 +738,22 @@ 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 - }}; + std::array arr{ + {0x47, 0x18, 0x38, 0x23, 0x25, 0x74, 0x4b, 0xfd, 0xb4, 0x11, 0x99, 0xed, + 0x17, 0x7d, 0x3e, 0x43} + }; - uuid guid(std::begin(arr), std::end(arr)); - REQUIRE(to_string(guid) == - "47183823-2574-4bfd-b411-99ed177d3e43"s); + 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 - }; + uuid::value_type arr[16] = {// NOLINT + 0x47, 0x18, 0x38, 0x23, 0x25, 0x74, 0x4b, 0xfd, + 0xb4, 0x11, 0x99, 0xed, 0x17, 0x7d, 0x3e, 0x43}; - uuid guid(std::begin(arr), std::end(arr)); - REQUIRE(to_string(guid) == - "47183823-2574-4bfd-b411-99ed177d3e43"s); + auto const guid = uuid(std::begin(arr), std::end(arr)); + REQUIRE(to_string(guid) == "47183823-2574-4bfd-b411-99ed177d3e43"s); } } @@ -813,44 +762,31 @@ TEST_CASE("uuid: Test array constructors", "[uuid]") using namespace std::string_literals; { - uuid guid{{ - 0x47, 0x18, 0x38, 0x23, - 0x25, 0x74, - 0x4b, 0xfd, - 0xb4, 0x11, - 0x99, 0xed, 0x17, 0x7d, 0x3e, 0x43 - }}; + 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); + REQUIRE(to_string(guid) == "47183823-2574-4bfd-b411-99ed177d3e43"s); } { - std::array arr{{ - 0x47, 0x18, 0x38, 0x23, - 0x25, 0x74, - 0x4b, 0xfd, - 0xb4, 0x11, - 0x99, 0xed, 0x17, 0x7d, 0x3e, 0x43 - }}; + auto arr = std::array{ + {0x47, 0x18, 0x38, 0x23, 0x25, 0x74, 0x4b, 0xfd, 0xb4, 0x11, 0x99, 0xed, + 0x17, 0x7d, 0x3e, 0x43} + }; - uuid guid(arr); - REQUIRE(to_string(guid) == - "47183823-2574-4bfd-b411-99ed177d3e43"s); + 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 - }; + uuid::value_type arr[16]{// NOLINT + 0x47, 0x18, 0x38, 0x23, 0x25, 0x74, 0x4b, 0xfd, + 0xb4, 0x11, 0x99, 0xed, 0x17, 0x7d, 0x3e, 0x43}; - uuid guid(arr); - REQUIRE(to_string(guid) == - "47183823-2574-4bfd-b411-99ed177d3e43"s); + auto const guid = uuid(arr); + REQUIRE(to_string(guid) == "47183823-2574-4bfd-b411-99ed177d3e43"s); } } @@ -874,21 +810,15 @@ TEST_CASE("Test comparison", "[uuid]") auto engine = uuid_random_generator::engine_type{}; seed_rng(engine); - uuid_random_generator gen{engine}; + auto gen = uuid_random_generator{engine}; auto id = gen(); REQUIRE(empty < id); - std::set ids{ - uuid{}, - gen(), - gen(), - gen(), - gen() - }; + auto ids = std::set{uuid{}, gen(), gen(), gen(), gen()}; REQUIRE(ids.size() == 5); - REQUIRE(ids.find(uuid{}) != ids.end()); + REQUIRE(ids.contains(uuid{}) == true); } TEST_CASE("uuid: Test hashing", "[uuid]") @@ -904,15 +834,9 @@ TEST_CASE("uuid: Test hashing", "[uuid]") auto engine = uuid_random_generator::engine_type{}; seed_rng(engine); - uuid_random_generator gen{ engine }; - - std::unordered_set ids{ - uuid{}, - gen(), - gen(), - gen(), - gen() - }; + uuid_random_generator gen{engine}; + + std::unordered_set ids{uuid{}, gen(), gen(), gen(), gen()}; REQUIRE(ids.size() == 5); REQUIRE(ids.find(uuid{}) != ids.end()); @@ -950,7 +874,7 @@ TEST_CASE("uuid: Test constexpr", "[uuid]") TEST_CASE("uuid: Test size", "[uuid]") { - REQUIRE(sizeof(uuid) == 16); + REQUIRE(sizeof(uuid) == 16); } TEST_CASE("uuid: Test assignment", "[uuid]") @@ -973,16 +897,13 @@ TEST_CASE("uuid: Test trivial", "[uuid]") 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 - }}; + std::array arr{ + {0x47, 0x18, 0x38, 0x23, 0x25, 0x74, 0x4b, 0xfd, 0xb4, 0x11, 0x99, 0xed, 0x17, 0x7d, + 0x3e, 0x43} + }; { - uuid id{ arr }; + uuid id{arr}; REQUIRE(!id.is_nil()); auto view = id.as_bytes(); @@ -990,12 +911,13 @@ TEST_CASE("uuid: Test as_bytes", "[uuid]") } { - const uuid id{ arr }; + 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) +// NOLINTEND(bugprone-unchecked-optional-access) diff --git a/nihil.uuid/uuid.ccm b/nihil.uuid/uuid.ccm index 4aa424e..a7a4770 100644 --- a/nihil.uuid/uuid.ccm +++ b/nihil.uuid/uuid.ccm @@ -1,66 +1,42 @@ -/* - * 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. - */ - -module; - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include +// 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.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')); + return static_cast(ch - static_cast('0')); if (ch >= static_cast('a') && ch <= static_cast('f')) - return static_cast( - 10 + ch - static_cast('a')); + 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 static_cast(10 + ch - static_cast('A')); return 0; } @@ -68,17 +44,14 @@ template 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')); + 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 +[[nodiscard]] constexpr auto +to_string_view(TChar const *str) noexcept -> std::basic_string_view { if (str) return str; @@ -87,9 +60,7 @@ template template [[nodiscard]] constexpr auto to_string_view(StringType const &str) noexcept - -> std::basic_string_view< - typename StringType::value_type, - typename StringType::traits_type> + -> std::basic_string_view { return str; } @@ -106,9 +77,8 @@ struct sha1 reset(); } - [[nodiscard]] inline static auto - left_rotate(std::uint32_t value, std::size_t const count) noexcept - -> std::uint32_t + [[nodiscard]] static auto + left_rotate(std::uint32_t value, std::size_t const count) noexcept -> std::uint32_t { return (value << count) ^ (value >> (32 - count)); } @@ -126,7 +96,7 @@ struct sha1 auto process_byte(this sha1 &self, std::uint8_t octet) -> void { - self.m_block[self.m_blockByteIndex++] = octet; + self.m_block.at(self.m_blockByteIndex++) = octet; ++self.m_byteCount; if (self.m_blockByteIndex == block_bytes) { @@ -135,13 +105,10 @@ struct sha1 } } - auto process_block(this sha1 &self, - void const *const start, - void const *const end) - -> void + auto process_block(this sha1 &self, void const *const start, void const *const end) -> void { - auto *first = static_cast(start); - auto *last = static_cast(end); + auto const *first = static_cast(start); + auto const *last = static_cast(end); while (first != last) { self.process_byte(*first); @@ -149,12 +116,9 @@ struct sha1 } } - auto process_bytes(this sha1 &self, - void const *const data, - size_t const len) - -> void + auto process_bytes(this sha1 &self, void const *const data, std::size_t const len) -> void { - auto *block = static_cast(data); + auto *block = static_cast(data); self.process_block(block, block + len); } @@ -178,14 +142,10 @@ struct sha1 self.process_byte(0); self.process_byte(0); self.process_byte(0); - self.process_byte(static_cast( - (bit_count >> 24) & 0xFF)); - self.process_byte(static_cast( - (bit_count >> 16) & 0xFF)); - self.process_byte(static_cast( - (bit_count >> 8) & 0xFF)); - self.process_byte(static_cast( - (bit_count) & 0xFF)); + 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; } @@ -193,57 +153,50 @@ struct sha1 auto get_digest_bytes(this sha1 &self) -> digest8_t { auto d32 = self.get_digest(); - auto digest = digest8_t{}; - - auto di = std::size_t{0}; - - digest[di++] = static_cast(d32[0] >> 24); - digest[di++] = static_cast(d32[0] >> 16); - digest[di++] = static_cast(d32[0] >> 8); - digest[di++] = static_cast(d32[0] >> 0); - digest[di++] = static_cast(d32[1] >> 24); - digest[di++] = static_cast(d32[1] >> 16); - digest[di++] = static_cast(d32[1] >> 8); - digest[di++] = static_cast(d32[1] >> 0); - - digest[di++] = static_cast(d32[2] >> 24); - digest[di++] = static_cast(d32[2] >> 16); - digest[di++] = static_cast(d32[2] >> 8); - digest[di++] = static_cast(d32[2] >> 0); - - digest[di++] = static_cast(d32[3] >> 24); - digest[di++] = static_cast(d32[3] >> 16); - digest[di++] = static_cast(d32[3] >> 8); - digest[di++] = static_cast(d32[3] >> 0); - - digest[di++] = static_cast(d32[4] >> 24); - digest[di++] = static_cast(d32[4] >> 16); - digest[di++] = static_cast(d32[4] >> 8); - digest[di++] = static_cast(d32[4] >> 0); - - return 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(); + auto w = std::array{}; for (std::size_t i = 0; i < 16; i++) { - w[i] = static_cast( - self.m_block[i * 4 + 0]) << 24; - w[i] |= static_cast( - self.m_block[i * 4 + 1]) << 16; - w[i] |= static_cast( - self.m_block[i * 4 + 2]) << 8; - w[i] |= static_cast( - self.m_block[i * 4 + 3]); + 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[i] = left_rotate((w[i - 3] ^ w[i - 8] ^ - w[i - 14] ^ w[i - 16]), 1); + 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]; @@ -270,8 +223,7 @@ private: k = 0xCA62C1D6; } - auto temp = std::uint32_t{left_rotate(a, 5) - + f + e + k + w[i]}; + auto temp = std::uint32_t{left_rotate(a, 5) + f + e + k + w.at(i)}; e = d; d = c; c = left_rotate(b, 30); @@ -286,19 +238,17 @@ private: self.m_digest[4] += e; } - digest32_t m_digest; - std::array m_block; - std::size_t m_blockByteIndex; - std::size_t m_byteCount; + 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"; +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"; +inline constexpr std::wstring_view empty_guid = L"00000000-0000-0000-0000-000000000000"; template inline constexpr std::string_view guid_encoder = "0123456789abcdef"; @@ -345,19 +295,19 @@ inline constexpr std::wstring_view guid_encoder = L"0123456789abcdef"; // indicated by a bit pattern in octet 8, marked with N in // xxxxxxxx-xxxx-xxxx-Nxxx-xxxxxxxxxxxx -export enum struct uuid_variant { +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 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 + // RFC 4122/DCE 1.1 // N bit pattern: 10xx // > big-endian byte order rfc, @@ -365,17 +315,17 @@ export enum struct uuid_variant { // Microsoft Corporation backward compatibility // N bit pattern: 110x // > little endian byte order - // > formely used in the Component Object Model (COM) library + // > formely used in the Component Object Model (COM) library microsoft, // reserved for possible future definition - // N bit pattern: 111x + // 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 { +// 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 @@ -393,25 +343,25 @@ export enum struct uuid_version { // 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> +export template , + typename Allocator = std::allocator> auto to_string(uuid const &id) -> std::basic_string; // -------------------------------------------------------------------------------------------------------------------------- // uuid class // -------------------------------------------------------------------------------------------------------------------------- -export struct uuid { +export struct uuid +{ using value_type = std::uint8_t; constexpr uuid() noexcept = default; - uuid(value_type(&arr)[16]) noexcept // NOLINT + uuid(value_type (&arr)[16]) noexcept // NOLINT { std::ranges::copy(arr, std::ranges::begin(data)); } - constexpr uuid(std::array const &arr) noexcept + explicit constexpr uuid(std::array const &arr) noexcept : data{arr} { } @@ -428,7 +378,7 @@ export struct uuid { std::ranges::copy(bytes, std::ranges::begin(data)); } - template + template explicit uuid(ForwardIterator first, ForwardIterator last) { if (std::distance(first, last) != 16) @@ -439,11 +389,11 @@ export struct uuid { [[nodiscard]] constexpr auto variant() const noexcept -> uuid_variant { - if ((data[8] & 0x80) == 0x00) + if ((data[8] & 0x80U) == 0x00U) return uuid_variant::ncs; - else if ((data[8] & 0xC0) == 0x80) + else if ((data[8] & 0xC0U) == 0x80U) return uuid_variant::rfc; - else if ((data[8] & 0xE0) == 0xC0) + else if ((data[8] & 0xE0U) == 0xC0U) return uuid_variant::microsoft; else return uuid_variant::reserved; @@ -451,15 +401,15 @@ export struct uuid { [[nodiscard]] constexpr auto version() const noexcept -> uuid_version { - if ((data[6] & 0xF0) == 0x10) + if ((data[6] & 0xF0U) == 0x10U) return uuid_version::time_based; - else if ((data[6] & 0xF0) == 0x20) + else if ((data[6] & 0xF0U) == 0x20U) return uuid_version::dce_security; - else if ((data[6] & 0xF0) == 0x30) + else if ((data[6] & 0xF0U) == 0x30U) return uuid_version::name_based_md5; - else if ((data[6] & 0xF0) == 0x40) + else if ((data[6] & 0xF0U) == 0x40U) return uuid_version::random_number_based; - else if ((data[6] & 0xF0) == 0x50) + else if ((data[6] & 0xF0U) == 0x50U) return uuid_version::name_based_sha1; else return uuid_version::none; @@ -467,10 +417,7 @@ export struct uuid { [[nodiscard]] constexpr auto is_nil() const noexcept -> bool { - for (auto i : data) - if (i != 0) - return false; - return true; + return std::ranges::all_of(data, [](auto i) { return i == 0; }); } auto swap(uuid &other) noexcept -> void @@ -478,18 +425,14 @@ export struct uuid { data.swap(other.data); } - [[nodiscard]] inline auto as_bytes() const - -> std::span + [[nodiscard]] auto as_bytes() const -> std::span { return std::span( - reinterpret_cast(data.data()), - 16); + reinterpret_cast(data.data()), 16); } template - [[nodiscard]] constexpr static auto - is_valid_uuid(StringType const &in_str) noexcept - -> bool + [[nodiscard]] constexpr static auto is_valid_uuid(StringType const &in_str) noexcept -> bool { auto str = to_string_view(in_str); auto firstDigit = true; @@ -505,7 +448,7 @@ export struct uuid { if (hasBraces && str.back() != '}') return false; - for (size_t i = hasBraces; i < str.size() - hasBraces; ++i) { + for (std::size_t i = hasBraces; i < str.size() - hasBraces; ++i) { if (str[i] == '-') continue; @@ -528,15 +471,14 @@ export struct uuid { template [[nodiscard]] constexpr static auto - from_string(StringType const & in_str) noexcept - -> std::optional + from_string(StringType const &in_str) -> std::optional { auto str = to_string_view(in_str); bool firstDigit = true; - size_t hasBraces = 0; - size_t index = 0; + auto hasBraces = std::size_t{0}; + auto index = std::size_t{0}; - std::array data{ { 0 } }; + auto data = std::array{}; if (str.empty()) return {}; @@ -546,7 +488,7 @@ export struct uuid { if (hasBraces && str.back() != '}') return {}; - for (size_t i = hasBraces; i < str.size() - hasBraces; ++i) { + for (std::size_t i = hasBraces; i < str.size() - hasBraces; ++i) { if (str[i] == '-') continue; @@ -555,10 +497,11 @@ export struct uuid { } if (firstDigit) { - data[index] = static_cast(hex2char(str[i]) << 4); + data.at(index) = static_cast(hex2char(str[i]) << 4); firstDigit = false; } else { - data[index] = static_cast(data[index] | hex2char(str[i])); + data.at(index) = + static_cast(data.at(index) | hex2char(str[i])); index++; firstDigit = true; } @@ -572,19 +515,17 @@ export struct uuid { } private: - std::array data{ { 0 } }; + 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) + friend auto operator<<(std::basic_ostream &s, uuid const &id) -> std::basic_ostream &; - template - friend auto to_string(uuid const &id) - -> std::basic_string; + template + friend auto to_string(uuid const &id) -> std::basic_string; friend std::hash; }; @@ -594,36 +535,35 @@ private: // -------------------------------------------------------------------------------------------------------------------------- export [[nodiscard]] -auto operator== (uuid const &lhs, uuid const &rhs) noexcept -> bool +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 +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 +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 +[[nodiscard]] auto to_string(uuid const &id) -> std::basic_string { - auto uustr = std::basic_string( - std::from_range, empty_guid); + 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[index] >> 4 & 0x0f]; - uustr[++i] = guid_encoder[id.data[index] & 0x0f]; + uustr[i] = guid_encoder[id.data.at(index) >> 4U & 0x0FU]; + uustr[++i] = guid_encoder[id.data.at(index) & 0x0FU]; index++; } @@ -634,13 +574,12 @@ export template auto operator<<(std::basic_ostream &s, uuid const &id) -> std::basic_ostream & { - s << to_string(id); - return s; + return s << to_string(id); } export auto swap(uuid &lhs, uuid &rhs) noexcept -> void { - lhs.swap(rhs); + lhs.swap(rhs); } /*********************************************************************** @@ -648,31 +587,31 @@ export auto swap(uuid &lhs, uuid &rhs) noexcept -> void */ // 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 -}}; +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 -}}; +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 -}}; +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 -}}; +export uuid uuid_namespace_x500{ + {0x6b, 0xa7, 0xb8, 0x14, 0x9d, 0xad, 0x11, 0xd1, 0x80, 0xb4, 0x00, 0xc0, 0x4f, 0xd4, 0x30, + 0xc8} +}; /*********************************************************************** * uuid generators @@ -695,20 +634,16 @@ struct basic_uuid_random_generator [[nodiscard]] auto operator()() -> uuid { - alignas(std::uint32_t) auto bytes = std::array{}; - - for (int i = 0; i < 16; i += 4) - *reinterpret_cast(bytes.data() + i) = - distribution(*generator); + std::ranges::generate(bytes, [&] { return distribution(*generator); }); // variant must be 10xxxxxx - bytes[8] &= 0xBF; - bytes[8] |= 0x80; + bytes[8] &= 0xBFU; + bytes[8] |= 0x80U; // version must be 0100xxxx - bytes[6] &= 0x4F; - bytes[6] |= 0x40; + bytes[6] &= 0x4FU; + bytes[6] |= 0x40U; return uuid{std::begin(bytes), std::end(bytes)}; } @@ -749,18 +684,14 @@ private: } template - auto process_characters( - std::basic_string_view const str) -> void + auto process_characters(std::basic_string_view const str) -> void { for (std::uint32_t c : str) { - hasher.process_byte(static_cast(c & 0xFF)); + hasher.process_byte(static_cast(c & 0xFFU)); if constexpr (!std::is_same_v) { - hasher.process_byte( - static_cast((c >> 8) & 0xFF)); - hasher.process_byte( - static_cast((c >> 16) & 0xFF)); - hasher.process_byte( - static_cast((c >> 24) & 0xFF)); + hasher.process_byte(static_cast((c >> 8U) & 0xFFU)); + hasher.process_byte(static_cast((c >> 16U) & 0xFFU)); + hasher.process_byte(static_cast((c >> 24U) & 0xFFU)); } } } @@ -770,17 +701,16 @@ private: auto digest = hasher.get_digest_bytes(); // variant must be 0b10xxxxxx - digest[8] &= 0xBF; - digest[8] |= 0x80; + digest[8] &= 0xBFU; + digest[8] |= 0x80U; // version must be 0b0101xxxx - digest[6] &= 0x5F; - digest[6] |= 0x50; + digest[6] &= 0x5FU; + digest[6] |= 0x50U; return uuid(std::span(digest).subspan(0, 16)); } -private: uuid nsuuid; sha1 hasher; }; @@ -791,15 +721,14 @@ private: export auto random_uuid() -> uuid { auto rd = std::random_device(); - auto seed_data = std::array {}; + 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 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(); + return gen(); } } // namespace nihil @@ -810,30 +739,27 @@ export template <> struct hash { using argument_type = nihil::uuid; - using result_type = std::size_t; + using result_type = std::size_t; - [[nodiscard]] auto operator()(argument_type const &uuid) const - -> result_type + [[nodiscard]] auto operator()(argument_type const &uuid) const noexcept -> result_type { - std::uint64_t l = - static_cast(uuid.data[0]) << 56 | - static_cast(uuid.data[1]) << 48 | - static_cast(uuid.data[2]) << 40 | - static_cast(uuid.data[3]) << 32 | - static_cast(uuid.data[4]) << 24 | - static_cast(uuid.data[5]) << 16 | - static_cast(uuid.data[6]) << 8 | - static_cast(uuid.data[7]); - - std::uint64_t h = - static_cast(uuid.data[8]) << 56 | - static_cast(uuid.data[9]) << 48 | - static_cast(uuid.data[10]) << 40 | - static_cast(uuid.data[11]) << 32 | - static_cast(uuid.data[12]) << 24 | - static_cast(uuid.data[13]) << 16 | - static_cast(uuid.data[14]) << 8 | - static_cast(uuid.data[15]); + 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); } -- cgit v1.2.3