aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.clang-format11
-rw-r--r--.clang-tidy1
-rw-r--r--CMakeLists.txt4
-rw-r--r--nihil.cli/CMakeLists.txt2
-rw-r--r--nihil.cli/command.cc12
-rw-r--r--nihil.cli/command.ccm6
-rw-r--r--nihil.cli/command_node.cc19
-rw-r--r--nihil.cli/command_node.ccm17
-rw-r--r--nihil.cli/command_tree.cc22
-rw-r--r--nihil.cli/command_tree.ccm14
-rw-r--r--nihil.cli/dispatch_command.cc20
-rw-r--r--nihil.cli/dispatch_command.ccm31
-rw-r--r--nihil.cli/registry.cc30
-rw-r--r--nihil.cli/registry.ccm22
-rw-r--r--nihil.cli/test.cc24
-rw-r--r--nihil.cli/usage_error.ccm16
-rw-r--r--nihil.config/CMakeLists.txt17
-rw-r--r--nihil.config/nihil.config.ccm7
-rw-r--r--nihil.config/option.cc20
-rw-r--r--nihil.config/option.ccm37
-rw-r--r--nihil.config/read.cc50
-rw-r--r--nihil.config/read.ccm47
-rw-r--r--nihil.config/store.cc14
-rw-r--r--nihil.config/store.ccm27
-rw-r--r--nihil.config/string.cc62
-rw-r--r--nihil.config/string.ccm95
-rw-r--r--nihil.config/string.test.cc32
-rw-r--r--nihil.config/tests/CMakeLists.txt13
-rw-r--r--nihil.config/tests/string.cc36
-rw-r--r--nihil.config/write.cc41
-rw-r--r--nihil.config/write.ccm42
-rw-r--r--nihil.core/CMakeLists.txt2
-rw-r--r--nihil.core/errc.cc16
-rw-r--r--nihil.core/errc.ccm41
-rw-r--r--nihil.core/features.ccm13
-rw-r--r--nihil.core/nihil.core.ccm8
-rw-r--r--nihil.core/nihil.hh21
-rw-r--r--nihil.error/CMakeLists.txt6
-rw-r--r--nihil.error/error.cc160
-rw-r--r--nihil.error/error.ccm361
-rw-r--r--nihil.error/sys_error.ccm18
-rw-r--r--nihil.error/test.cc35
-rw-r--r--nihil.flagset/CMakeLists.txt1
-rw-r--r--nihil.flagset/flagset.ccm17
-rw-r--r--nihil.flagset/test.cc41
-rw-r--r--nihil.generator/CMakeLists.txt1
-rw-r--r--nihil.generator/byte_allocator.ccm7
-rw-r--r--nihil.generator/coroutine_traits.ccm6
-rw-r--r--nihil.generator/elements_of.ccm5
-rw-r--r--nihil.generator/forward.ccm5
-rw-r--r--nihil.generator/generator.ccm7
-rw-r--r--nihil.generator/generator.test.cc9
-rw-r--r--nihil.generator/generator_promise.ccm6
-rw-r--r--nihil.generator/generator_promise_base.ccm7
-rw-r--r--nihil.generator/manual_lifetime.ccm7
-rw-r--r--nihil.generator/nihil.generator.ccm6
-rw-r--r--nihil.generator/promise_base_alloc.ccm6
-rw-r--r--nihil.generator/util.ccm7
-rw-r--r--nihil.guard/CMakeLists.txt1
-rw-r--r--nihil.guard/guard.ccm9
-rw-r--r--nihil.guard/test.cc6
-rw-r--r--nihil.match/CMakeLists.txt1
-rw-r--r--nihil.match/match.ccm11
-rw-r--r--nihil.match/test.cc8
-rw-r--r--nihil.monad/CMakeLists.txt2
-rw-r--r--nihil.monad/monad.ccm13
-rw-r--r--nihil.monad/test.cc9
-rw-r--r--nihil.posix/CMakeLists.txt14
-rw-r--r--nihil.posix/argv.ccm14
-rw-r--r--nihil.posix/argv.test.cc10
-rw-r--r--nihil.posix/ensure_dir.ccm12
-rw-r--r--nihil.posix/execl.ccm33
-rw-r--r--nihil.posix/execl.test.cc5
-rw-r--r--nihil.posix/execlp.ccm10
-rw-r--r--nihil.posix/execlp.test.cc5
-rw-r--r--nihil.posix/execshell.ccm5
-rw-r--r--nihil.posix/execshell.test.cc5
-rw-r--r--nihil.posix/executor.ccm17
-rw-r--r--nihil.posix/execv.ccm43
-rw-r--r--nihil.posix/execv.test.cc5
-rw-r--r--nihil.posix/execvp.ccm23
-rw-r--r--nihil.posix/execvp.test.cc1
-rw-r--r--nihil.posix/fd.ccm31
-rw-r--r--nihil.posix/fd.test.cc10
-rw-r--r--nihil.posix/fexecv.ccm58
-rw-r--r--nihil.posix/fexecvp.ccm37
-rw-r--r--nihil.posix/find_in_path.ccm14
-rw-r--r--nihil.posix/getenv.ccm22
-rw-r--r--nihil.posix/getenv.test.cc12
-rw-r--r--nihil.posix/open.ccm10
-rw-r--r--nihil.posix/open.test.cc1
-rw-r--r--nihil.posix/open_in_path.ccm14
-rw-r--r--nihil.posix/open_in_path.test.cc1
-rw-r--r--nihil.posix/paths.ccm27
-rw-r--r--nihil.posix/posix.ccm4
-rw-r--r--nihil.posix/process.ccm15
-rw-r--r--nihil.posix/read_file.ccm27
-rw-r--r--nihil.posix/rename.ccm16
-rw-r--r--nihil.posix/spawn.ccm128
-rw-r--r--nihil.posix/stat.ccm13
-rw-r--r--nihil.posix/stat.test.cc1
-rw-r--r--nihil.posix/tempfile.ccm22
-rw-r--r--nihil.posix/tempfile.test.cc7
-rw-r--r--nihil.posix/unistd.ccm23
-rw-r--r--nihil.posix/unlink.ccm28
-rw-r--r--nihil.posix/write_file.ccm43
-rw-r--r--nihil.std/CMakeLists.txt7
-rw-r--r--nihil.std/nihil.std.ccm429
-rw-r--r--nihil.ucl/CMakeLists.txt34
-rw-r--r--nihil.ucl/array.ccm424
-rw-r--r--nihil.ucl/array.test.cc (renamed from nihil.ucl/tests/array.cc)141
-rw-r--r--nihil.ucl/boolean.cc106
-rw-r--r--nihil.ucl/boolean.ccm138
-rw-r--r--nihil.ucl/boolean.test.cc (renamed from nihil.ucl/tests/boolean.cc)99
-rw-r--r--nihil.ucl/emit.cc21
-rw-r--r--nihil.ucl/emit.ccm179
-rw-r--r--nihil.ucl/emit.test.cc (renamed from nihil.ucl/tests/emit.cc)11
-rw-r--r--nihil.ucl/errc.cc49
-rw-r--r--nihil.ucl/errc.ccm33
-rw-r--r--nihil.ucl/integer.cc102
-rw-r--r--nihil.ucl/integer.ccm165
-rw-r--r--nihil.ucl/integer.test.cc (renamed from nihil.ucl/tests/integer.cc)21
-rw-r--r--nihil.ucl/map.ccm263
-rw-r--r--nihil.ucl/map.test.cc (renamed from nihil.ucl/tests/map.cc)56
-rw-r--r--nihil.ucl/nihil.ucl.ccm8
-rw-r--r--nihil.ucl/object.cc114
-rw-r--r--nihil.ucl/object.ccm182
-rw-r--r--nihil.ucl/object.test.cc (renamed from nihil.ucl/tests/object.cc)5
-rw-r--r--nihil.ucl/object_cast.ccm19
-rw-r--r--nihil.ucl/parse.test.cc (renamed from nihil.ucl/tests/parse.cc)10
-rw-r--r--nihil.ucl/parser.cc102
-rw-r--r--nihil.ucl/parser.ccm158
-rw-r--r--nihil.ucl/real.cc104
-rw-r--r--nihil.ucl/real.ccm151
-rw-r--r--nihil.ucl/real.test.cc (renamed from nihil.ucl/tests/real.cc)67
-rw-r--r--nihil.ucl/string.cc187
-rw-r--r--nihil.ucl/string.ccm357
-rw-r--r--nihil.ucl/string.test.cc (renamed from nihil.ucl/tests/string.cc)74
-rw-r--r--nihil.ucl/tests/CMakeLists.txt20
-rw-r--r--nihil.ucl/type.cc62
-rw-r--r--nihil.ucl/type.ccm87
-rw-r--r--nihil.util/CMakeLists.txt20
-rw-r--r--nihil.util/capture_stream.ccm19
-rw-r--r--nihil.util/capture_stream.test.cc (renamed from nihil.util/test_capture_stream.cc)10
-rw-r--r--nihil.util/ctype.ccm73
-rw-r--r--nihil.util/ctype.test.cc (renamed from nihil.util/test_ctype.cc)9
-rw-r--r--nihil.util/next_word.ccm37
-rw-r--r--nihil.util/next_word.test.cc (renamed from nihil.util/test_next_word.cc)12
-rw-r--r--nihil.util/nihil.util.ccm8
-rw-r--r--nihil.util/parse_size.ccm75
-rw-r--r--nihil.util/parse_size.test.cc (renamed from nihil.util/test_parse_size.cc)8
-rw-r--r--nihil.util/save_errno.ccm35
-rw-r--r--nihil.util/skipws.ccm29
-rw-r--r--nihil.util/skipws.test.cc (renamed from nihil.util/test_skipws.cc)15
-rw-r--r--nihil.util/tabulate.ccm55
-rw-r--r--nihil.util/tabulate.test.cc (renamed from nihil.util/test_tabulate.cc)9
-rw-r--r--nihil.uuid/CMakeLists.txt1
-rw-r--r--nihil.uuid/test.cc334
-rw-r--r--nihil.uuid/uuid.ccm446
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(),
- &macro_handler::handle, handler.get());
+ ::ucl_parser_register_macro(self.get_parser(), cname.c_str(),
+ &macro_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);
}