diff options
| author | Lexi Winter <lexi@le-fay.org> | 2025-07-01 17:07:04 +0100 |
|---|---|---|
| committer | Lexi Winter <lexi@le-fay.org> | 2025-07-01 17:07:04 +0100 |
| commit | 2e2d1bd3b6c7776b77c33b94f30ead89367a71e6 (patch) | |
| tree | 54d37ffadf8e677938d9b7a28e4e9b71be1e75c1 | |
| parent | 36427c0966faa7aecd586b397ed9b845f18172f5 (diff) | |
| download | nihil-2e2d1bd3b6c7776b77c33b94f30ead89367a71e6.tar.gz nihil-2e2d1bd3b6c7776b77c33b94f30ead89367a71e6.tar.bz2 | |
add nihil.std
159 files changed, 3305 insertions, 4311 deletions
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 <expected> -#include <functional> -#include <iostream> -#include <print> -#include <string> - // For EX_USAGE. While <sysexits.h> 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 <expected> -#include <functional> -#include <string> - 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<int, error> 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 <expected> -#include <iostream> -#include <print> -#include <string> - -#include <unistd.h> +#include <unistd.h> // _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 <expected> -#include <string> - +// 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 <iostream> -#include <memory> -#include <print> -#include <ranges> -#include <string> -#include <vector> - +// 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 <map> -#include <memory> -#include <optional> -#include <ranges> -#include <vector> - +// 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 <cstdio> -#include <functional> -#include <iostream> -#include <map> -#include <print> -#include <ranges> -#include <string> -#include <utility> - -#include <unistd.h> +#include <stdlib.h> // getprogname, NOLINT +#include <unistd.h> // 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 <functional> -#include <iostream> -#include <map> -#include <string> -#include <utility> - +// 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 <cstdio> -#include <exception> -#include <memory> -#include <vector> - +// 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<std::shared_ptr<command_node>>(); 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 <memory> -#include <span> - +// 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<std::shared_ptr<command_node>>; } // 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 <iostream> -#include <vector> +// This source code is released into the public domain. #include <catch2/catch_test_macros.hpp> +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<char const *>{ "cmd", "sub1", nullptr }; - auto argv = const_cast<char **>(args.data()); + auto *argv = const_cast<char **>(args.data()); - int ret = nihil::dispatch_command( + auto const ret = nihil::dispatch_command( static_cast<int>(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<char const *>{ "cmd", "sub2", nullptr }; - auto argv = const_cast<char **>(args.data()); + auto *argv = const_cast<char **>(args.data()); - int ret = nihil::dispatch_command( + auto const ret = nihil::dispatch_command( static_cast<int>(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<char const *>{ "nocomd", "sub", nullptr }; - auto argv = const_cast<char **>(args.data()); + auto *argv = const_cast<char **>(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<char const *>{ "cmd", nullptr }; - auto argv = const_cast<char **>(args.data()); + auto *argv = const_cast<char **>(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 <string> - +// 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 <coroutine> -#include <expected> -#include <iostream> -#include <string> - +// 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 <expected> -#include <iosfwd> -#include <string> - +// 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 <coroutine> -#include <expected> -#include <filesystem> -#include <format> -#include <iterator> -#include <string> - -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<void, error> -{ - // 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 <expected> -#include <filesystem> - +// 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<void, error>; +// Load the configuration from a file. +export [[nodiscard]] auto +read_from(std::filesystem::path const &filename) -> std::expected<void, error> +{ + // 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 <coroutine> -#include <expected> -#include <filesystem> -#include <format> -#include <map> - +// 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 <coroutine> -#include <expected> -#include <string> -#include <map> - +// 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 <coroutine> -#include <expected> -#include <format> -#include <string> - -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<void, error> -{ - m_storage = new_value; - return {}; -} - -auto string::get_ucl() const -> std::expected<ucl::object, error> -{ - return ucl::string(m_storage); -} - -auto string::set_ucl(ucl::object const &uclobj) -> std::expected<void, error> -{ - auto obj = co_await object_cast<ucl::string>(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 <expected> -#include <format> -#include <string> - +// 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<void, error> override; - - /* - * Convert this option to a UCL object. - */ - [[nodiscard]] auto get_ucl() const - -> std::expected<ucl::object, error> override; - - /* - * Set this option from a UCL object. - */ - [[nodiscard]] auto set_ucl(ucl::object const &uclobj) - -> std::expected<void, error> 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<void, error> override + { + m_storage = new_value; + return {}; + } + + // Convert this option to a UCL object. + [[nodiscard]] auto get_ucl() const -> std::expected<ucl::object, error> override + { + return ucl::make_string(m_storage); + } + + // Set this option from a UCL object. + [[nodiscard]] auto set_ucl(ucl::object const &uclobj) -> std::expected<void, error> override + { + auto obj = co_await object_cast<ucl::string>(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 <catch2/catch_test_macros.hpp> + +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 <string> - -#include <catch2/catch_test_macros.hpp> - -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 <coroutine> -#include <expected> -#include <filesystem> -#include <format> -#include <utility> - -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<void, error> -{ - auto uclconfig = ucl::map<ucl::object>(); - - // 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 <expected> -#include <filesystem> - +// 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<void, error>; +// Write all config values (except defaults) to disk. +export [[nodiscard]] auto +write_to(std::filesystem::path const &filename) -> std::expected<void, error> +{ + auto uclconfig = ucl::map<ucl::object>(); + + // 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 <string> -#include <system_error> - +// 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 <string> -#include <system_error> - +// 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<nihil::errc> : true_type {}; - -} // namespace std +template<> +struct std::is_error_condition_enum<nihil::errc> : 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(<sys/param.h>) # include <sys/param.h> #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 <iostream> -#include <memory> -#include <optional> -#include <string> -#include <system_error> -#include <variant> - -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<error>(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<error>(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<error>(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<error> -{ - 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<std::error_code> -{ - auto const *code = std::get_if<std::error_code>(&self.m_error); - if (code) - return {*code}; - return {}; -} - -auto error::condition(this error const &self) - -> std::optional<std::error_condition> -{ - auto const *condition = std::get_if<std::error_condition>(&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 <iosfwd> -#include <format> -#include <memory> -#include <optional> -#include <string> -#include <system_error> -#include <utility> -#include <variant> - +// 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<std::monostate, std::string, std::error_code, std::error_condition>; + +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<typename Cause> + // 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<error>(std::move(cause))) + { + } + + // Create an error from a freeform string and an error code enum cause. + template <typename Cause> requires(std::is_error_code_enum<Cause>::value || - std::is_error_condition_enum<Cause>::value) + std::is_error_condition_enum<Cause>::value) error(std::string_view what, Cause &&cause) : error(what, error(std::forward<Cause>(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<error>(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<error>(std::move(cause))) + { + } // Create an error from an std::error_code enum. + explicit error(auto errc) + requires(std::is_error_code_enum<decltype(errc)>::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<decltype(errc)>::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<decltype(errc)>::value) - : error(make_error_code(errc)) - {} + requires(std::is_error_condition_enum<decltype(errc)>::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<decltype(errc)>::value) : error(make_error_condition(errc), std::move(cause)) - {} - - explicit error(auto errc) - requires(std::is_error_condition_enum<decltype(errc)>::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<error>; + [[nodiscard]] auto cause(this error const &self) -> std::shared_ptr<error> const & + { + return self.m_cause; + } // Return the root cause of this error, which may be this object. // For errors caused by an OS error, this will typically be the // error_code error. - [[nodiscard]] auto root_cause(this error const &) -> 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<std::error_code>; + [[nodiscard]] auto code(this error const &self) -> std::optional<std::error_code> + { + auto const *code = std::get_if<std::error_code>(&self.m_error); + if (code) + return {*code}; + return {}; + } // Return this error's error_condition, if any. - [[nodiscard]] auto condition(this error const &) - -> std::optional<std::error_condition>; + [[nodiscard]] auto condition(this error const &self) -> std::optional<std::error_condition> + { + auto const *condition = std::get_if<std::error_condition>(&self.m_error); + if (condition) + 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<typename T> + operator std::expected<T, error> () && // NOLINT + { + return std::unexpected{std::move(*this)}; + } + + template<typename T> + operator std::expected<T, error> () const // NOLINT + { + return std::unexpected{*this}; + } + + operator std::unexpected<error> () && // NOLINT + { + return std::unexpected{std::move(*this)}; + } - [[nodiscard]] auto what() const noexcept -> char const * final; + operator std::unexpected<error> () 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<std::string> 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<decltype(rhs)>::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<decltype(rhs)>::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<decltype(rhs)>::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<decltype(rhs)>::value) + { + return lhs.condition() == rhs; + } +}; } // namespace nihil // Make error formattable. -export template<> +export template <> struct std::formatter<nihil::error, char> { - template<typename ParseContext> + template <typename ParseContext> constexpr auto parse(ParseContext &ctx) -> ParseContext::iterator { return ctx.begin(); } - template<typename FormatContext> - auto format(nihil::error const &e, FormatContext &ctx) const - -> FormatContext::iterator + template <typename FormatContext> + 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 <cerrno> + +export module nihil.error:sys_error; + +import nihil.std; + +namespace nihil { + +// Allow access to errno without having to include <cerrno>. +export [[nodiscard]] auto sys_error() -> std::errc +{ + return static_cast<std::errc>(errno); +} + +} // 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 <cerrno> -#include <cstring> -#include <system_error> +// This source code is released into the public domain. #include <catch2/catch_test_macros.hpp> +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 <concepts> -#include <cstdint> -#include <iostream> -#include <limits> -#include <print> - -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 <concepts> -#include <format> -#include <sstream> +// This source code is released into the public domain. #include <catch2/catch_test_macros.hpp> +import nihil.std; import nihil.flagset; namespace { - -struct test_tag{}; +struct test_tag +{ +}; using testflags = nihil::flagset<unsigned, test_tag>; 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<testflags>); @@ -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<unsigned>(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 <memory> -#include <type_traits> - export module nihil.generator:byte_allocator; +import nihil.std; + namespace nihil { template <typename Alloc> 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 <coroutine> -#include <memory> - 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 <concepts> - 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 <type_traits> - 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 <cassert> -#include <coroutine> -#include <exception> -#include <memory> -#include <ranges> -#include <type_traits> -#include <utility> 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 <coroutine> -#include <ranges> -#include <vector> +// This source code is released into the public domain. #include <catch2/catch_test_macros.hpp> +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 <coroutine> -#include <ranges> - 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 <coroutine> -#include <exception> -#include <memory> - 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 <concepts> -#include <memory> - export module nihil.generator:manual_lifetime; +import nihil.std; + namespace nihil { template <typename T> 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 <ranges> - 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 <cstdlib> -#include <memory> - 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 <concepts> -#include <memory> - 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 <concepts> -#include <functional> -#include <optional> -#include <utility> - 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 <catch2/catch_test_macros.hpp> 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 <variant> - +// This source code is released into the public domain. export module nihil.match; +import nihil.std; + namespace nihil { export template<class... Ts> 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 <string> -#include <variant> +// This source code is released into the public domain. #include <catch2/catch_test_macros.hpp> +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 <coroutine> -#include <exception> -#include <expected> -#include <optional> -#include <utility> - export module nihil.monad; +import nihil.std; + namespace nihil { /********************************************************************** @@ -228,8 +222,7 @@ struct expected_promise<void, E> : expected_promise_base<void, E> { self.data->emplace(std::move(err)); } - void return_value(this expected_promise &self, - std::expected<void, E> o) + void return_value(this expected_promise &self, std::expected<void, E> 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 <coroutine> -#include <expected> -#include <optional> +// This source code is released into the public domain. #include <catch2/catch_test_macros.hpp> +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 <memory> -#include <ranges> -#include <string> -#include <vector> - +// 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 <algorithm> -#include <string> -#include <type_traits> -#include <vector> +// This source code is released into the public domain #include <catch2/catch_test_macros.hpp> +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 <expected> -#include <filesystem> -#include <system_error> - +// 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 <expected> -#include <string> - -#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 <catch2/catch_test_macros.hpp> +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 <expected> -#include <format> -#include <string> - 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<execv, error> +execlp(std::filesystem::path const &file, auto &&...args) -> std::expected<execv, error> { 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 <catch2/catch_test_macros.hpp> +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 <string> - 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 <catch2/catch_test_macros.hpp> +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 <concepts> -#include <type_traits> - +// 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 <expected> -#include <filesystem> -#include <string> - -#include <unistd.h> +#include <unistd.h> // 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<void, error> { - ::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<std::filesystem::path, fd> 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 <catch2/catch_test_macros.hpp> +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 <coroutine> -#include <string> -#include <expected> -#include <format> - 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<execv, error> +execvp(std::filesystem::path const &file, argv &&args) -> std::expected<execv, error> { - 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 <catch2/catch_test_macros.hpp> +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 <coroutine> -#include <expected> -#include <ranges> -#include <span> -#include <stdexcept> -#include <system_error> - #include <fcntl.h> #include <unistd.h> 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<fd, error> 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<fd, er if (ret != -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 dup(). @@ -180,7 +179,7 @@ export [[nodiscard]] auto raw_dup(fd const &self) -> std::expected<int, error> 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<in if (ret != -1) return newfd; - return std::unexpected(error(std::errc(errno))); + return std::unexpected(error(sys_error())); } // Call fcntl() on this fd. Prefer one of the type-safe wrappers to this, if available. @@ -199,7 +198,7 @@ export [[nodiscard]] auto fcntl(fd const &fd, int op, int arg = 0) { auto const ret = ::fcntl(fd.get(), op, arg); if (ret == -1) - return std::unexpected(error(std::errc(errno))); + return std::unexpected(error(sys_error())); return ret; } @@ -277,7 +276,7 @@ export [[nodiscard]] auto pipe() -> std::expected<std::pair<fd, fd>, error> auto fds = std::array<int, 2>{}; 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 <coroutine> -#include <span> -#include <stdexcept> +// This source code is released into the public domain. #include <fcntl.h> +#include <unistd.h> #include <catch2/catch_test_macros.hpp> +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 <expected> -#include <string> - -#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<void, error> - { - ::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 <expected> -#include <filesystem> -#include <format> -#include <string> - -#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<execv, error> -{ - 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 <expected> -#include <filesystem> -#include <optional> -#include <ranges> - -#include <paths.h> -#include <unistd.h> +#include <unistd.h> // 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<std::filesystem::path, error> { - 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 <cerrno> -#include <expected> -#include <string> -#include <system_error> -#include <vector> - -#include <unistd.h> +#include <stdlib.h> // 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<std::string, error> { + 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<std: return std::unexpected(error(std::errc(errno))); } #else // NIHIL_HAVE_GETENV_R - errno = 0; - auto *v = ::getenv(cvarname.c_str()); // NOLINT + // getenv() may not set errno on failure, so initialise it to a reasonable value. + errno = ENOENT; + auto const *v = ::getenv(cvarname.c_str()); // NOLINT if (v != nullptr) return {std::string(v)}; - if (errno != 0) - return std::unexpected(error(std::errc(errno))); - - return std::unexpected(error(std::errc::no_such_file_or_directory)); + return error(sys_error()); #endif // NIHIL_HAVE_GETENV_R } diff --git a/nihil.posix/getenv.test.cc b/nihil.posix/getenv.test.cc index 9e10c16..3ba1d94 100644 --- a/nihil.posix/getenv.test.cc +++ b/nihil.posix/getenv.test.cc @@ -1,18 +1,14 @@ -/* - * This source code is released into the public domain. - */ - -#include <ranges> -#include <string> -#include <system_error> +// This source code is released into the public domain. #include <unistd.h> #include <catch2/catch_test_macros.hpp> +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 <expected> -#include <filesystem> - #include <fcntl.h> #include <unistd.h> 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 <catch2/catch_test_macros.hpp> +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 <expected> -#include <filesystem> -#include <optional> -#include <ranges> -#include <string> - -#include <paths.h> - 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<fd, error> { - 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 <catch2/catch_test_macros.hpp> +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 <paths.h> + +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 <expected> -#include <optional> -#include <system_error> -#include <utility> - #include <sys/wait.h> 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 <algorithm> -#include <expected> -#include <filesystem> -#include <iterator> -#include <ranges> -#include <span> -#include <system_error> - -#include <fcntl.h> -#include <unistd.h> - +// 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<char> auto &&iter) +read_file(std::filesystem::path const &filename, std::output_iterator<char> auto &&iter) -> std::expected<void, error> { auto file = co_await open(filename, open_read); @@ -38,7 +19,7 @@ read_file(std::filesystem::path const &filename, auto buffer = std::array<char, bufsize>{}; 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 <expected> -#include <filesystem> - 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<void, error> { - 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 <algorithm> -#include <cerrno> -#include <coroutine> -#include <expected> -#include <filesystem> -#include <format> -#include <iterator> -#include <print> -#include <string> -#include <utility> - -#include <sys/types.h> -#include <sys/wait.h> - -#include <fcntl.h> -#include <unistd.h> - 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<fd_read_pipe, error> +export [[nodiscard]] auto make_fd_read_pipe(int fdno) -> std::expected<fd_read_pipe, error> { 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<fd_write_pipe, error> +export [[nodiscard]] auto make_fd_write_pipe(int fdno) -> std::expected<fd_write_pipe, error> { 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_write_pipe, error> * 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<fd_file, error> { 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<fd_file, error> +export [[nodiscard]] inline auto stdin_devnull() -> std::expected<fd_file, error> { return make_fd_file(stdin_fileno, "/dev/null", open_read); } -export [[nodiscard]] inline auto -stdout_devnull() -> std::expected<fd_file, error> +export [[nodiscard]] inline auto stdout_devnull() -> std::expected<fd_file, error> { return make_fd_file(stdout_fileno, "/dev/null", open_write); } -export [[nodiscard]] inline auto -stderr_devnull() -> std::expected<fd_file, error> +export [[nodiscard]] inline auto stderr_devnull() -> std::expected<fd_file, error> { return make_fd_file(stderr_fileno, "/dev/null", open_write); } @@ -182,8 +153,9 @@ stderr_devnull() -> std::expected<fd_file, error> * Capture the output of a pipe in the parent and read it into an * output iterator. */ -export template<std::output_iterator<char> Iterator> -struct fd_capture final { +export template <std::output_iterator<char> 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<char> auto &&it) +export [[nodiscard]] auto make_capture(int fdno, std::output_iterator<char> auto &&it) -> std::expected<fd_capture<decltype(it)>, error> { auto pipe = co_await make_fd_write_pipe(fdno); - co_return fd_capture(std::move(pipe), - std::forward<decltype(it)>(it)); + co_return fd_capture(std::move(pipe), std::forward<decltype(it)>(it)); } -export [[nodiscard]] auto -make_capture(int fdno, std::string &str) +export [[nodiscard]] auto make_capture(int fdno, std::string &str) -> std::expected<fd_capture<decltype(std::back_inserter(str))>, 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<process, error> +spawn(executor auto &&executor, auto &&...actions) -> std::expected<process, error> { - 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 <expected> -#include <filesystem> - #include <sys/stat.h> 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<struct ::stat, error> { + 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<struct ::stat, error> { + 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 <algorithm> -#include <cstdint> -#include <expected> -#include <filesystem> -#include <random> -#include <string> - 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<temporary_file, } if (flags & tempfile_unlink) { - auto ec = std::error_code(); - remove(filename, ec); + std::ignore = unlink(filename); return temporary_file(std::move(*fd)); } diff --git a/nihil.posix/tempfile.test.cc b/nihil.posix/tempfile.test.cc index b1c7604..fcaafdc 100644 --- a/nihil.posix/tempfile.test.cc +++ b/nihil.posix/tempfile.test.cc @@ -1,11 +1,8 @@ -/* - * This source code is released into the public domain. - */ - -#include <filesystem> +// This source code is released into the public domain. #include <catch2/catch_test_macros.hpp> +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 <unistd.h> + +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 <unistd.h> + +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<void, error> +{ + 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 <coroutine> -#include <expected> -#include <filesystem> -#include <ranges> -#include <system_error> -#include <vector> - -#include <fcntl.h> -#include <unistd.h> - +// 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<decltype(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 "<filename>.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 "<filename>.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 <cstddef> +#include <cstdint> +#include <cstdio> +#include <cstdlib> +#include <cstring> + +#include <algorithm> +#include <array> +#include <charconv> +#include <compare> +#include <concepts> +#include <coroutine> +#include <exception> +#include <expected> +#include <filesystem> +#include <format> +#include <functional> +#include <initializer_list> +#include <iostream> +#include <iterator> +#include <limits> +#include <list> +#include <locale> +#include <map> +#include <memory> +#include <optional> +#include <print> +#include <random> +#include <ranges> +#include <set> +#include <span> +#include <sstream> +#include <stdexcept> +#include <string> +#include <system_error> +#include <type_traits> +#include <unordered_set> +#include <utility> +#include <variant> +#include <vector> + +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; + +// <algorithm> +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 + +// <array> +using std::array; + +// <charconv> +using std::from_chars; +using std::to_chars; +using std::to_chars_result; +using std::chars_format; + +// <compare> +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; + +// <concepts> +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; + +// <coroutine> +using std::coroutine_handle; +using std::coroutine_traits; +using std::noop_coroutine; +using std::suspend_always; +using std::suspend_never; + +// <cstddef> +using std::byte; +using std::ptrdiff_t; +using std::size_t; +using std::to_integer; + +// <cstdint> +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; + +// <cstdlib> +using std::exit; +using std::quick_exit; + +// <cstdio> +using std::FILE; +using std::fprintf; +using std::printf; +using std::rename; + +// <cstring> +using std::strerror; + +// <exception> +using std::current_exception; +using std::exception; +using std::exception_ptr; +using std::rethrow_exception; + +// <expected> +using std::unexpected; +using std::bad_expected_access; +using std::unexpect; +using std::unexpect_t; +using std::expected; + +// <filesystem> +namespace filesystem { +using std::filesystem::path; +using std::filesystem::create_directories; +using std::filesystem::exists; +} + +// <format> +using std::format; +using std::formatter; +using std::format_to; +using std::format_to_n; +using std::runtime_format; +using std::format_error; + +// <functional> +using std::function; +using std::invoke; +using std::ref; +using std::cref; +using std::reference_wrapper; + +// <iostream> +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; + +// <initializer_list> +using std::initializer_list; + +// <iterator> +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; + +// <limits> +using std::numeric_limits; + +// <list> +using std::list; + +// <locale> +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; + +// <memory> +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; + +// <optional> +using std::nullopt; +using std::optional; + +// <print> +using std::print; +using std::println; + +// <random> +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; + +// <ranges> +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; + +// <set> +using std::set; + +// <span> +using std::as_bytes; +using std::as_writable_bytes; +using std::dynamic_extent; +using std::span; + +// <sstream> +using std::basic_istringstream; +using std::basic_ostringstream; +using std::istringstream; +using std::ostringstream; + +// <stdexcept> +using std::logic_error; +using std::runtime_error; +using std::out_of_range; + +// <string> +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; +} +} + +// <system_error> +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; + +// <type_traits> +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; + +// <unordered_set> +using std::unordered_set; + +// <utility> +using std::exchange; +using std::forward; +using std::hash; +using std::ignore; +using std::make_pair; +using std::move; +using std::pair; + +// <variant> +using std::get_if; +using std::monostate; +using std::variant; +using std::visit; + +// <vector> +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 <cassert> -#include <cerrno> -#include <cstdint> -#include <cstdlib> -#include <format> -#include <iostream> -#include <string> -#include <system_error> -#include <utility> - #include <ucl.h> export module nihil.ucl:array; +import nihil.std; import :object; namespace nihil::ucl { -export template<datatype T> +export template <datatype T> struct array; -export template<datatype T> -struct array_iterator { +// +// The array iterator. This is hardened, i.e. it always checks bounds. +// +export template <datatype T> +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<T>; - ::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<datatype T> [[nodiscard]] -auto operator+(array_iterator<T> const &lhs, - typename array_iterator<T>::difference_type rhs) --> array_iterator<T> -{ - auto copy = lhs; - copy += rhs; - return copy; -} - -export template<datatype T> [[nodiscard]] -auto operator+(typename array_iterator<T>::difference_type lhs, - array_iterator<T> const &rhs) - -> array_iterator<T> -{ - 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<datatype T> [[nodiscard]] -auto operator-(array_iterator<T> const &lhs, - typename array_iterator<T>::difference_type rhs) - -> array_iterator<T> -{ - 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<datatype T = object> -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 <datatype T = object> +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<T>; - /* - * 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<object_type>( - ::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<object_type>( - ::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<std::input_iterator Iterator> - requires(std::convertible_to<std::iter_value_t<Iterator>, T>) - array(Iterator first, Iterator last) - : array() + // Create an array from a range. + template <std::ranges::range Range> + requires(std::convertible_to<std::ranges::range_value_t<Range>, 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<std::ranges::range Range> - requires(std::convertible_to<std::ranges::range_value_t<Range>, 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 <std::input_iterator Iterator> + requires(std::convertible_to<std::iter_value_t<Iterator>, 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<T> 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<datatype T> [[nodiscard]] -auto operator==(array<T> const &a, array<T> const &b) -> bool -{ - if (a.size() != b.size()) - return false; +private: + // + // Comparison operators. + // - for (typename array<T>::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<datatype T> -auto operator<<(std::ostream &strm, array<T> 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<typename T> +// std::formatter for an array. The output format is a list of values +// on a single line: [1, 2, 3]. +export template <typename T> struct std::formatter<nihil::ucl::array<T>, char> { - template<class ParseContext> - constexpr ParseContext::iterator parse(ParseContext& ctx) + template <class ParseContext> + constexpr auto parse(ParseContext &ctx) -> ParseContext::iterator { + (void)this; return ctx.begin(); } - template<class FmtContext> - FmtContext::iterator format(nihil::ucl::array<T> const &o, - FmtContext& ctx) const + template <class FmtContext> + auto format(nihil::ucl::array<T> const &o, FmtContext &ctx) const -> FmtContext::iterator { auto it = ctx.out(); bool first = true; diff --git a/nihil.ucl/tests/array.cc b/nihil.ucl/array.test.cc index 866fa45..89394a0 100644 --- a/nihil.ucl/tests/array.cc +++ b/nihil.ucl/array.test.cc @@ -1,18 +1,12 @@ -/* - * This source code is released into the public domain. - */ - -#include <algorithm> -#include <concepts> -#include <expected> -#include <ranges> -#include <string> +// This source code is released into the public domain. #include <catch2/catch_test_macros.hpp> #include <ucl.h> +import nihil.std; import nihil.ucl; +namespace { TEST_CASE("ucl: array: invariants", "[ucl]") { using namespace nihil::ucl; @@ -27,42 +21,44 @@ TEST_CASE("ucl: array: invariants", "[ucl]") static_assert(std::equality_comparable<array<>>); static_assert(std::totally_ordered<array<>>); static_assert(std::swappable<array<>>); - + static_assert(std::ranges::sized_range<array<integer>>); - static_assert(std::same_as<std::ranges::range_value_t<array<integer>>, - integer>); + static_assert(std::same_as<std::ranges::range_value_t<array<integer>>, integer>); } TEST_CASE("ucl: array: constructor", "[ucl]") { using namespace nihil::ucl; - SECTION("default") { + SECTION("default") + { auto arr = array<integer>(); REQUIRE(arr.size() == 0); REQUIRE(str(arr.type()) == "array"); } - SECTION("from range") { + SECTION("from range") + { auto vec = std::vector{integer(1), integer(42)}; - auto arr = array<integer>(std::from_range, vec); + auto arr = array<integer>(vec); REQUIRE(arr.size() == 2); REQUIRE(arr[0] == 1); REQUIRE(arr[1] == 42); } - SECTION("from iterator pair") { + SECTION("from iterator pair") + { auto vec = std::vector{integer(1), integer(42)}; - auto arr = array<integer>(std::ranges::begin(vec), - std::ranges::end(vec)); + auto arr = array<integer>(std::ranges::begin(vec), std::ranges::end(vec)); REQUIRE(arr.size() == 2); REQUIRE(arr[0] == 1); REQUIRE(arr[1] == 42); } - SECTION("from initializer_list") { + SECTION("from initializer_list") + { auto arr = array<integer>{integer(1), integer(42)}; REQUIRE(arr.size() == 2); @@ -75,9 +71,10 @@ 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); + 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<integer>(ref, uarr); @@ -86,34 +83,38 @@ TEST_CASE("ucl: array: construct from UCL object", "[ucl]") ::ucl_object_unref(uarr); } - SECTION("noref, correct type") { - auto uarr = ::ucl_object_typed_new(UCL_ARRAY); - auto uint = ::ucl_object_fromint(42); + 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<integer>(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); + 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<integer>(noref, uarr); REQUIRE_THROWS_AS(arr[0], type_mismatch); } - SECTION("ref, wrong type") { - auto uobj = ::ucl_object_frombool(true); + 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); + SECTION("noref, wrong type") + { + auto *uobj = ::ucl_object_frombool(true); REQUIRE_THROWS_AS(array(noref, uobj), type_mismatch); @@ -125,10 +126,8 @@ TEST_CASE("ucl: array: swap", "[ucl]") { // do not add using namespace nihil::ucl - auto arr1 = nihil::ucl::array<nihil::ucl::integer>{ - nihil::ucl::integer(1), - nihil::ucl::integer(2) - }; + auto arr1 = nihil::ucl::array<nihil::ucl::integer>{nihil::ucl::integer(1), + nihil::ucl::integer(2)}; auto arr2 = nihil::ucl::array<nihil::ucl::integer>{ nihil::ucl::integer(3), @@ -169,9 +168,7 @@ TEST_CASE("ucl: array: compare", "[ucl]") { using namespace nihil::ucl; - auto arr = array<integer>{ - integer(1), integer(42), integer(666) - }; + auto arr = array<integer>{integer(1), integer(42), integer(666)}; auto arr2 = array<integer>(); REQUIRE(arr != arr2); @@ -181,9 +178,7 @@ TEST_CASE("ucl: array: compare", "[ucl]") arr2.push_back(integer(666)); REQUIRE(arr == arr2); - auto arr3 = array<integer>{ - integer(1), integer(1), integer(1) - }; + auto arr3 = array<integer>{integer(1), integer(1), integer(1)}; REQUIRE(arr != arr3); } @@ -237,33 +232,33 @@ TEST_CASE("ucl: array: emit", "[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"); + REQUIRE(output == "array [\n" + " 1,\n" + " 42,\n" + " 666,\n" + "]\n"); } TEST_CASE("ucl: array: format", "[ucl]") { using namespace nihil::ucl; - SECTION("empty array") { + SECTION("empty array") + { auto arr = array<integer>(); REQUIRE(std::format("{}", arr) == "[]"); } - SECTION("bare array") { - auto arr = array<integer>{ - integer(1), integer(42), integer(666) - }; + SECTION("bare array") + { + auto arr = array<integer>{integer(1), integer(42), integer(666)}; auto output = std::format("{}", arr); REQUIRE(output == "[1, 42, 666]"); } - SECTION("parsed array") { + SECTION("parsed array") + { auto ucl = parse("array = [1, 42, 666];").value(); auto arr = object_cast<array<integer>>(ucl["array"]).value(); @@ -276,7 +271,8 @@ TEST_CASE("ucl: array: print to ostream", "[ucl]") { using namespace nihil::ucl; - SECTION("empty array") { + SECTION("empty array") + { auto arr = array<integer>(); auto strm = std::ostringstream(); strm << arr; @@ -284,17 +280,17 @@ TEST_CASE("ucl: array: print to ostream", "[ucl]") REQUIRE(strm.str() == "[]"); } - SECTION("bare array") { - auto arr = array<integer>{ - integer(1), integer(42), integer(666) - }; + SECTION("bare array") + { + auto arr = array<integer>{integer(1), integer(42), integer(666)}; auto strm = std::ostringstream(); strm << arr; REQUIRE(strm.str() == "[1, 42, 666]"); } - SECTION("parsed array") { + SECTION("parsed array") + { auto ucl = parse("array = [1, 42, 666];").value(); auto arr = object_cast<array<integer>>(ucl["array"]).value(); auto strm = std::ostringstream(); @@ -325,12 +321,10 @@ TEST_CASE("ucl: array is a sized_range", "[ucl]") 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 arr_as_ints = arr | std::views::transform(&integer::value); auto int_vec = std::vector<integer::contained_type>(); std::ranges::copy(arr_as_ints, std::back_inserter(int_vec)); REQUIRE(int_vec == std::vector<std::int64_t>{1, 42, 666}); - } TEST_CASE("ucl: array: bad object_cast", "[ucl]") @@ -435,16 +429,18 @@ TEST_CASE("array iterator: invalid operations", "[ucl]") { using namespace nihil::ucl; - auto arr = array<integer>{ integer(42) }; + auto arr = array<integer>{integer(42)}; auto it = arr.begin(); - SECTION("decrement before start") { + 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") { + SECTION("increment past end") + { ++it; REQUIRE(it == arr.end()); @@ -453,7 +449,8 @@ TEST_CASE("array iterator: invalid operations", "[ucl]") REQUIRE_THROWS_AS(it + 1, std::logic_error); } - SECTION("dereference iterator at end") { + SECTION("dereference iterator at end") + { REQUIRE_THROWS_AS(it[1], std::logic_error); ++it; @@ -462,17 +459,21 @@ TEST_CASE("array iterator: invalid operations", "[ucl]") REQUIRE_THROWS_AS(*it, std::logic_error); } - SECTION("compare with different array") { - auto arr2 = array<integer>{ integer(42) }; + SECTION("compare with different array") + { + auto arr2 = array<integer>{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") { + SECTION("compare with empty iterator") + { auto it2 = array_iterator<integer>(); 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 <compare> -#include <cstdlib> -#include <expected> -#include <system_error> - -#include <ucl.h> - -module nihil.ucl; - -import nihil.error; - -namespace nihil::ucl { - -auto make_boolean(boolean::contained_type value) - -> std::expected<boolean, error> -{ - 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<object_type>( - ::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<object_type>( - ::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 <cassert> -#include <cstdint> -#include <cstdlib> -#include <expected> -#include <format> -#include <string> - #include <ucl.h> 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<boolean, error>; + 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<int>(a.value()) <=> static_cast<int>(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<int>(a.value()) <=> static_cast<int>(b); + } +}; + +// Boolean constructors. This returns an error instead of throwing. +export [[nodiscard]] auto +make_boolean(boolean::contained_type const value = false) -> std::expected<boolean, error> +{ + 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<bool>. - */ -export template<> +// std::formatter for a boolean. This provides the same format operations +// as std::formatter<bool>. +export template <> struct std::formatter<nihil::ucl::boolean, char> { std::formatter<bool> base_formatter; - template<class ParseContext> - constexpr ParseContext::iterator parse(ParseContext& ctx) + template <class ParseContext> + constexpr auto parse(ParseContext &ctx) -> ParseContext::iterator { return base_formatter.parse(ctx); } - template<class FmtContext> - FmtContext::iterator format(nihil::ucl::boolean const &o, - FmtContext& ctx) const + template <class FmtContext> + auto format(nihil::ucl::boolean const &o, FmtContext &ctx) const -> FmtContext::iterator { return base_formatter.format(o.value(), ctx); } diff --git a/nihil.ucl/tests/boolean.cc b/nihil.ucl/boolean.test.cc index f7ef95e..9fb0148 100644 --- a/nihil.ucl/tests/boolean.cc +++ b/nihil.ucl/boolean.test.cc @@ -1,16 +1,15 @@ -/* - * This source code is released into the public domain. - */ - -#include <concepts> -#include <string> +// This source code is released into the public domain. #include <catch2/catch_test_macros.hpp> #include <ucl.h> +import nihil.std; import nihil.ucl; -TEST_CASE("ucl: boolean: invariants", "[ucl]") +namespace { +inline auto constexpr *test_tags = "[nihil][nihil.ucl][nihil.ucl.boolean]"; + +TEST_CASE("ucl: boolean: invariants", test_tags) { using namespace nihil::ucl; @@ -27,18 +26,24 @@ TEST_CASE("ucl: boolean: invariants", "[ucl]") static_assert(std::swappable<boolean>); } -TEST_CASE("ucl: boolean: constructor", "[ucl]") +SCENARIO("Constructing a ucl::boolean", test_tags) { using namespace nihil::ucl; - SECTION("default") { + GIVEN ("A default-constructed boolean") { auto b = boolean(); - REQUIRE(b == false); + + THEN ("The value is false") { + REQUIRE(b == false); + } } - SECTION("with value") { + GIVEN ("A boolean constructed from a true value") { auto b = boolean(true); - REQUIRE(b == true); + + THEN ("The value is true") { + REQUIRE(b == true); + } } } @@ -46,8 +51,9 @@ TEST_CASE("ucl: boolean: construct from UCL object", "[ucl]") { using namespace nihil::ucl; - SECTION("ref, correct type") { - auto uobj = ::ucl_object_frombool(true); + SECTION("ref, correct type") + { + auto *uobj = ::ucl_object_frombool(true); auto i = boolean(ref, uobj); REQUIRE(i == true); @@ -55,23 +61,26 @@ TEST_CASE("ucl: boolean: construct from UCL object", "[ucl]") ::ucl_object_unref(uobj); } - SECTION("noref, correct type") { - auto uobj = ::ucl_object_frombool(true); + 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); + 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); + SECTION("noref, wrong type") + { + auto *uobj = ::ucl_object_fromint(1); REQUIRE_THROWS_AS(boolean(noref, uobj), type_mismatch); @@ -83,12 +92,14 @@ TEST_CASE("ucl: boolean: make_boolean", "[ucl]") { using namespace nihil::ucl; - SECTION("default value") { + SECTION("default value") + { auto b = make_boolean().value(); REQUIRE(b == false); } - SECTION("explicit value") { + SECTION("explicit value") + { auto b = make_boolean(true).value(); REQUIRE(b == true); } @@ -97,7 +108,7 @@ TEST_CASE("ucl: boolean: make_boolean", "[ucl]") 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); @@ -117,14 +128,11 @@ TEST_CASE("ucl: boolean: key()", "[ucl]") { using namespace nihil::ucl; - auto err = parse("a_bool = true"); - REQUIRE(err); - - auto obj = *err; - REQUIRE(object_cast<boolean>(obj["a_bool"])->key() == "a_bool"); + auto obj = parse("a_bool = true").value(); + REQUIRE(object_cast<boolean>(obj["a_bool"]).value().key() == "a_bool"); - auto b = nihil::ucl::boolean(true); - REQUIRE(b.key() == ""); + auto b = boolean(true); + REQUIRE(b.key().empty() == true); } TEST_CASE("ucl: boolean: comparison", "[ucl]") @@ -133,22 +141,26 @@ TEST_CASE("ucl: boolean: comparison", "[ucl]") auto b = boolean(true); - SECTION("operator==") { + SECTION("operator==") + { REQUIRE(b == true); REQUIRE(b == boolean(true)); } - SECTION("operator!=") { + SECTION("operator!=") + { REQUIRE(b != false); REQUIRE(b != boolean(false)); } - SECTION("operator<") { + SECTION("operator<") + { REQUIRE(b <= true); REQUIRE(b <= nihil::ucl::boolean(true)); } - SECTION("operator>") { + SECTION("operator>") + { REQUIRE(b > false); REQUIRE(b > nihil::ucl::boolean(false)); } @@ -172,8 +184,7 @@ TEST_CASE("ucl: boolean: parse and emit", "[ucl]") auto ucl = parse("bool = true;").value(); auto output = std::string(); - emit(ucl, nihil::ucl::emitter::configuration, - std::back_inserter(output)); + emit(ucl, nihil::ucl::emitter::configuration, std::back_inserter(output)); REQUIRE(output == "bool = true;\n"); } @@ -182,12 +193,14 @@ TEST_CASE("ucl: boolean: format", "[ucl]") { using namespace nihil::ucl; - SECTION("bare boolean") { + SECTION("bare boolean") + { auto str = std::format("{}", boolean(true)); REQUIRE(str == "true"); } - SECTION("parsed boolean") { + SECTION("parsed boolean") + { auto obj = parse("bool = true;").value(); auto b = object_cast<boolean>(obj["bool"]).value(); @@ -195,7 +208,8 @@ TEST_CASE("ucl: boolean: format", "[ucl]") REQUIRE(str == "true"); } - SECTION("with format string") { + SECTION("with format string") + { auto str = std::format("{: >5}", boolean(true)); REQUIRE(str == " true"); } @@ -205,14 +219,16 @@ TEST_CASE("ucl: boolean: print to ostream", "[ucl]") { using namespace nihil::ucl; - SECTION("bare boolean") { + SECTION("bare boolean") + { auto strm = std::ostringstream(); strm << boolean(true); REQUIRE(strm.str() == "true"); } - SECTION("parsed boolean") { + SECTION("parsed boolean") + { auto obj = parse("bool = true;").value(); auto i = object_cast<boolean>(obj["bool"]).value(); @@ -222,3 +238,4 @@ TEST_CASE("ucl: boolean: print to ostream", "[ucl]") 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 <iostream> -#include <iterator> - -module nihil.ucl; - -namespace nihil::ucl { - -auto operator<<(std::ostream &stream, object const &o) --> std::ostream & -{ - emit(o, emitter::json, std::ostream_iterator<char>(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 <array> -#include <charconv> -#include <cstdlib> -#include <format> -#include <iterator> -#include <iosfwd> -#include <span> -#include <string> -#include <utility> - #include <ucl.h> 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<std::output_iterator<char> 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<emit_wrapper *>(ud); - - while (nchars--) - *self->iterator++ = static_cast<char>(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 <std::output_iterator<char> 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<char>(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<emit_wrapper *>(ud); - - for (auto c : std::span(str, len)) - *self->iterator++ = static_cast<char>(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<std::int64_t>::digits10; - auto buf = std::array<char, bufsize>(); + auto &self = check_magic(ud); - auto *self = static_cast<emit_wrapper *>(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<char, std::numeric_limits<std::int64_t>::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<double>::digits10; - auto buf = std::array<char, bufsize>(); - - auto *self = static_cast<emit_wrapper *>(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<char, std::numeric_limits<double>::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<emit_wrapper *>(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<char> auto &&it) - -> void +export auto +emit(object const &object, emitter const format, std::output_iterator<char> auto &&it) -> void { auto ucl_format = static_cast<ucl_emitter>(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<char>(stream)); + return stream; +} } // namespace nihil::ucl -/* - * Specialisation of std::formatter<> for object. - */ -template<std::derived_from<nihil::ucl::object> T> +// Specialisation of std::formatter<> for object. Note that most derived +// UCL types override this. +template <std::derived_from<nihil::ucl::object> T> struct std::formatter<T, char> { nihil::ucl::emitter emitter = nihil::ucl::emitter::json; - template<class ParseContext> - constexpr ParseContext::iterator parse(ParseContext& ctx) + template <class ParseContext> + constexpr auto parse(ParseContext &ctx) -> ParseContext::iterator { auto it = ctx.begin(); auto end = ctx.end(); @@ -179,8 +173,7 @@ struct std::formatter<T, char> 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<T, char> return it; } - template<class FmtContext> - FmtContext::iterator format(nihil::ucl::object const &o, - FmtContext& ctx) const + template <class FmtContext> + 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<T, char> 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/tests/emit.cc b/nihil.ucl/emit.test.cc index a7dcd71..51c4e0e 100644 --- a/nihil.ucl/tests/emit.cc +++ b/nihil.ucl/emit.test.cc @@ -1,14 +1,11 @@ -/* - * This source code is released into the public domain. - */ - -#include <format> -#include <sstream> +// This source code is released into the public domain. #include <catch2/catch_test_macros.hpp> +import nihil.std; import nihil.ucl; +namespace { TEST_CASE("ucl: emit to std::ostream", "[ucl]") { using namespace std::literals; @@ -91,3 +88,5 @@ TEST_CASE("ucl: emit YAML with std::format", "[ucl]") " 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 <string> -#include <system_error> - -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<int>(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<errc>(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 <string> -#include <system_error> - -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<nihil::ucl::errc> : 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 <compare> -#include <cstdlib> -#include <expected> -#include <system_error> - -#include <ucl.h> - -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<object_type>( - ::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<object_type>( - ::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<integer, error> -{ - 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 <compare> -#include <cstdint> -#include <cstdlib> -#include <expected> -#include <format> -#include <utility> - #include <ucl.h> 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<integer, error>; + 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<integer, error> +{ + 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<std::int64_t>::max())) throw std::out_of_range("literal out of range"); return integer(static_cast<std::int64_t>(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<std::int64_t>. - */ -export template<> +// std::formatter for an integer. This provides the same format operations +// as std::formatter<std::int64_t>. +export template <> struct std::formatter<nihil::ucl::integer, char> { std::formatter<std::int64_t> base_formatter; - template<class ParseContext> - constexpr ParseContext::iterator parse(ParseContext& ctx) + template <class ParseContext> + constexpr auto parse(ParseContext &ctx) -> ParseContext::iterator { return base_formatter.parse(ctx); } - template<class FmtContext> - FmtContext::iterator format(nihil::ucl::integer const &o, - FmtContext& ctx) const + template <class FmtContext> + auto format(nihil::ucl::integer const &o, FmtContext &ctx) const -> FmtContext::iterator { return base_formatter.format(o.value(), ctx); } diff --git a/nihil.ucl/tests/integer.cc b/nihil.ucl/integer.test.cc index 6584764..68567e9 100644 --- a/nihil.ucl/tests/integer.cc +++ b/nihil.ucl/integer.test.cc @@ -1,16 +1,12 @@ -/* - * This source code is released into the public domain. - */ - -#include <concepts> -#include <cstdint> -#include <string> +// This source code is released into the public domain. #include <catch2/catch_test_macros.hpp> #include <ucl.h> +import nihil.std; import nihil.ucl; +namespace { TEST_CASE("ucl: integer: invariants", "[ucl]") { using namespace nihil::ucl; @@ -67,7 +63,7 @@ TEST_CASE("ucl: integer: construct from UCL object", "[ucl]") using namespace nihil::ucl; SECTION("ref, correct type") { - auto uobj = ::ucl_object_fromint(42); + auto *uobj = ::ucl_object_fromint(42); auto i = integer(ref, uobj); REQUIRE(i == 42); @@ -76,14 +72,14 @@ TEST_CASE("ucl: integer: construct from UCL object", "[ucl]") } SECTION("noref, correct type") { - auto uobj = ::ucl_object_fromint(42); + auto *uobj = ::ucl_object_fromint(42); auto i = integer(noref, uobj); REQUIRE(i == 42); } SECTION("ref, wrong type") { - auto uobj = ::ucl_object_frombool(true); + auto *uobj = ::ucl_object_frombool(true); REQUIRE_THROWS_AS(integer(ref, uobj), type_mismatch); @@ -91,7 +87,7 @@ TEST_CASE("ucl: integer: construct from UCL object", "[ucl]") } SECTION("noref, wrong type") { - auto uobj = ::ucl_object_frombool(true); + auto *uobj = ::ucl_object_frombool(true); REQUIRE_THROWS_AS(integer(noref, uobj), type_mismatch); @@ -147,7 +143,7 @@ TEST_CASE("ucl: integer: key()", "[ucl]") SECTION("bare integer, no key") { auto i = 42_ucl; - REQUIRE(i.key() == ""); + REQUIRE(i.key().empty() == true); } } @@ -245,3 +241,4 @@ TEST_CASE("ucl: integer: print to ostream", "[ucl]") 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 <cassert> -#include <cstdint> -#include <cstdlib> -#include <format> -#include <memory> -#include <optional> -#include <string> -#include <system_error> - #include <ucl.h> 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<datatype T> +export template <datatype T> struct map; -template<datatype T> -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 <datatype T> +struct map_iterator +{ using difference_type = std::ptrdiff_t; using value_type = std::pair<std::string_view, T>; 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<T>; - map_iterator(::ucl_object_t const *obj) + explicit map_iterator(::ucl_object_t const *obj) : m_state(std::make_shared<state>(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<state> m_state; }; -export template<datatype T = object> -struct map final : object { - inline static constexpr object_type ucl_type = object_type::object; +export template <datatype T = object> +struct map final : object +{ + static constexpr auto ucl_type = object_type::object; using value_type = std::pair<std::string_view, T>; using size_type = std::size_t; using difference_type = std::ptrdiff_t; using iterator = map_iterator<T>; + 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<object_type>( - ::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<object_type>( - ::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<std::input_iterator Iterator> - requires(std::convertible_to<std::iter_value_t<Iterator>, value_type>) - map(Iterator first, Iterator last) - : map() + // Create a map from a range of value types. + template <std::ranges::range Range> + requires(std::convertible_to<std::ranges::range_value_t<Range>, 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<std::ranges::range Range> - requires(std::convertible_to<std::ranges::range_value_t<Range>, - 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 <std::input_iterator Iterator> + requires(std::convertible_to<std::iter_value_t<Iterator>, 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<value_type> 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<T> + // Access a map element by key. + [[nodiscard]] auto find(this map const &self, std::string_view const key) -> std::optional<T> { - 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<T> + // 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<T> { - 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/tests/map.cc b/nihil.ucl/map.test.cc index 7240cb3..6d31af2 100644 --- a/nihil.ucl/tests/map.cc +++ b/nihil.ucl/map.test.cc @@ -1,16 +1,14 @@ -/* - * This source code is released into the public domain. - */ - -#include <concepts> +// This source code is released into the public domain. #include <catch2/catch_test_macros.hpp> #include <ucl.h> +import nihil.std; import nihil.ucl; //NOLINTBEGIN(bugprone-unchecked-optional-access) +namespace { TEST_CASE("ucl: map: invariants", "[ucl]") { using namespace nihil::ucl; @@ -43,9 +41,9 @@ TEST_CASE("ucl: map: construct from initializer_list", "[ucl]") using namespace std::literals; auto map = nihil::ucl::map<integer>{ - {"1"sv, integer(1)}, - {"42"sv, integer(42)}, - }; + {"1"sv, integer(1)}, + {"42"sv, integer(42)}, + }; REQUIRE(str(map.type()) == "object"); REQUIRE(map["1"] == 1); @@ -58,11 +56,11 @@ TEST_CASE("ucl: map: construct from range", "[ucl]") using namespace std::literals; auto vec = std::vector<std::pair<std::string_view, integer>>{ - {"1"sv, integer(1)}, - {"42"sv, integer(42)}, - }; + {"1"sv, integer(1)}, + {"42"sv, integer(42)}, + }; - auto map = nihil::ucl::map<integer>(std::from_range, vec); + auto map = nihil::ucl::map<integer>(vec); REQUIRE(str(map.type()) == "object"); REQUIRE(map["1"] == 1); @@ -75,9 +73,9 @@ TEST_CASE("ucl: map: construct from iterator pair", "[ucl]") using namespace std::literals; auto vec = std::vector<std::pair<std::string_view, integer>>{ - {"1"sv, integer(1)}, - {"42"sv, integer(42)}, - }; + {"1"sv, integer(1)}, + {"42"sv, integer(42)}, + }; auto map = nihil::ucl::map<integer>(std::ranges::begin(vec), std::ranges::end(vec)); @@ -107,9 +105,9 @@ TEST_CASE("ucl: map: find", "[ucl]") using namespace std::literals; auto map = nihil::ucl::map<integer>{ - {"1"sv, integer(1)}, - {"42"sv, integer(42)}, - }; + {"1"sv, integer(1)}, + {"42"sv, integer(42)}, + }; auto obj = map.find("42"); REQUIRE(obj.value() == 42); @@ -124,11 +122,11 @@ TEST_CASE("ucl: map: iterate", "[ucl]") using namespace std::literals; auto map = nihil::ucl::map<integer>{ - {"1"sv, integer(1)}, - {"42"sv, integer(42)}, - }; + {"1"sv, integer(1)}, + {"42"sv, integer(42)}, + }; - auto i = 0u; + auto i = 0U; for (auto [key, value] : map) { if (key == "1") @@ -155,9 +153,9 @@ TEST_CASE("ucl: map: remove", "[uc]") using namespace nihil::ucl; auto map = nihil::ucl::map<integer>{ - {"1"sv, integer(1)}, - {"42"sv, integer(42)}, - }; + {"1"sv, integer(1)}, + {"42"sv, integer(42)}, + }; REQUIRE(map.find("42") != std::nullopt); REQUIRE(map.remove("42") == true); @@ -173,9 +171,9 @@ TEST_CASE("ucl: map: pop", "[uc]") using namespace nihil::ucl; auto map = nihil::ucl::map<integer>{ - {"1"sv, integer(1)}, - {"42"sv, integer(42)}, - }; + {"1"sv, integer(1)}, + {"42"sv, integer(42)}, + }; REQUIRE(map.find("42") != std::nullopt); @@ -189,4 +187,6 @@ TEST_CASE("ucl: map: pop", "[uc]") 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 <cstdlib> -#include <string> -#include <utility> - -#include <ucl.h> - -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<object_type>(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 <compare> -#include <cstddef> -#include <string> +// A UCL object. The object is immutable and internally refcounted, so it +// may be copied as needed. #include <ucl.h> 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<object_type>(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<object_type>(::ucl_object_type(uobj)) != type) + throw type_mismatch(type, static_cast<object_type>(::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<object_type>(::ucl_object_type(uobj)) != type) + throw type_mismatch(type, static_cast<object_type>(::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/tests/object.cc b/nihil.ucl/object.test.cc index 3ad180e..557653c 100644 --- a/nihil.ucl/tests/object.cc +++ b/nihil.ucl/object.test.cc @@ -1,11 +1,10 @@ -/* - * This source code is released into the public domain. - */ +// This source code is released into the public domain. #include <catch2/catch_test_macros.hpp> #include <ucl.h> +import nihil.std; import nihil.ucl; TEST_CASE("ucl object: get_ucl_object", "[ucl]") 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 <coroutine> -#include <cstdlib> -#include <expected> - #include <ucl.h> 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<datatype To> @@ -80,7 +73,7 @@ struct convert_check<array<T>> export template<datatype To> auto object_cast(object const &from) -> std::expected<To, type_mismatch> { - auto uobj = from.get_ucl_object(); + auto const *uobj = from.get_ucl_object(); co_await convert_check<To>{}.check(uobj); co_return To(nihil::ucl::ref, uobj); diff --git a/nihil.ucl/tests/parse.cc b/nihil.ucl/parse.test.cc index 43ce219..79a722d 100644 --- a/nihil.ucl/tests/parse.cc +++ b/nihil.ucl/parse.test.cc @@ -1,14 +1,12 @@ -/* - * This source code is released into the public domain. - */ - -#include <string> +// This source code is released into the public domain. #include <catch2/catch_test_macros.hpp> #include <catch2/matchers/catch_matchers_floating_point.hpp> +import nihil.std; import nihil.ucl; +namespace { TEST_CASE("ucl parse: iterate array", "[ucl]") { using namespace std::literals; @@ -53,3 +51,5 @@ TEST_CASE("ucl parse: iterate hash", "[ucl]") REQUIRE(object_cast<string>(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 <expected> -#include <functional> -#include <string> - -#include <ucl.h> - -module nihil.ucl; - -import nihil.error; - -namespace nihil::ucl { - -auto make_parser(int flags) -> std::expected<parser, error> -{ - 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<macro_handler *>(ud); - auto string = std::string_view( - reinterpret_cast<char const *>(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<object> -{ - 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 <coroutine> -#include <expected> -#include <format> -#include <functional> -#include <memory> -#include <string> -#include <vector> - #include <ucl.h> 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<macro_callback_t> 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<macro_handler *>(ud); + auto string = std::string_view(reinterpret_cast<char const *>(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<std::invocable<std::string_view> F> - auto register_macro(this parser &self, - std::string_view name, - F &&func) - -> void - requires (std::same_as<bool, std::invoke_result<F>>) + + template <std::invocable<std::string_view> F> + auto register_macro(this parser &self, std::string_view name, F &&func) -> void + requires(std::same_as<bool, std::invoke_result<F>>) { - auto handler = std::make_unique<macro_handler>( - std::forward<F>(func)); + auto handler = std::make_unique<macro_handler>(std::forward<F>(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<void, error> - // Only bytes (chars) are permitted. + // Only bytes (chars) are permitted. requires(sizeof(std::ranges::range_value_t<decltype(data)>) == 1) { auto *p = self.get_parser(); - auto dptr = reinterpret_cast<unsigned char const *>( - std::ranges::data(data)); + auto dptr = reinterpret_cast<unsigned char const *>(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<void, error> - requires (!std::ranges::contiguous_range<decltype(data)>) + [[nodiscard]] auto + add(this parser &self, std::ranges::range auto &&data) -> std::expected<void, error> + requires(!std::ranges::contiguous_range<decltype(data)>) { - auto cdata = std::vector<char>( - std::from_range, - std::forward<decltype(data)>(data)); + auto cdata = std::vector<char>(std::from_range, std::forward<decltype(data)>(data)); co_await self.add(std::move(cdata)); co_return {}; } // Return the top object of this parser. - [[nodiscard]] auto top(this parser &self) -> map<object>; + [[nodiscard]] auto top(this parser &self) -> map<object> + { + 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<parser, error>; +export [[nodiscard]] auto make_parser(int flags = 0) -> std::expected<parser, error> +{ + 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<map<object>, error> +parse(int flags, std::ranges::range auto &&data) -> std::expected<map<object>, error> { auto p = co_await make_parser(flags); co_await p.add(std::forward<decltype(data)>(data)); co_return p.top(); } -export [[nodiscard]] auto -parse(std::ranges::range auto &&data) - -> std::expected<map<object>, error> +export [[nodiscard]] auto parse(std::ranges::range auto &&data) -> std::expected<map<object>, error> { co_return co_await parse(0, std::forward<decltype(data)>(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 <cassert> -#include <compare> -#include <cstdlib> -#include <expected> -#include <string> -#include <system_error> - -#include <ucl.h> - -module nihil.ucl; - -import nihil.error; - -namespace nihil::ucl { - -auto make_real(real::contained_type value) - -> std::expected<real, error> -{ - 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<object_type>( - ::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<object_type>( - ::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 <compare> -#include <expected> -#include <format> -#include <utility> - #include <ucl.h> 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<real, error>; +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<real, error> +{ + 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<long double>(std::numeric_limits<double>::max()) || d < static_cast<long double>(std::numeric_limits<double>::min())) @@ -80,32 +108,31 @@ export constexpr auto operator""_ucl (long double d) -> real return real(static_cast<double>(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<double>; - */ -export template<> +// std::formatter for a real. This provides the same format operations +// as std::formatter<double>; +export template <> struct std::formatter<nihil::ucl::real, char> { std::formatter<double> base_formatter; - template<class ParseContext> - constexpr ParseContext::iterator parse(ParseContext& ctx) + template <class ParseContext> + constexpr auto parse(ParseContext &ctx) -> ParseContext::iterator { return base_formatter.parse(ctx); } - template<class FmtContext> - FmtContext::iterator format(nihil::ucl::real const &o, - FmtContext& ctx) const + template <class FmtContext> + auto format(nihil::ucl::real const &o, FmtContext &ctx) const -> FmtContext::iterator { return base_formatter.format(o.value(), ctx); } diff --git a/nihil.ucl/tests/real.cc b/nihil.ucl/real.test.cc index 421917e..e880d9a 100644 --- a/nihil.ucl/tests/real.cc +++ b/nihil.ucl/real.test.cc @@ -1,16 +1,13 @@ -/* - * This source code is released into the public domain. - */ - -#include <concepts> -#include <string> +// This source code is released into the public domain. #include <catch2/catch_test_macros.hpp> #include <catch2/matchers/catch_matchers_floating_point.hpp> #include <ucl.h> +import nihil.std; import nihil.ucl; +namespace { TEST_CASE("ucl: real: invariants", "[ucl]") { using namespace nihil::ucl; @@ -32,12 +29,12 @@ TEST_CASE("ucl: real: constructor", "[ucl]") { using namespace nihil::ucl; - SECTION("default") { + SECTION ("default") { auto r = real(); REQUIRE(r == 0); } - SECTION("with value") { + SECTION ("with value") { auto r = real(42.1); REQUIRE_THAT(r.value(), Catch::Matchers::WithinRel(42.1)); } @@ -45,7 +42,7 @@ TEST_CASE("ucl: real: constructor", "[ucl]") TEST_CASE("ucl: real: literal", "[ucl]") { - SECTION("with namespace nihil::ucl::literals") { + SECTION ("with namespace nihil::ucl::literals") { using namespace nihil::ucl::literals; auto r = 42.5_ucl; @@ -53,7 +50,7 @@ TEST_CASE("ucl: real: literal", "[ucl]") REQUIRE_THAT(r.value(), Catch::Matchers::WithinRel(42.5)); } - SECTION("with namespace nihil::literals") { + SECTION ("with namespace nihil::literals") { using namespace nihil::literals; auto r = 42.5_ucl; @@ -66,8 +63,8 @@ TEST_CASE("ucl: real: construct from UCL object", "[ucl]") { using namespace nihil::ucl; - SECTION("ref, correct type") { - auto uobj = ::ucl_object_fromdouble(42); + SECTION ("ref, correct type") { + auto *uobj = ::ucl_object_fromdouble(42); auto r = real(ref, uobj); REQUIRE(r == 42); @@ -75,23 +72,23 @@ TEST_CASE("ucl: real: construct from UCL object", "[ucl]") ::ucl_object_unref(uobj); } - SECTION("noref, correct type") { - auto uobj = ::ucl_object_fromdouble(42); + 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); + 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); + SECTION ("noref, wrong type") { + auto *uobj = ::ucl_object_fromint(42); REQUIRE_THROWS_AS(real(noref, uobj), type_mismatch); @@ -103,12 +100,12 @@ TEST_CASE("ucl: real: make_real", "[ucl]") { using namespace nihil::ucl; - SECTION("default value") { + SECTION ("default value") { auto i = make_real().value(); REQUIRE(i == 0); } - SECTION("explicit value") { + SECTION ("explicit value") { auto i = make_real(42).value(); REQUIRE(i == 42); } @@ -117,7 +114,7 @@ TEST_CASE("ucl: real: make_real", "[ucl]") 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); @@ -139,15 +136,15 @@ TEST_CASE("ucl: real: key()", "[ucl]") { using namespace nihil::ucl; - SECTION("parsed with key") { + SECTION ("parsed with key") { auto obj = parse("a_real = 42.5").value(); auto r = object_cast<real>(obj["a_real"]).value(); REQUIRE(r.key() == "a_real"); } - SECTION("bare real, no key") { + SECTION ("bare real, no key") { auto i = 42.5_ucl; - REQUIRE(i.key() == ""); + REQUIRE(i.key().empty() == true); } } @@ -157,22 +154,22 @@ TEST_CASE("ucl: real: comparison", "[ucl]") auto i = nihil::ucl::real(42.5); - SECTION("operator==") { + SECTION ("operator==") { REQUIRE(i == 42.5); REQUIRE(i == 42.5_ucl); } - SECTION("operator!=") { + SECTION ("operator!=") { REQUIRE(i != 1); REQUIRE(i != 1._ucl); } - SECTION("operator<") { + SECTION ("operator<") { REQUIRE(i < 43); REQUIRE(i < 43._ucl); } - SECTION("operator>") { + SECTION ("operator>") { REQUIRE(i > 1); REQUIRE(i > 1._ucl); } @@ -186,8 +183,7 @@ TEST_CASE("ucl: real: parse", "[ucl]") auto v = obj["value"]; REQUIRE(v.key() == "value"); - REQUIRE_THAT(object_cast<real>(v).value().value(), - Catch::Matchers::WithinRel(42.1)); + REQUIRE_THAT(object_cast<real>(v).value().value(), Catch::Matchers::WithinRel(42.1)); } TEST_CASE("ucl: real: parse and emit", "[ucl]") @@ -206,12 +202,12 @@ TEST_CASE("ucl: real: format", "[ucl]") { using namespace nihil::ucl; - SECTION("bare real") { + SECTION ("bare real") { auto str = std::format("{}", 42.5_ucl); REQUIRE(str == "42.5"); } - SECTION("parsed real") { + SECTION ("parsed real") { auto obj = parse("real = 42.5;").value(); auto r = object_cast<real>(obj["real"]).value(); @@ -219,7 +215,7 @@ TEST_CASE("ucl: real: format", "[ucl]") REQUIRE(str == "42.5"); } - SECTION("with format string") { + SECTION ("with format string") { auto str = std::format("{:10.5f}", 42.5_ucl); REQUIRE(str == " 42.50000"); } @@ -229,14 +225,14 @@ TEST_CASE("ucl: real: print to ostream", "[ucl]") { using namespace nihil::ucl; - SECTION("bare real") { + SECTION ("bare real") { auto strm = std::ostringstream(); strm << 42.5_ucl; REQUIRE(strm.str() == "42.5"); } - SECTION("parsed real") { + SECTION ("parsed real") { auto obj = parse("real = 42.5;").value(); auto i = object_cast<real>(obj["real"]).value(); @@ -246,3 +242,4 @@ TEST_CASE("ucl: real: print to ostream", "[ucl]") 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 <cstdlib> -#include <expected> -#include <iosfwd> -#include <string> -#include <system_error> - -#include <ucl.h> - -module nihil.ucl; - -import nihil.error; - -namespace nihil::ucl { - -auto make_string() -> std::expected<string, error> -{ - return make_string(std::string_view("")); -} - -auto make_string(char const *s) -> std::expected<string, error> -{ - return make_string(std::string_view(s)); -} - -auto make_string(std::string_view s) -> std::expected<string, error> -{ - 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<object_type>( - ::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<object_type>( - ::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 <cstdlib> -#include <expected> -#include <format> -#include <iosfwd> -#include <string> - #include <ucl.h> 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,159 +25,236 @@ 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<std::ranges::contiguous_range Range> - requires (!std::same_as<std::string_view, Range> && - std::same_as<char, std::ranges::range_value_t<Range>>) - 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<std::ranges::range Range> - requires (!std::ranges::contiguous_range<Range> && - std::same_as<char, std::ranges::range_value_t<Range>>) - 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<std::input_iterator Iterator> - requires (std::same_as<char, std::iter_value_t<Iterator>>) + // 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 <std::ranges::contiguous_range Range> + requires(!std::same_as<std::string_view, Range> && + std::same_as<char, std::ranges::range_value_t<Range>>) + 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 <std::ranges::range Range> + requires(!std::ranges::contiguous_range<Range> && + std::same_as<char, std::ranges::range_value_t<Range>>) + explicit string(Range range) + : string(std::string(std::from_range, std::forward<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 <std::input_iterator Iterator> + requires(std::same_as<char, std::iter_value_t<Iterator>>) 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<string, error>; +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<string, error>; +export [[nodiscard]] auto make_string(std::string_view s) -> std::expected<string, error> +{ + 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<string, error> +{ + return make_string(std::string_view("")); +} // From C literal -export [[nodiscard]] auto -make_string(char const *) -> std::expected<string, error>; +export [[nodiscard]] auto make_string(char const *s) -> std::expected<string, error> +{ + return make_string(std::string_view(s)); +} // From contiguous range -export template<std::ranges::contiguous_range Range> -requires (!std::same_as<std::string_view, Range> && - std::same_as<char, std::ranges::range_value_t<Range>>) +export template <std::ranges::contiguous_range Range> +requires(!std::same_as<std::string_view, Range> && + std::same_as<char, std::ranges::range_value_t<Range>>) [[nodiscard]] auto make_string(Range &&range) { - return make_string(std::string_view(range)); + return make_string(std::string_view(std::forward<Range>(range))); } // From non-contiguous range -export template<std::ranges::range Range> -requires (!std::ranges::contiguous_range<Range> && - std::same_as<char, std::ranges::range_value_t<Range>>) +export template <std::ranges::range Range> +requires(!std::ranges::contiguous_range<Range> && + std::same_as<char, std::ranges::range_value_t<Range>>) [[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>(range))); } // From iterator pair -export template<std::input_iterator Iterator> -requires (std::same_as<char, std::iter_value_t<Iterator>>) +export template <std::input_iterator Iterator> +requires(std::same_as<char, std::iter_value_t<Iterator>>) [[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. */ export auto operator<<(std::ostream &, string const &) -> std::ostream &; @@ -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<std::string_view>. */ -export template<> +export template <> struct std::formatter<nihil::ucl::string, char> { std::formatter<std::string_view> base_formatter; - template<class ParseContext> - constexpr ParseContext::iterator parse(ParseContext& ctx) + template <class ParseContext> + constexpr auto parse(ParseContext &ctx) -> ParseContext::iterator { return base_formatter.parse(ctx); } - template<class FmtContext> - FmtContext::iterator format(nihil::ucl::string const &o, - FmtContext& ctx) const + template <class FmtContext> + auto format(nihil::ucl::string const &o, FmtContext &ctx) const -> FmtContext::iterator { return base_formatter.format(o.value(), ctx); } diff --git a/nihil.ucl/tests/string.cc b/nihil.ucl/string.test.cc index 6409b8d..68c57e8 100644 --- a/nihil.ucl/tests/string.cc +++ b/nihil.ucl/string.test.cc @@ -1,18 +1,12 @@ -/* - * This source code is released into the public domain. - */ - -#include <concepts> -#include <list> -#include <sstream> -#include <string> -#include <vector> +// This source code is released into the public domain. #include <catch2/catch_test_macros.hpp> #include <ucl.h> +import nihil.std; import nihil.ucl; +namespace { TEST_CASE("ucl: string: invariants", "[ucl]") { using namespace nihil::ucl; @@ -127,23 +121,23 @@ TEST_CASE("ucl: string: construct from UCL object", "[ucl]") using namespace nihil::ucl; SECTION("ref, correct type") { - auto uobj = ::ucl_object_fromstring("testing"); + auto *uobj = ::ucl_object_fromstring("testing"); - auto s = string(ref, uobj); + auto const s = string(ref, uobj); REQUIRE(s == "testing"); ::ucl_object_unref(uobj); } SECTION("noref, correct type") { - auto uobj = ::ucl_object_fromstring("testing"); + auto *uobj = ::ucl_object_fromstring("testing"); - auto s = string(noref, uobj); + auto const s = string(noref, uobj); REQUIRE(s == "testing"); } SECTION("ref, wrong type") { - auto uobj = ::ucl_object_frombool(true); + auto *uobj = ::ucl_object_frombool(true); REQUIRE_THROWS_AS(string(ref, uobj), type_mismatch); @@ -151,7 +145,7 @@ TEST_CASE("ucl: string: construct from UCL object", "[ucl]") } SECTION("noref, wrong type") { - auto uobj = ::ucl_object_frombool(true); + auto *uobj = ::ucl_object_frombool(true); REQUIRE_THROWS_AS(string(noref, uobj), type_mismatch); @@ -165,53 +159,53 @@ TEST_CASE("ucl: string: make_string", "[ucl]") using namespace std::literals; SECTION("empty string") { - auto str = make_string().value(); + auto const str = make_string().value(); REQUIRE(str.type() == object_type::string); REQUIRE(str == ""); } SECTION("from string literal") { - auto str = make_string("testing").value(); + auto const 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(); + auto const 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(); + auto const 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(); + 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 s = std::list{'t', 'e', 's', 't', 'i', 'n', 'g'}; - auto str = make_string(s).value(); + 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 s = std::vector{'t', 'e', 's', 't', 'i', 'n', 'g'}; - auto str = make_string(s.begin(), s.end()).value(); + 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 s = std::list{'t', 'e', 's', 't', 'i', 'n', 'g'}; - auto str = make_string(s.begin(), s.end()).value(); + 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"); } @@ -234,22 +228,19 @@ TEST_CASE("ucl: string: value()", "[ucl]") { using namespace nihil::ucl; - auto s = string("te\"st"); - REQUIRE(s.value() == "te\"st"); + auto const s = string(R"(te"st)"); + REQUIRE(s.value() == R"(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<string>(obj["a_string"])->key() == "a_string"); + auto obj = parse(R"(a_string = "test")").value(); + REQUIRE(object_cast<string>(obj["a_string"]).value().key() == "a_string"); - auto s = string("test"); - REQUIRE(s.key() == ""); + auto const s = string("test"); + REQUIRE(s.key().empty() == true); } TEST_CASE("ucl: string: size", "[ucl]") @@ -280,7 +271,7 @@ TEST_CASE("ucl: string: iterate", "[ucl]") auto end = str.end(); static_assert(std::sentinel_for<decltype(end), - decltype(begin)>); + decltype(begin)>); REQUIRE(*begin == 't'); ++begin; @@ -339,7 +330,7 @@ TEST_CASE("ucl: string: parse", "[ucl]") { using namespace nihil::ucl; - auto obj = parse("value = \"te\\\"st\"").value(); + auto obj = parse(R"(value = "te\"st")").value(); auto v = obj["value"]; REQUIRE(v.key() == "value"); @@ -350,7 +341,7 @@ TEST_CASE("ucl: string: emit", "[ucl]") { using namespace nihil::ucl; - auto ucl = parse("str = \"te\\\"st\";").value(); + auto ucl = parse(R"(str = "te\"st";)").value(); auto output = std::string(); emit(ucl, emitter::configuration, std::back_inserter(output)); @@ -371,7 +362,7 @@ TEST_CASE("ucl: string: format", "[ucl]") } SECTION("parsed string") { - auto obj = parse("string = \"te\\\"st\";").value(); + auto obj = parse(R"(string = "te\"st";)").value(); auto s = object_cast<string>(obj["string"]).value(); auto str = std::format("{}", s); @@ -399,7 +390,7 @@ TEST_CASE("ucl: string: print to ostream", "[ucl]") } SECTION("parsed string") { - auto obj = parse("string = \"te\\\"st\";").value(); + auto obj = parse(R"(string = "te\"st";)").value(); auto s = object_cast<string>(obj["string"]).value(); auto strm = std::ostringstream(); @@ -413,3 +404,4 @@ TEST_CASE("ucl: string: print to ostream", "[ucl]") 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/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 <format> - -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 <concepts> -#include <format> -#include <stdexcept> -#include <string> - #include <ucl.h> 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<typename T> +export template <typename T> concept datatype = requires(T o) { { o.get_ucl_object() } -> std::convertible_to<::ucl_object_t const *>; { o.type() } -> std::same_as<object_type>; @@ -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 <iostream> - +// 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<typename Char, typename Traits> struct capture_stream { capture_stream(std::basic_ostream<Char, Traits> &stream) diff --git a/nihil.util/test_capture_stream.cc b/nihil.util/capture_stream.test.cc index 27c8596..a4821b7 100644 --- a/nihil.util/test_capture_stream.cc +++ b/nihil.util/capture_stream.test.cc @@ -1,13 +1,11 @@ -/* - * This source code is released into the public domain. - */ - -#include <iostream> +// This source code is released into the public domain. #include <catch2/catch_test_macros.hpp> +import nihil.std; import nihil.util; +namespace { TEST_CASE("nihil.util: capture", "[nihil][nihil.util]") { SECTION("std::cout with release()") { @@ -42,3 +40,5 @@ TEST_CASE("nihil.util: capture", "[nihil][nihil.util]") 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 <concepts> -#include <locale> - +// 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<decltype(c)>; auto &facet = std::use_facet<ctype>(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/test_ctype.cc b/nihil.util/ctype.test.cc index 62721d1..d000b45 100644 --- a/nihil.util/test_ctype.cc +++ b/nihil.util/ctype.test.cc @@ -1,11 +1,12 @@ -/* - * This source code is released into the public domain. - */ +// This source code is released into the public domain. #include <catch2/catch_test_macros.hpp> +import nihil.std; import nihil.util; +namespace { + TEST_CASE("ctype: space", "[ctype]") { auto is_utf8_space = nihil::ctype_is(std::ctype_base::space, @@ -371,3 +372,5 @@ TEST_CASE("ctype: graph", "[ctype]") { 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 <algorithm> -#include <locale> -#include <ranges> -#include <string> -#include <utility> - +// 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<typename Char> [[nodiscard]] -auto next_word(std::basic_string_view<Char> text, - std::locale const &locale = std::locale()) - -> std::pair<std::basic_string_view<Char>, - std::basic_string_view<Char>> +// Return the next word from a string_view. Skips leading whitespace, so +// calling this repeatedly will return each word from the string. +export template <typename Char> +[[nodiscard]] +auto next_word(std::basic_string_view<Char> text, std::locale const &locale = std::locale()) + -> std::pair<std::basic_string_view<Char>, std::basic_string_view<Char>> { 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<typename Char> -auto next_word(std::basic_string_view<Char> *text, - std::locale const &locale = std::locale()) +export template <typename Char> +auto next_word(std::basic_string_view<Char> *text, std::locale const &locale = std::locale()) -> std::basic_string_view<Char> { auto [word, rest] = next_word(*text, locale); diff --git a/nihil.util/test_next_word.cc b/nihil.util/next_word.test.cc index 7e61237..87d491a 100644 --- a/nihil.util/test_next_word.cc +++ b/nihil.util/next_word.test.cc @@ -1,14 +1,12 @@ -/* - * This source code is released into the public domain. - */ - -#include <locale> -#include <string> +// This source code is released into the public domain. #include <catch2/catch_test_macros.hpp> +import nihil.std; import nihil.util; +namespace { + TEST_CASE("next_word: basic", "[next_word]") { using namespace std::literals; @@ -63,3 +61,5 @@ TEST_CASE("next_word: locale", "[next_word]") 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 <algorithm> -#include <coroutine> -#include <cstdint> -#include <expected> -#include <ranges> -#include <string> -#include <system_error> -#include <utility> - +// 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<typename Char> +export template <typename Char> auto get_multiplier(Char c) -> std::expected<std::uint64_t, error> { 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<typename T, typename Char> [[nodiscard]] -auto parse_size(std::basic_string_view<Char> str) - -> std::expected<T, error> +// 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 <typename T, typename Char> +[[nodiscard]] +auto parse_size(std::basic_string_view<Char> str) -> std::expected<T, error> { // 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<Char>( - std::ranges::begin(str), it); + auto num_str = std::basic_string_view<Char>(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<T>::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<T>(c - '0'); if ((std::numeric_limits<T>::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<Char> 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<T>::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<typename T> -[[nodiscard]] inline auto parse_size(char const *s) +export template <typename T> +[[nodiscard]] auto parse_size(char const *s) { return parse_size<T>(std::string_view(s)); } -export template<typename T> -[[nodiscard]] inline auto parse_size(wchar_t const *s) +export template <typename T> +[[nodiscard]] auto parse_size(wchar_t const *s) { return parse_size<T>(std::wstring_view(s)); } -} +} // namespace nihil diff --git a/nihil.util/test_parse_size.cc b/nihil.util/parse_size.test.cc index 692039b..d79912a 100644 --- a/nihil.util/test_parse_size.cc +++ b/nihil.util/parse_size.test.cc @@ -1,12 +1,8 @@ -/* - * This source code is released into the public domain. - */ - -#include <cstdint> -#include <system_error> +// This source code is released into the public domain. #include <catch2/catch_test_macros.hpp> +import nihil.std; import nihil.core; import nihil.error; import nihil.util; 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 <cerrno> + +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 <algorithm> -#include <locale> -#include <ranges> -#include <string> - +// 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<typename Char> [[nodiscard]] -auto skipws(std::basic_string_view<Char> text, - std::locale const &locale = std::locale()) +// Remove leading whitespace from a string. +export template <typename Char> +[[nodiscard]] +auto skipws(std::basic_string_view<Char> text, std::locale const &locale = std::locale()) -> std::basic_string_view<Char> { auto is_space = ctype_is(std::ctype_base::space, locale); @@ -29,10 +16,8 @@ auto skipws(std::basic_string_view<Char> text, return {nonws, std::ranges::end(text)}; } -export template<typename Char> -auto skipws(std::basic_string_view<Char> *text, - std::locale const &locale = std::locale()) - -> void +export template <typename Char> +auto skipws(std::basic_string_view<Char> *text, std::locale const &locale = std::locale()) -> void { *text = skipws(*text, locale); } diff --git a/nihil.util/test_skipws.cc b/nihil.util/skipws.test.cc index 837c1f3..0cb741c 100644 --- a/nihil.util/test_skipws.cc +++ b/nihil.util/skipws.test.cc @@ -1,17 +1,14 @@ -/* - * This source code is released into the public domain. - */ - -#include <locale> -#include <string> -using namespace std::literals; +// This source code is released into the public domain. #include <catch2/catch_test_macros.hpp> +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 "); @@ -20,6 +17,8 @@ TEST_CASE("skipws: basic", "[skipws]") TEST_CASE("skipws: pointer", "[skipws]") { + using namespace std::literals; + auto s = "foo"sv; nihil::skipws(&s); REQUIRE(s == "foo"); @@ -39,6 +38,8 @@ TEST_CASE("skipws: pointer", "[skipws]") 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 <algorithm> -#include <cstdlib> -#include <format> -#include <ranges> -#include <iterator> -#include <vector> - +// 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<typename Char> struct field_spec { enum align_t { left, right }; diff --git a/nihil.util/test_tabulate.cc b/nihil.util/tabulate.test.cc index 8dee796..408cc18 100644 --- a/nihil.util/test_tabulate.cc +++ b/nihil.util/tabulate.test.cc @@ -1,13 +1,8 @@ -/* - * This source code is released into the public domain. - */ - -#include <iterator> -#include <string> -#include <vector> +// This source code is released into the public domain. #include <catch2/catch_test_macros.hpp> +import nihil.std; import nihil.util; using namespace std::literals; 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 <algorithm> -#include <random> -#include <set> -#include <unordered_set> - #include <catch2/catch_test_macros.hpp> -//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 <typename EngineT, std::size_t StateSize = EngineT::state_size> -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<device_type, numbers_needed>{}; 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<int, std::mt19937::state_size> {}; + auto seed_data = std::array<int, std::mt19937::state_size>{}; 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<int, std::mt19937::state_size> {}; + auto seed_data = std::array<int, std::mt19937::state_size>{}; 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<int, std::mt19937::state_size> {}; + auto seed_data = std::array<int, std::mt19937::state_size>{}; 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<int, std::mt19937::state_size> {}; + auto seed_data = std::array<int, std::mt19937::state_size>{}; 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<int, std::mt19937::state_size> {}; + auto seed_data = std::array<int, std::mt19937::state_size>{}; 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<std::mt19937>(seq); + auto generator = std::make_unique<std::mt19937>(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<int, std::mt19937::state_size> {}; + auto seed_data = std::array<int, std::mt19937::state_size>{}; 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<int, 6> {}; + auto seed_data = std::array<int, 6>{}; 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<std::ranlux48_base> 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<int, 6> {}; + auto seed_data = std::array<int, 6>{}; 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<std::ranlux48_base>(seq); + auto generator = std::make_unique<std::ranlux48_base>(seq); basic_uuid_random_generator<std::ranlux48_base> 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<int, 6> {}; + auto seed_data = std::array<int, 6>{}; 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<std::ranlux48_base> 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<wchar_t>(empty) == - L"00000000-0000-0000-0000-000000000000"); + REQUIRE(to_string(empty) == "00000000-0000-0000-0000-000000000000"); + REQUIRE(to_string<wchar_t>(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<uuid::value_type, 16> arr{{ - 0x47, 0x18, 0x38, 0x23, - 0x25, 0x74, - 0x4b, 0xfd, - 0xb4, 0x11, - 0x99, 0xed, 0x17, 0x7d, 0x3e, 0x43 - }}; + std::array<uuid::value_type, 16> 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<uuid::value_type, 16> arr{{ - 0x47, 0x18, 0x38, 0x23, - 0x25, 0x74, - 0x4b, 0xfd, - 0xb4, 0x11, - 0x99, 0xed, 0x17, 0x7d, 0x3e, 0x43 - }}; + auto arr = std::array<uuid::value_type, 16>{ + {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<uuid> 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<uuid> ids{ - uuid{}, - gen(), - gen(), - gen(), - gen() - }; + uuid_random_generator gen{engine}; + + std::unordered_set<uuid> 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<uuid::value_type, 16> arr{{ - 0x47, 0x18, 0x38, 0x23, - 0x25, 0x74, - 0x4b, 0xfd, - 0xb4, 0x11, - 0x99, 0xed, 0x17, 0x7d, 0x3e, 0x43 - }}; + std::array<uuid::value_type, 16> 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 <algorithm> -#include <array> -#include <atomic> -#include <chrono> -#include <cstring> -#include <functional> -#include <iomanip> -#include <iterator> -#include <memory> -#include <numeric> -#include <optional> -#include <random> -#include <ranges> -#include <span> -#include <string> -#include <sstream> -#include <string_view> -#include <type_traits> +// 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 <typename TChar> [[nodiscard]] constexpr auto hex2char(TChar const ch) noexcept -> unsigned char { if (ch >= static_cast<TChar>('0') && ch <= static_cast<TChar>('9')) - return static_cast<unsigned char>( - ch - static_cast<TChar>('0')); + return static_cast<unsigned char>(ch - static_cast<TChar>('0')); if (ch >= static_cast<TChar>('a') && ch <= static_cast<TChar>('f')) - return static_cast<unsigned char>( - 10 + ch - static_cast<TChar>('a')); + return static_cast<unsigned char>(10 + ch - static_cast<TChar>('a')); if (ch >= static_cast<TChar>('A') && ch <= static_cast<TChar>('F')) - return static_cast<unsigned char>( - 10 + ch - static_cast<TChar>('A')); + return static_cast<unsigned char>(10 + ch - static_cast<TChar>('A')); return 0; } @@ -68,17 +44,14 @@ template <typename TChar> template <typename TChar> [[nodiscard]] constexpr auto is_hex(TChar const ch) noexcept -> bool { - return (ch >= static_cast<TChar>('0') && - ch <= static_cast<TChar>('9')) || - (ch >= static_cast<TChar>('a') && - ch <= static_cast<TChar>('f')) || - (ch >= static_cast<TChar>('A') && - ch <= static_cast<TChar>('F')); + return (ch >= static_cast<TChar>('0') && ch <= static_cast<TChar>('9')) || + (ch >= static_cast<TChar>('a') && ch <= static_cast<TChar>('f')) || + (ch >= static_cast<TChar>('A') && ch <= static_cast<TChar>('F')); } template <typename TChar> -[[nodiscard]] constexpr auto to_string_view(TChar const *str) noexcept - -> std::basic_string_view<TChar> +[[nodiscard]] constexpr auto +to_string_view(TChar const *str) noexcept -> std::basic_string_view<TChar> { if (str) return str; @@ -87,9 +60,7 @@ template <typename TChar> template <typename StringType> [[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<typename StringType::value_type, typename StringType::traits_type> { 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<uint8_t const *>(start); - auto *last = static_cast<uint8_t const *>(end); + auto const *first = static_cast<std::uint8_t const *>(start); + auto const *last = static_cast<std::uint8_t const *>(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<uint8_t const *>(data); + auto *block = static_cast<std::uint8_t const *>(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<unsigned char>( - (bit_count >> 24) & 0xFF)); - self.process_byte(static_cast<unsigned char>( - (bit_count >> 16) & 0xFF)); - self.process_byte(static_cast<unsigned char>( - (bit_count >> 8) & 0xFF)); - self.process_byte(static_cast<unsigned char>( - (bit_count) & 0xFF)); + self.process_byte(static_cast<unsigned char>((bit_count >> 24U) & 0xFFU)); + self.process_byte(static_cast<unsigned char>((bit_count >> 16U) & 0xFFU)); + self.process_byte(static_cast<unsigned char>((bit_count >> 8U) & 0xFFU)); + self.process_byte(static_cast<unsigned char>((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<uint8_t>(d32[0] >> 24); - digest[di++] = static_cast<uint8_t>(d32[0] >> 16); - digest[di++] = static_cast<uint8_t>(d32[0] >> 8); - digest[di++] = static_cast<uint8_t>(d32[0] >> 0); - digest[di++] = static_cast<uint8_t>(d32[1] >> 24); - digest[di++] = static_cast<uint8_t>(d32[1] >> 16); - digest[di++] = static_cast<uint8_t>(d32[1] >> 8); - digest[di++] = static_cast<uint8_t>(d32[1] >> 0); - - digest[di++] = static_cast<uint8_t>(d32[2] >> 24); - digest[di++] = static_cast<uint8_t>(d32[2] >> 16); - digest[di++] = static_cast<uint8_t>(d32[2] >> 8); - digest[di++] = static_cast<uint8_t>(d32[2] >> 0); - - digest[di++] = static_cast<uint8_t>(d32[3] >> 24); - digest[di++] = static_cast<uint8_t>(d32[3] >> 16); - digest[di++] = static_cast<uint8_t>(d32[3] >> 8); - digest[di++] = static_cast<uint8_t>(d32[3] >> 0); - - digest[di++] = static_cast<uint8_t>(d32[4] >> 24); - digest[di++] = static_cast<uint8_t>(d32[4] >> 16); - digest[di++] = static_cast<uint8_t>(d32[4] >> 8); - digest[di++] = static_cast<uint8_t>(d32[4] >> 0); - - return digest; + return { + static_cast<std::uint8_t>(d32[0] >> 24U), + static_cast<std::uint8_t>(d32[0] >> 16U), + static_cast<std::uint8_t>(d32[0] >> 8U), + static_cast<std::uint8_t>(d32[0] >> 0U), + + static_cast<std::uint8_t>(d32[1] >> 24U), + static_cast<std::uint8_t>(d32[1] >> 16U), + static_cast<std::uint8_t>(d32[1] >> 8U), + static_cast<std::uint8_t>(d32[1] >> 0U), + + static_cast<std::uint8_t>(d32[2] >> 24U), + static_cast<std::uint8_t>(d32[2] >> 16U), + static_cast<std::uint8_t>(d32[2] >> 8U), + static_cast<std::uint8_t>(d32[2] >> 0U), + + static_cast<std::uint8_t>(d32[3] >> 24U), + static_cast<std::uint8_t>(d32[3] >> 16U), + static_cast<std::uint8_t>(d32[3] >> 8U), + static_cast<std::uint8_t>(d32[3] >> 0U), + + static_cast<std::uint8_t>(d32[4] >> 24U), + static_cast<std::uint8_t>(d32[4] >> 16U), + static_cast<std::uint8_t>(d32[4] >> 8U), + static_cast<std::uint8_t>(d32[4] >> 0U), + }; } private: auto process_block(this sha1 &self) -> void { - auto w = std::array<std::uint32_t, 80>(); + auto w = std::array<std::uint32_t, 80>{}; for (std::size_t i = 0; i < 16; i++) { - w[i] = static_cast<std::uint32_t>( - self.m_block[i * 4 + 0]) << 24; - w[i] |= static_cast<std::uint32_t>( - self.m_block[i * 4 + 1]) << 16; - w[i] |= static_cast<std::uint32_t>( - self.m_block[i * 4 + 2]) << 8; - w[i] |= static_cast<std::uint32_t>( - self.m_block[i * 4 + 3]); + w.at(i) = static_cast<std::uint32_t>(self.m_block.at((i * 4) + 0)) << 24U; + w.at(i) |= static_cast<std::uint32_t>(self.m_block.at((i * 4) + 1)) << 16U; + w.at(i) |= static_cast<std::uint32_t>(self.m_block.at((i * 4) + 2)) << 8U; + w.at(i) |= static_cast<std::uint32_t>(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<std::uint8_t, 64> m_block; - std::size_t m_blockByteIndex; - std::size_t m_byteCount; + digest32_t m_digest{}; + std::array<std::uint8_t, 64> m_block{}; + std::size_t m_blockByteIndex{}; + std::size_t m_byteCount{}; }; template <typename CharT> -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<wchar_t> - = L"00000000-0000-0000-0000-000000000000"; +inline constexpr std::wstring_view empty_guid<wchar_t> = L"00000000-0000-0000-0000-000000000000"; template <typename CharT> inline constexpr std::string_view guid_encoder = "0123456789abcdef"; @@ -345,19 +295,19 @@ inline constexpr std::wstring_view guid_encoder<wchar_t> = 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 CharT = char, - typename Traits = std::char_traits<CharT>, - typename Allocator = std::allocator<CharT>> +export template <typename CharT = char, typename Traits = std::char_traits<CharT>, + typename Allocator = std::allocator<CharT>> auto to_string(uuid const &id) -> std::basic_string<CharT, Traits, Allocator>; // -------------------------------------------------------------------------------------------------------------------------- // 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<value_type, 16> const &arr) noexcept + explicit constexpr uuid(std::array<value_type, 16> const &arr) noexcept : data{arr} { } @@ -428,7 +378,7 @@ export struct uuid { std::ranges::copy(bytes, std::ranges::begin(data)); } - template<typename ForwardIterator> + template <typename ForwardIterator> 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<std::byte const, 16> + [[nodiscard]] auto as_bytes() const -> std::span<std::byte const, 16> { return std::span<std::byte const, 16>( - reinterpret_cast<std::byte const*>(data.data()), - 16); + reinterpret_cast<std::byte const *>(data.data()), 16); } template <typename StringType> - [[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 <typename StringType> [[nodiscard]] constexpr static auto - from_string(StringType const & in_str) noexcept - -> std::optional<uuid> + from_string(StringType const &in_str) -> std::optional<uuid> { 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<uint8_t, 16> data{ { 0 } }; + auto data = std::array<std::uint8_t, 16>{}; 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<uint8_t>(hex2char(str[i]) << 4); + data.at(index) = static_cast<std::uint8_t>(hex2char(str[i]) << 4); firstDigit = false; } else { - data[index] = static_cast<uint8_t>(data[index] | hex2char(str[i])); + data.at(index) = + static_cast<std::uint8_t>(data.at(index) | hex2char(str[i])); index++; firstDigit = true; } @@ -572,19 +515,17 @@ export struct uuid { } private: - std::array<value_type, 16> data{ { 0 } }; + std::array<value_type, 16> data{{0}}; friend auto operator==(uuid const &, uuid const &) noexcept -> bool; friend auto operator<(uuid const &, uuid const &) noexcept -> bool; template <class Elem, class Traits> - friend auto operator<<(std::basic_ostream<Elem, Traits> &s, - uuid const &id) + friend auto operator<<(std::basic_ostream<Elem, Traits> &s, uuid const &id) -> std::basic_ostream<Elem, Traits> &; - template<class CharT, class Traits, class Allocator> - friend auto to_string(uuid const &id) - -> std::basic_string<CharT, Traits, Allocator>; + template <class CharT, class Traits, class Allocator> + friend auto to_string(uuid const &id) -> std::basic_string<CharT, Traits, Allocator>; friend std::hash<uuid>; }; @@ -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 <typename CharT, typename Traits, typename Allocator> -[[nodiscard]] auto to_string(uuid const &id) - -> std::basic_string<CharT, Traits, Allocator> +[[nodiscard]] auto to_string(uuid const &id) -> std::basic_string<CharT, Traits, Allocator> { - auto uustr = std::basic_string<CharT, Traits, Allocator>( - std::from_range, empty_guid<CharT>); + auto uustr = + std::basic_string<CharT, Traits, Allocator>(std::from_range, empty_guid<CharT>); 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<CharT>[id.data[index] >> 4 & 0x0f]; - uustr[++i] = guid_encoder<CharT>[id.data[index] & 0x0f]; + uustr[i] = guid_encoder<CharT>[id.data.at(index) >> 4U & 0x0FU]; + uustr[++i] = guid_encoder<CharT>[id.data.at(index) & 0x0FU]; index++; } @@ -634,13 +574,12 @@ export template <class Elem, class Traits> auto operator<<(std::basic_ostream<Elem, Traits> &s, uuid const &id) -> std::basic_ostream<Elem, Traits> & { - 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<std::uint8_t, 16>{}; - - for (int i = 0; i < 16; i += 4) - *reinterpret_cast<std::uint32_t *>(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 <typename CharT, typename Traits> - auto process_characters( - std::basic_string_view<CharT, Traits> const str) -> void + auto process_characters(std::basic_string_view<CharT, Traits> const str) -> void { for (std::uint32_t c : str) { - hasher.process_byte(static_cast<std::uint8_t>(c & 0xFF)); + hasher.process_byte(static_cast<std::uint8_t>(c & 0xFFU)); if constexpr (!std::is_same_v<CharT, char>) { - hasher.process_byte( - static_cast<uint8_t>((c >> 8) & 0xFF)); - hasher.process_byte( - static_cast<uint8_t>((c >> 16) & 0xFF)); - hasher.process_byte( - static_cast<uint8_t>((c >> 24) & 0xFF)); + hasher.process_byte(static_cast<std::uint8_t>((c >> 8U) & 0xFFU)); + hasher.process_byte(static_cast<std::uint8_t>((c >> 16U) & 0xFFU)); + hasher.process_byte(static_cast<std::uint8_t>((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<int, std::mt19937::state_size> {}; + auto seed_data = std::array<int, std::mt19937::state_size>{}; 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<nihil::uuid> { 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<uint64_t>(uuid.data[0]) << 56 | - static_cast<uint64_t>(uuid.data[1]) << 48 | - static_cast<uint64_t>(uuid.data[2]) << 40 | - static_cast<uint64_t>(uuid.data[3]) << 32 | - static_cast<uint64_t>(uuid.data[4]) << 24 | - static_cast<uint64_t>(uuid.data[5]) << 16 | - static_cast<uint64_t>(uuid.data[6]) << 8 | - static_cast<uint64_t>(uuid.data[7]); - - std::uint64_t h = - static_cast<uint64_t>(uuid.data[8]) << 56 | - static_cast<uint64_t>(uuid.data[9]) << 48 | - static_cast<uint64_t>(uuid.data[10]) << 40 | - static_cast<uint64_t>(uuid.data[11]) << 32 | - static_cast<uint64_t>(uuid.data[12]) << 24 | - static_cast<uint64_t>(uuid.data[13]) << 16 | - static_cast<uint64_t>(uuid.data[14]) << 8 | - static_cast<uint64_t>(uuid.data[15]); + auto const l = static_cast<uint64_t>(uuid.data[0]) << 56U | + static_cast<uint64_t>(uuid.data[1]) << 48U | + static_cast<uint64_t>(uuid.data[2]) << 40U | + static_cast<uint64_t>(uuid.data[3]) << 32U | + static_cast<uint64_t>(uuid.data[4]) << 24U | + static_cast<uint64_t>(uuid.data[5]) << 16U | + static_cast<uint64_t>(uuid.data[6]) << 8U | + static_cast<uint64_t>(uuid.data[7]); + + auto const h = static_cast<uint64_t>(uuid.data[8]) << 56U | + static_cast<uint64_t>(uuid.data[9]) << 48U | + static_cast<uint64_t>(uuid.data[10]) << 40U | + static_cast<uint64_t>(uuid.data[11]) << 32U | + static_cast<uint64_t>(uuid.data[12]) << 24U | + static_cast<uint64_t>(uuid.data[13]) << 16U | + static_cast<uint64_t>(uuid.data[14]) << 8U | + static_cast<uint64_t>(uuid.data[15]); return std::hash<std::uint64_t>{}(l ^ h); } |
